matrix-org / waterfall

A cascading stream forwarding unit for scalable, distributed voice and video conferencing over Matrix
Apache License 2.0
97 stars 5 forks source link

Only use one UDP port #47

Open SimonBrandner opened 1 year ago

SimonBrandner commented 1 year ago

@daniel-abramov:

As discussed during the daily, I'm adding a little bit more context here: we won't be able to achieve the desired effect by just setting a range of UDP ports to a single port, instead, what we need, is to enable a so-called UDP Muxing in Pion.

As we know, UDP (unlike TCP) does not really have a concept of a connection on a protocol level, so it's about sending and receiving datagrams from/to an address. Pion does not have a global WebRTC state out-of-the-box that would allow reading the information from a single UDP socket and then forwarding it to the appropriate peer connection that is meant to be associated with it. So to enable this behavior, we should rely upon ice.NewMultiUDPMuxFromPort(ourPort) and then configure the engine accordingly: webrtc.SettingEngine.setICEUDPMux(ourConfig) (then we can create a new peer connection specifying the configured settings engine).

Note, that it's probably not something we want to have before stabilizing the SFU since enabling this option might have some undesired side-effects that must be treated accordingly, there is an open issue about it: https://github.com/pion/ice/issues/518

Perhaps a better idea would be to start with something that LiveKit has, i.e. allow configuration of the range of ports used for the UDP along with the support of the ICE-TCP. This should give us a good starting point. Then we could embed a TURN server into the SFU (the TURN server normally has fixed configured ports for both UDP and TCP) and only after it consider the usage of the UDPMux.

dbkr commented 1 year ago

I guess my question here is exactly what the ICE UDP muxing does - looks like it gathers source addreses based on ice credentials in the check packets and then routes media packets based on the source addresses it matched. I couldn't find out for sure, but I guess this is necessary because otherwise there's no way of knowing which DTLS session to use to decrypt a packet (and since they're DTLSed, you can't just route based on SSRC). In which case, yeah, this is probably something we want to think about a little further down the line.

daniel-abramov commented 1 year ago

Something like this, yeah, though I think the issue that you described is rather an implication of a more generic problem, let me explain how I understood it (hope that my understanding is correct).

Imagine we don't use UDP mux, and just use different UDP sockets (each peer connection has its own dedicated socket). Code-wise it means that somewhere inside a PeerConnection there is a socket and I imagine that when you read the data it essentially just calls socket.read() and then passes the data down the line to process the received packet.

Now imagine we enforce the usage of a single UDP port naively (using the same architecture, without changes in code), now 5 separate PeerConnections will essentially have the same socket (bound to the same local address and port). If all of them continue to read from the socket the same way they did it before, each PeerConnection will end up reading a random datagram from a socket that may not necessarily have been addressed to this particular PeerConnection. What should PeerConnection do with it? - Well, the only thing it could do is to either drop it (treating it as an unknown or invalid message) or use some sort of a look-up table to understand which PeerConnection this packet was meant for (based on e.g. sender's address and port). This "look-up table" must then be some sort of a shared state between PeerConnections, so that they know to who the message must be forwarded. Alternatively, the underlying socket must be moved outside of the PeerConnection and read in a single place while dispatching the messages to the right PeerConnection based on the addresses. This is exactly what the UDP Muxing in Pion does I suppose (from a quick peek in their code it looks like Pion does indeed something similar though more elaborated).

dbkr commented 1 year ago

Exactly - without DTLS this lookup would just be done on the SSRC of the incoming packet so it's trivial, but in a DTLS world you need to know which set of keys to use to decrypt the packet, so you end up guessing based on the sender address.

daniel-abramov commented 1 year ago

UPD. An example to try out once we want to implement it: https://github.com/pion/webrtc/blob/master/examples/ice-single-port/main.go#L85