-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QUIC role determination from ICE #103
Comments
We should remember that the app can choose. This is just the default role (or "auto"). I think the motivation originally was related to SDP offer/answer. In RFC 5245, the offerer must be controlling and the answerer controlled (assuming both are full, not lite). But the answerer will typically get an ICE check through first, and so it's more efficient if it acts as the DTLS client. Thus, controlled == DTLS client. I'm guessing we've kind of inherited this.. But I don't know. Maybe there are other reasons I can't remember or think of. We don't have the constraint about the ICE role being controlled from the offerer. And we don't need to worry about backwards compat with SDP. So perhaps we should make controlling == client. As you say, that makes a lot more sense in a client/server world when the server is using ICE lite. By the way, the ORTC IceTransport includes an "iceLite" field on the RTCIceParameters dictionary. The WebRTC one doesn't. Should we make an issue for that? Without it, there's no way to tell the browser it's talking to a lite endpoint. |
The app can choose, but only by the value of remoteParameters.role passed into start, "a RTCQuicTransport object assumes a QUIC role of auto upon construction." Otherwise the value is determined by the ICE role once the RTCIceTransport gets to the connected state. |
Oh, right. I forgot that the QUIC role can end up being chosen before
start() is called because we listen for QUIC packets, and that we only
"pick" the remote role, not the local role. That does seem like a problem,
and now that I think about it it's worse than what you pointe out.
Here are some of the cases that could happen:
1. ICE completes with controlling role and QUIC handshake completes in
client role before client calls start({.role = "server"}). Result:
app-chosen role conflicts with browser-chosen role.
2. ICE completes with controlled role and QUIC handshake completes in
server role before client calls start({.role = "client"}). Result:
app-chosen role conflicts with browser-chosen role.
3. ICE completes with controlled role and we receive a QUIC client hello
before calling start and sending a QUIC client hello (perhaps remote side
isn't "auto", or doesn't honor ICE role conflicts properly). Now the
locally chosen QUIC role conflicts with the remotely chosen QUIC role (both
trying to be client).
4. ICE completes with controlling role and we chose the QUIC server role
and so does the remote side (perhaps remote side is ICE-lite, or doesn't
honor ICE role conflicts properly). Now we'll never get connected. I think
this is your situation: server is ICE-lite, client doesn't pass in the role
(and defaults to "auto") and both end up being in the server role. Oops.
That seems like a lot of footguns laying around.
So, some ideas of how we could fix this:
A. Instead of letting the app specify the *remote* role, let it select the
*local* role in the constructor and get rid of "auto". That avoids pretty
much all of the problems, except we lose the ability to offer either and
let the remote side choose while we listen as server. For that, just start
one in server mode and then close it and create a different one in client
mode (or allow a limited-use setRole("client")). That allows the use cases
where you want the remote side to pick while still being explicit (pick
"server", wait for reply, switch to "client" if the other side picked
"server").
Code would look like:
// p2p mode where the local side picks server
const quic = new RTCQuicTransport(ice, "server");
const remoteFingerprints = await doSignaling();
quic.start(remoteFingerprints);
// client/server mode
const quic = new RTCQuicTransport(ice, "client");
const remoteFingerprints = await doSignaling();
quic.start(remoteFingerprints);
// p2p mode where the remote side picks
let quic = new RTCQuicTransport(ice, "server");
const [remoteRole, remoteFingerprints] = await doSignaling();
if (remoteRole == "server") {
// could also be quic.setRole("client") instead.
quic.stop();
quic = new RTCQuicTransport(ice, "client");
}
quic.start(remoteFingerprints);
B. Don't listen for QUIC packets before start(). Instead, break start()
into 3 methods: listen(), verify(), and connect():
- listen() acts as a server role and listens for packets. It should be
called before learning the remote fingerprints. It may not be called after
connect().
- connect() acts as a client role and sends a client hello. It may be
called before learning the remote fingerprints. It may be called after
listen() to switch to from server to client mode.
- verify(fingerprints) is a follow-up to both to verify remote
fingerprints. It may be called at any time and returns a promise for when
the connection has been verified (and is not connected).
Code would look like:
// p2p mode where the local side picks server
const quic = new RTCQuicTransport(ice);
quic.listen();
const remoteFingerprints = await doSignaling();
await quic.verify(remoteFingerprints);
// client/server mode
const quic = new RTCQuicTransport(ice);
quic.connect();
const remoteFingerprints = doSignaling();
await quic.verify(remoteFingerprints);
// p2p mode where the remote side picks
let quic = new RTCQuicTransport(ice);
quic.listen();
const [remoteRole, remoteFingerprints] = doSignaling();
if (remoteRole == "server") {
quic.connect();
}
await quic.verify(remoteFingerprints);
I like how B doesn't do anything until you call methods (no constructor
behavior). But I like the simplicity of A (just pick your role). Both
seem like an improvement to "auto" and setting the *remote* role.
Is there a use case that I'm missing with either of these, or some reason
why they would be a bad idea?
…On Fri, Dec 28, 2018 at 10:10 AM Seth Hampson ***@***.***> wrote:
The app can choose, but only by the value of remoteParameters.role passed
into start, "*a RTCQuicTransport object assumes a QUIC role of auto upon
construction*." Otherwise the value is determined by the ICE role once
the RTCIceTransport gets to the connected state.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#103 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AHaf-sL_fb-c3zyPloQ4HjCxOk6IhDObks5u9l6lgaJpZM4ZZO7z>
.
|
In classic webRTC DLTS role isn't determined by ICE role, it is determined by a=setup passive/active/actpass I'm not saying that we should preserve the mistakes of the past, just that it is a decision of the UA - albeit somewhat constrained. |
Currently we don't pick a role if QUIC packets come early (as done in WebRTC's DtlsTransport here), and according to the current spec it is only determined by the ICE role. Another idea: connect() & setRemoteFingerprints() (each side listens) In any case the developer needs to know which side is calling the other (one side has to initiate the handshake or set the role). I think how the fingerprints are verified is a separate discussion, with 2 options:
|
In general, a goal of the existing design (which was focussed on ORTC) was to minimize the total handshake time since that is important to developers in scenarios such as games. So ICE parameters might be sent along and as soon as ice.start() is called, the Offerer can respond to incoming checks (this can't happen before start is called because the role isn't established and conflicts could result). Receipt of a successful check will allow the Answer to begin the QUIC negotiation as a client, but negotiation cannot conclude (and media cannot be rendered) without the fingerprint verification via RTCQuicTransport.start(). A question: Can 0-RTT keys be established in P2P QUIC? After an initial certificate exchange and fingerprint verification between peers, it is possible to construct a subsequent RTCQuicTransport with the same certificates and would this enable 0-RTT? Just wondering if you would still need RTCQuicTransport.start() in such a situation. The fingerprint verification would have previously been done, but perhaps other checks (certification expiration?) would be needed. Another question: Is there interest in supporting WebRTC-QUIC as an extension to WebRTC 1.0 in the absence of WebRTC-ICE? If so, then we'd need to define the QUIC SDP negotiation, which would presumably be akin to RFC 8122 DTLS negotiation (e.g. a=fingerprint and a=setup lines). |
Editor's meeting notes: we think we can resolve the issues by just always being "auto" and not allowing a role choice by the web app. This has the downside that in a client/server situation with an ICE-lite server will be a QUIC client. But once we have the client/server API, one won't need to use ICE-lite on the server. |
Also - we are willing to revisit this if we see a strong use case/need. Currently we don't see a strong enough reason to support choosing the role. |
FYI - ICE lite will cause the server to be in ICE controlled role. The current spec says that ICE controlled role causes the QUIC transport to be in client mode. This actually causes a desirable behaviour for faster setup the server will issue the "HELLO" (with less round trips that the server being set in a server role). It might be odd that a server is in a client QUIC role but that's the nature of having a double handshake (ICE handshake first, then QUIC handshake after) to minimize round trips. |
Is the QUIC role appropriate based upon ICE? Currently the determination is:
I see that this is taken from DtlsTransport and I'm wondering what the motivation was. A potential problem could be a server that implements ICE Lite, which requires to be the controlled role, which is a QUIC client in our case.
The text was updated successfully, but these errors were encountered: