Search code examples
javascriptwebrtcstunturnice

Would caching ICE candidates and sdp not work even if we know exactly the route of the connection?


I understand that in P2P and more dynamic environment caching ICE candidates and sdp will not be a good practice, because the stuff you cache might not be able to apply for the next WebRTC connection. But how about the under the circumstances where we know exactly how the route of connection should be?

To be more specific,

  1. let's say that we have 1 TURN server (no load-balancing, so no internal routing there)
  2. and 2 peers with fixed IP that would like to connect from time to time with WebRTC.

In this case, we know exactly what the IPs of the peers are, and we know exactly what the IP of the TURN server (assuming it won't change), would it be okay to cache ICE candidate (the TURN) and SDP or parts of SDP just to bypass the ICE candidate and SDP exchanging part?


Solution

  • No. Offers and answers contain more than just how to connect. They contain a unique fingerprint for this connection instance, since it's possible to establish any number of secure connections between the same two known IPs, even concurrently.

    Compare the createOffer() results from two different RTCPeerConnection objects and you'll see they differ. Fingerprint aside, they also contain what ports the local RTCPeerConnection has decided to send/receive individual media over, which can vary.

    To use an earlier cached version, you would need to tell not just the remote RTCPeerConnection object what ports to use, but your local one as well. And that demonstrably does not work:

    const [pc1, pc2, pc3] = [1,2,3].map(() => new RTCPeerConnection());
    
    (async () => {
      try {
        [pc1, pc2].forEach(pc => pc.createDataChannel("dummy"));
        pc3.ondatachannel = () => console.log("pc3 ondatachannel");
        await pc1.createOffer();
        await pc1.setLocalDescription(await pc2.createOffer()); // Uh oh! pc2 not pc1
        await pc3.setRemoteDescription(pc1.localDescription);
        await pc3.setLocalDescription(await pc3.createAnswer());
        await pc1.setRemoteDescription(pc3.localDescription);
      } catch (e) {
        console.log(e);
      }
    })();
    
    pc1.onicecandidate = e => pc3.addIceCandidate(e.candidate);
    pc3.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    pc1.oniceconnectionstatechange = e => console.log(pc1.iceConnectionState);
    pc3.ontrack = e => video.srcObject = e.streams[0];

    In the latest Chrome this yields:

    InvalidModificationError: The SDP does not match the previously generated SDP for this type
    

    ...which is correct, since the latest WebRTC spec forbids SDP munging between createOffer and setLocalDescription.

    In Firefox, negotiation actually completes, but no media or datachannel events fire.

    Even with a TURN server, there's no way around that the fingerprints won't match.