@@ -75,14 +75,15 @@ impl WebRtcConnection {
7575 /// (e.g. `"stun.cloudflare.com:3478"`)
7676 ///
7777 /// The connection spawns a tokio task that:
78- /// 1. Binds a UDP socket for the ICE agent
78+ /// 1. Obtains the shared UDP socket from the UdpMux
7979 /// 2. Runs the str0m event loop
8080 /// 3. Decodes incoming Opus audio → PCM16 → `audio_tx`
8181 /// 4. Encodes outgoing PCM16 → Opus → RTP via str0m writer
8282 /// 5. Forwards data channel messages bidirectionally
8383 pub async fn from_offer (
8484 offer_json : serde_json:: Value ,
8585 stun_server : & str ,
86+ mux : Arc < super :: multiplexer:: UdpMux > ,
8687 ) -> Result < ( Self , serde_json:: Value ) , Box < dyn std:: error:: Error + Send + Sync > > {
8788 let id = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
8889 let offer: SdpOffer = serde_json:: from_value ( offer_json) ?;
@@ -95,63 +96,67 @@ impl WebRtcConnection {
9596 // Create str0m Rtc instance
9697 let mut rtc = RtcConfig :: new ( ) . set_ice_lite ( ice_lite) . build ( Instant :: now ( ) ) ;
9798
98- // Bind a UDP socket for WebRTC traffic.
99- // WEBRTC__BIND_IP controls the bind address (default: 0.0.0.0).
100- // Some UDP proxy deployments require binding to a specific address
101- // so that outbound packets are sourced correctly.
102- let bind_ip = std:: env:: var ( "WEBRTC__BIND_IP" ) . unwrap_or_else ( |_| "0.0.0.0" . to_string ( ) ) ;
103- let bind_port = std:: env:: var ( "WEBRTC__UDP_PORT" )
104- . ok ( )
105- . and_then ( |v| v. parse :: < u16 > ( ) . ok ( ) )
106- . unwrap_or ( 0 ) ;
107- let bind_addr = format ! ( "{}:{}" , bind_ip, bind_port) ;
108-
109- let socket = UdpSocket :: bind ( & bind_addr) . await ?;
99+ let socket = mux. socket ( ) ;
110100 let bound_addr = socket. local_addr ( ) ?;
111- info ! ( "[webrtc:{}] Bound UDP socket on {}" , & id[ ..8 ] , bind_addr ) ;
101+ info ! ( "[webrtc:{}] Using shared UDP socket on {}" , & id[ ..8 ] , bound_addr ) ;
112102
113- let configured_public_ip = std:: env:: var ( "WEBRTC__PUBLIC_IP" )
103+ let configured_public_ips : Vec < std :: net :: IpAddr > = std:: env:: var ( "WEBRTC__PUBLIC_IP" )
114104 . ok ( )
115- . and_then ( |v| v. parse :: < std:: net:: IpAddr > ( ) . ok ( ) ) ;
116- let local_ip = if let Some ( ip) = configured_public_ip {
117- info ! ( "[webrtc:{}] Using WEBRTC__PUBLIC_IP={}" , & id[ ..8 ] , ip) ;
118- ip
105+ . map ( |s| {
106+ s. split ( ',' )
107+ . filter_map ( |p| p. trim ( ) . parse :: < std:: net:: IpAddr > ( ) . ok ( ) )
108+ . collect ( )
109+ } )
110+ . unwrap_or_default ( ) ;
111+
112+ let local_ips = if !configured_public_ips. is_empty ( ) {
113+ info ! ( "[webrtc:{}] Using WEBRTC__PUBLIC_IP(s)={:?}" , & id[ ..8 ] , configured_public_ips) ;
114+ configured_public_ips. clone ( )
119115 } else if !bound_addr. ip ( ) . is_unspecified ( ) && !bound_addr. ip ( ) . is_loopback ( ) {
120116 info ! ( "[webrtc:{}] Bound to specific IP {}; using as public IP" , & id[ ..8 ] , bound_addr. ip( ) ) ;
121- bound_addr. ip ( )
117+ vec ! [ bound_addr. ip( ) ]
122118 } else {
123119 let probe = UdpSocket :: bind ( "0.0.0.0:0" ) . await ?;
124120 probe. connect ( "8.8.8.8:80" ) . await ?;
125- probe. local_addr ( ) ?. ip ( )
121+ vec ! [ probe. local_addr( ) ?. ip( ) ]
126122 } ;
127- let host_addr = std:: net:: SocketAddr :: new ( local_ip, bound_addr. port ( ) ) ;
128123
129- // Add host candidate (local network address)
130- let host_candidate = Candidate :: host ( host_addr, "udp" )
131- . map_err ( |e| format ! ( "Failed to create host ICE candidate: {}" , e) ) ?;
132- rtc. add_local_candidate ( host_candidate) ;
133- info ! ( "[webrtc:{}] Host candidate: {}" , & id[ ..8 ] , host_addr) ;
124+ let mut primary_host_addr = None ;
125+
126+ for ip in & local_ips {
127+ let host_addr = std:: net:: SocketAddr :: new ( * ip, bound_addr. port ( ) ) ;
128+ if primary_host_addr. is_none ( ) {
129+ primary_host_addr = Some ( host_addr) ;
130+ }
131+ // Add host candidate (local network address)
132+ let host_candidate = Candidate :: host ( host_addr, "udp" )
133+ . map_err ( |e| format ! ( "Failed to create host ICE candidate: {}" , e) ) ?;
134+ rtc. add_local_candidate ( host_candidate) ;
135+ info ! ( "[webrtc:{}] Host candidate: {}" , & id[ ..8 ] , host_addr) ;
136+ }
134137
135138 // Resolve public IP via STUN Binding Request.
136139 // Skip srflx in explicit local override mode to avoid advertising
137140 // unreachable candidates that can win pair selection.
138- if configured_public_ip . is_none ( ) {
141+ if configured_public_ips . is_empty ( ) {
139142 if let Ok ( stun_addr) = tokio:: net:: lookup_host ( stun_server)
140143 . await
141144 . map ( |mut addrs| addrs. next ( ) )
142145 {
143146 if let Some ( stun_addr) = stun_addr {
144147 match super :: stun:: stun_binding ( stun_addr) . await {
145148 Some ( public_addr) => {
146- let srflx = Candidate :: server_reflexive ( public_addr, host_addr, "udp" )
147- . map_err ( |e| format ! ( "Failed to create srflx candidate: {}" , e) ) ?;
148- rtc. add_local_candidate ( srflx) ;
149- info ! (
150- "[webrtc:{}] Server-reflexive candidate: {} (via STUN {})" ,
151- & id[ ..8 ] ,
152- public_addr,
153- stun_server
154- ) ;
149+ if let Some ( host_addr) = primary_host_addr {
150+ let srflx = Candidate :: server_reflexive ( public_addr, host_addr, "udp" )
151+ . map_err ( |e| format ! ( "Failed to create srflx candidate: {}" , e) ) ?;
152+ rtc. add_local_candidate ( srflx) ;
153+ info ! (
154+ "[webrtc:{}] Server-reflexive candidate: {} (via STUN {})" ,
155+ & id[ ..8 ] ,
156+ public_addr,
157+ stun_server
158+ ) ;
159+ }
155160 }
156161 None => {
157162 warn ! (
@@ -178,26 +183,46 @@ impl WebRtcConnection {
178183
179184 let answer_json = serde_json:: to_value ( & answer) ?;
180185
186+ let sdp_str = answer_json. get ( "sdp" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) ;
187+ let ufrag = sdp_str
188+ . lines ( )
189+ . find_map ( |l| l. strip_prefix ( "a=ice-ufrag:" ) )
190+ . unwrap_or ( "" )
191+ . to_string ( ) ;
192+ if ufrag. is_empty ( ) {
193+ return Err ( "Failed to extract ice-ufrag from SDP answer — cannot register with UdpMux" . into ( ) ) ;
194+ }
195+
181196 // Create channels
182197 let ( audio_tx, audio_rx) = mpsc:: unbounded_channel :: < Bytes > ( ) ;
183198 let ( control_event_tx, control_rx) = mpsc:: unbounded_channel ( ) ;
184199 let ( control_cmd_tx, control_cmd_rx) = mpsc:: unbounded_channel ( ) ;
185200 let ( audio_out_tx, audio_out_rx) = mpsc:: unbounded_channel ( ) ;
201+ let ( mux_tx, mux_rx) = mpsc:: unbounded_channel ( ) ;
202+
203+ mux. register ( ufrag. clone ( ) , mux_tx) . await ;
186204
187205 let id_for_task = id. clone ( ) ;
188206 let control_event_tx_task = control_event_tx. clone ( ) ;
207+ let mux_for_task = mux. clone ( ) ;
208+ let ufrag_for_task = ufrag. clone ( ) ;
189209
190210 // Spawn the str0m event loop as a tokio task
191211 let task_handle = tokio:: spawn ( async move {
192212 if let Err ( e) = run_rtc_loop (
193213 id_for_task. clone ( ) ,
194214 rtc,
195215 socket,
196- host_addr,
216+ local_ips,
217+ bound_addr. port ( ) ,
218+ primary_host_addr. expect ( "primary_host_addr should always be populated by local_ips" ) ,
197219 audio_tx,
198220 control_event_tx_task,
199221 control_cmd_rx,
200222 audio_out_rx,
223+ mux_rx,
224+ mux_for_task,
225+ ufrag_for_task,
201226 )
202227 . await
203228 {
@@ -229,23 +254,47 @@ impl Drop for WebRtcConnection {
229254
230255// ── str0m Event Loop ────────────────────────────────────────────
231256
257+ struct UdpMuxGuard {
258+ mux : Arc < super :: multiplexer:: UdpMux > ,
259+ ufrag : String ,
260+ }
261+
262+ impl Drop for UdpMuxGuard {
263+ fn drop ( & mut self ) {
264+ let mux = self . mux . clone ( ) ;
265+ let ufrag = self . ufrag . clone ( ) ;
266+ tokio:: spawn ( async move {
267+ mux. unregister ( & ufrag) . await ;
268+ } ) ;
269+ }
270+ }
271+
232272/// The main Sans I/O event loop for a single WebRTC connection.
233273///
234274/// Runs until the ICE connection disconnects or an error occurs.
235275/// Uses tokio's `UdpSocket` for async I/O instead of blocking sockets.
236276async fn run_rtc_loop (
237277 id : String ,
238278 mut rtc : Rtc ,
239- socket : UdpSocket ,
240- local_addr : std:: net:: SocketAddr ,
279+ socket : Arc < UdpSocket > ,
280+ local_ips : Vec < std:: net:: IpAddr > ,
281+ bound_port : u16 ,
282+ primary_host_addr : std:: net:: SocketAddr ,
241283 audio_tx : mpsc:: UnboundedSender < Bytes > ,
242284 control_tx : mpsc:: UnboundedSender < TransportEvent > ,
243285 mut control_rx : mpsc:: UnboundedReceiver < TransportCommand > ,
244286 mut audio_out_rx : mpsc:: UnboundedReceiver < RtcInternalCmd > ,
287+ mut mux_rx : mpsc:: UnboundedReceiver < ( Bytes , std:: net:: SocketAddr ) > ,
288+ mux : Arc < super :: multiplexer:: UdpMux > ,
289+ ufrag : String ,
245290) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
246291 let tag = & id[ ..8 ] ;
247- let socket = Arc :: new ( socket) ;
248- let mut buf = vec ! [ 0u8 ; 2000 ] ;
292+ // socket is already Arc<UdpSocket> (shared from UdpMux); no re-wrapping needed.
293+
294+ let _mux_guard = UdpMuxGuard {
295+ mux : mux. clone ( ) ,
296+ ufrag : ufrag. clone ( ) ,
297+ } ;
249298
250299 // Opus decoder for incoming audio (browser → server)
251300 let mut opus_decoder: Option < opus:: Decoder > = None ;
@@ -333,24 +382,37 @@ async fn run_rtc_loop(
333382 tokio:: select! {
334383 biased;
335384
336- // Incoming UDP packet
337- result = socket . recv_from ( & mut buf ) => {
385+ // Incoming UDP packet from multiplexer
386+ result = mux_rx . recv ( ) => {
338387 match result {
339- Ok ( ( n, source) ) => {
340- let contents = & buf[ ..n] ;
388+ Some ( ( packet, source) ) => {
389+ let is_stun = packet. len( ) >= 20 && ( packet[ 0 ] == 0x00 || packet[ 0 ] == 0x01 ) ;
390+ if !is_stun {
391+ tracing:: trace!( "[webrtc:{}] Feeding RTP/other packet (len={}) from {} to str0m" , tag, packet. len( ) , source) ;
392+ } else {
393+ tracing:: debug!( "[webrtc:{}] Feeding STUN packet (len={}, type={:02x}{:02x}) from {} to str0m" , tag, packet. len( ) , packet[ 0 ] , packet[ 1 ] , source) ;
394+ }
395+ let dest_ip = if local_ips. contains( & source. ip( ) ) {
396+ source. ip( )
397+ } else {
398+ primary_host_addr. ip( )
399+ } ;
400+ let destination = std:: net:: SocketAddr :: new( dest_ip, bound_port) ;
401+
341402 let input = Input :: Receive (
342403 Instant :: now( ) ,
343404 Receive {
344405 proto: Protocol :: Udp ,
345406 source,
346- destination: local_addr ,
347- contents: contents . try_into( ) ?,
407+ destination,
408+ contents: ( & packet [ .. ] ) . try_into( ) ?,
348409 } ,
349410 ) ;
350411 rtc. handle_input( input) ?;
351412 }
352- Err ( e) => {
353- warn!( "[webrtc:{}] UDP recv error: {}" , tag, e) ;
413+ None => {
414+ warn!( "[webrtc:{}] mux_rx closed" , tag) ;
415+ return Ok ( ( ) ) ;
354416 }
355417 }
356418 }
0 commit comments