Re: Issue 173: DTLS handshake packets arrive because of connectivity checks before DTLS transport setup

Robin Raymond said:

What do we do about DTLS packets that can arrive before there's a DTLS transport to receive the DTLS packets?

How this can happen:
1) Alice sends an offer containing ICE candidates (i.e. Alice has an IceGatherer but has not called IceTransport.start() because remote ufrag/password is not yet known)
2) Bob sends his response ufrag / password via signalling channel and starts ICE connectivity checks (i.e. he has called IceTransport.start() method)
3) Alice receives connectivity checks and responds (aka as agreed in last ORTC CG meeting re: issue#170<https://github.com/openpeer/ortc/issues/170> ) and she sends an ICE consent reply back to Bob (but she still has not received ufrag/password via signalling yet so she cannot call IceTransport.start() yet)

[BA] I am not so sure that a response can be safely sent prior to calling iceTransport.start(), due to the inability of an IceGatherer to detect role conflicts.

4) Bob receives ICE connectivity consent reply and therefore believes a channel is open to send the DtlsTransport client "hello" packet
5) Alice receives DtlsTransport client hello packet but does not have any IceTransport or DtlsTransport started to receive the DTLS client "hello" packets.

[BA] If the response to an ICE connectivity check can't be safely sent until IceTransport.start() is called, then the IceTransport and probably the DtlsTransport will be determinable  (e.g. if the DtlsTransport is constructed from the IceTransport before iceTransport.start() is called).

While eventually Alice will receive the signalling necessary to setup an IceTransport and DtlsTransport, she may receive DTLS packets prematurely.

Possible options:
1) Drop DTLS packets incoming, force DTLS client re-transmit (i.e. consequence is a slow started or disrupted connections);

[BA] DTLS packets shouldn't be sent until a response to an ICE connectivity check is received.  If this requires iceTransport.start() to have been called, then I think it is ok for an IceGatherer receiving an incoming DTLS packet prior to iceTransport.start() to drop them.

2) Buffer DTLS packets incoming for a period of time and deliver packets to eventual IceTransport.start() which specifies a matching remote ufrag/password (i.e. consequence is insufficient buffering, over-buffering (DOS attack), or timeouts that drop packets pre-maturely before signaling arrives).

[BA] If DTLS packets can't arrive until iceTransport.start() is called and a DtlsTransport has been constructed from that iceTransport, then the incoming DTLS packets can be passed to the appropriate DtlsTransport, which can buffer them until dtlsTransport.start() is called.

3) Put IceTransport into a .listen() mode of operation with a pre-wired DTLS transport (i.e. consequence is a larger / more complex ORTC API surface and one that must handle forking scenarios). This idea was briefly mentioned in last ORTC CG meeting.

[BA] Not sure why we would need a "pre-wired DTLS transport" when a DtlsTransport object can be constructed from the IceTransport, and can start receiving incoming DTLS packets once iceTransport.start() is called.

I personally favour option 2 or 3 options but not 1. If we are going to respond to connectivity checks (as per issue #170<https://github.com/openpeer/ortc/issues/170> ) then we are giving consent to the remote side to send data. To incorrectly drop incoming data despite giving consent seems wrong.

[BA] I would vote for option 2 - with buffering occurring in the DtlsTransport, not the IceTransport.

If we do 3, then we need to be concerned with forking. If Alice sends out a set of offer candidates but multiple answers come back then a single IceTransport in .listen() mode will not be sufficient. We may want to consider having the gatherer in a .listen() mode [where an event is fired to tell of an unknown incoming connectivity consent check from an unknown remote ufrag never seen before) and thus having an IceTransport.accept() to specify where the incoming ICE connectivity checks must be processed.

E.g.

partial interface IceGatherer {
void listen();
attribute EventHandler onunhandledice;
}

dictionary IceUnhandledEventInit : EventInit {
DOMString remoteUFrag;
};

partial interface IceTransport {
//...
void accept(DOMString remoteUFrag);
}

This API could handle forked replies too. This could also allow the connectivity checks to be replied from a particular IceTransport rather than from the IceGatherer (so long as there's a short buffering of incoming ICE connectivity checks exists for unhandled remote ufrag packets). This would also allow for wiring of a DTLS transport to handle the transport before the consent was given to the remote side (although replying to the incoming DTLS client "hello" would not be possible since no reverse ice consent would be granted). In other words, incoming connectivity checks are handled but outgoing connectivity checks are still not possible since the remote password would remain unknown until signalling arrived but at least the DTLS transport could be wired up before any DTLS packets would / could arrive.

So IMHO, it's either buffer all packets until signalling arrives, or do a listen()/accept() scenario modelled after TCP listen()/accept().

Received on Thursday, 16 April 2015 16:22:41 UTC