Feature Proposal: Support Full ICE in mediasoup

Currently, mediasoup only supports ICE-lite, which has implementation simplicity as its primary advantage. However, there are several technical and practical reasons to support Full ICE.

Limitations of ICE-lite

  1. WHEP cannot be implemented with a single HTTP request

ICE-lite requires the remote peer (i.e., the client) to initiate connectivity checks. This makes it impossible to complete WHEP negotiation (SDP exchange + ICE connection) in a single HTTP request, as specified in WHEP. Instead, multiple round-trips are required, increasing connection latency and complexity.

With Full ICE, the server can act as the controlling agent, perform aggressive nomination, and complete ICE negotiation immediately, thus enabling WHEP in a single request.

  1. Lack of Consent Freshness and ICE disconnect detection

ICE-lite does not send STUN Binding Indications or perform connectivity checks after the initial connection is established. This means:

  • The server cannot detect if the remote peer has silently disconnected.
  • Media may continue to be sent to a peer that is no longer reachable.
  • Server resources (RTP streams, Consumers, etc.) are unnecessarily consumed.

Full ICE would provide consent freshness as per RFC 7675, allowing the server to detect and clean up dead connections.

  1. Consumers must be created in a paused state

Due to ICE-lite limitations, mediasoup must pause newly created Consumers and wait for the client to respond before resuming. This is a workaround to avoid race conditions where media is sent before the client’s RTCPeerConnection is ready to handle it.

Code from mediasoup demo shows:

await consumerPeer.request(
	'newConsumer',
	{
		peerId         : producerPeer.id,
		producerId     : producer.id,
		id             : consumer.id,
		kind           : consumer.kind,
		rtpParameters  : consumer.rtpParameters,
		type           : consumer.type,
		appData        : producer.appData,
		producerPaused : consumer.producerPaused
	});

// Now that we got the positive response from the remote endpoint, resume
// the Consumer so the remote endpoint will receive the a first RTP packet
// of this new stream once its PeerConnection is already ready to process
// and associate it.
await consumer.resume();

consumerPeer.notify(
	'consumerScore',
	{
		consumerId : consumer.id,
		score      : consumer.score
	})
	.catch(() => {});

resolve();

This is unnecessary with Full ICE, as the server can use aggressive nomination and determine the best candidate pair itself.

Practical Considerations

  • ICE-lite reduces server-side processing slightly, but the difference is negligible compared to RTP encryption, SRTP handling, and network I/O.
  • Most importantly, there are no meaningful CPU or network savings, and ICE-lite creates longer setup times.
  • Implementing Full ICE will enable proper WHEP/WHIP support, improved reliability, and better standards compliance.

References

RFC 8445 Appendix A:

ICE allows a lite implementation to have a single IPv4 host candidate and several IPv6 addresses. In that case, candidate pairs are selected by the controlling agent using a static algorithm, such as the one in RFC 6724, which is recommended by this specification. However, static mechanisms for address selection are always prone to error, since they can never reflect the actual topology or provide actual guarantees on connectivity. They are always heuristics. Consequently, if an ICE agent is implementing ICE just to select between its IPv4 and IPv6 addresses, and none of its IP addresses are behind NAT, usage of full ICE is still RECOMMENDED in order to provide the most robust form of address selection possible.

RFC 5245 Appendix A:

It is important to note that the lite implementation was added to this specification to provide a stepping stone to full implementation. Even for devices that are always connected to the public Internet with just a single IPv4 address, a full implementation is preferable if achievable. A full implementation will reduce call setup times, since ICE’s aggressive mode can be used. Full implementations also obtain the security benefits of ICE unrelated to NAT traversal; in particular, the voice hammer attack described in Section 18 is prevented only for full implementations, not lite.

As specified: The WebRTC-HTTP Ingest Protocol (WHIP) uses an HTTP POST request to perform a single-shot SDP offer/answer. So I don’t know what do you mean with your previous statement.

  1. Lack of Consent Freshness and ICE disconnect detection

Indeed mediasoup properly handles ICE consent freshness and will close the transport upon the given timeout without having received such messages. So it’s not true that the server does not detect dead connections.

  1. Consumers must be created in a paused state

This is unrelated to ICE. This is done to avoid a client receiving media from a stream for which the client may not be ready.

  1. WHEP cannot be implemented with a single HTTP request in mediasoup

In mediasoup, creating a Consumer requires the server to wait for the client to be “ready”, and the Consumer must initially be created in a paused state. The client is then expected to respond that its RTCPeerConnection is ready before the server resumes the Consumer. This results in the need for an additional HTTP request or client-side signaling step, which breaks the ideal single-request flow defined by WHEP.

This behavior is a direct consequence of using ICE-lite, as the server cannot reliably detect when the remote RTCPeerConnection is fully established unless the client explicitly notifies it.

⸻

  1. Lack of Consent Freshness and ICE disconnect detection

You’re right here, and I appreciate the correction. As discussed in issue #1127, mediasoup does implement consent freshness and ICE disconnection detection, even in ICE-lite mode.

However, it’s important to point out that ICE-lite does not initiate consent requests, and cannot detect failures on its own—it relies entirely on the client continuing to send STUN keepalives. With ICE full, both endpoints actively participate in consent checks, making the system more robust.

⸻

  1. Consumers must be created in a paused state

While not strictly an ICE issue, this behavior is influenced by ICE-lite.

In a normal WebRTC session between two full ICE endpoints, the ICE connection state and DTLS handshake provide enough signal for the sender to start transmitting media immediately once the connection is “connected” and “completed”. But in mediasoup’s ICE-lite mode, the server has no way to verify that the client is fully ready unless the client sends an explicit message—hence the need to pause and wait.

This limitation adds extra complexity to clients and breaks the ideal WHEP workflow, which assumes the server can send media immediately after the SDP negotiation without waiting for a separate “I’m ready” message from the client.

In WHEP:

  • Client/browser sends HTTP POST request with a SDP offer. Let’s assume server is ICE Lite (mediasoup) so the SDP offer doesn’t contain any ICE candidate.
  • Server replies 20X to the HTTP POST with a SDP answer containing server’s ICE candidates.
  • Client receives the SDP answer and processes it. At this point the PeerConnection in the client/browser is ready to send or receive RTP.
  • Client initiates ICE procedures by sending STUN Binding requests to server.
  • Server learns from those STUN requests and marks ICE as “connected”, and it replies to those STUN requests with STUN responses.
  • RTP starts (from client or from server, depending on the SDP negotiation).
  • No need to resume any server side Consumer here. Once the WebRtcTransport is connected mediasoup itself will request a video keyframe to the remote Producer.

The scenario you describe is the opposite: When browser receives a new consumer information that must be consumed on an already existing RecvTransport in mediasoup-client. In that case, indeed the client needs to send some “I’m ready” to server so server resumes the server side Consumer. But that’s not how WHEP works. In WHEP the browser sends an SDP offer (in this example to receive media from mediasoup) and client receives an SDP answer that contains all the info about the Consumers, so by the time ICE and DTLS is connected, the PeerConnection has already been provisioned with all the consumer’s info, so it won’t fail to match any received RTP video keyframe. No need to signal “I’m ready to consume this stream” to mediasoup.

And what’s wrong with that? It’s the same, mediasoup will mark the transport as disconnected if no ICE consent request was received from client in 30 seconds. It’s the very same as if mediasoup sends ICE consent requests to client and doesn’t receive a response in 30 seconds.

Thank you very much for the detailed explanation!

Now I understand why the mediasoup-client requires the Consumer to notify “I’m ready”.

So WHEP is indeed fully implementable in mediasoup as-is, and I have no further concerns on this. Thanks again for the clarification!

3 Likes