RE: Proposal for IceGatherer.getOrCreateTransport

It looks like it is in fact possible for rtcpIceGatherer.getOrCreateTransport to know which RTP IceTransport to associate it with. 

The specification does not currently state that rtpIceGatherer.createAssociatedGatherer() MUST create an RTCP iceGatherer with the same RTCIceParameters and RTCIceGatherOptions as the original RTP one, but in SDP the ICE ufrag/password has to be the same for RTP and RTCP (at least within an m-line).  So we should add some text to mandate this. 

If no matching RTCIceTransport associated with rtcpIceGatherer can be found (possibly because one was not created), then the browser can locate the rtpIceGatherer which created rtcpIceGatherer via a call to rtpIceGatherer.createAssociatedGatherer(), then attempt to locate an RTCIceTransport which is associated with rtpIceGatherer and which has the remoteUsernameFragment set to the value of the argument passed to rtcpIceGatherer.getOrCreateTransport().  

Once that RTCIceTransport rtpIceTransport has been found, then rtpIceTransport.createAssociatedTransport() can be called.    

It is a bit convoluted (my head hurts thinking about it) but here is some updated text that explains how this might work: 

getOrCreateTransport
Retrieve the associated RTCIceTransport with a matching remoteUsernameFragment value, or create one from scratch. If rtcpIceGatherer.getOrCreateTransport() is called and rtcpIceGatherer.component is "RTCP", and no RTCIceTransport associated with rtcpIceGatherer matches, locate the RTCIceGatherer rtpIceGatherer from which rtcpIceGatherer was created, then attempt to locate an RTCIceTransport with matching remoteUsernameFragment associated with rtpIceGatherer. If one is found, then rtpIceGatherer.getOrCreateTransport().createAssociatedTransport() is called implicitly; if it is not found, then an "InvalidStateError" exception is thrown.



________________________________________
From: Justin Skinner [skinnzer@live.com]
Sent: Tuesday, June 30, 2015 10:58 AM
To: Bernard Aboba; Peter Thatcher
Cc: public-ortc@w3.org; Robin Raymond
Subject: RE: Proposal for IceGatherer.getOrCreateTransport

Might I suggest adding the rtpTransport object as a second (optional) parameter?

Verbosity is a double edged sword in interpreted languages.

________________________________
From: Bernard.Aboba@microsoft.com
To: pthatcher@google.com
CC: public-ortc@w3.org; robin@hookflash.com
Date: Tue, 30 Jun 2015 05:41:17 +0000
Subject: Re: Proposal for IceGatherer.getOrCreateTransport

Looking at the sample code below, there is something that does not look correct:


var rtpTransport = iceRtpGatherer.getOrCreateTransport(response.icertp.usernameFragment);
var rtcpTransport = iceRtcpGatherer.getOrCreateTransport(response.icertcp.usernameFragment);

In the case where an RTCP transport is created, how can getOrCreateTransport know the RTP IceTransport to associate it with?

Do we need .getOrCreateAssociatedTransport, with rtpTransport as an argument??



On Jun 29, 2015, at 12:05 PM, Bernard Aboba <Bernard.Aboba@microsoft.com<mailto:Bernard.Aboba@microsoft.com>> wrote:


During the last ORTC CG meeting, the slides proposed iceTransport.getOrCreateTransport, but your proposal for iceGatherer.getOrCreateTransport makes more sense to me.



From previous discussion, I believe we need to add a remoteUsernameFragment attribute to the RTCIceTransport (both remote ufrag and role would be auto-latched):



Partial interface RTCIceTransport : RTCStatsProvider {

    readonly    attribute DOMString      remoteUsernameFragment<http://internaut.com:8080/~baboba/ortc/ortc-7-04-2015.html#widl-RTCIceTransport-iceGatherer>;

    readonly    attribute RTCIceRole           role<http://internaut.com:8080/~baboba/ortc/ortc-7-04-2015.html#widl-RTCIceTransport-role>;

};



BTW, in a forking scenario the proposal simplifies determining which IceTransport corresponds to an incoming IceCandidate.



Revised Example 5 from Section 3.11:



// Example to demonstrate forking when RTP and RTCP are not multiplexed,

// so that both RTP and RTCP IceGatherer and IceTransport objects are needed.

// Include some helper functions

import "helper.js";

// Create ICE gather options

var gatherOptions = new RTCIceGatherOptions();

gatherOptions.gatherPolicy = RTCIceGatherPolicy.relay;

gatherOptions.iceservers = [ { urls: "stun:stun1.example.net<http://example.net>" } , { urls:"turn:turn.example.org", username: "user", credential:"myPassword"} ];

// Create ICE gatherer objects

var iceRtpGatherer = new RTCIceGatherer(gatherOptions);

var iceRtcpGatherer = iceRtpGatherer.createAssociatedGatherer();

// Prepare to signal local candidates

iceRtpGatherer.onlocalcandidate = function (event) {

  mySendLocalCandidate(event.candidate, RTCIceComponent.RTP);

};

iceRtcpGatherer.onlocalcandidate = function (event) {

  mySendLocalCandidate(event.candidate, RTCIceComponent.RTCP);

};

// Create the ICE transport arrays

var iceRtpTransports = [];

var iceRtcpTransports = [];

for (var i = 0 ; i < 10; i++){

  iceRtpTransports.push(new RTCIceTransport(iceRtpGatherer));

  iceRtcpTransports.push(iceRtpTransport.createAssociatedTransport());

}

// Set up response function

mySignaller.onResponse = function(responseSignaller,response) {

  // We may get N responses

  // Start the RTP and RTCP ICE transports so that outgoing ICE connectivity checks can begin

  var rtpTransport =  iceRtpGatherer.getOrCreateTransport(response.icertp.usernameFragment);

  var rtcpTransport = iceRtcpGatherer.getOrCreateTransport(response.icertcp.usernameFragment);

  rtpTransport.start(iceRtpGatherer, response.icertp);

  rtcpTransport.start(iceRtcpGatherer, response.icertcp);

  // Prepare to add ICE candidates signalled by the remote peer

  responseSignaller.onRemoteCandidate = function(remote) {

    // Locate the ICE transport that the signaled candidate relates to by matching the usernameFragment.

    if (remote.component === RTCIceComponent.RTP){

       rtpTransport = iceRtpGatherer.getOrCreateTransport(remote.parameters.usernameFragment);

       rtpTransport.addRemoteCandidate(remote.candidate);

    } else {

       rtcpTransport = iceRtcpGatherer.getOrCreateTransport(remote.parameters.usernameFragment);

       rtcpTransport.addRemoteCandidate(remote.candidate);

    }

  };

};

mySignaller.send({

   "icertp": iceRtpGatherer.getLocalParameters(),

   "icertcp": iceRtcpGatherer.getLocalParameters()

});







From: Peter Thatcher [mailto:pthatcher@google.com]
Sent: Thursday, June 11, 2015 1:47 AM
To: public-ortc@w3.org<mailto:public-ortc@w3.org>
Subject: Proposal for IceGatherer.getOrCreateTransport



At the last ORTC meeting, we realized that doing the following three things at the same time is hard:



1.  Respond to STUN binding requests before the remote ufrag is known.

2.  Not buffer STUN packets in the IceGatherer.

3.  Support call forking



​I said I would propose a solution :).



​If we drop #3, all we need is to pass an IceGatherer into an IceTransport constructor, and that makes the IceTransport able to receive the STUN traffic for incoming binding requests and send back responses.



But if we have multiple remote ICE ufrags (as we do with call forking) , then we need a way to match up the remote ufrags in the incoming STUN traffic with the remote ufrags received.  A naive approach would be to do something like:



if (iceTransport1.remoteUfrag == signalledRemoteUfrag) {

  iceTransport1.start(... signalledRemoteUfrag ...);

}



Because it's possible for the browser to receive a STUN binding request and send a binding response in between those two lines, in which case the ufrags won't match.



The only solution I can come up with is to have an atomic operation that means "give me the ice transport with the matching ufrag, or create one if there isn't one", so that you know that the iceTransport you have has the correct ufrag and there won't be a mismatch".





Thus:



partial interface IceGatherer {

  IceTransport getOrCreateTransport(DOMString remoteUfrag);

}



Which can then be used like so:



var iceTransport = iceGatherer.getOrCreateTransport(signalledRemoteUfrag);

iceTransport.start(.... signalledRemoteUfrag ...);





And even if there are N incoming remote ufrags, the one returned is always going to have the given ufrag.  In a full forking situation, you might do something like this:





var gatherer = new IceGatherer();

// Create pool

var transports = [];

for (var i = 0; i < 10; i++) {

  transports.push(new IceTransport(gatherer));

}

// Later, when you get the remote parameters:

var transport = gatherer.getOrCreateTransport(remoteParams.ufrag);

transport.start(gatherer, remoteParams);





The only issue I can see is that you might get a DTLS packet after responding to a STUN binding request but before receiving signalling.  I'm not sure if that's a serious enough problem to try and fix.  Worst case is that DTLS takes a little longer to setup when doing call forking.  And if did think it was a serious issue, we could have each IceTransport buffer a few DTLS packets and that would probably be enough, with a very small buffer.

Received on Tuesday, 30 June 2015 21:17:05 UTC