Skip to main content

rustrtc/transports/ice/
mod.rs

1pub mod conn;
2pub mod shared_tcp;
3pub mod shared_udp;
4pub mod stun;
5#[cfg(test)]
6mod tests;
7pub mod turn;
8pub mod upnp;
9
10// Re-export UPnP types
11pub use upnp::{
12    DEFAULT_LEASE_DURATION, DEFAULT_UPNP_DISCOVERY_TIMEOUT, MAX_LEASE_DURATION, MIN_LEASE_DURATION,
13    UpnpPortMapper,
14};
15
16use crate::config::{BufferDropStrategy, IceServer, IceTransportPolicy, RtcConfiguration};
17use crate::transports::ice::turn::{TurnClient, TurnCredentials};
18use crate::transports::{PacketReceiver, get_local_ip};
19use bytes::Bytes;
20use futures::future::BoxFuture;
21use futures::stream::{FuturesUnordered, StreamExt};
22use std::collections::{HashMap, VecDeque};
23use std::io::ErrorKind;
24use std::net::{IpAddr, SocketAddr};
25use std::sync::Arc;
26use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
27
28use std::time::{Duration, Instant};
29
30use anyhow::{Context, Result, anyhow, bail};
31use tokio::net::{TcpListener, TcpStream, UdpSocket, lookup_host};
32use tokio::sync::{Mutex, broadcast, mpsc, oneshot, watch};
33use tokio::time::timeout;
34use tracing::{debug, instrument, trace, warn};
35
36#[cfg(any(test, feature = "simulator"))]
37use self::stun::random_u32;
38use self::stun::{
39    StunAttribute, StunClass, StunDecoded, StunMessage, StunMethod, random_bytes, random_u64,
40};
41
42pub(crate) const MAX_STUN_MESSAGE: usize = 1500;
43#[cfg(any(test, feature = "simulator"))]
44static PACKET_LOSS_RATE: AtomicU32 = AtomicU32::new(u32::MAX);
45
46pub(crate) fn should_drop_packet() -> bool {
47    #[cfg(not(any(test, feature = "simulator")))]
48    return false;
49
50    #[cfg(any(test, feature = "simulator"))]
51    {
52        let mut rate = PACKET_LOSS_RATE.load(Ordering::Relaxed);
53        if rate == u32::MAX {
54            rate = std::env::var("RUSTRTC_PACKET_LOSS")
55                .ok()
56                .and_then(|s| s.parse::<f64>().ok())
57                .map(|f| (f * 100.0) as u32)
58                .unwrap_or(0);
59            PACKET_LOSS_RATE.store(rate, Ordering::Relaxed);
60        }
61
62        if rate == 0 {
63            return false;
64        }
65
66        let rand_val = random_u32() % 10000;
67        let drop = rand_val < rate;
68        if drop {
69            trace!("SIMULATOR: Dropping packet (rate={}%)", rate as f64 / 100.0);
70        }
71        drop
72    }
73}
74
75/// Simulator hook: delay STUN Binding responses so a nominally fast path can
76/// be made slower than a lower-priority one, reproducing the ICE nomination
77/// race seen across cascaded NAT (the controlling side picked a fast
78/// srflx/relay pair while the controlled side selected the host path).
79///
80/// `RUSTRTC_STUN_RESPOND_DELAY_MS=<ms>` delays every response sent from a
81/// direct (non-TURN) socket. Only used by tests.
82#[cfg(any(test, feature = "simulator"))]
83async fn simulate_stun_respond_delay(sender: &IceSocketWrapper) {
84    let spec = match std::env::var("RUSTRTC_STUN_RESPOND_DELAY_MS").ok() {
85        Some(s) => s,
86        None => return,
87    };
88    let Ok(ms) = spec.trim().parse::<u64>() else {
89        return;
90    };
91    if ms == 0 {
92        return;
93    }
94    if matches!(
95        sender,
96        IceSocketWrapper::Turn(_, _)
97            | IceSocketWrapper::TcpListener(_)
98            | IceSocketWrapper::TcpStream(_, _, _)
99    ) {
100        return;
101    }
102    trace!("SIMULATOR: delaying STUN response by {}ms", ms);
103    tokio::time::sleep(Duration::from_millis(ms)).await;
104}
105
106/// Statistics for monitoring buffer behavior
107#[derive(Debug)]
108struct BufferStats {
109    pub packets_received: AtomicU64,
110    pub packets_dropped: AtomicU64,
111    pub current_size: AtomicU32,
112    pub peak_size: AtomicU32,
113    pub last_log_time: parking_lot::Mutex<Instant>,
114}
115
116impl Default for BufferStats {
117    fn default() -> Self {
118        Self {
119            packets_received: AtomicU64::new(0),
120            packets_dropped: AtomicU64::new(0),
121            current_size: AtomicU32::new(0),
122            peak_size: AtomicU32::new(0),
123            last_log_time: parking_lot::Mutex::new(Instant::now()),
124        }
125    }
126}
127
128#[derive(Debug)]
129enum IceCommand {
130    StartGathering,
131    RunChecks,
132}
133
134#[derive(Debug, Clone)]
135pub struct IceTransport {
136    inner: Arc<IceTransportInner>,
137}
138
139pub(crate) struct IceTransportInner {
140    state: watch::Sender<IceTransportState>,
141    _state_rx_keeper: watch::Receiver<IceTransportState>,
142    gathering_state: watch::Sender<IceGathererState>,
143    /// Keeper receiver for the gathering_state watch channel. Without a live
144    /// receiver, `watch::Sender::send()` with zero receivers returns Err and
145    /// never stores the value — a late subscriber would miss the `Complete`
146    /// update and `wait_for_gathering_complete()` would hang forever. Every
147    /// other watch channel in this struct holds a keeper receiver for exactly
148    /// this reason.
149    _gathering_state_rx_keeper: watch::Receiver<IceGathererState>,
150    role: parking_lot::Mutex<IceRole>,
151    selected_pair: parking_lot::Mutex<Option<IceCandidatePair>>,
152    local_candidates: Mutex<Vec<IceCandidate>>,
153    remote_candidates: parking_lot::Mutex<Vec<IceCandidate>>,
154    gather_state: parking_lot::Mutex<IceGathererState>,
155    config: RtcConfiguration,
156    gatherer: IceGatherer,
157    local_parameters: parking_lot::Mutex<IceParameters>,
158    remote_parameters: parking_lot::Mutex<Option<IceParameters>>,
159    pending_transactions: parking_lot::Mutex<HashMap<[u8; 12], oneshot::Sender<StunDecoded>>>,
160    data_receiver: parking_lot::Mutex<Option<Arc<dyn PacketReceiver>>>,
161    /// Ring buffer for packets when no receiver is registered yet.
162    /// Uses VecDeque for efficient pop_front removal.
163    buffered_packets: parking_lot::Mutex<VecDeque<(Vec<u8>, SocketAddr)>>,
164    /// Statistics for monitoring buffer behavior
165    buffer_stats: Arc<BufferStats>,
166    selected_socket: watch::Sender<Option<IceSocketWrapper>>,
167    _socket_rx_keeper: watch::Receiver<Option<IceSocketWrapper>>,
168    selected_rtcp_socket: watch::Sender<Option<IceSocketWrapper>>,
169    _rtcp_socket_rx_keeper: watch::Receiver<Option<IceSocketWrapper>>,
170    selected_pair_notifier: watch::Sender<Option<IceCandidatePair>>,
171    _selected_pair_rx_keeper: watch::Receiver<Option<IceCandidatePair>>,
172    /// Reference epoch used to interpret `last_received_nanos`.
173    created_at: Instant,
174    /// Nanoseconds since `created_at` when the most recent packet arrived.
175    /// Stored atomically so the per-packet receive path records the timestamp
176    /// without taking a mutex.
177    last_received_nanos: AtomicU64,
178    candidate_tx: broadcast::Sender<IceCandidate>,
179    cmd_tx: mpsc::UnboundedSender<IceCommand>,
180    checking_pairs: Mutex<std::collections::HashSet<(SocketAddr, SocketAddr)>>,
181    /// Signals when the controlling-side nomination is complete.
182    /// `true` = nomination succeeded, `false` = nomination failed (but ICE is still connected).
183    /// Controlled side immediately sends `true` (no nomination to do).
184    nomination_complete: watch::Sender<Option<bool>>,
185    _nomination_complete_rx: watch::Receiver<Option<bool>>,
186    /// Guards against overlapping `run_turn_refresh` invocations: the refresh
187    /// timer tick skips when a previous refresh is still in flight instead of
188    /// cancelling it (which used to orphan pending transactions).
189    turn_refresh_in_progress: std::sync::atomic::AtomicBool,
190    /// Guards against overlapping UPnP mapping refreshes (same skip-on-in-flight
191    /// pattern as `turn_refresh_in_progress`).
192    upnp_refresh_in_progress: std::sync::atomic::AtomicBool,
193}
194
195impl std::fmt::Debug for IceTransportInner {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        f.debug_struct("IceTransportInner")
198            .field("state", &self.state)
199            .field("role", &self.role)
200            .field("selected_pair", &self.selected_pair)
201            .field("local_candidates", &self.local_candidates)
202            .field("remote_candidates", &self.remote_candidates)
203            .field("gather_state", &self.gather_state)
204            .field("config", &self.config)
205            .field("gatherer", &self.gatherer)
206            .field("local_parameters", &self.local_parameters)
207            .field("remote_parameters", &self.remote_parameters)
208            .field("pending_transactions", &self.pending_transactions)
209            .field("data_receiver", &"PacketReceiver")
210            .field("buffered_packets", &self.buffered_packets.lock().len())
211            .field("buffer_stats", &self.buffer_stats)
212            .field("selected_socket", &self.selected_socket)
213            .field("selected_rtcp_socket", &self.selected_rtcp_socket)
214            .field("selected_pair_notifier", &self.selected_pair_notifier)
215            .field("candidate_tx", &self.candidate_tx)
216            .field("cmd_tx", &self.cmd_tx)
217            .field("nomination_complete", &self.nomination_complete)
218            .finish()
219    }
220}
221
222struct IceTransportRunner {
223    inner: Arc<IceTransportInner>,
224    socket_rx: mpsc::UnboundedReceiver<IceSocketWrapper>,
225    candidate_rx: broadcast::Receiver<IceCandidate>,
226    cmd_rx: mpsc::UnboundedReceiver<IceCommand>,
227    state_rx: watch::Receiver<IceTransportState>,
228}
229
230impl IceTransportRunner {
231    async fn run(mut self) {
232        let mut interval = tokio::time::interval_at(
233            tokio::time::Instant::now() + Duration::from_secs(1),
234            Duration::from_secs(1),
235        );
236        // TURN refresh interval. Kept well under both the 300 s permission
237        // timeout AND typical UDP NAT mapping idle timeouts (~30 s on many
238        // carrier/CGNAT deployments): each Refresh is bidirectional traffic
239        // on the client<->TURN-server 5-tuple, so this also keeps that NAT
240        // mapping warm for relays that are gathered but not selected (and
241        // therefore carry no media ChannelData). 25 s is safely under all
242        // those budgets while staying cheap (tiny authenticated packets).
243        let mut turn_refresh_interval = tokio::time::interval_at(
244            tokio::time::Instant::now() + Duration::from_secs(25),
245            Duration::from_secs(25),
246        );
247        // UPnP mapping refresh interval. Long-lived sessions (> 1 hour) outlive
248        // the default router lease (3600s); re-issuing AddPortMapping keeps the
249        // mapping alive so inbound P2P traffic isn't lost mid-call.
250        let mut upnp_refresh_interval = tokio::time::interval_at(
251            tokio::time::Instant::now() + self.inner.config.upnp_refresh_interval,
252            self.inner.config.upnp_refresh_interval,
253        );
254        let mut read_futures: FuturesUnordered<BoxFuture<'static, ()>> = FuturesUnordered::new();
255        let mut gathering_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
256        let mut turn_refresh_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
257        let mut upnp_refresh_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
258
259        loop {
260            tokio::select! {
261                res = self.state_rx.changed() => {
262                    if res.is_err() {
263                        break;
264                    }
265                    if matches!(*self.state_rx.borrow(), IceTransportState::Closed | IceTransportState::Failed) {
266                        break;
267                    }
268                }
269                Some(socket) = self.socket_rx.recv() => {
270                    match socket {
271                        IceSocketWrapper::Udp(s) => {
272                            read_futures.push(Box::pin(Self::run_udp_read_loop(s, self.inner.clone())));
273                        }
274                        IceSocketWrapper::SharedUdp(handle) => {
275                            read_futures.push(Box::pin(Self::run_shared_udp_read_loop(handle, self.inner.clone())));
276                        }
277                        IceSocketWrapper::TcpListener(l) => {
278                            read_futures.push(Box::pin(Self::run_tcp_listen_loop(l, self.inner.clone())));
279                        }
280                        IceSocketWrapper::TcpStream(read, write, peer) => {
281                            read_futures.push(Box::pin(Self::run_tcp_read_loop(
282                                read,
283                                write,
284                                peer,
285                                self.inner.clone(),
286                            )));
287                        }
288                        IceSocketWrapper::Turn(c, addr) => {
289                            read_futures.push(Box::pin(Self::run_turn_read_loop(c, addr, self.inner.clone())));
290                        }
291                    }
292                }
293                res = self.candidate_rx.recv() => {
294                    match res {
295                        Ok(_) => {
296                            let inner = self.inner.clone();
297                            read_futures.push(Box::pin(async move {
298                                perform_connectivity_checks_async(inner).await;
299                            }));
300                        }
301                        Err(broadcast::error::RecvError::Closed) => break,
302                        Err(broadcast::error::RecvError::Lagged(_)) => continue,
303                    }
304                }
305                Some(cmd) = self.cmd_rx.recv() => {
306                    trace!("Runner received command: {:?}", cmd);
307                    match cmd {
308                        IceCommand::StartGathering => {
309                            let inner = self.inner.clone();
310                            gathering_future = Box::pin(async move {
311                                if let Err(e) = inner.gatherer.gather().await {
312                                    debug!("Gathering failed: {}", e);
313                                }
314                                {
315                                    let mut buffer = inner.local_candidates.lock().await;
316                                    *buffer = inner.gatherer.local_candidates();
317                                }
318                                *inner.gather_state.lock() = IceGathererState::Complete;
319                                let _ = inner.gathering_state.send(IceGathererState::Complete);
320                            });
321                        }
322                        IceCommand::RunChecks => {
323                            let inner = self.inner.clone();
324                            // Spawn connectivity checks in a separate task so they don't
325                            // block the runner's event loop. This is critical for TCP
326                            // candidates: the check may block on TcpStream::connect while
327                            // the runner still needs to process pending socket_rx messages
328                            // (e.g. TcpListener accept loops) to complete the connection.
329                            let rt_handle = inner.config.runtime_handle.clone();
330                            crate::spawn_rtc(
331                                rt_handle.as_ref(),
332                                tracing::Span::current(),
333                                async move {
334                                    perform_connectivity_checks_async(inner).await;
335                                },
336                            );
337                        }
338                    }
339                }
340                _ = interval.tick() => {
341                    if let Some(f) = Self::run_keepalive_tick(&self.inner).await {
342                        read_futures.push(f);
343                    }
344                }
345                _ = turn_refresh_interval.tick() => {
346                    // Only start a new refresh if the previous one has
347                    // completed. If still running (e.g. server slow), skip
348                    // this tick rather than reassigning the future, which
349                    // would cancel the in-flight refresh and orphan its
350                    // pending transactions. The in-progress flag is cleared
351                    // by `run_turn_refresh` on every exit path.
352                    if !self
353                        .inner
354                        .turn_refresh_in_progress
355                        .load(std::sync::atomic::Ordering::SeqCst)
356                    {
357                        let inner = self.inner.clone();
358                        turn_refresh_future = Box::pin(async move {
359                            Self::run_turn_refresh(&inner).await;
360                        });
361                    }
362                }
363                _ = &mut turn_refresh_future => {
364                    turn_refresh_future = Box::pin(futures::future::pending());
365                }
366                _ = upnp_refresh_interval.tick() => {
367                    // UPnP SOAP calls can be slow (hundreds of ms). Run them in
368                    // a detached future so the runner's 1s keepalive tick is
369                    // never blocked. Guard with an in-progress flag so slow
370                    // routers don't pile up overlapping refreshes.
371                    if !self
372                        .inner
373                        .upnp_refresh_in_progress
374                        .swap(true, std::sync::atomic::Ordering::SeqCst)
375                    {
376                        let inner = self.inner.clone();
377                        upnp_refresh_future = Box::pin(async move {
378                            inner.gatherer.renew_upnp_mappings().await;
379                            inner
380                                .upnp_refresh_in_progress
381                                .store(false, std::sync::atomic::Ordering::SeqCst);
382                        });
383                    }
384                }
385                _ = &mut upnp_refresh_future => {
386                    upnp_refresh_future = Box::pin(futures::future::pending());
387                }
388                Some(_) = read_futures.next() => {
389                    // Read loop finished
390                }
391                _ = &mut gathering_future => {
392                    gathering_future = Box::pin(futures::future::pending());
393                }
394            }
395        }
396    }
397
398    async fn run_udp_read_loop(socket: Arc<UdpSocket>, inner: Arc<IceTransportInner>) {
399        let mut buf = [0u8; 1500];
400        let mut marshal_buf = Vec::with_capacity(1500);
401        let mut state_rx = inner.state.subscribe();
402        let sender = IceSocketWrapper::Udp(socket.clone());
403        trace!("Read loop started for {:?}", socket.local_addr());
404        loop {
405            tokio::select! {
406                res = socket.readable() => {
407                    if let Err(e) = res {
408                        debug!("Socket readable wait error: {}", e);
409                        break;
410                    }
411
412                    loop {
413                        let (len, addr) = match socket.try_recv_from(&mut buf) {
414                            Ok(v) => v,
415                            Err(e) if e.kind() == ErrorKind::WouldBlock => {
416                                break;
417                            }
418                            Err(e) => {
419                                debug!("Socket recv error: {}", e);
420                                return;
421                            }
422                        };
423
424                        let packet = &buf[..len];
425                        if len > 0 {
426                            handle_packet(
427                                packet,
428                                addr,
429                                inner.clone(),
430                                sender.clone(),
431                                &mut marshal_buf,
432                            )
433                            .await;
434                        }
435                    }
436                }
437                res = state_rx.changed() => {
438                    if res.is_err() || matches!(*state_rx.borrow(), IceTransportState::Closed | IceTransportState::Failed) {
439                        // routine teardown; one line per read loop is noisy at debug
440                        trace!("Read loop stopping (IceTransport Closed or Failed)");
441                        break;
442                    }
443                }
444            }
445        }
446    }
447
448    /// Read loop for a shared (muxed) UDP socket. Packets arrive via the
449    /// handle's receiver (fed by the shared demux loop); outbound replies go out
450    /// through the same handle.
451    async fn run_shared_udp_read_loop(
452        handle: Arc<shared_udp::SharedUdpHandle>,
453        inner: Arc<IceTransportInner>,
454    ) {
455        let mut state_rx = inner.state.subscribe();
456        let mut marshal_buf = Vec::with_capacity(1500);
457        let sender = IceSocketWrapper::SharedUdp(handle.clone());
458        trace!("Shared UDP read loop started");
459        loop {
460            let packet_opt = tokio::select! {
461                biased;
462                res = state_rx.changed() => {
463                    if res.is_err()
464                        || matches!(
465                            *state_rx.borrow(),
466                            IceTransportState::Closed | IceTransportState::Failed
467                        )
468                    {
469                        debug!("Shared UDP read loop stopping (IceTransport Closed or Failed)");
470                        break;
471                    }
472                    continue;
473                }
474                pkt = handle.recv() => pkt,
475            };
476            match packet_opt {
477                Some((packet, addr)) => {
478                    handle_packet(
479                        &packet,
480                        addr,
481                        inner.clone(),
482                        sender.clone(),
483                        &mut marshal_buf,
484                    )
485                    .await;
486                }
487                None => break,
488            }
489        }
490    }
491
492    async fn run_turn_read_loop(
493        client: Arc<TurnClient>,
494        relayed_addr: SocketAddr,
495        inner: Arc<IceTransportInner>,
496    ) {
497        let mut buf = [0u8; 1500];
498        let mut marshal_buf = Vec::with_capacity(1500);
499        let mut state_rx = inner.state.subscribe();
500        trace!("Read loop started for TURN client {}", relayed_addr);
501        loop {
502            let recv_future = async { client.recv(&mut buf).await };
503
504            tokio::select! {
505                result = recv_future => {
506                    match result {
507                        Ok(len) => {
508                            if len > 0 {
509                                IceTransport::handle_turn_packet(&buf[..len], &inner, &client, relayed_addr, &mut marshal_buf).await;
510                            }
511                        }
512                        Err(e) => {
513                            if e.to_string().contains("deadline has elapsed") {
514                                continue;
515                            }
516                            debug!("TURN client recv error: {}", e);
517                            break;
518                        }
519                    }
520                }
521                res = state_rx.changed() => {
522                    if res.is_err() || matches!(*state_rx.borrow(), IceTransportState::Closed | IceTransportState::Failed) {
523                        trace!("TURN Read loop stopping (IceTransport Closed or Failed)");
524                        break;
525                    }
526                }
527            }
528        }
529    }
530
531    async fn run_tcp_listen_loop(listener: Arc<TcpListener>, inner: Arc<IceTransportInner>) {
532        let mut state_rx = inner.state.subscribe();
533        let local_addr = match listener.local_addr() {
534            Ok(a) => a,
535            Err(e) => {
536                debug!("TCP listener local_addr error: {}", e);
537                return;
538            }
539        };
540        trace!("TCP listen loop started for {:?}", local_addr);
541        loop {
542            tokio::select! {
543                accept_res = listener.accept() => {
544                    match accept_res {
545                        Ok((stream, peer_addr)) => {
546                            trace!("TCP accepted connection from {}", peer_addr);
547                            let wrapper = split_tcp_stream(stream, peer_addr);
548                            inner.gatherer.store_tcp_stream(local_addr, wrapper.clone());
549                            let _ = inner.gatherer.socket_tx.send(wrapper);
550                        }
551                        Err(e) => {
552                            debug!("TCP accept error: {}", e);
553                            break;
554                        }
555                    }
556                }
557                res = state_rx.changed() => {
558                    if res.is_err() || matches!(*state_rx.borrow(), IceTransportState::Closed | IceTransportState::Failed) {
559                        debug!("TCP listen loop stopping (IceTransport Closed or Failed)");
560                        break;
561                    }
562                }
563            }
564        }
565    }
566
567    async fn run_tcp_read_loop(
568        read: Arc<Mutex<TcpReadHalf>>,
569        write: Arc<Mutex<TcpWriteHalf>>,
570        peer_addr: SocketAddr,
571        inner: Arc<IceTransportInner>,
572    ) {
573        let mut buf = [0u8; 65_535];
574        let mut marshal_buf = Vec::with_capacity(1500);
575        let mut state_rx = inner.state.subscribe();
576        let sender = IceSocketWrapper::TcpStream(read, write, peer_addr);
577        trace!("TCP read loop started for peer {}", peer_addr);
578        loop {
579            tokio::select! {
580                result = sender.recv_from(&mut buf) => {
581                    match result {
582                        Ok((len, addr)) => {
583                            if len > 0 {
584                                handle_packet(
585                                    &buf[..len],
586                                    addr,
587                                    inner.clone(),
588                                    sender.clone(),
589                                    &mut marshal_buf,
590                                )
591                                .await;
592                            }
593                        }
594                        Err(e) => {
595                            debug!("TCP recv error from {}: {}", peer_addr, e);
596                            break;
597                        }
598                    }
599                }
600                res = state_rx.changed() => {
601                    if res.is_err() || matches!(*state_rx.borrow(), IceTransportState::Closed | IceTransportState::Failed) {
602                        debug!("TCP read loop stopping (IceTransport Closed or Failed)");
603                        break;
604                    }
605                }
606            }
607        }
608    }
609
610    /// Returns an optional cleanup future that should be pushed into `read_futures`
611    /// by the caller. The future waits up to 5 s for the keepalive response then
612    /// removes the transaction from `pending_transactions`.
613    async fn run_keepalive_tick(inner: &Arc<IceTransportInner>) -> Option<BoxFuture<'static, ()>> {
614        let state = *inner.state.borrow();
615        if state == IceTransportState::Connected || state == IceTransportState::Disconnected {
616            if inner.config.transport_mode == crate::TransportMode::WebRtc {
617                let last_nanos = inner.last_received_nanos.load(Ordering::Relaxed);
618                let now_nanos = inner.created_at.elapsed().as_nanos() as u64;
619                let elapsed = Duration::from_nanos(now_nanos.saturating_sub(last_nanos));
620                let ice_conn_timeout = inner.config.ice_connection_timeout;
621                let tcp_selected = inner
622                    .selected_pair
623                    .lock()
624                    .as_ref()
625                    .map(|pair| pair.local.transport == "tcp")
626                    .unwrap_or(false);
627                // ICE-TCP recv-only peers (e.g. WHEP) may not send STUN for several seconds
628                // while DTLS/SRTP comes up; do not flap to Disconnected on the UDP 5s heuristic.
629                let disconnect_threshold = if tcp_selected {
630                    ice_conn_timeout.saturating_sub(Duration::from_secs(1))
631                } else {
632                    inner.config.ice_disconnect_threshold
633                };
634                if elapsed > ice_conn_timeout {
635                    let _ = inner.state.send(IceTransportState::Failed);
636                } else if elapsed > disconnect_threshold {
637                    if state != IceTransportState::Disconnected {
638                        let _ = inner.state.send(IceTransportState::Disconnected);
639                    }
640                } else if state == IceTransportState::Disconnected {
641                    let _ = inner.state.send(IceTransportState::Connected);
642                }
643            }
644
645            // Send Keepalive
646            let pair_opt = inner.selected_pair.lock().clone();
647            if let Some(pair) = pair_opt {
648                let socket = inner
649                    ._socket_rx_keeper
650                    .borrow()
651                    .clone()
652                    .or_else(|| resolve_socket(inner, &pair));
653                if let Some(socket) = socket {
654                    let tx_id = random_bytes::<12>();
655                    let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
656
657                    let remote_params = inner.remote_parameters.lock().clone();
658                    if let Some(params) = remote_params {
659                        let username = format!(
660                            "{}:{}",
661                            params.username_fragment,
662                            inner.local_parameters.lock().username_fragment
663                        );
664                        msg.attributes.push(StunAttribute::Username(username));
665                        msg.attributes
666                            .push(StunAttribute::Priority(pair.local.priority));
667
668                        if let Ok(bytes) = msg.encode(Some(params.password.as_bytes()), true) {
669                            // Register transaction to avoid "Unmatched transaction" logs
670                            let (tx, rx) = oneshot::channel();
671                            {
672                                let mut map = inner.pending_transactions.lock();
673                                map.insert(tx_id, tx);
674                            }
675
676                            let inner_weak = Arc::downgrade(inner);
677                            let cleanup: BoxFuture<'static, ()> = Box::pin(async move {
678                                let _ = timeout(Duration::from_secs(5), rx).await;
679                                if let Some(inner) = inner_weak.upgrade() {
680                                    let mut map = inner.pending_transactions.lock();
681                                    map.remove(&tx_id);
682                                }
683                            });
684
685                            let _ = socket.send_to(&bytes, pair.remote.address).await;
686                            return Some(cleanup);
687                        }
688                    } else if inner.config.transport_mode != crate::TransportMode::WebRtc
689                        && let Ok(bytes) = msg.encode(None, false)
690                    {
691                        let _ = socket.send_to(&bytes, pair.remote.address).await;
692                    }
693                }
694            }
695        }
696        None
697    }
698
699    /// Periodically refresh TURN allocations, permissions, and channel bindings
700    /// to prevent them from expiring. Per RFC 5766:
701    ///   - Allocation lifetime: 600s (default), refresh before expiry
702    ///   - Permission lifetime: 300s, must be refreshed
703    ///   - ChannelBind lifetime: 600s, must be refreshed
704    ///
705    /// This runs every ~25s — well under all three timeouts AND under typical
706    /// UDP NAT mapping idle timeouts, so the client<->TURN-server 5-tuple stays
707    /// mapped even for relays that carry no media (the bidirectional Refresh
708    /// traffic refreshes the NAT). See the interval setup in `run`.
709    ///
710    /// Each request is awaited directly (no spawning) so that a 401/438
711    /// stale-nonce response is detected immediately and the nonce is refreshed
712    /// before retrying — preventing silent refresh failures that eventually let
713    /// ChannelBindings expire and cause SCTP disconnects.
714    async fn run_turn_refresh(inner: &Arc<IceTransportInner>) {
715        // Acquire the in-progress guard. If a previous refresh is somehow still
716        // running (e.g. the runner raced two ticks), bail out instead of running
717        // two concurrent refreshes that could interleave nonce updates.
718        if inner
719            .turn_refresh_in_progress
720            .swap(true, std::sync::atomic::Ordering::SeqCst)
721        {
722            return;
723        }
724        // RAII: guarantees the flag is cleared on every exit path, including
725        // early returns and panics, so the timer never deadlocks on refresh.
726        struct RefreshGuard<'a>(&'a std::sync::atomic::AtomicBool);
727        impl Drop for RefreshGuard<'_> {
728            fn drop(&mut self) {
729                self.0.store(false, std::sync::atomic::Ordering::SeqCst);
730            }
731        }
732        let _guard = RefreshGuard(&inner.turn_refresh_in_progress);
733
734        let state = *inner.state.borrow();
735        if state != IceTransportState::Connected && state != IceTransportState::Disconnected {
736            return;
737        }
738
739        let all_clients: Vec<(SocketAddr, Arc<TurnClient>)> = {
740            let clients = inner.gatherer.turn_clients.lock();
741            clients.iter().map(|(k, v)| (*k, v.clone())).collect()
742        };
743
744        if all_clients.is_empty() {
745            return;
746        }
747
748        let pair_opt = inner.selected_pair.lock().clone();
749
750        let remote_addr_for_perm = pair_opt.as_ref().map(|p| p.remote.address);
751
752        for (relay_local_addr, client) in all_clients {
753            Self::refresh_one_turn_client(inner, relay_local_addr, &client, remote_addr_for_perm)
754                .await;
755        }
756    }
757
758    async fn refresh_one_turn_client(
759        inner: &Arc<IceTransportInner>,
760        _relay_local_addr: SocketAddr,
761        client: &Arc<TurnClient>,
762        remote_addr_opt: Option<SocketAddr>,
763    ) {
764        async fn send_and_await_inner(
765            client: &Arc<TurnClient>,
766            inner: &Arc<IceTransportInner>,
767            bytes: Vec<u8>,
768            tx_id: [u8; 12],
769        ) -> Option<StunDecoded> {
770            let (tx, rx) = oneshot::channel();
771            inner.pending_transactions.lock().insert(tx_id, tx);
772            if let Err(e) = client.send(&bytes).await {
773                debug!("TURN refresh send failed: {}", e);
774                inner.pending_transactions.lock().remove(&tx_id);
775                return None;
776            }
777            match timeout(Duration::from_secs(5), rx).await {
778                Ok(Ok(msg)) => Some(msg),
779                _ => {
780                    inner.pending_transactions.lock().remove(&tx_id);
781                    None
782                }
783            }
784        }
785
786        // 1. Refresh the allocation (extends lifetime).
787        //    On 401/438 update the nonce and retry once.
788        'alloc: for attempt in 0..2u8 {
789            match client.create_refresh_packet().await {
790                Ok((bytes, tx_id)) => {
791                    match send_and_await_inner(client, inner, bytes, tx_id).await {
792                        Some(msg) if msg.class == StunClass::SuccessResponse => {
793                            trace!("TURN allocation refreshed successfully");
794                            break 'alloc;
795                        }
796                        Some(msg)
797                            if matches!(msg.error_code, Some(401) | Some(438)) && attempt == 0 =>
798                        {
799                            // Stale nonce: update and retry
800                            if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
801                                debug!(
802                                    "TURN Refresh got {}: updating nonce, retrying",
803                                    msg.error_code.unwrap_or(0)
804                                );
805                                client.update_nonce(realm, nonce).await;
806                            }
807                            continue 'alloc;
808                        }
809                        Some(msg) => {
810                            debug!("TURN Refresh failed: error={:?}", msg.error_code);
811                        }
812                        None => {
813                            debug!("TURN Refresh timeout or send error");
814                        }
815                    }
816                }
817                Err(e) => debug!("TURN Refresh packet creation failed: {}", e),
818            }
819            break;
820        }
821
822        // 2. Refresh permission for the remote peer (if known).
823        //    This keeps the relay→peer path warm even when the selected pair uses a
824        //    host/srflx candidate, so that the relay path is available as a fallback.
825        if let Some(remote_addr) = remote_addr_opt {
826            'perm: for attempt in 0..2u8 {
827                match client.create_permission_packet(remote_addr).await {
828                    Ok((bytes, tx_id)) => {
829                        match send_and_await_inner(client, inner, bytes, tx_id).await {
830                            Some(msg) if msg.class == StunClass::SuccessResponse => {
831                                trace!("TURN permission refreshed for {}", remote_addr);
832                                break 'perm;
833                            }
834                            Some(msg)
835                                if matches!(msg.error_code, Some(401) | Some(438))
836                                    && attempt == 0 =>
837                            {
838                                if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
839                                    debug!(
840                                        "TURN CreatePermission got {}: updating nonce, retrying",
841                                        msg.error_code.unwrap_or(0)
842                                    );
843                                    client.update_nonce(realm, nonce).await;
844                                }
845                                continue 'perm;
846                            }
847                            Some(msg) => {
848                                debug!(
849                                    "TURN CreatePermission refresh failed: error={:?}",
850                                    msg.error_code
851                                );
852                            }
853                            None => {
854                                debug!("TURN CreatePermission refresh timeout or send error");
855                            }
856                        }
857                    }
858                    Err(e) => debug!("TURN CreatePermission packet creation failed: {}", e),
859                }
860                break;
861            }
862        }
863
864        // 3. Refresh channel bindings for all bound peers.
865        //    On 401/438 update the nonce and retry once per channel.
866        let bound_peers = client.bound_peers().await;
867        let num_bindings = bound_peers.len();
868        for peer in bound_peers {
869            if let Some(channel) = client.get_channel(peer).await {
870                'chan: for attempt in 0..2u8 {
871                    match client.create_channel_rebind_packet(peer, channel).await {
872                        Ok((bytes, tx_id)) => {
873                            match send_and_await_inner(client, inner, bytes, tx_id).await {
874                                Some(msg) if msg.class == StunClass::SuccessResponse => {
875                                    trace!(
876                                        "TURN ChannelBind refreshed: {} -> ch {}",
877                                        peer, channel
878                                    );
879                                    break 'chan;
880                                }
881                                Some(msg)
882                                    if matches!(msg.error_code, Some(401) | Some(438))
883                                        && attempt == 0 =>
884                                {
885                                    if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
886                                        debug!(
887                                            "TURN ChannelBind got {}: updating nonce, retrying ch {}",
888                                            msg.error_code.unwrap_or(0),
889                                            channel
890                                        );
891                                        client.update_nonce(realm, nonce).await;
892                                    }
893                                    continue 'chan;
894                                }
895                                Some(msg) => {
896                                    debug!(
897                                        "TURN ChannelBind refresh failed: ch={} error={:?}",
898                                        channel, msg.error_code
899                                    );
900                                }
901                                None => {
902                                    debug!(
903                                        "TURN ChannelBind refresh timeout or send error: ch={}",
904                                        channel
905                                    );
906                                }
907                            }
908                        }
909                        Err(e) => {
910                            debug!("TURN ChannelBind refresh packet creation failed: {}", e);
911                        }
912                    }
913                    break;
914                }
915            }
916        }
917
918        debug!(
919            "TURN refresh done: allocation + {} permission + {} channel bindings",
920            if remote_addr_opt.is_some() { 1 } else { 0 },
921            num_bindings
922        );
923    }
924}
925
926impl IceTransport {
927    pub fn new(config: RtcConfiguration) -> (Self, impl std::future::Future<Output = ()> + Send) {
928        let (candidate_tx, _) = broadcast::channel(100);
929        let (socket_tx, socket_rx) = tokio::sync::mpsc::unbounded_channel();
930        let gatherer = IceGatherer::new(config.clone(), candidate_tx.clone(), socket_tx);
931        let (state_tx, state_rx) = watch::channel(IceTransportState::New);
932        let runner_state_rx = state_tx.subscribe();
933        let (gathering_state_tx, gathering_state_rx) = watch::channel(IceGathererState::New);
934        let (selected_socket_tx, selected_socket_rx) = watch::channel(None);
935        let (selected_rtcp_socket_tx, selected_rtcp_socket_rx) = watch::channel(None);
936        let (selected_pair_tx, selected_pair_rx) = watch::channel(None);
937        let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
938        let (nomination_complete_tx, nomination_complete_rx) = watch::channel(None);
939
940        let inner = IceTransportInner {
941            state: state_tx,
942            _state_rx_keeper: state_rx,
943            gathering_state: gathering_state_tx,
944            _gathering_state_rx_keeper: gathering_state_rx,
945            role: parking_lot::Mutex::new(IceRole::Controlled),
946            selected_pair: parking_lot::Mutex::new(None),
947            local_candidates: Mutex::new(Vec::new()),
948            remote_candidates: parking_lot::Mutex::new(Vec::new()),
949            gather_state: parking_lot::Mutex::new(IceGathererState::New),
950            config: config.clone(),
951            gatherer,
952            local_parameters: parking_lot::Mutex::new(IceParameters::generate()),
953            remote_parameters: parking_lot::Mutex::new(None),
954            pending_transactions: parking_lot::Mutex::new(HashMap::new()),
955            data_receiver: parking_lot::Mutex::new(None),
956            buffered_packets: parking_lot::Mutex::new(VecDeque::new()),
957            selected_socket: selected_socket_tx,
958            _socket_rx_keeper: selected_socket_rx,
959            selected_rtcp_socket: selected_rtcp_socket_tx,
960            _rtcp_socket_rx_keeper: selected_rtcp_socket_rx,
961            selected_pair_notifier: selected_pair_tx,
962            _selected_pair_rx_keeper: selected_pair_rx,
963            created_at: Instant::now(),
964            last_received_nanos: AtomicU64::new(0),
965            candidate_tx: candidate_tx.clone(),
966            cmd_tx,
967            checking_pairs: Mutex::new(std::collections::HashSet::new()),
968            nomination_complete: nomination_complete_tx,
969            _nomination_complete_rx: nomination_complete_rx,
970            turn_refresh_in_progress: std::sync::atomic::AtomicBool::new(false),
971            upnp_refresh_in_progress: std::sync::atomic::AtomicBool::new(false),
972            buffer_stats: Arc::new(BufferStats::default()),
973        };
974        let inner = Arc::new(inner);
975        inner.gatherer.set_transport(Arc::downgrade(&inner));
976
977        let runner = IceTransportRunner {
978            inner: inner.clone(),
979            socket_rx,
980            candidate_rx: candidate_tx.subscribe(),
981            cmd_rx,
982            state_rx: runner_state_rx,
983        };
984
985        (Self { inner }, runner.run())
986    }
987
988    pub fn state(&self) -> IceTransportState {
989        *self.inner.state.borrow()
990    }
991
992    pub fn subscribe_state(&self) -> watch::Receiver<IceTransportState> {
993        self.inner.state.subscribe()
994    }
995
996    pub fn subscribe_gathering_state(&self) -> watch::Receiver<IceGathererState> {
997        self.inner.gathering_state.subscribe()
998    }
999
1000    pub fn subscribe_candidates(&self) -> broadcast::Receiver<IceCandidate> {
1001        self.inner.candidate_tx.subscribe()
1002    }
1003
1004    pub fn subscribe_selected_socket(&self) -> watch::Receiver<Option<IceSocketWrapper>> {
1005        self.inner.selected_socket.subscribe()
1006    }
1007
1008    pub(crate) fn subscribe_selected_rtcp_socket(
1009        &self,
1010    ) -> watch::Receiver<Option<IceSocketWrapper>> {
1011        self.inner.selected_rtcp_socket.subscribe()
1012    }
1013
1014    pub fn subscribe_selected_pair(&self) -> watch::Receiver<Option<IceCandidatePair>> {
1015        self.inner.selected_pair_notifier.subscribe()
1016    }
1017
1018    /// Subscribe to the nomination-complete signal.
1019    /// Yields `Some(true)` when nomination succeeds, `Some(false)` when it fails.
1020    /// The controlled side yields `Some(true)` immediately (it has no nomination to perform).
1021    pub fn subscribe_nomination_complete(&self) -> watch::Receiver<Option<bool>> {
1022        self.inner.nomination_complete.subscribe()
1023    }
1024
1025    /// When the controlling peer has no local TCP candidates it may connect inbound
1026    /// without sending USE-CANDIDATE. Complete nomination once a passive TCP stream exists.
1027    pub fn nudge_passive_tcp_nomination(&self) {
1028        if *self.inner.role.lock() != IceRole::Controlled {
1029            return;
1030        }
1031        if self.inner.nomination_complete.borrow().is_some() {
1032            return;
1033        }
1034        let inner = self.inner.clone();
1035        debug!("ICE: nudging passive TCP nomination (controlled, awaiting inbound TCP)");
1036        let rt_handle = inner.config.runtime_handle.clone();
1037        crate::spawn_rtc(
1038            rt_handle.as_ref(),
1039            tracing::Span::current(),
1040            async move {
1041                let streams: Vec<_> = inner
1042                    .gatherer
1043                    .tcp_streams
1044                    .lock()
1045                    .values()
1046                    .cloned()
1047                    .collect();
1048                for wrapper in streams {
1049                    if let IceSocketWrapper::TcpStream(_, _, peer) = wrapper {
1050                        complete_controlled_inbound_tcp_nomination(&wrapper, peer, inner).await;
1051                        return;
1052                    }
1053                }
1054            },
1055        );
1056    }
1057
1058    pub fn gather_state(&self) -> IceGathererState {
1059        self.inner.gatherer.state()
1060    }
1061
1062    pub fn role(&self) -> IceRole {
1063        *self.inner.role.lock()
1064    }
1065
1066    pub fn local_candidates(&self) -> Vec<IceCandidate> {
1067        self.inner.gatherer.local_candidates()
1068    }
1069
1070    pub(crate) fn local_rtcp_addr(&self) -> Option<SocketAddr> {
1071        self.inner
1072            .gatherer
1073            .local_candidates()
1074            .into_iter()
1075            .find(|candidate| candidate.component == 2)
1076            .map(|candidate| candidate.address)
1077    }
1078
1079    pub fn remote_candidates(&self) -> Vec<IceCandidate> {
1080        self.inner.remote_candidates.lock().clone()
1081    }
1082
1083    pub fn local_parameters(&self) -> IceParameters {
1084        self.inner.local_parameters.lock().clone()
1085    }
1086
1087    pub fn set_remote_parameters(&self, params: IceParameters) {
1088        *self.inner.remote_parameters.lock() = Some(params);
1089    }
1090
1091    fn start_keepalive(&self) {
1092        // Handled by runner
1093    }
1094
1095    pub fn start_gathering(&self) -> Result<()> {
1096        {
1097            let mut state = self.inner.gather_state.lock();
1098            if *state == IceGathererState::Complete || *state == IceGathererState::Gathering {
1099                return Ok(());
1100            }
1101            *state = IceGathererState::Gathering;
1102            let _ = self.inner.gathering_state.send(IceGathererState::Gathering);
1103        }
1104
1105        let _ = self.inner.cmd_tx.send(IceCommand::StartGathering);
1106        Ok(())
1107    }
1108
1109    pub fn start(&self, remote: IceParameters) -> Result<()> {
1110        self.start_gathering()?;
1111        self.start_keepalive();
1112        {
1113            let mut params = self.inner.remote_parameters.lock();
1114            *params = Some(remote);
1115        }
1116        if let Err(e) = self.inner.state.send(IceTransportState::Checking) {
1117            debug!("start: failed to set state to Checking: {}", e);
1118        }
1119        self.try_connectivity_checks();
1120        Ok(())
1121    }
1122
1123    pub async fn start_direct(&self, remote_addr: SocketAddr) -> Result<()> {
1124        self.start_gathering()?;
1125        self.start_keepalive();
1126
1127        // Wait for a suitable local candidate
1128        // If remote is not loopback, we prefer a non-loopback local candidate to avoid os error 49 (EADDRNOTAVAIL)
1129        let mut rx = self.subscribe_candidates();
1130        let start = Instant::now();
1131        let timeout_dur = Duration::from_secs(2);
1132
1133        let is_suitable = |c: &IceCandidate| -> bool {
1134            if !remote_addr.ip().is_loopback() && c.address.ip().is_loopback() {
1135                return false;
1136            }
1137            true
1138        };
1139
1140        let mut best_local: Option<IceCandidate> = None;
1141
1142        // 1. Check existing candidates
1143        {
1144            let candidates = self.inner.gatherer.local_candidates();
1145            for c in candidates {
1146                if is_suitable(&c) {
1147                    best_local = Some(c);
1148                    break;
1149                }
1150            }
1151        }
1152
1153        // 2. If not found, wait for more
1154        if best_local.is_none() {
1155            loop {
1156                let remaining = timeout_dur
1157                    .checked_sub(start.elapsed())
1158                    .unwrap_or(Duration::ZERO);
1159                if remaining.is_zero() {
1160                    break;
1161                }
1162
1163                match timeout(remaining, rx.recv()).await {
1164                    Ok(Ok(c)) => {
1165                        if is_suitable(&c) {
1166                            best_local = Some(c);
1167                            break;
1168                        }
1169                    }
1170                    _ => break,
1171                }
1172            }
1173        }
1174
1175        // 3. Fallback to any candidate
1176        let local = if let Some(best) = best_local {
1177            best
1178        } else if let Some(first) = self.inner.gatherer.local_candidates().first() {
1179            first.clone()
1180        } else {
1181            bail!("No local candidates gathered for direct connection");
1182        };
1183
1184        let remote = IceCandidate::host(remote_addr, 1);
1185        let pair = IceCandidatePair::new(local, remote);
1186
1187        *self.inner.selected_pair.lock() = Some(pair.clone());
1188        let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
1189        if let Some(socket) = resolve_socket(&self.inner, &pair) {
1190            let _ = self.inner.selected_socket.send(Some(socket.clone()));
1191            publish_selected_rtcp_socket(&self.inner, Some(socket));
1192        }
1193        let _ = self.inner.state.send(IceTransportState::Connected);
1194        Ok(())
1195    }
1196
1197    /// Set up a direct UDP socket for RTP mode without any ICE gathering,
1198    /// STUN lookups, or connectivity checks.
1199    /// Binds a single socket, registers it, and marks the transport as connected.
1200    pub async fn setup_direct_rtp(&self, remote_addr: SocketAddr) -> Result<SocketAddr> {
1201        self.setup_direct_rtp_with_rtcp(remote_addr, false).await
1202    }
1203
1204    pub(crate) async fn setup_direct_rtp_with_rtcp(
1205        &self,
1206        remote_addr: SocketAddr,
1207        bind_rtcp: bool,
1208    ) -> Result<SocketAddr> {
1209        let bind_ip = if let Some(bind_ip_str) = &self.inner.config.bind_ip {
1210            bind_ip_str.parse::<IpAddr>().unwrap_or_else(|_| {
1211                get_local_ip().unwrap_or(IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED))
1212            })
1213        } else if let Ok(ip) = get_local_ip() {
1214            ip
1215        } else {
1216            IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)
1217        };
1218
1219        let socket = self.inner.gatherer.bind_socket(bind_ip).await?;
1220        let local_addr = socket.local_addr()?;
1221        let socket = Arc::new(socket);
1222
1223        // Store the socket
1224        self.inner.gatherer.sockets.lock().push(socket.clone());
1225
1226        // Register the socket wrapper for the read loop (handled by runner)
1227        let _ = self
1228            .inner
1229            .gatherer
1230            .socket_tx
1231            .send(IceSocketWrapper::Udp(socket.clone()));
1232
1233        // Build a local candidate for SDP generation
1234        let mut cand_addr = local_addr;
1235        let mut upnp_external_addr = None;
1236
1237        // Try UPnP if enabled (for RTP mode behind NAT)
1238        if self.inner.config.enable_upnp && !local_addr.ip().is_loopback() && !local_addr.is_ipv6()
1239        {
1240            let mut mapper = UpnpPortMapper::with_lease_duration(
1241                local_addr,
1242                self.inner.config.upnp_lease_duration,
1243            );
1244            if let Err(e) = mapper.discover().await {
1245                trace!("UPnP discovery failed for RTP mode: {}", e);
1246            } else if let Ok(ext_addr) = mapper.add_mapping(0).await {
1247                debug!(
1248                    "UPnP mapping created for RTP mode: {} -> {}",
1249                    local_addr, ext_addr
1250                );
1251                cand_addr.set_ip(ext_addr.ip());
1252                cand_addr.set_port(ext_addr.port());
1253                upnp_external_addr = Some(ext_addr);
1254                self.inner.gatherer.upnp_mappers.lock().push(mapper);
1255            } else {
1256                debug!("UPnP mapping failed for RTP mode, using local address");
1257            }
1258        }
1259
1260        // Fall back to external_ip config if UPnP not available
1261        if upnp_external_addr.is_none() {
1262            if let Some(ext_ip) = &self.inner.config.external_ip {
1263                if let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
1264                    && !bind_ip.is_loopback()
1265                {
1266                    cand_addr.set_ip(parsed_ip);
1267                }
1268            } else if bind_ip.is_unspecified()
1269                && let Ok(local_ip) = get_local_ip()
1270            {
1271                cand_addr.set_ip(local_ip);
1272            }
1273        }
1274
1275        // Apply external_port override (for NAT port forwarding)
1276        if upnp_external_addr.is_none()
1277            && let Some(ext_port) = self.inner.config.external_port
1278            && !bind_ip.is_loopback()
1279        {
1280            cand_addr.set_port(ext_port);
1281        }
1282
1283        let mut local_candidate = IceCandidate::host(cand_addr, 1);
1284        if cand_addr != local_addr {
1285            local_candidate.related_address = Some(local_addr);
1286        }
1287        let mut rtcp_socket = None;
1288        let mut rtcp_candidate = None;
1289        if bind_rtcp {
1290            let (rtcp, candidate) =
1291                bind_direct_rtcp_socket(&self.inner, local_addr, cand_addr.ip()).await?;
1292            rtcp_socket = Some(rtcp);
1293            rtcp_candidate = Some(candidate);
1294        }
1295        self.inner.gatherer.push_candidate(local_candidate.clone());
1296        if let Some(candidate) = rtcp_candidate {
1297            self.inner.gatherer.push_candidate(candidate);
1298        }
1299
1300        // Set gathering as complete
1301        *self.inner.gatherer.state.lock() = IceGathererState::Complete;
1302        let _ = self.inner.gathering_state.send(IceGathererState::Complete);
1303
1304        // Set up the selected pair
1305        let remote_candidate = IceCandidate::host(remote_addr, 1);
1306        let pair = IceCandidatePair::new(local_candidate, remote_candidate);
1307        *self.inner.selected_pair.lock() = Some(pair.clone());
1308        let _ = self.inner.selected_pair_notifier.send(Some(pair));
1309        let _ = self
1310            .inner
1311            .selected_socket
1312            .send(Some(IceSocketWrapper::Udp(socket.clone())));
1313        let rtcp_socket = rtcp_socket.unwrap_or_else(|| socket.clone());
1314        let _ = self
1315            .inner
1316            .selected_rtcp_socket
1317            .send(Some(IceSocketWrapper::Udp(rtcp_socket)));
1318        let _ = self.inner.state.send(IceTransportState::Connected);
1319
1320        Ok(cand_addr)
1321    }
1322
1323    /// Set up a direct UDP socket for RTP mode (offer side, no remote addr yet).
1324    /// Binds a socket and registers the local candidate, but does NOT set the
1325    /// selected pair or transition to Connected.
1326    pub async fn setup_direct_rtp_offer(&self) -> Result<SocketAddr> {
1327        self.setup_direct_rtp_offer_with_rtcp(false).await
1328    }
1329
1330    pub(crate) async fn setup_direct_rtp_offer_with_rtcp(
1331        &self,
1332        bind_rtcp: bool,
1333    ) -> Result<SocketAddr> {
1334        let bind_ip = if let Some(bind_ip_str) = &self.inner.config.bind_ip {
1335            bind_ip_str.parse::<IpAddr>().unwrap_or_else(|_| {
1336                get_local_ip().unwrap_or(IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED))
1337            })
1338        } else if let Ok(ip) = get_local_ip() {
1339            ip
1340        } else {
1341            IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)
1342        };
1343
1344        let socket = self.inner.gatherer.bind_socket(bind_ip).await?;
1345        let local_addr = socket.local_addr()?;
1346        let socket = Arc::new(socket);
1347
1348        self.inner.gatherer.sockets.lock().push(socket.clone());
1349        let _ = self
1350            .inner
1351            .gatherer
1352            .socket_tx
1353            .send(IceSocketWrapper::Udp(socket));
1354
1355        let mut cand_addr = local_addr;
1356        let mut upnp_external_addr = None;
1357
1358        // Try UPnP if enabled (for RTP mode behind NAT)
1359        if self.inner.config.enable_upnp && !local_addr.ip().is_loopback() && !local_addr.is_ipv6()
1360        {
1361            let mut mapper = UpnpPortMapper::with_lease_duration(
1362                local_addr,
1363                self.inner.config.upnp_lease_duration,
1364            );
1365            if let Err(e) = mapper.discover().await {
1366                trace!("UPnP discovery failed for RTP offer mode: {}", e);
1367            } else if let Ok(ext_addr) = mapper.add_mapping(0).await {
1368                debug!(
1369                    "UPnP mapping created for RTP offer mode: {} -> {}",
1370                    local_addr, ext_addr
1371                );
1372                cand_addr.set_ip(ext_addr.ip());
1373                cand_addr.set_port(ext_addr.port());
1374                upnp_external_addr = Some(ext_addr);
1375                self.inner.gatherer.upnp_mappers.lock().push(mapper);
1376            } else {
1377                debug!("UPnP mapping failed for RTP offer mode, using local address");
1378            }
1379        }
1380
1381        // Fall back to external_ip config if UPnP not available
1382        if upnp_external_addr.is_none() {
1383            if let Some(ext_ip) = &self.inner.config.external_ip {
1384                if let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
1385                    && !bind_ip.is_loopback()
1386                {
1387                    cand_addr.set_ip(parsed_ip);
1388                }
1389            } else if bind_ip.is_unspecified()
1390                && let Ok(local_ip) = get_local_ip()
1391            {
1392                cand_addr.set_ip(local_ip);
1393            }
1394        }
1395
1396        // Apply external_port override (for NAT port forwarding)
1397        if upnp_external_addr.is_none()
1398            && let Some(ext_port) = self.inner.config.external_port
1399            && !bind_ip.is_loopback()
1400        {
1401            cand_addr.set_port(ext_port);
1402        }
1403
1404        let mut local_candidate = IceCandidate::host(cand_addr, 1);
1405        if cand_addr != local_addr {
1406            local_candidate.related_address = Some(local_addr);
1407        }
1408        let mut rtcp_socket = None;
1409        let mut rtcp_candidate = None;
1410        if bind_rtcp {
1411            let (rtcp, candidate) =
1412                bind_direct_rtcp_socket(&self.inner, local_addr, cand_addr.ip()).await?;
1413            rtcp_socket = Some(rtcp);
1414            rtcp_candidate = Some(candidate);
1415        }
1416        self.inner.gatherer.push_candidate(local_candidate);
1417        if let Some(candidate) = rtcp_candidate {
1418            self.inner.gatherer.push_candidate(candidate);
1419        }
1420        if let Some(rtcp_socket) = rtcp_socket {
1421            let _ = self
1422                .inner
1423                .selected_rtcp_socket
1424                .send(Some(IceSocketWrapper::Udp(rtcp_socket)));
1425        }
1426
1427        *self.inner.gatherer.state.lock() = IceGathererState::Complete;
1428        let _ = self.inner.gathering_state.send(IceGathererState::Complete);
1429
1430        Ok(cand_addr)
1431    }
1432
1433    /// Complete the RTP direct connection by setting the remote address.
1434    /// Call after setup_direct_rtp_offer when the answer arrives with the remote address.
1435    pub fn complete_direct_rtp(&self, remote_addr: SocketAddr) {
1436        let remote_candidate = IceCandidate::host(remote_addr, 1);
1437        let local_candidate = self
1438            .inner
1439            .gatherer
1440            .local_candidates()
1441            .into_iter()
1442            .find(|candidate| candidate.component == 1)
1443            .unwrap_or_else(|| {
1444                IceCandidate::host(
1445                    SocketAddr::new(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), 0),
1446                    1,
1447                )
1448            });
1449        let pair = IceCandidatePair::new(local_candidate, remote_candidate);
1450        *self.inner.selected_pair.lock() = Some(pair.clone());
1451        let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
1452        if let Some(socket) = resolve_socket(&self.inner, &pair) {
1453            let _ = self.inner.selected_socket.send(Some(socket.clone()));
1454            publish_selected_rtcp_socket(&self.inner, Some(socket));
1455        }
1456        let _ = self.inner.state.send(IceTransportState::Connected);
1457    }
1458
1459    /// Best-effort explicit destruction of all TURN allocations (RFC 5766 §7.4).
1460    ///
1461    /// Sends a Refresh request with LIFETIME=0 to each TURN server. The server
1462    /// releases the allocation on receipt of the request; the success response
1463    /// is informational. Runs on a detached task so it never blocks `stop()` /
1464    /// `PeerConnection::close()`.
1465    fn destroy_turn_allocations_best_effort(&self) {
1466        // Synchronous, no spawn/await: build a Refresh(LIFETIME=0) packet and
1467        // fire it off via non-blocking UDP. We do NOT wait for a response or
1468        // retry on stale-nonce — the server destroys the allocation on receipt
1469        // (RFC 5766 §7.4) and eventually times it out if the packet is lost.
1470        let clients: Vec<Arc<TurnClient>> = {
1471            let map = self.inner.gatherer.turn_clients.lock();
1472            map.values().cloned().collect()
1473        };
1474        for client in &clients {
1475            if let Ok((bytes, _tx_id)) = client.create_destroy_packet_sync()
1476                && client.try_send_sync(&bytes)
1477            {
1478                trace!("TURN allocation destroy Refresh(LIFETIME=0) sent (best-effort)");
1479            }
1480        }
1481    }
1482
1483    pub fn stop(&self) {
1484        // Best-effort explicit TURN allocation destruction (RFC 5766 §7.4).
1485        // Spawned BEFORE flipping state to Closed so the TURN read loops that
1486        // dispatch the success/stale-nonce responses are still alive. The server
1487        // releases the allocation on receipt of Refresh(LIFETIME=0) regardless
1488        // of whether we observe the response, so this is robust even if the
1489        // detached task races the read-loop shutdown.
1490        self.destroy_turn_allocations_best_effort();
1491
1492        // Best-effort UPnP port mapping cleanup. Guarded: only spawn when a
1493        // tokio runtime is alive (normal close). During runtime teardown
1494        // (Drop) this is skipped — port mapping leases expire on the router.
1495        if let Ok(handle) = tokio::runtime::Handle::try_current() {
1496            let upnp_clone = self.inner.gatherer.clone();
1497            handle.spawn(async move {
1498                upnp_clone.cleanup_upnp_mappings().await;
1499            });
1500        }
1501
1502        let _ = self.inner.state.send(IceTransportState::Closed);
1503        let _ = self.inner.selected_socket.send(None);
1504        let _ = self.inner.selected_rtcp_socket.send(None);
1505        let _ = self.inner.selected_pair_notifier.send(None);
1506        *self.inner.selected_pair.lock() = None;
1507        self.inner.gatherer.sockets.lock().clear();
1508        self.inner.gatherer.tcp_listeners.lock().clear();
1509        self.inner.gatherer.tcp_streams.lock().clear();
1510        self.inner.gatherer.shared_tcp_regs.lock().clear();
1511        self.inner.gatherer.shared_udp_regs.lock().clear();
1512        self.inner.gatherer.turn_clients.lock().clear();
1513        // Drop the shared-UDP handle so the demux port's per-session state is
1514        // released immediately instead of waiting for Arc<IceTransportInner>.
1515        *self.inner.gatherer.shared_udp_socket.lock() = None;
1516    }
1517
1518    /// Force the ICE transport into a specific state (test-only).
1519    ///
1520    /// Used to simulate ICE reconnect cycles in unit tests without running a
1521    /// full ICE stack.  Production code must never call this.
1522    #[cfg(test)]
1523    pub fn force_state_for_test(&self, state: IceTransportState) {
1524        let _ = self.inner.state.send(state);
1525    }
1526
1527    pub fn set_role(&self, role: IceRole) {
1528        *self.inner.role.lock() = role;
1529    }
1530
1531    pub fn add_remote_candidate(&self, candidate: IceCandidate) {
1532        let mut list = self.inner.remote_candidates.lock();
1533        list.push(candidate);
1534        drop(list);
1535        self.try_connectivity_checks();
1536    }
1537
1538    pub fn select_pair(&self, pair: IceCandidatePair) {
1539        *self.inner.selected_pair.lock() = Some(pair.clone());
1540        let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
1541        if let Some(socket) = resolve_socket(&self.inner, &pair) {
1542            let _ = self.inner.selected_socket.send(Some(socket.clone()));
1543            publish_selected_rtcp_socket(&self.inner, Some(socket));
1544        }
1545        let _ = self.inner.state.send(IceTransportState::Connected);
1546    }
1547
1548    pub fn config(&self) -> &RtcConfiguration {
1549        &self.inner.config
1550    }
1551
1552    pub fn get_selected_socket(&self) -> Option<IceSocketWrapper> {
1553        if let Some(socket) = self.inner._socket_rx_keeper.borrow().clone() {
1554            return Some(socket);
1555        }
1556        let pair = self.inner.selected_pair.lock().clone()?;
1557        resolve_socket(&self.inner, &pair)
1558    }
1559
1560    pub fn get_selected_pair(&self) -> Option<IceCandidatePair> {
1561        self.inner.selected_pair.lock().clone()
1562    }
1563
1564    pub async fn set_data_receiver(&self, receiver: Arc<dyn PacketReceiver>) {
1565        {
1566            let mut rx_lock = self.inner.data_receiver.lock();
1567            *rx_lock = Some(receiver.clone());
1568        }
1569
1570        let packets: Vec<_> = {
1571            let mut buffer = self.inner.buffered_packets.lock();
1572            if buffer.is_empty() {
1573                return;
1574            }
1575            debug!(
1576                count = buffer.len(),
1577                "Flushing buffered RTP packets to newly registered data_receiver"
1578            );
1579            buffer.drain(..).collect()
1580        };
1581
1582        let mut marshal_buf = Vec::new();
1583        for (packet, addr) in packets {
1584            receiver
1585                .receive(Bytes::from(packet), addr, &mut marshal_buf)
1586                .await;
1587        }
1588    }
1589
1590    fn try_connectivity_checks(&self) {
1591        let _ = self.inner.cmd_tx.send(IceCommand::RunChecks);
1592    }
1593
1594    async fn handle_turn_packet(
1595        packet: &[u8],
1596        inner: &Arc<IceTransportInner>,
1597        client: &Arc<TurnClient>,
1598        relayed_addr: SocketAddr,
1599        marshal_buf: &mut Vec<u8>,
1600    ) {
1601        // Check for ChannelData (0x4000 - 0x7FFF)
1602        if packet.len() >= 4 {
1603            let channel_num = u16::from_be_bytes([packet[0], packet[1]]);
1604            if (0x4000..=0x7FFF).contains(&channel_num) {
1605                let len = u16::from_be_bytes([packet[2], packet[3]]) as usize;
1606                if packet.len() >= 4 + len {
1607                    let data = &packet[4..4 + len];
1608                    if let Some(peer_addr) = client.get_peer(channel_num).await {
1609                        handle_packet(
1610                            data,
1611                            peer_addr,
1612                            inner.clone(),
1613                            IceSocketWrapper::Turn(client.clone(), relayed_addr),
1614                            marshal_buf,
1615                        )
1616                        .await;
1617                    }
1618                }
1619                return;
1620            }
1621        }
1622
1623        if let Ok(msg) = StunMessage::decode(packet) {
1624            if msg.class == StunClass::Indication && msg.method == StunMethod::Data {
1625                if let Some(data) = &msg.data
1626                    && let Some(peer_addr) = msg.xor_peer_address
1627                {
1628                    handle_packet(
1629                        data,
1630                        peer_addr,
1631                        inner.clone(),
1632                        IceSocketWrapper::Turn(client.clone(), relayed_addr),
1633                        marshal_buf,
1634                    )
1635                    .await;
1636                }
1637            } else {
1638                // Handle other TURN messages (e.g. CreatePermission response)
1639                handle_packet(
1640                    packet,
1641                    relayed_addr,
1642                    inner.clone(),
1643                    IceSocketWrapper::Turn(client.clone(), relayed_addr),
1644                    marshal_buf,
1645                )
1646                .await;
1647            }
1648        }
1649    }
1650}
1651
1652async fn perform_connectivity_checks_async(inner: Arc<IceTransportInner>) {
1653    let state = *inner.state.borrow();
1654    if state != IceTransportState::Checking {
1655        return;
1656    }
1657
1658    // If we already have a selected pair, don't run more checks
1659    if inner.selected_pair.lock().is_some() {
1660        return;
1661    }
1662
1663    let remotes = inner.remote_candidates.lock().clone();
1664    let role = *inner.role.lock();
1665
1666    if remotes.is_empty() {
1667        return;
1668    }
1669
1670    let mut locals = inner.gatherer.local_candidates();
1671
1672    // Controlling agents may have no gathered locals when UDP is disabled and no TCP
1673    // passive port range is configured. Synthesize active TCP locals so we open
1674    // outbound connections to remote passive TCP candidates (RFC 6544).
1675    if locals.is_empty() && role == IceRole::Controlling {
1676        use std::net::{IpAddr, Ipv4Addr};
1677        for remote in &remotes {
1678            if remote.transport == "tcp" && remote.tcp_type == Some(TcpType::Passive) {
1679                locals.push(IceCandidate::tcp(
1680                    SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
1681                    remote.component,
1682                    "active",
1683                ));
1684            }
1685        }
1686    }
1687
1688    if locals.is_empty() {
1689        return;
1690    }
1691
1692    let mut pairs = Vec::new();
1693
1694    for local in &locals {
1695        for remote in &remotes {
1696            if local.transport != remote.transport {
1697                continue;
1698            }
1699            if local.component != remote.component {
1700                continue;
1701            }
1702            // Filter out Loopback -> Non-Loopback to avoid EADDRNOTAVAIL (os error 49)
1703            if local.address.ip().is_loopback() && !remote.address.ip().is_loopback() {
1704                continue;
1705            }
1706            if local.address.is_ipv4() != remote.address.is_ipv4() {
1707                continue;
1708            }
1709            // For Controlled role with TCP passive candidates, skip connectivity checks.
1710            // The pair will be selected when the Controlling side connects via TCP
1711            // and sends USE-CANDIDATE.
1712            if role == IceRole::Controlled
1713                && local.transport == "tcp"
1714                && local.tcp_type == Some(TcpType::Passive)
1715            {
1716                continue;
1717            }
1718            pairs.push(IceCandidatePair::new(local.clone(), remote.clone()));
1719        }
1720    }
1721
1722    // Sort by priority
1723    pairs.sort_by_key(|p| std::cmp::Reverse(p.priority(role)));
1724
1725    // If configured, demote host candidate pairs behind NAT so that srflx
1726    // pairs are checked first.  A host behind NAT may pass a single STUN
1727    // binding check but then fail the DTLS handshake, whereas the srflx
1728    // candidate (mapped public IP) works reliably.
1729    // Only demote when the remote is NOT also a private host — same-LAN pairs
1730    // (e.g. 192.168.1.x ↔ 192.168.1.y) keep their high priority.
1731    if inner.config.prefer_srflx_over_natted_host {
1732        let is_private_ip = |ip: std::net::IpAddr| -> bool {
1733            match ip {
1734                std::net::IpAddr::V4(v4) => v4.is_private(),
1735                std::net::IpAddr::V6(v6) => v6.is_unique_local(),
1736            }
1737        };
1738        let is_behind_nat = |pair: &IceCandidatePair| -> bool {
1739            pair.local.typ == IceCandidateType::Host
1740                && is_private_ip(pair.local.address.ip())
1741                && !(pair.remote.typ == IceCandidateType::Host
1742                    && is_private_ip(pair.remote.address.ip()))
1743        };
1744        pairs.sort_by(|a, b| {
1745            let a_natted = is_behind_nat(a);
1746            let b_natted = is_behind_nat(b);
1747            if a_natted != b_natted {
1748                return a_natted.cmp(&b_natted);
1749            }
1750            b.priority(role).cmp(&a.priority(role))
1751        });
1752    }
1753
1754    let mut pairs_to_check = Vec::new();
1755    {
1756        let mut checking = inner.checking_pairs.lock().await;
1757        for pair in pairs {
1758            let key = (pair.local.address, pair.remote.address);
1759            if !checking.contains(&key) {
1760                checking.insert(key);
1761                pairs_to_check.push(pair);
1762            }
1763        }
1764    }
1765
1766    if pairs_to_check.is_empty() {
1767        return;
1768    }
1769    let mut checks = futures::stream::FuturesUnordered::new();
1770
1771    for pair in pairs_to_check {
1772        let inner = inner.clone();
1773        let local = pair.local.clone();
1774        let remote = pair.remote.clone();
1775
1776        checks.push(async move {
1777            let key = (local.address, remote.address);
1778            let res = perform_binding_check(&local, &remote, &inner, role, false).await;
1779
1780            {
1781                let mut checking = inner.checking_pairs.lock().await;
1782                checking.remove(&key);
1783            }
1784
1785            match res {
1786                Ok(_) => Some(IceCandidatePair::new(local, remote)),
1787                Err(e) => {
1788                    debug!(
1789                        "ICE connectivity check failed: {} -> {}: {}",
1790                        local.address, remote.address, e
1791                    );
1792                    None
1793                }
1794            }
1795        });
1796    }
1797
1798    if checks.is_empty() {
1799        return;
1800    }
1801
1802    use futures::stream::StreamExt;
1803    let mut successful_pairs: Vec<IceCandidatePair> = Vec::new();
1804
1805    // Collect successful pairs. Once the first usable pair arrives, only wait a
1806    // short grace window for additional (possibly higher-priority) pairs before
1807    // proceeding to nomination. This keeps a single unreachable candidate (e.g.
1808    // a stale LAN host candidate returning EHOSTDOWN/EHOSTUNREACH) from stalling
1809    // connection setup for the full STUN timeout — all checks still run
1810    // concurrently, but nomination is no longer gated on the slowest failing pair.
1811    const NOMINATION_GRACE: Duration = Duration::from_millis(200);
1812    loop {
1813        let next = if successful_pairs.is_empty() {
1814            checks.next().await
1815        } else {
1816            tokio::select! {
1817                biased;
1818                res = checks.next() => res,
1819                _ = tokio::time::sleep(NOMINATION_GRACE) => break,
1820            }
1821        };
1822
1823        match next {
1824            Some(pair) => {
1825                if let Some(pair) = pair {
1826                    // Skip duplicates (same local+remote already collected)
1827                    let key = (pair.local.address, pair.remote.address);
1828                    if !successful_pairs
1829                        .iter()
1830                        .any(|p| (p.local.address, p.remote.address) == key)
1831                    {
1832                        successful_pairs.push(pair);
1833                    }
1834                }
1835            }
1836            None => break,
1837        }
1838    }
1839
1840    if successful_pairs.is_empty() {
1841        // Don't transition to Failed — trickle ICE candidates may arrive
1842        // later via add_remote_candidate and trigger new connectivity checks.
1843        // The disconnect monitor (ice_connection_timeout) handles long-term
1844        // connectivity failure.
1845        return;
1846    }
1847
1848    // Sort by priority: host > srflx > relay.  P2P first, relay last.
1849    successful_pairs.sort_by_key(|p| std::cmp::Reverse(p.priority(role)));
1850
1851    for p in &successful_pairs {
1852        debug!(
1853            "ICE successful pair ({}): local {} {:?} -> remote {} {:?}",
1854            if role == IceRole::Controlling {
1855                "controlling"
1856            } else {
1857                "controlled"
1858            },
1859            p.local.address, p.local.typ, p.remote.address, p.remote.typ
1860        );
1861    }
1862
1863    if role == IceRole::Controlling {
1864        // Signal Connected so the PeerConnection starts waiting for nomination_complete.
1865        let _ = inner.state.send(IceTransportState::Connected);
1866
1867        // Launch ALL nomination checks in parallel, but select the
1868        // highest-priority pair that succeeds — not merely the first one to
1869        // complete. A fast lower-priority path (e.g. a TURN relay whose
1870        // socket was pre-allocated during gathering) can otherwise win the
1871        // race against a higher-priority host path that the peer actually
1872        // selected, leaving the two ends with incompatible pairs and a
1873        // broken DTLS media path (seen across cascaded NAT: the controlling
1874        // side chose a srflx/relay pair while the controlled side selected a
1875        // host <-> peer-reflexive pair, so neither direction carried DTLS).
1876        //
1877        // To avoid paying the N × nomination_timeout latency of sequential
1878        // nomination when the highest-priority pairs are unreachable, all
1879        // nominations still run concurrently; we only apply a short grace
1880        // window after the first success so a slower-but-higher-priority pair
1881        // still gets a chance to win. The window is skipped entirely when the
1882        // globally highest-priority pair already succeeded or nothing else is
1883        // still in flight (the common single-pair LAN case).
1884        const NOMINATION_GRACE: Duration = Duration::from_millis(200);
1885        let mut nom_checks = futures::stream::FuturesUnordered::new();
1886        for pair in &successful_pairs {
1887            let inner_c = inner.clone();
1888            let local = pair.local.clone();
1889            let remote = pair.remote.clone();
1890            debug!(
1891                "Controlling agent nominating pair: {} -> {}",
1892                local.address, remote.address
1893            );
1894            nom_checks.push(async move {
1895                let result = perform_binding_check(&local, &remote, &inner_c, role, true).await;
1896                (IceCandidatePair::new(local, remote), result)
1897            });
1898        }
1899
1900        let highest_priority = successful_pairs[0].clone();
1901        let mut successful_nominations: Vec<IceCandidatePair> = Vec::new();
1902        let mut in_flight = nom_checks.len();
1903        loop {
1904            let next = if successful_nominations.is_empty() {
1905                nom_checks.next().await
1906            } else {
1907                tokio::select! {
1908                    biased;
1909                    res = nom_checks.next() => res,
1910                    _ = tokio::time::sleep(NOMINATION_GRACE) => None,
1911                }
1912            };
1913            let Some((pair, result)) = next else { break };
1914            in_flight -= 1;
1915            match result {
1916                Ok(_) => {
1917                    debug!(
1918                        "Nomination succeeded: {} -> {}",
1919                        pair.local.address, pair.remote.address
1920                    );
1921                    let key = (pair.local.address, pair.remote.address);
1922                    if !successful_nominations
1923                        .iter()
1924                        .any(|p| (p.local.address, p.remote.address) == key)
1925                    {
1926                        successful_nominations.push(pair.clone());
1927                    }
1928                    // The globally highest-priority pair succeeded — nothing
1929                    // better to wait for.
1930                    if pair.local.address == highest_priority.local.address
1931                        && pair.remote.address == highest_priority.remote.address
1932                    {
1933                        break;
1934                    }
1935                }
1936                Err(e) => {
1937                    debug!(
1938                        "Nomination failed for {} -> {}: {}",
1939                        pair.local.address, pair.remote.address, e
1940                    );
1941                }
1942            }
1943            if in_flight == 0 {
1944                break;
1945            }
1946        }
1947
1948        // Set selected_pair: use the highest-priority successful nomination,
1949        // or the highest-priority pair on all-fail (for best-effort data flow).
1950        successful_nominations.sort_by_key(|p| std::cmp::Reverse(p.priority(role)));
1951        let final_pair = successful_nominations
1952            .first()
1953            .cloned()
1954            .unwrap_or_else(|| successful_pairs[0].clone());
1955        let nominated = !successful_nominations.is_empty();
1956        *inner.selected_pair.lock() = Some(final_pair.clone());
1957        let _ = inner.selected_pair_notifier.send(Some(final_pair.clone()));
1958        if let Some(socket) = resolve_socket(&inner, &final_pair) {
1959            let _ = inner.selected_socket.send(Some(socket.clone()));
1960            publish_selected_rtcp_socket(&inner, Some(socket));
1961        }
1962        debug!(
1963            "ICE checks complete. Selected pair: {} -> {}",
1964            final_pair.local.address, final_pair.remote.address
1965        );
1966
1967        if nominated {
1968            let _ = inner.nomination_complete.send(Some(true));
1969        } else {
1970            debug!(
1971                "All {} nomination attempts failed",
1972                successful_pairs.len()
1973            );
1974            let _ = inner.nomination_complete.send(Some(false));
1975            let _ = inner.state.send(IceTransportState::Failed);
1976        }
1977    } else {
1978        // Controlled side: select best pair but don't nominate.
1979        // nomination_complete is signalled when we receive USE-CANDIDATE
1980        // from the controlling agent.
1981        //
1982        // Once the peer has nominated a pair, that choice is authoritative
1983        // and this selection must not run again: check rounds re-triggered
1984        // by late (e.g. peer-reflexive) candidates would otherwise stomp the
1985        // nominated pair with a locally-preferred one the peer never chose.
1986        if inner.nomination_complete.borrow().is_some() {
1987            debug!("ICE checks complete (controlled): keeping peer-nominated pair");
1988            return;
1989        }
1990        let pair = &successful_pairs[0];
1991        *inner.selected_pair.lock() = Some(pair.clone());
1992        let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
1993        if let Some(socket) = resolve_socket(&inner, pair) {
1994            let _ = inner.selected_socket.send(Some(socket.clone()));
1995            publish_selected_rtcp_socket(&inner, Some(socket));
1996        }
1997        let _ = inner.state.send(IceTransportState::Connected);
1998        if pair.local.transport == "tcp" {
1999            let _ = inner.nomination_complete.send(Some(true));
2000        }
2001        debug!(
2002            "ICE checks complete. Selected pair: {} -> {}",
2003            pair.local.address, pair.remote.address
2004        );
2005    }
2006}
2007
2008fn resolve_socket(inner: &IceTransportInner, pair: &IceCandidatePair) -> Option<IceSocketWrapper> {
2009    if pair.local.typ == IceCandidateType::Relay {
2010        let clients = inner.gatherer.turn_clients.lock();
2011        clients
2012            .get(&pair.local.address)
2013            .map(|c| IceSocketWrapper::Turn(c.clone(), pair.local.address))
2014    } else if pair.local.transport == "tcp" {
2015        // Prefer the accepted inbound stream that matches the nominated remote peer.
2016        // get_tcp_socket() keys by listener local_addr and may return a stale socket
2017        // when multiple sessions share a passive port range.
2018        let streams = inner.gatherer.tcp_streams.lock();
2019        for wrapper in streams.values() {
2020            if let IceSocketWrapper::TcpStream(_, _, peer) = wrapper
2021                && *peer == pair.remote.address
2022            {
2023                return Some(wrapper.clone());
2024            }
2025        }
2026        drop(streams);
2027        inner.gatherer.get_tcp_socket(pair.local.base_address())
2028    } else {
2029        // Shared UDP mux socket backs the single host candidate when active.
2030        if pair.local.typ == IceCandidateType::Host
2031            && let Some(shared) = inner.gatherer.shared_udp_socket.lock().clone()
2032        {
2033            return Some(shared);
2034        }
2035        let socket = inner.gatherer.get_socket(pair.local.base_address());
2036        if socket.is_none() {
2037            debug!(
2038                "resolve_socket: failed to find socket for {}",
2039                pair.local.base_address()
2040            );
2041        }
2042        socket.map(IceSocketWrapper::Udp)
2043    }
2044}
2045
2046fn publish_selected_socket(
2047    inner: &IceTransportInner,
2048    pair: &IceCandidatePair,
2049    inbound: Option<&IceSocketWrapper>,
2050) {
2051    // Inbound TCP is authoritative for passive ICE-TCP: the controlling peer
2052    // connected to us on this stream and nominated it via USE-CANDIDATE.
2053    let socket = match inbound {
2054        Some(s @ IceSocketWrapper::TcpStream(_, _, _)) => Some(s.clone()),
2055        _ => resolve_socket(inner, pair),
2056    };
2057    if let Some(socket) = socket {
2058        debug!(
2059            pair_local = %pair.local.address,
2060            pair_remote = %pair.remote.address,
2061            socket = %socket.diag(),
2062            inbound_tcp = matches!(inbound, Some(IceSocketWrapper::TcpStream(_, _, _))),
2063            "ICE: published selected socket"
2064        );
2065        let _ = inner.selected_socket.send(Some(socket.clone()));
2066        publish_selected_rtcp_socket(inner, Some(socket));
2067    }
2068}
2069
2070async fn complete_controlled_inbound_tcp_nomination(
2071    sender: &IceSocketWrapper,
2072    addr: SocketAddr,
2073    inner: Arc<IceTransportInner>,
2074) {
2075    if *inner.role.lock() != IceRole::Controlled {
2076        return;
2077    }
2078    let IceSocketWrapper::TcpStream(read, _, _) = sender else {
2079        return;
2080    };
2081    if inner.nomination_complete.borrow().is_some() {
2082        if let Some(pair) = inner.selected_pair.lock().clone() {
2083            publish_selected_socket(&inner, &pair, Some(sender));
2084        }
2085        return;
2086    }
2087
2088    let local_addr: SocketAddr = {
2089        let s = read.lock().await;
2090        s.local_addr()
2091            .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
2092    };
2093
2094    let locals = inner.gatherer.local_candidates();
2095    let local_cand = locals.iter().find(|c| {
2096        c.base_address() == local_addr
2097            || (c.transport == "tcp"
2098                && c.base_address().port() == local_addr.port()
2099                && (c.base_address().ip().is_unspecified() || local_addr.ip().is_unspecified()))
2100    });
2101
2102    let pair = {
2103        let remotes = inner.remote_candidates.lock();
2104        let remote_cand = remotes.iter().find(|c| c.address == addr);
2105        if let (Some(l), Some(r)) = (local_cand, remote_cand) {
2106            Some(IceCandidatePair::new(l.clone(), r.clone()))
2107        } else {
2108            None
2109        }
2110    };
2111
2112    if let Some(pair) = pair {
2113        trace!(
2114            "Controlled agent selected pair via inbound TCP nomination: {} -> {}",
2115            pair.local.address, pair.remote.address
2116        );
2117        *inner.selected_pair.lock() = Some(pair.clone());
2118        let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
2119        publish_selected_socket(&inner, &pair, Some(sender));
2120        let _ = inner.state.send(IceTransportState::Connected);
2121    } else {
2122        debug!(
2123            "Inbound TCP nomination: synthesizing pair for {} -> {}",
2124            local_addr, addr
2125        );
2126        let local_cand = locals.iter().find(|c| {
2127            c.transport == "tcp"
2128                && c.tcp_type == Some(TcpType::Passive)
2129                && (c.base_address().port() == local_addr.port()
2130                    || c.address.port() == local_addr.port())
2131        });
2132        let remote_cand = {
2133            let remotes = inner.remote_candidates.lock();
2134            remotes.iter().find(|c| c.address == addr).cloned()
2135        };
2136        if let (Some(l), Some(r)) = (local_cand, remote_cand) {
2137            let pair = IceCandidatePair::new(l.clone(), r.clone());
2138            *inner.selected_pair.lock() = Some(pair.clone());
2139            let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
2140            publish_selected_socket(&inner, &pair, Some(sender));
2141            let _ = inner.state.send(IceTransportState::Connected);
2142        } else {
2143            let _ = inner.selected_socket.send(Some(sender.clone()));
2144            publish_selected_rtcp_socket(&inner, Some(sender.clone()));
2145        }
2146    }
2147    let _ = inner.nomination_complete.send(Some(true));
2148    let pair_summary = inner
2149        .selected_pair
2150        .lock()
2151        .as_ref()
2152        .map(|p| format!("{} -> {}", p.local.address, p.remote.address))
2153        .unwrap_or_else(|| format!("(no pair) peer={addr}"));
2154    debug!(
2155        peer = %addr,
2156        local_bind = %local_addr,
2157        pair = %pair_summary,
2158        socket = %sender.diag(),
2159        "ICE: passive TCP nomination complete"
2160    );
2161}
2162
2163fn resolve_rtcp_socket(inner: &IceTransportInner) -> Option<IceSocketWrapper> {
2164    let candidate = inner
2165        .gatherer
2166        .local_candidates()
2167        .into_iter()
2168        .find(|candidate| candidate.component == 2)?;
2169
2170    if candidate.typ == IceCandidateType::Relay {
2171        let clients = inner.gatherer.turn_clients.lock();
2172        clients
2173            .get(&candidate.address)
2174            .map(|client| IceSocketWrapper::Turn(client.clone(), candidate.address))
2175    } else if candidate.transport == "tcp" {
2176        inner.gatherer.get_tcp_socket(candidate.base_address())
2177    } else {
2178        let socket = inner.gatherer.get_socket(candidate.base_address());
2179        if socket.is_none() {
2180            debug!(
2181                "resolve_rtcp_socket: failed to find socket for {}",
2182                candidate.base_address()
2183            );
2184        }
2185        socket.map(IceSocketWrapper::Udp)
2186    }
2187}
2188
2189fn publish_selected_rtcp_socket(inner: &IceTransportInner, fallback: Option<IceSocketWrapper>) {
2190    if let Some(socket) = resolve_rtcp_socket(inner).or(fallback) {
2191        let _ = inner.selected_rtcp_socket.send(Some(socket));
2192    }
2193}
2194
2195async fn bind_direct_rtcp_socket(
2196    inner: &IceTransportInner,
2197    rtp_base: SocketAddr,
2198    advertised_ip: IpAddr,
2199) -> Result<(Arc<UdpSocket>, IceCandidate)> {
2200    let rtcp_bind_addr = rtp_base
2201        .port()
2202        .checked_add(1)
2203        .map(|port| SocketAddr::new(rtp_base.ip(), port));
2204    let rtcp = if let Some(addr) = rtcp_bind_addr {
2205        match UdpSocket::bind(addr).await {
2206            Ok(socket) => socket,
2207            Err(err) => {
2208                debug!(
2209                    "Failed to bind RTCP socket on {}, falling back to ephemeral port: {}",
2210                    addr, err
2211                );
2212                UdpSocket::bind(SocketAddr::new(rtp_base.ip(), 0)).await?
2213            }
2214        }
2215    } else {
2216        UdpSocket::bind(SocketAddr::new(rtp_base.ip(), 0)).await?
2217    };
2218    let local_rtcp_addr = rtcp.local_addr()?;
2219    let rtcp = Arc::new(rtcp);
2220    inner.gatherer.sockets.lock().push(rtcp.clone());
2221    let _ = inner
2222        .gatherer
2223        .socket_tx
2224        .send(IceSocketWrapper::Udp(rtcp.clone()));
2225
2226    let mut rtcp_cand_addr = local_rtcp_addr;
2227    rtcp_cand_addr.set_ip(advertised_ip);
2228    let mut candidate = IceCandidate::host(rtcp_cand_addr, 2);
2229    if rtcp_cand_addr != local_rtcp_addr {
2230        candidate.related_address = Some(local_rtcp_addr);
2231    }
2232    Ok((rtcp, candidate))
2233}
2234
2235async fn handle_packet(
2236    packet: &[u8],
2237    addr: SocketAddr,
2238    inner: Arc<IceTransportInner>,
2239    sender: IceSocketWrapper,
2240    marshal_buf: &mut Vec<u8>,
2241) {
2242    if should_drop_packet() {
2243        return;
2244    }
2245    inner.last_received_nanos.store(
2246        inner.created_at.elapsed().as_nanos() as u64,
2247        Ordering::Relaxed,
2248    );
2249    let b = packet[0];
2250    if b < 2 {
2251        // STUN
2252        match StunMessage::decode(packet) {
2253            Ok(msg) => {
2254                if msg.class == StunClass::Request {
2255                    // Always respond to STUN Binding Requests on any transport mode
2256                    // (RFC 5389 compliance). Sending a Binding Response lets the remote
2257                    // peer (e.g. Linphone) confirm the media port is reachable even when
2258                    // it sends a STUN probe before its first real RTP packet.
2259                    //
2260                    // Address latching (updating the selected pair's remote IP based on
2261                    // the incoming source) is a separate concern gated by
2262                    // `enable_latching` inside handle_stun_request — it is NOT the same
2263                    // as "should we even reply to this STUN message".
2264                    handle_stun_request(&sender, &msg, addr, inner).await;
2265                } else if msg.class == StunClass::SuccessResponse {
2266                    let mut map = inner.pending_transactions.lock();
2267                    if let Some(tx) = map.remove(&msg.transaction_id) {
2268                        let _ = tx.send(msg);
2269                    } else {
2270                        trace!(
2271                            "Unmatched transaction {:?} Pending transactions: {:?}",
2272                            msg.transaction_id,
2273                            map.keys()
2274                        );
2275                    }
2276                } else if msg.class == StunClass::ErrorResponse {
2277                    trace!("Received STUN Error Response from {}", addr);
2278                    debug!(
2279                        "Received STUN Error Response from {}: {:?}",
2280                        addr, msg.error_code
2281                    );
2282                    if let Some(code) = msg.error_code {
2283                        if code == 401 {
2284                            let remote_params = inner.remote_parameters.lock().clone();
2285                            debug!(
2286                                "STUN 401 received. Current remote params: {:?}",
2287                                remote_params
2288                            );
2289                        }
2290                        trace!("Error code: {}", code);
2291                    }
2292                    // Dispatch error responses to pending transactions so that callers
2293                    // waiting on send_and_await_inner (e.g. TURN Refresh, ChannelBind)
2294                    // can receive 401/438 and retry with a fresh nonce instead of timing out.
2295                    let mut map = inner.pending_transactions.lock();
2296                    if let Some(tx) = map.remove(&msg.transaction_id) {
2297                        let _ = tx.send(msg);
2298                    }
2299                }
2300            }
2301            Err(e) => {
2302                debug!("Failed to decode STUN packet from {}: {}", addr, e);
2303            }
2304        }
2305    } else {
2306        // DTLS or RTP
2307        let receiver = inner.data_receiver.lock().clone();
2308        if let Some(rx) = receiver {
2309            rx.receive(Bytes::copy_from_slice(packet), addr, marshal_buf)
2310                .await;
2311        } else {
2312            let mut buffer = inner.buffered_packets.lock();
2313            let stats = inner.buffer_stats.clone();
2314            let capacity = inner.config.rtp_buffer_capacity;
2315
2316            stats.packets_received.fetch_add(1, Ordering::Relaxed);
2317
2318            if buffer.len() >= capacity {
2319                match inner.config.buffer_drop_strategy {
2320                    BufferDropStrategy::DropOldest => {
2321                        buffer.pop_front();
2322                        buffer.push_back((packet.to_vec(), addr));
2323                    }
2324                    BufferDropStrategy::DropNew => {
2325                        // Rate-limit: warn on the first drop and every 1000th
2326                        // thereafter, so a persistently-full buffer does not
2327                        // spam per packet (drops are also counted in buffer_stats).
2328                        let dropped = stats.packets_dropped.load(Ordering::Relaxed);
2329                        if dropped == 0 || dropped.is_multiple_of(1000) {
2330                            tracing::warn!(src = %addr, capacity, "RTP buffer full — dropping inbound packet (DropNew strategy)");
2331                        }
2332                    }
2333                }
2334                stats.packets_dropped.fetch_add(1, Ordering::Relaxed);
2335            } else {
2336                buffer.push_back((packet.to_vec(), addr));
2337            }
2338
2339            // Update statistics
2340            let current_size = buffer.len() as u32;
2341            stats.current_size.store(current_size, Ordering::Relaxed);
2342
2343            // Track peak size
2344            let mut peak = stats.peak_size.load(Ordering::Relaxed);
2345            while current_size > peak {
2346                match stats.peak_size.compare_exchange_weak(
2347                    peak,
2348                    current_size,
2349                    Ordering::Relaxed,
2350                    Ordering::Relaxed,
2351                ) {
2352                    Ok(_) => break,
2353                    Err(current) => peak = current,
2354                }
2355            }
2356
2357            // Periodic logging
2358            let mut last_log = stats.last_log_time.lock();
2359            if last_log.elapsed() >= inner.config.buffer_stats_log_interval {
2360                let received = stats.packets_received.load(Ordering::Relaxed);
2361                let dropped = stats.packets_dropped.load(Ordering::Relaxed);
2362                let peak_size = stats.peak_size.load(Ordering::Relaxed);
2363                trace!(
2364                    "Buffer stats: received={}, dropped={}, current={}, peak={}, capacity={}",
2365                    received, dropped, current_size, peak_size, capacity
2366                );
2367                *last_log = Instant::now();
2368            }
2369        }
2370    }
2371}
2372
2373async fn handle_stun_request(
2374    sender: &IceSocketWrapper,
2375    msg: &StunDecoded,
2376    addr: SocketAddr,
2377    inner: Arc<IceTransportInner>,
2378) {
2379    let response = StunMessage::binding_success_response(msg.transaction_id, addr);
2380
2381    #[cfg(any(test, feature = "simulator"))]
2382    simulate_stun_respond_delay(sender).await;
2383
2384    let password = inner.local_parameters.lock().password.clone();
2385    if let Ok(bytes) = response.encode(Some(password.as_bytes()), true) {
2386        match sender.send_to(&bytes, addr).await {
2387            Ok(_) => trace!("Sent STUN Response to {}", addr),
2388            Err(e) => {
2389                if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
2390                    match io_err.kind() {
2391                        std::io::ErrorKind::HostUnreachable
2392                        | std::io::ErrorKind::NetworkUnreachable => {
2393                            debug!("Failed to send STUN Response to {}: {}", addr, e);
2394                        }
2395                        _ => {
2396                            if io_err.raw_os_error() == Some(65)
2397                                || io_err.raw_os_error() == Some(49)
2398                            {
2399                                debug!("Failed to send STUN Response to {}: {}", addr, e);
2400                            } else {
2401                                debug!("Failed to send STUN Response to {}: {}", addr, e);
2402                            }
2403                        }
2404                    }
2405                } else {
2406                    debug!("Failed to send STUN Response to {}: {}", addr, e);
2407                }
2408            }
2409        }
2410    } else {
2411        debug!("Failed to encode STUN Response");
2412    }
2413
2414    // Check if we know this candidate
2415    let mut known = false;
2416    {
2417        let remotes = inner.remote_candidates.lock();
2418        for cand in remotes.iter() {
2419            if cand.address == addr {
2420                known = true;
2421                break;
2422            }
2423        }
2424    }
2425
2426    if !known {
2427        debug!("Discovered peer reflexive candidate: {}", addr);
2428        let transport = match sender {
2429            IceSocketWrapper::Udp(_) | IceSocketWrapper::SharedUdp(_) => "udp",
2430            IceSocketWrapper::TcpListener(_) | IceSocketWrapper::TcpStream(_, _, _) => "tcp",
2431            IceSocketWrapper::Turn(_, _) => "udp",
2432        };
2433        let mut candidate = IceCandidate::host(addr, 1); // Use host for now, or prflx
2434        candidate.typ = IceCandidateType::PeerReflexive;
2435        candidate.transport = transport.to_string();
2436        candidate.foundation = IceCandidate::compute_foundation(
2437            IceCandidateType::PeerReflexive,
2438            candidate.base_address(),
2439            transport,
2440        );
2441        candidate.priority = if transport == "tcp" {
2442            IceCandidate::priority_for_tcp(IceCandidateType::PeerReflexive, 1, TcpType::Passive)
2443        } else {
2444            IceCandidate::priority_for(IceCandidateType::PeerReflexive, 1)
2445        };
2446
2447        let mut list = inner.remote_candidates.lock();
2448        list.push(candidate);
2449        drop(list);
2450
2451        let _ = inner.cmd_tx.send(IceCommand::RunChecks);
2452    }
2453    // ICE-layer latching retargets the selected pair when an inbound STUN
2454    // arrives from the same port on a different IP. That is an RTP/SRTP-mode
2455    // concept (plain RTP peers may legitimately change source addresses
2456    // mid-stream). For WebRTC the SRTP media path must stay pinned to the
2457    // nominated pair: retargeting it onto an address the peer never nominated
2458    // blackholes media while consent keepalives still flow on the original
2459    // socket.
2460    if inner.config.enable_latching
2461        && inner.config.transport_mode != crate::TransportMode::WebRtc
2462    {
2463        let current_pair = inner.selected_pair.lock().clone();
2464        if let Some(pair) = current_pair
2465            && pair.remote.address.port() == addr.port()
2466            && pair.remote.address.ip() != addr.ip()
2467        {
2468            debug!(
2469                "RTP latching: updating remote address from {} to {}",
2470                pair.remote.address, addr
2471            );
2472            let mut new_remote = pair.remote.clone();
2473            new_remote.address = addr;
2474            let new_pair = IceCandidatePair::new(pair.local.clone(), new_remote);
2475            *inner.selected_pair.lock() = Some(new_pair.clone());
2476            let _ = inner.selected_pair_notifier.send(Some(new_pair.clone()));
2477            publish_selected_socket(&inner, &new_pair, Some(sender));
2478        }
2479    }
2480
2481    complete_controlled_inbound_tcp_nomination(sender, addr, inner.clone()).await;
2482
2483    if msg.use_candidate {
2484        let role = *inner.role.lock();
2485        if role == IceRole::Controlled {
2486            // TCP passive nomination is handled above; UDP still uses USE-CANDIDATE below.
2487            if matches!(sender, IceSocketWrapper::TcpStream(_, _, _)) {
2488                return;
2489            }
2490            // RFC 8445 §7.3.1.5: once a pair has been nominated, subsequent
2491            // USE-CANDIDATE (e.g. keepalives from other candidates) must not
2492            // trigger re-nomination.  We still process them here, but only to
2493            // upgrade to a strictly higher-priority pair: the controlling side
2494            // nominates every successful pair in parallel, so a fast
2495            // lower-priority path (e.g. TURN relay) routinely arrives BEFORE a
2496            // slower host path the peer actually prefers. Latching the first
2497            // arrival would leave the two ends on incompatible pairs (see
2498            // cascaded-NAT reports). Mirror the controlling side's priority
2499            // check so both sides converge on the best nominated pair.
2500            let already_nominated = inner.nomination_complete.borrow().is_some();
2501            // The controlling agent's nomination is authoritative. A pair
2502            // selected earlier by our own check completion is only
2503            // provisional: on a multihomed host it can differ from the
2504            // pair the peer actually validated - it may even be a path
2505            // that only works in one direction (e.g. a source address the
2506            // peer's network drops on the return leg) - so it must be
2507            // replaced by the pair this request arrived on.
2508            let local_addr: SocketAddr = match sender {
2509                IceSocketWrapper::Udp(s) => s
2510                    .local_addr()
2511                    .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
2512                IceSocketWrapper::SharedUdp(h) => h
2513                    .local_addr()
2514                    .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
2515                IceSocketWrapper::TcpListener(l) => l
2516                    .local_addr()
2517                    .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
2518                IceSocketWrapper::TcpStream(read, _, _) => {
2519                    let s = read.lock().await;
2520                    s.local_addr()
2521                        .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
2522                }
2523                IceSocketWrapper::Turn(_, addr) => *addr,
2524            };
2525
2526            let locals = inner.gatherer.local_candidates();
2527            let local_cand = locals.iter().find(|c| c.base_address() == local_addr);
2528
2529            let pair = {
2530                let remotes = inner.remote_candidates.lock();
2531                let remote_cand = remotes.iter().find(|c| c.address == addr);
2532                if let (Some(l), Some(r)) = (local_cand, remote_cand) {
2533                    Some(IceCandidatePair::new(l.clone(), r.clone()))
2534                } else {
2535                    None
2536                }
2537            };
2538
2539            if let Some(pair) = pair {
2540                let should_select = {
2541                    let selected = inner.selected_pair.lock();
2542                    match selected.as_ref() {
2543                        Some(cur) => {
2544                            let same = cur.local.address == pair.local.address
2545                                && cur.remote.address == pair.remote.address;
2546                            if same {
2547                                false
2548                            } else if already_nominated {
2549                                // RFC 8445 §7.3.1.5: the pair the controlling
2550                                // agent nominated first is authoritative for
2551                                // the rest of the session. Swapping to a
2552                                // "better" pair mid-session retargets media
2553                                // onto a path the peer never selected (e.g.
2554                                // the peer's NAT silently drops it), while
2555                                // consent keepalives keep flowing on the
2556                                // original socket — the remote then hears
2557                                // permanent silence. Freeze the pair instead.
2558                                debug!(
2559                                    "Ignoring post-nomination UseCandidate {} -> {} (frozen on {} -> {})",
2560                                    pair.local.address,
2561                                    pair.remote.address,
2562                                    cur.local.address,
2563                                    cur.remote.address
2564                                );
2565                                false
2566                            } else {
2567                                true
2568                            }
2569                        }
2570                        None => true,
2571                    }
2572                };
2573                if should_select {
2574                    debug!(
2575                        "Controlled agent selected pair via UseCandidate: {} -> {}",
2576                        pair.local.address, pair.remote.address
2577                    );
2578                    *inner.selected_pair.lock() = Some(pair.clone());
2579                    let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
2580                    publish_selected_socket(&inner, &pair, Some(sender));
2581                } else {
2582                    trace!(
2583                        "Controlled agent keeping current pair (UseCandidate {} -> {})",
2584                        pair.local.address, pair.remote.address
2585                    );
2586                }
2587                let _ = inner.state.send(IceTransportState::Connected);
2588                let _ = inner.nomination_complete.send(Some(true));
2589            } else {
2590                debug!(
2591                    "Received UseCandidate but could not find UDP pair for {} -> {}",
2592                    local_addr, addr
2593                );
2594                let _ = inner.nomination_complete.send(Some(true));
2595            }
2596        }
2597    }
2598}
2599
2600struct TransactionGuard<'a> {
2601    map: &'a parking_lot::Mutex<HashMap<[u8; 12], oneshot::Sender<StunDecoded>>>,
2602    tx_id: [u8; 12],
2603}
2604
2605impl<'a> Drop for TransactionGuard<'a> {
2606    fn drop(&mut self) {
2607        // debug!("TransactionGuard: dropping tx={:?}", self.tx_id);
2608        let mut map = self.map.lock();
2609        map.remove(&self.tx_id);
2610    }
2611}
2612
2613async fn perform_binding_check(
2614    local: &IceCandidate,
2615    remote: &IceCandidate,
2616    inner: &Arc<IceTransportInner>,
2617    role: IceRole,
2618    nominated: bool,
2619) -> Result<()> {
2620    // Handle TCP candidates separately — establish connection and perform STUN over TCP
2621    if local.transport == "tcp" && remote.transport == "tcp" {
2622        return perform_tcp_binding_check(local, remote, inner, role, nominated).await;
2623    }
2624
2625    // For Controlled role with TCP passive candidates, don't initiate outbound checks
2626    if role == IceRole::Controlled && local.transport == "tcp" {
2627        return Ok(());
2628    }
2629
2630    // For non-TCP candidates, transport must be UDP
2631    if remote.transport != "udp" {
2632        bail!("only UDP connectivity checks are supported");
2633    }
2634
2635    let local_params = inner.local_parameters.lock().clone();
2636    let remote_params = match inner.remote_parameters.lock().clone() {
2637        Some(p) => p,
2638        None => bail!("no remote params"),
2639    };
2640
2641    let tx_id = random_bytes::<12>();
2642    // debug!("perform_binding_check: starting check for {} -> {} tx={:?}", local.address, remote.address, tx_id);
2643
2644    let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
2645    let username = format!(
2646        "{}:{}",
2647        remote_params.username_fragment, local_params.username_fragment
2648    );
2649    msg.attributes.push(StunAttribute::Username(username));
2650    msg.attributes.push(StunAttribute::Priority(local.priority));
2651    match role {
2652        IceRole::Controlling => {
2653            msg.attributes
2654                .push(StunAttribute::IceControlling(local_params.tie_breaker));
2655            if nominated {
2656                msg.attributes.push(StunAttribute::UseCandidate);
2657            }
2658        }
2659        IceRole::Controlled => msg
2660            .attributes
2661            .push(StunAttribute::IceControlled(local_params.tie_breaker)),
2662    }
2663    let bytes = msg.encode(Some(remote_params.password.as_bytes()), true)?;
2664
2665    let (tx, mut rx) = oneshot::channel();
2666    {
2667        let mut map = inner.pending_transactions.lock();
2668        map.insert(tx_id, tx);
2669    }
2670
2671    // Ensure transaction is removed when this future is dropped
2672    let _guard = TransactionGuard {
2673        map: &inner.pending_transactions,
2674        tx_id,
2675    };
2676
2677    let (socket, turn_client) = if local.typ == IceCandidateType::Relay {
2678        let gatherer = &inner.gatherer;
2679        let clients = gatherer.turn_clients.lock();
2680        let client = clients.get(&local.address).cloned();
2681        (None, client)
2682    } else {
2683        let socket = inner.gatherer.get_socket(local.base_address());
2684        (socket, None)
2685    };
2686
2687    if local.typ == IceCandidateType::Relay {
2688        let client = turn_client
2689            .as_ref()
2690            .ok_or_else(|| anyhow!("TURN client not found for relay candidate"))?;
2691
2692        let (perm_bytes, perm_tx_id) = client.create_permission_packet(remote.address).await?;
2693
2694        let (perm_tx, perm_rx) = oneshot::channel();
2695        {
2696            let mut map = inner.pending_transactions.lock();
2697            map.insert(perm_tx_id, perm_tx);
2698        }
2699
2700        trace!("Sending CreatePermission to TURN server");
2701        if let Err(e) = client.send(&perm_bytes).await {
2702            debug!("CreatePermission send failed: {}", e);
2703            return Err(e);
2704        }
2705
2706        match timeout(inner.config.stun_timeout, perm_rx).await {
2707            Ok(Ok(msg)) => {
2708                if msg.class == StunClass::ErrorResponse {
2709                    bail!("CreatePermission failed: {:?}", msg.error_code);
2710                }
2711
2712                // Try ChannelBind if not already bound
2713                if client.get_channel(remote.address).await.is_none()
2714                    && let Ok((bind_bytes, bind_tx_id, channel_num)) =
2715                        client.create_channel_bind_packet(remote.address).await
2716                {
2717                    let (bind_tx, bind_rx) = oneshot::channel();
2718                    {
2719                        let mut map = inner.pending_transactions.lock();
2720                        map.insert(bind_tx_id, bind_tx);
2721                    }
2722
2723                    if client.send(&bind_bytes).await.is_ok() {
2724                        let client_clone = client.clone();
2725                        let remote_addr = remote.address;
2726                        let inner_weak = Arc::downgrade(inner);
2727                        let timeout_dur = inner.config.stun_timeout;
2728
2729                        match timeout(timeout_dur, bind_rx).await {
2730                            Ok(Ok(msg)) => {
2731                                if msg.class == StunClass::SuccessResponse {
2732                                    client_clone.add_channel(remote_addr, channel_num).await;
2733                                }
2734                            }
2735                            _ => {
2736                                // Timeout or error: clean up pending transaction
2737                                if let Some(inner) = inner_weak.upgrade() {
2738                                    let mut map = inner.pending_transactions.lock();
2739                                    map.remove(&bind_tx_id);
2740                                }
2741                            }
2742                        }
2743                    }
2744                }
2745            }
2746            _ => {
2747                let mut map = inner.pending_transactions.lock();
2748                map.remove(&perm_tx_id);
2749                bail!("CreatePermission timeout");
2750            }
2751        }
2752    } else if socket.is_none() {
2753        bail!("no socket found for local candidate");
2754    }
2755
2756    let start = Instant::now();
2757    let mut rto = Duration::from_millis(500);
2758    let max_timeout = if nominated {
2759        inner.config.nomination_timeout
2760    } else {
2761        inner.config.stun_timeout
2762    };
2763
2764    loop {
2765        if let Some(client) = &turn_client {
2766            let sent = if let Some(channel) = client.get_channel(remote.address).await {
2767                client.send_channel_data(channel, &bytes).await
2768            } else {
2769                client.send_indication(remote.address, &bytes).await
2770            };
2771
2772            if let Err(e) = sent {
2773                debug!("TURN send failed: {}", e);
2774                return Err(e);
2775            }
2776        } else if local.transport == "tcp" {
2777            // For TCP transport (active side): connect to remote and send STUN
2778            let tcp_stream = match TcpStream::connect(remote.address).await {
2779                Ok(stream) => {
2780                    stream.set_nodelay(true).ok();
2781                    stream
2782                }
2783                Err(e) => {
2784                    debug!("TCP connect to {} failed: {}", remote.address, e);
2785                    return Err(e.into());
2786                }
2787            };
2788            use tokio::io::{AsyncReadExt, AsyncWriteExt};
2789            let mut tcp_stream = tcp_stream;
2790            let mut framed = Vec::with_capacity(2 + bytes.len());
2791            let flen = bytes.len() as u16;
2792            framed.extend_from_slice(&flen.to_be_bytes());
2793            framed.extend_from_slice(&bytes);
2794            tcp_stream.write_all(&framed).await?;
2795            // Read STUN response with TCP framing
2796            match timeout(inner.config.stun_timeout, async {
2797                let mut len_buf = [0u8; 2];
2798                tcp_stream.read_exact(&mut len_buf).await?;
2799                let resp_len = u16::from_be_bytes(len_buf) as usize;
2800                let mut resp_buf = vec![0u8; resp_len];
2801                tcp_stream.read_exact(&mut resp_buf).await?;
2802                StunMessage::decode(&resp_buf).map_err(|e| anyhow!(e))
2803            })
2804            .await
2805            {
2806                Ok(Ok(parsed)) => {
2807                    if parsed.class == StunClass::SuccessResponse {
2808                        return Ok(());
2809                    }
2810                    return Err(anyhow!("TCP binding check failed: unexpected response"));
2811                }
2812                Ok(Err(e)) => return Err(e),
2813                Err(_) => return Err(anyhow!("TCP binding check timeout")),
2814            }
2815        } else if let Some(socket) = &socket
2816            && let Err(e) = socket.send_to(&bytes, remote.address).await
2817        {
2818            let is_fatal = matches!(
2819                e.kind(),
2820                std::io::ErrorKind::BrokenPipe
2821                    | std::io::ErrorKind::ConnectionReset
2822                    | std::io::ErrorKind::NotConnected
2823            );
2824            if is_fatal {
2825                debug!(
2826                    "socket.send_to {} fatal error, aborting nomination: {}",
2827                    remote.address, e
2828                );
2829                return Err(e.into());
2830            }
2831            // Transient error (e.g., EHOSTUNREACH / os error 65 during route setup).
2832            // Treat as a dropped send — wait for next RTO and retry.
2833        }
2834
2835        let timeout_fut = tokio::time::sleep(max_timeout.saturating_sub(start.elapsed()));
2836        let rto_fut = tokio::time::sleep(rto);
2837
2838        tokio::select! {
2839            res = &mut rx => {
2840                let parsed = match res {
2841                    Ok(msg) => msg,
2842                    Err(_) => bail!("channel closed"),
2843                };
2844
2845                if parsed.transaction_id != tx_id {
2846                    bail!("binding response transaction mismatch");
2847                }
2848                if parsed.method != StunMethod::Binding {
2849                    bail!("unexpected STUN method in binding response");
2850                }
2851                if parsed.class != StunClass::SuccessResponse {
2852                    bail!("binding request failed");
2853                }
2854                return Ok(());
2855            }
2856            _ = timeout_fut => {
2857                bail!("timeout");
2858            }
2859            _ = rto_fut => {
2860                if start.elapsed() >= max_timeout {
2861                    continue;
2862                }
2863                trace!("Retransmitting STUN Request to {} tx={:?}", remote.address, tx_id);
2864                rto = std::cmp::min(rto * 2, Duration::from_millis(1600));
2865            }
2866        }
2867    }
2868}
2869
2870/// Perform a STUN binding check over a TCP connection.
2871///
2872/// For TCP candidates (RFC 6544):
2873/// 1. Connect to the remote peer's TCP address
2874/// 2. Send the STUN binding request over the TCP stream
2875/// 3. Read the response, decode it, and deliver it to the pending transaction
2876/// 4. Store the stream for later media use
2877///
2878/// RFC 4571 STUN/TCP framing used by WebRTC (length prefix + message).
2879fn frame_stun_for_tcp(data: &[u8]) -> Vec<u8> {
2880    let len = data.len() as u16;
2881    let mut framed = Vec::with_capacity(2 + data.len());
2882    framed.extend_from_slice(&len.to_be_bytes());
2883    framed.extend_from_slice(data);
2884    framed
2885}
2886
2887type TcpReadHalf = tokio::net::tcp::OwnedReadHalf;
2888type TcpWriteHalf = tokio::net::tcp::OwnedWriteHalf;
2889
2890fn split_tcp_stream(stream: TcpStream, peer: SocketAddr) -> IceSocketWrapper {
2891    if let Err(e) = stream.set_nodelay(true) {
2892        debug!("TCP set_nodelay failed: {}", e);
2893    }
2894    let (read, write) = stream.into_split();
2895    IceSocketWrapper::TcpStream(
2896        Arc::new(Mutex::new(read)),
2897        Arc::new(Mutex::new(write)),
2898        peer,
2899    )
2900}
2901
2902pub(crate) async fn attach_demuxed_tcp_stream(
2903    inner: Arc<IceTransportInner>,
2904    stream: TcpStream,
2905    peer_addr: SocketAddr,
2906    listen_addr: SocketAddr,
2907    first_packet: Vec<u8>,
2908) {
2909    let wrapper = split_tcp_stream(stream, peer_addr);
2910    inner
2911        .gatherer
2912        .store_tcp_stream(listen_addr, wrapper.clone());
2913    let _ = inner.gatherer.socket_tx.send(wrapper.clone());
2914    let mut marshal_buf = Vec::new();
2915    handle_packet(&first_packet, peer_addr, inner, wrapper, &mut marshal_buf).await;
2916}
2917
2918pub(crate) async fn tcp_write_all(write: &Arc<Mutex<TcpWriteHalf>>, data: &[u8]) -> Result<()> {
2919    let mut offset = 0;
2920    while offset < data.len() {
2921        let guard = write.lock().await;
2922        loop {
2923            match guard.try_write(&data[offset..]) {
2924                Ok(0) => guard.writable().await?,
2925                Ok(n) => {
2926                    offset += n;
2927                    break;
2928                }
2929                Err(e) if e.kind() == ErrorKind::WouldBlock => guard.writable().await?,
2930                Err(e) => return Err(anyhow!("TCP write failed: {}", e)),
2931            }
2932        }
2933    }
2934    Ok(())
2935}
2936
2937async fn perform_tcp_binding_check(
2938    local: &IceCandidate,
2939    remote: &IceCandidate,
2940    inner: &Arc<IceTransportInner>,
2941    role: IceRole,
2942    nominated: bool,
2943) -> Result<()> {
2944    debug!(
2945        "perform_tcp_binding_check: {} -> {}",
2946        local.address, remote.address
2947    );
2948    let local_params = inner.local_parameters.lock().clone();
2949    let remote_params = match inner.remote_parameters.lock().clone() {
2950        Some(p) => p,
2951        None => bail!("no remote params"),
2952    };
2953
2954    let tx_id = random_bytes::<12>();
2955    let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
2956    let username = format!(
2957        "{}:{}",
2958        remote_params.username_fragment, local_params.username_fragment
2959    );
2960    msg.attributes.push(StunAttribute::Username(username));
2961    msg.attributes.push(StunAttribute::Priority(local.priority));
2962    match role {
2963        IceRole::Controlling => {
2964            msg.attributes
2965                .push(StunAttribute::IceControlling(local_params.tie_breaker));
2966            if nominated {
2967                msg.attributes.push(StunAttribute::UseCandidate);
2968            }
2969        }
2970        IceRole::Controlled => msg
2971            .attributes
2972            .push(StunAttribute::IceControlled(local_params.tie_breaker)),
2973    }
2974    let bytes = msg.encode(Some(remote_params.password.as_bytes()), true)?;
2975
2976    // Establish TCP connection to the remote peer
2977    let connect_timeout = inner.config.stun_timeout;
2978    let stream = timeout(connect_timeout, TcpStream::connect(remote.address))
2979        .await
2980        .map_err(|_| anyhow!("TCP connect timeout to {}", remote.address))?
2981        .map_err(|e| anyhow!("TCP connect to {} failed: {}", remote.address, e))?;
2982
2983    let local_addr = stream.local_addr()?;
2984    let wrapper = split_tcp_stream(stream, remote.address);
2985    let write = match &wrapper {
2986        IceSocketWrapper::TcpStream(_, write, _) => write.clone(),
2987        _ => bail!("split_tcp_stream invariant"),
2988    };
2989
2990    // Register the TCP stream with the runner so its read loop handles incoming STUN responses
2991    inner.gatherer.store_tcp_stream(local_addr, wrapper.clone());
2992    let _ = inner.gatherer.socket_tx.send(wrapper);
2993
2994    // Register pending transaction
2995    let (tx, mut rx) = oneshot::channel();
2996    {
2997        let mut map = inner.pending_transactions.lock();
2998        map.insert(tx_id, tx);
2999    }
3000    let _guard = TransactionGuard {
3001        map: &inner.pending_transactions,
3002        tx_id,
3003    };
3004
3005    // Send STUN binding request over TCP (RFC 4571 framed)
3006    {
3007        let framed = frame_stun_for_tcp(&bytes);
3008        tcp_write_all(&write, &framed).await?;
3009    }
3010
3011    // Wait for response (with retransmissions) via read loop → pending_transactions
3012    let start = Instant::now();
3013    let mut rto = Duration::from_millis(500);
3014    let max_timeout = if nominated {
3015        inner.config.nomination_timeout
3016    } else {
3017        inner.config.stun_timeout
3018    };
3019
3020    loop {
3021        let timeout_fut = tokio::time::sleep(max_timeout.saturating_sub(start.elapsed()));
3022        let rto_fut = tokio::time::sleep(rto);
3023
3024        tokio::select! {
3025            res = &mut rx => {
3026                let parsed = match res {
3027                    Ok(msg) => msg,
3028                    Err(_) => bail!("channel closed"),
3029                };
3030                if parsed.transaction_id != tx_id {
3031                    bail!("binding response transaction mismatch");
3032                }
3033                if parsed.method != StunMethod::Binding {
3034                    bail!("unexpected STUN method in binding response");
3035                }
3036                if parsed.class != StunClass::SuccessResponse {
3037                    bail!("binding request failed");
3038                }
3039                return Ok(());
3040            }
3041            _ = timeout_fut => {
3042                bail!("timeout");
3043            }
3044            _ = rto_fut => {
3045                if start.elapsed() >= max_timeout {
3046                    continue;
3047                }
3048                trace!("TCP Retransmitting STUN Request to {} tx={:?}", remote.address, tx_id);
3049                rto = std::cmp::min(rto * 2, Duration::from_millis(1600));
3050                let framed = frame_stun_for_tcp(&bytes);
3051                let _ = tcp_write_all(&write, &framed).await;
3052            }
3053        }
3054    }
3055}
3056
3057#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3058pub enum IceTransportState {
3059    New,
3060    Checking,
3061    Connected,
3062    Completed,
3063    Failed,
3064    Disconnected,
3065    Closed,
3066}
3067
3068#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3069pub enum IceGathererState {
3070    New,
3071    Gathering,
3072    Complete,
3073}
3074
3075#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3076pub enum IceRole {
3077    Controlling,
3078    Controlled,
3079}
3080
3081/// TCP candidate type per RFC 6544 § 4.5.
3082#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3083pub enum TcpType {
3084    Active,
3085    Passive,
3086    So,
3087}
3088
3089impl TcpType {
3090    fn as_str(&self) -> &'static str {
3091        match self {
3092            TcpType::Active => "active",
3093            TcpType::Passive => "passive",
3094            TcpType::So => "so",
3095        }
3096    }
3097
3098    fn from_str(s: &str) -> Option<Self> {
3099        match s {
3100            "active" => Some(TcpType::Active),
3101            "passive" => Some(TcpType::Passive),
3102            "so" => Some(TcpType::So),
3103            _ => None,
3104        }
3105    }
3106}
3107
3108#[derive(Debug, Clone, PartialEq, Eq)]
3109pub struct IceCandidate {
3110    pub foundation: String,
3111    pub priority: u32,
3112    pub address: SocketAddr,
3113    pub typ: IceCandidateType,
3114    pub transport: String,
3115    pub tcp_type: Option<TcpType>,
3116    pub related_address: Option<SocketAddr>,
3117    pub component: u16,
3118}
3119
3120impl IceCandidate {
3121    fn compute_foundation(typ: IceCandidateType, base_addr: SocketAddr, transport: &str) -> String {
3122        use std::collections::hash_map::DefaultHasher;
3123        use std::hash::{Hash, Hasher};
3124
3125        let mut hasher = DefaultHasher::new();
3126        typ.hash(&mut hasher);
3127        base_addr.ip().hash(&mut hasher);
3128        transport.hash(&mut hasher);
3129        format!("{:x}", hasher.finish())
3130    }
3131
3132    /// Set the TCP type on this candidate (for peer-reflexive discovery over TCP).
3133    pub fn with_tcp_type(mut self, tcp_type: TcpType) -> Self {
3134        self.tcp_type = Some(tcp_type);
3135        self.transport = "tcp".into();
3136        self
3137    }
3138
3139    pub fn host(address: SocketAddr, component: u16) -> Self {
3140        Self {
3141            foundation: Self::compute_foundation(IceCandidateType::Host, address, "udp"),
3142            priority: IceCandidate::priority_for(IceCandidateType::Host, component),
3143            address,
3144            typ: IceCandidateType::Host,
3145            transport: "udp".into(),
3146            tcp_type: None,
3147            related_address: None,
3148            component,
3149        }
3150    }
3151
3152    pub fn host_tcp(address: SocketAddr, component: u16, tcp_type: TcpType) -> Self {
3153        Self {
3154            foundation: Self::compute_foundation(IceCandidateType::Host, address, "tcp"),
3155            priority: IceCandidate::priority_for_tcp(IceCandidateType::Host, component, tcp_type),
3156            address,
3157            typ: IceCandidateType::Host,
3158            transport: "tcp".into(),
3159            tcp_type: Some(tcp_type),
3160            related_address: None,
3161            component,
3162        }
3163    }
3164
3165    pub fn tcp(address: SocketAddr, component: u16, tcptype_str: &str) -> Self {
3166        let transport = "tcp";
3167        let tcp_type = TcpType::from_str(tcptype_str).unwrap_or(TcpType::Passive);
3168        Self {
3169            foundation: Self::compute_foundation(IceCandidateType::Host, address, transport),
3170            priority: IceCandidate::priority_for_tcp(IceCandidateType::Host, component, tcp_type),
3171            address,
3172            typ: IceCandidateType::Host,
3173            transport: transport.into(),
3174            tcp_type: Some(tcp_type),
3175            related_address: None,
3176            component,
3177        }
3178    }
3179
3180    pub fn base_address(&self) -> SocketAddr {
3181        if self.typ == IceCandidateType::ServerReflexive || self.typ == IceCandidateType::Host {
3182            self.related_address.unwrap_or(self.address)
3183        } else {
3184            self.address
3185        }
3186    }
3187
3188    fn server_reflexive(base: SocketAddr, mapped: SocketAddr, component: u16) -> Self {
3189        Self {
3190            foundation: Self::compute_foundation(IceCandidateType::ServerReflexive, base, "udp"),
3191            priority: IceCandidate::priority_for(IceCandidateType::ServerReflexive, component),
3192            address: mapped,
3193            typ: IceCandidateType::ServerReflexive,
3194            transport: "udp".into(),
3195            tcp_type: None,
3196            related_address: Some(base),
3197            component,
3198        }
3199    }
3200
3201    fn relay(mapped: SocketAddr, component: u16, transport: &str) -> Self {
3202        Self {
3203            foundation: Self::compute_foundation(IceCandidateType::Relay, mapped, transport),
3204            priority: IceCandidate::priority_for(IceCandidateType::Relay, component),
3205            address: mapped,
3206            typ: IceCandidateType::Relay,
3207            transport: transport.into(),
3208            tcp_type: None,
3209            related_address: None,
3210            component,
3211        }
3212    }
3213
3214    fn priority_for(typ: IceCandidateType, component: u16) -> u32 {
3215        let type_pref = match typ {
3216            IceCandidateType::Host => 126u32,
3217            IceCandidateType::PeerReflexive => 110u32,
3218            IceCandidateType::ServerReflexive => 100u32,
3219            IceCandidateType::Relay => 0u32,
3220        };
3221        let local_pref = 65_535u32;
3222        let component = component.min(256) as u32;
3223        (type_pref << 24) | (local_pref << 8) | (256 - component)
3224    }
3225
3226    /// Priority for TCP candidates per RFC 6544 § 4.1.
3227    /// TCP candidates use a different local preference to distinguish
3228    /// between active, passive, and SO types, while UDP candidates always
3229    /// use the full 65535 local preference.
3230    fn priority_for_tcp(typ: IceCandidateType, component: u16, tcp_type: TcpType) -> u32 {
3231        let type_pref = match typ {
3232            IceCandidateType::Host => 126u32,
3233            IceCandidateType::PeerReflexive => 110u32,
3234            IceCandidateType::ServerReflexive => 100u32,
3235            IceCandidateType::Relay => 0u32,
3236        };
3237        // RFC 6544 § 4.1: local preference for TCP candidates
3238        let local_pref = match tcp_type {
3239            TcpType::Passive => 65535u32,
3240            TcpType::Active => 65534u32,
3241            TcpType::So => 65533u32,
3242        };
3243        let component = component.min(256) as u32;
3244        (type_pref << 24) | (local_pref << 8) | (256 - component)
3245    }
3246
3247    pub fn to_sdp(&self) -> String {
3248        let mut parts = vec![
3249            self.foundation.clone(),
3250            self.component.to_string(),
3251            self.transport.to_ascii_lowercase(),
3252            self.priority.to_string(),
3253            self.address.ip().to_string(),
3254            self.address.port().to_string(),
3255            "typ".into(),
3256            self.typ.as_str().into(),
3257        ];
3258        if let Some(tcp_type) = self.tcp_type {
3259            parts.push("tcptype".into());
3260            parts.push(tcp_type.as_str().into());
3261        }
3262        if let Some(addr) = self.related_address
3263            && self.typ != IceCandidateType::Host
3264        {
3265            parts.push("raddr".into());
3266            parts.push(addr.ip().to_string());
3267            parts.push("rport".into());
3268            parts.push(addr.port().to_string());
3269        }
3270        parts.join(" ")
3271    }
3272
3273    pub fn from_sdp(sdp: &str) -> Result<Self> {
3274        let parts: Vec<&str> = sdp.split_whitespace().collect();
3275        if parts.len() < 8 {
3276            bail!("invalid candidate");
3277        }
3278        // Handle "candidate:" prefix if present (though usually it's the attribute key)
3279        let start_idx = 0;
3280
3281        let foundation = parts[start_idx]
3282            .trim_start_matches("candidate:")
3283            .to_string();
3284        let component = parts[start_idx + 1].parse::<u16>()?;
3285        let transport = parts[start_idx + 2].to_ascii_lowercase();
3286        let priority = parts[start_idx + 3].parse::<u32>()?;
3287        let ip_str = parts[start_idx + 4];
3288        let port = parts[start_idx + 5].parse::<u16>()?;
3289        let typ_str = parts[start_idx + 7];
3290
3291        // IPv6 addresses need brackets when combined with port
3292        let address = if ip_str.contains(':') {
3293            format!("[{}]:{}", ip_str, port).parse()?
3294        } else {
3295            format!("{}:{}", ip_str, port).parse()?
3296        };
3297
3298        let typ = match typ_str {
3299            "host" => IceCandidateType::Host,
3300            "srflx" => IceCandidateType::ServerReflexive,
3301            "prflx" => IceCandidateType::PeerReflexive,
3302            "relay" => IceCandidateType::Relay,
3303            _ => bail!("unknown type"),
3304        };
3305
3306        // Parse optional tcptype attribute (RFC 6544)
3307        let tcp_type = if transport == "tcp" {
3308            // Search for "tcptype" keyword at even indices after position 8
3309            let mut i = 8;
3310            loop {
3311                if i + 1 >= parts.len() {
3312                    break None;
3313                }
3314                match parts[i] {
3315                    "tcptype" => {
3316                        break TcpType::from_str(parts[i + 1]);
3317                    }
3318                    _ => {
3319                        i += 2;
3320                    }
3321                }
3322            }
3323        } else {
3324            None
3325        };
3326
3327        Ok(Self {
3328            foundation,
3329            priority,
3330            address,
3331            typ,
3332            transport,
3333            tcp_type,
3334            related_address: None,
3335            component,
3336        })
3337    }
3338}
3339
3340#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3341pub enum IceCandidateType {
3342    Host,
3343    ServerReflexive,
3344    PeerReflexive,
3345    Relay,
3346}
3347
3348impl IceCandidateType {
3349    fn as_str(&self) -> &'static str {
3350        match self {
3351            IceCandidateType::Host => "host",
3352            IceCandidateType::ServerReflexive => "srflx",
3353            IceCandidateType::PeerReflexive => "prflx",
3354            IceCandidateType::Relay => "relay",
3355        }
3356    }
3357}
3358
3359#[derive(Debug, Clone, PartialEq, Eq)]
3360pub struct IceCandidatePair {
3361    pub local: IceCandidate,
3362    pub remote: IceCandidate,
3363    pub nominated: bool,
3364}
3365
3366impl IceCandidatePair {
3367    pub fn new(local: IceCandidate, remote: IceCandidate) -> Self {
3368        Self {
3369            local,
3370            remote,
3371            nominated: false,
3372        }
3373    }
3374
3375    pub fn priority(&self, role: IceRole) -> u64 {
3376        let g = self.local.priority as u64;
3377        let d = self.remote.priority as u64;
3378        let (g, d) = match role {
3379            IceRole::Controlling => (g, d),
3380            IceRole::Controlled => (d, g),
3381        };
3382        (1u64 << 32) * std::cmp::min(g, d) + 2 * std::cmp::max(g, d) + if g > d { 1 } else { 0 }
3383    }
3384}
3385
3386#[derive(Debug, Clone)]
3387pub struct IceParameters {
3388    pub username_fragment: String,
3389    pub password: String,
3390    pub ice_lite: bool,
3391    pub tie_breaker: u64,
3392}
3393
3394impl IceParameters {
3395    pub fn new(username_fragment: impl Into<String>, password: impl Into<String>) -> Self {
3396        Self {
3397            username_fragment: username_fragment.into(),
3398            password: password.into(),
3399            ice_lite: false,
3400            tie_breaker: random_u64(),
3401        }
3402    }
3403
3404    fn generate() -> Self {
3405        let ufrag = hex_encode(&random_bytes::<8>());
3406        let pwd = hex_encode(&random_bytes::<16>());
3407        Self {
3408            username_fragment: ufrag,
3409            password: pwd,
3410            ice_lite: false,
3411            tie_breaker: random_u64(),
3412        }
3413    }
3414}
3415
3416#[derive(Debug, Clone)]
3417pub struct IceTransportBuilder {
3418    config: RtcConfiguration,
3419    role: IceRole,
3420    servers: Vec<IceServer>,
3421}
3422
3423impl IceTransportBuilder {
3424    pub fn new(config: RtcConfiguration) -> Self {
3425        Self {
3426            config,
3427            role: IceRole::Controlled,
3428            servers: Vec::new(),
3429        }
3430    }
3431
3432    pub fn role(mut self, role: IceRole) -> Self {
3433        self.role = role;
3434        self
3435    }
3436
3437    pub fn server(mut self, server: IceServer) -> Self {
3438        self.servers.push(server);
3439        self
3440    }
3441
3442    pub fn build(self) -> (IceTransport, impl std::future::Future<Output = ()> + Send) {
3443        let mut config = self.config.clone();
3444        config.ice_servers.extend(self.servers);
3445        let (transport, runner) = IceTransport::new(config);
3446        transport.set_role(self.role);
3447        if let Err(err) = transport.start_gathering() {
3448            debug!("ICE gather failed: {}", err);
3449        }
3450        (transport, runner)
3451    }
3452}
3453
3454#[derive(Debug, Clone)]
3455struct IceGatherer {
3456    state: Arc<parking_lot::Mutex<IceGathererState>>,
3457    local_candidates: Arc<parking_lot::Mutex<Vec<IceCandidate>>>,
3458    sockets: Arc<parking_lot::Mutex<Vec<Arc<UdpSocket>>>>,
3459    tcp_listeners: Arc<parking_lot::Mutex<Vec<Arc<TcpListener>>>>,
3460    tcp_streams: Arc<parking_lot::Mutex<HashMap<SocketAddr, IceSocketWrapper>>>,
3461    shared_tcp_regs: Arc<parking_lot::Mutex<Vec<shared_tcp::SharedTcpRegistration>>>,
3462    shared_udp_regs: Arc<parking_lot::Mutex<Vec<shared_udp::SharedUdpRegistration>>>,
3463    /// The shared UDP mux socket wrapper (when `ice_udp_mux` is enabled).
3464    /// Stored so `resolve_socket` can return it for sending.
3465    shared_udp_socket: Arc<parking_lot::Mutex<Option<IceSocketWrapper>>>,
3466    transport_inner: Arc<parking_lot::Mutex<Option<std::sync::Weak<IceTransportInner>>>>,
3467    turn_clients: Arc<parking_lot::Mutex<HashMap<SocketAddr, Arc<TurnClient>>>>,
3468    upnp_mappers: Arc<parking_lot::Mutex<Vec<UpnpPortMapper>>>,
3469    config: RtcConfiguration,
3470    candidate_tx: broadcast::Sender<IceCandidate>,
3471    socket_tx: tokio::sync::mpsc::UnboundedSender<IceSocketWrapper>,
3472}
3473
3474impl IceGatherer {
3475    fn new(
3476        config: RtcConfiguration,
3477        candidate_tx: broadcast::Sender<IceCandidate>,
3478        socket_tx: tokio::sync::mpsc::UnboundedSender<IceSocketWrapper>,
3479    ) -> Self {
3480        Self {
3481            state: Arc::new(parking_lot::Mutex::new(IceGathererState::New)),
3482            local_candidates: Arc::new(parking_lot::Mutex::new(Vec::new())),
3483            sockets: Arc::new(parking_lot::Mutex::new(Vec::new())),
3484            tcp_listeners: Arc::new(parking_lot::Mutex::new(Vec::new())),
3485            tcp_streams: Arc::new(parking_lot::Mutex::new(HashMap::new())),
3486            shared_tcp_regs: Arc::new(parking_lot::Mutex::new(Vec::new())),
3487            shared_udp_regs: Arc::new(parking_lot::Mutex::new(Vec::new())),
3488            shared_udp_socket: Arc::new(parking_lot::Mutex::new(None)),
3489            transport_inner: Arc::new(parking_lot::Mutex::new(None)),
3490            turn_clients: Arc::new(parking_lot::Mutex::new(HashMap::new())),
3491            upnp_mappers: Arc::new(parking_lot::Mutex::new(Vec::new())),
3492            config,
3493            candidate_tx,
3494            socket_tx,
3495        }
3496    }
3497
3498    fn set_transport(&self, inner: std::sync::Weak<IceTransportInner>) {
3499        *self.transport_inner.lock() = Some(inner);
3500    }
3501
3502    fn push_tcp_passive_candidate(&self, local_addr: SocketAddr, bind_ip: IpAddr) {
3503        if let Some(ext_ip) = &self.config.external_ip
3504            && let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
3505        {
3506            if !bind_ip.is_loopback() {
3507                let mut ext_addr = local_addr;
3508                ext_addr.set_ip(parsed_ip);
3509                let mut cand = IceCandidate::tcp(ext_addr, 1, "passive");
3510                cand.related_address = Some(local_addr);
3511                self.push_candidate(cand);
3512            } else {
3513                self.push_candidate(IceCandidate::tcp(local_addr, 1, "passive"));
3514            }
3515        } else if bind_ip.is_unspecified() {
3516            let mut cand_addr = local_addr;
3517            if let Ok(local_ip) = get_local_ip() {
3518                cand_addr.set_ip(local_ip);
3519            }
3520            let mut cand = IceCandidate::tcp(cand_addr, 1, "passive");
3521            cand.related_address = Some(local_addr);
3522            self.push_candidate(cand);
3523        } else {
3524            self.push_candidate(IceCandidate::tcp(local_addr, 1, "passive"));
3525        }
3526    }
3527
3528    /// Get the UPnP mappers for manual cleanup
3529    #[allow(dead_code)]
3530    pub fn upnp_mappers(&self) -> Arc<parking_lot::Mutex<Vec<UpnpPortMapper>>> {
3531        self.upnp_mappers.clone()
3532    }
3533
3534    /// Clean up all UPnP port mappings
3535    #[allow(dead_code)]
3536    pub async fn cleanup_upnp_mappings(&self) {
3537        let mappers = self.upnp_mappers.lock().clone();
3538        for mapper in mappers {
3539            if let Err(e) = mapper.cleanup().await {
3540                trace!("Failed to clean up UPnP mappings: {}", e);
3541            }
3542        }
3543        self.upnp_mappers.lock().clear();
3544    }
3545
3546    /// Refresh all stale UPnP port mappings (best-effort).
3547    ///
3548    /// Called periodically by the ICE runner so long-lived sessions keep their
3549    /// router leases alive. Each mapper renews its own stale mappings and a
3550    /// single failure does not abort the rest.
3551    pub async fn renew_upnp_mappings(&self) {
3552        let mappers = self.upnp_mappers.lock().clone();
3553        for mapper in mappers {
3554            if let Err(e) = mapper.renew_all_stale().await {
3555                warn!("Failed to refresh UPnP mappings: {}", e);
3556            }
3557        }
3558    }
3559
3560    fn state(&self) -> IceGathererState {
3561        *self.state.lock()
3562    }
3563
3564    fn local_candidates(&self) -> Vec<IceCandidate> {
3565        self.local_candidates.lock().clone()
3566    }
3567
3568    async fn bind_socket(&self, ip: IpAddr) -> Result<UdpSocket> {
3569        if let (Some(start), Some(end)) = (self.config.rtp_start_port, self.config.rtp_end_port) {
3570            let start = start.saturating_add(start % 2);
3571            let end = end - (end % 2);
3572
3573            if start > end {
3574                bail!("No usable even RTP ports in range {}..={}", start, end);
3575            }
3576
3577            let port_count = (((end - start) / 2) + 1) as u64;
3578            let start_index = (random_u64() % port_count) as u16;
3579            let mut port = start + (start_index * 2);
3580
3581            for _ in 0..port_count {
3582                match UdpSocket::bind(SocketAddr::new(ip, port)).await {
3583                    Ok(socket) => return Ok(socket),
3584                    Err(_) => {
3585                        port = port.saturating_add(2);
3586                        if port > end {
3587                            port = start;
3588                        }
3589                    }
3590                }
3591            }
3592            bail!("No available even RTP ports in range {}..={}", start, end)
3593        } else {
3594            UdpSocket::bind(SocketAddr::new(ip, 0))
3595                .await
3596                .map_err(|e| anyhow!(e))
3597        }
3598    }
3599
3600    fn get_socket(&self, addr: SocketAddr) -> Option<Arc<UdpSocket>> {
3601        let found = {
3602            let sockets = self.sockets.lock();
3603            sockets.iter().find_map(|socket| {
3604                let local = socket.local_addr().ok()?;
3605                let matches =
3606                    local == addr || (local.ip().is_unspecified() && local.port() == addr.port());
3607                matches.then(|| socket.clone())
3608            })
3609        };
3610        if let Some(s) = found {
3611            return Some(s);
3612        }
3613        // Fallback: the shared UDP mux socket (sending side). It backs the single
3614        // host candidate when `ice_udp_mux` is enabled and is not stored in
3615        // `sockets` (to keep a single demux read loop).
3616        if let Some(IceSocketWrapper::SharedUdp(handle)) = self.shared_udp_socket.lock().clone()
3617            && let Ok(local) = handle.local_addr()
3618            && (local == addr || (local.ip().is_unspecified() && local.port() == addr.port()))
3619        {
3620            return Some(handle.socket().clone());
3621        }
3622        // Avoid unwrap in logging to prevent panic hiding
3623        let available: Vec<String> = self
3624            .sockets
3625            .lock()
3626            .iter()
3627            .map(|s| {
3628                s.local_addr()
3629                    .map(|a| a.to_string())
3630                    .unwrap_or_else(|_| "error".to_string())
3631            })
3632            .collect();
3633        trace!(
3634            "get_socket: no socket found for {}, available: {:?}",
3635            addr, available
3636        );
3637        None
3638    }
3639
3640    fn get_tcp_socket(&self, addr: SocketAddr) -> Option<IceSocketWrapper> {
3641        let streams = self.tcp_streams.lock();
3642        for (local_addr, wrapper) in streams.iter() {
3643            if *local_addr == addr {
3644                return Some(wrapper.clone());
3645            }
3646            // Match on port only if IP is unspecified (0.0.0.0)
3647            if local_addr.ip().is_unspecified() && local_addr.port() == addr.port() {
3648                return Some(wrapper.clone());
3649            }
3650        }
3651        trace!(
3652            "get_tcp_socket: no TCP stream found for {}, available: {:?}",
3653            addr,
3654            streams.keys().collect::<Vec<_>>()
3655        );
3656        None
3657    }
3658
3659    fn store_tcp_stream(&self, local_addr: SocketAddr, wrapper: IceSocketWrapper) {
3660        self.tcp_streams.lock().insert(local_addr, wrapper);
3661    }
3662
3663    #[instrument(skip(self))]
3664    async fn gather(&self) -> Result<()> {
3665        {
3666            let mut state = self.state.lock();
3667            if *state == IceGathererState::Complete {
3668                return Ok(());
3669            }
3670            *state = IceGathererState::Gathering;
3671        }
3672
3673        // Host gathering must complete first (creates sockets)
3674        let host_fut = async {
3675            if self.config.ice_transport_policy == IceTransportPolicy::All {
3676                if self.config.ice_gather_udp_hosts {
3677                    if let Err(e) = self.gather_host_candidates().await {
3678                        debug!("Host gathering failed: {}", e);
3679                    }
3680                } else if self.config.ice_tcp_policy == crate::config::IceTcpPolicy::Enabled {
3681                    // Outbound controlling peers with no TCP listen range advertise active locals.
3682                    // WHEP/answerer setups configure tcp_port_range_* for passive listeners;
3683                    // skip active placeholders so SDP does not contain invalid port 0 candidates.
3684                    let has_tcp_listen_range = match (
3685                        self.config.tcp_port_range_start,
3686                        self.config.tcp_port_range_end,
3687                    ) {
3688                        (Some(s), Some(e)) => s > 0 && e > 0 && s <= e,
3689                        _ => false,
3690                    };
3691                    if !has_tcp_listen_range
3692                        && let Err(e) = self.gather_tcp_active_candidates().await
3693                    {
3694                        debug!("TCP active gathering failed: {}", e);
3695                    }
3696                }
3697            }
3698        };
3699
3700        host_fut.await;
3701
3702        // TCP host candidate gathering
3703        if (self.config.tcp_port_range_start.is_some() || self.config.tcp_port_range_end.is_some())
3704            && let Err(e) = self.gather_tcp_host_candidates().await
3705        {
3706            debug!("TCP host gathering failed: {}", e);
3707        }
3708
3709        // STUN must complete before UPnP so we can detect double-NAT
3710        // and use STUN's public IP for UPnP candidates. Bounded so a
3711        // hung STUN/TURN server (no DNS timeout, no TCP connect timeout)
3712        // can never stall the whole gather — UPnP still runs and the
3713        // gather completes with whatever candidates arrived in time.
3714        let stun_public_ip = if self.config.enable_upnp {
3715            timeout(Duration::from_secs(5), self.gather_servers_and_get_public_ip())
3716                .await
3717                .unwrap_or_else(|_| {
3718                    debug!("STUN/TURN gathering timed out after 5s, skipping UPnP double-NAT detection");
3719                    None
3720                })
3721        } else {
3722            if let Err(e) = self.gather_servers().await {
3723                debug!("Server gathering failed: {}", e);
3724            }
3725            None
3726        };
3727
3728        // UPnP depends on host sockets and optionally STUN's public IP
3729        if self.config.enable_upnp
3730            && self.config.ice_transport_policy == IceTransportPolicy::All
3731            && let Err(e) = self.gather_upnp_candidates(stun_public_ip).await
3732        {
3733            debug!("UPnP gathering failed: {}", e);
3734        }
3735
3736        *self.state.lock() = IceGathererState::Complete;
3737        Ok(())
3738    }
3739
3740    /// Gather a host candidate backed by the process-wide shared UDP socket
3741    /// (single-port multiplexing). All PeerConnections sharing the same
3742    /// `ice_udp_mux_port` register their ufrag on the same socket; incoming
3743    /// packets are demuxed by ufrag / source address in `shared_udp`.
3744    async fn gather_shared_udp_host_candidate(&self) -> Result<()> {
3745        let port = self
3746            .config
3747            .ice_udp_mux_port
3748            .ok_or_else(|| anyhow!("ice_udp_mux is enabled but ice_udp_mux_port is not set"))?;
3749
3750        let bind_ip = if let Some(bind_ip_str) = &self.config.bind_ip {
3751            bind_ip_str
3752                .parse::<IpAddr>()
3753                .with_context(|| format!("invalid bind_ip {}", bind_ip_str))?
3754        } else {
3755            // Bind on the wildcard so the shared socket accepts on every
3756            // interface; the advertised candidate IP is rewritten below.
3757            IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)
3758        };
3759
3760        if self.config.disable_ipv6 && bind_ip.is_ipv6() {
3761            bail!("disable_ipv6 is set but bind_ip is IPv6");
3762        }
3763
3764        let bind_addr = SocketAddr::new(bind_ip, port);
3765
3766        let inner = self
3767            .transport_inner
3768            .lock()
3769            .as_ref()
3770            .and_then(|weak| weak.upgrade())
3771            .context("ICE transport unavailable during shared UDP gather")?;
3772        let ufrag = inner.local_parameters.lock().username_fragment.clone();
3773
3774        let (local_addr, handle, registration) = shared_udp::acquire(bind_addr, ufrag).await?;
3775
3776        self.shared_udp_regs.lock().push(registration);
3777
3778        let wrapper = IceSocketWrapper::SharedUdp(Arc::new(handle));
3779        *self.shared_udp_socket.lock() = Some(wrapper.clone());
3780        let _ = self.socket_tx.send(wrapper);
3781
3782        // Derive the advertised candidate address (mirror the per-IP path).
3783        let mut cand_addr = local_addr;
3784        if let Some(ext_ip) = &self.config.external_ip
3785            && let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
3786        {
3787            if !bind_ip.is_loopback() {
3788                cand_addr.set_ip(parsed_ip);
3789            }
3790        } else if bind_ip.is_unspecified()
3791            && let Ok(local_ip) = get_local_ip()
3792        {
3793            cand_addr.set_ip(local_ip);
3794        }
3795
3796        let mut cand = IceCandidate::host(cand_addr, 1);
3797        if cand_addr != local_addr {
3798            cand.related_address = Some(local_addr);
3799        }
3800        self.push_candidate(cand);
3801        Ok(())
3802    }
3803
3804    async fn gather_host_candidates(&self) -> Result<()> {
3805        let mut bind_ips = Vec::new();
3806
3807        if let Some(bind_ip_str) = &self.config.bind_ip {
3808            if let Ok(ip) = bind_ip_str.parse::<IpAddr>() {
3809                bind_ips.push(ip);
3810            }
3811        } else if self.config.transport_mode != crate::TransportMode::WebRtc {
3812            // Non-WebRTC mode: prefer a LAN IP if available.
3813            // Binding to 0.0.0.0 on macOS can lead to "No route to host" (os error 65)
3814            // if the destination is on a LAN segment but the OS picks a wrong default interface.
3815            if let Ok(ip) = get_local_ip() {
3816                bind_ips.push(ip);
3817            } else {
3818                bind_ips.push(IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED));
3819            }
3820        } else {
3821            // Default: bind to all LAN IPs. Loopback is only added on explicit
3822            // opt-in (`ice_include_loopback_candidates`) — advertising 127.0.0.1
3823            // to remote peers is useless and wastes their permissions/checks.
3824            if self.config.ice_include_loopback_candidates {
3825                bind_ips.push(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST));
3826            }
3827
3828            use local_ip_address::list_afinet_netifas;
3829            if let Ok(interfaces) = list_afinet_netifas() {
3830                for (name, addr) in interfaces {
3831                    if let IpAddr::V4(ip) = addr
3832                        && !ip.is_loopback()
3833                        && !bind_ips.contains(&IpAddr::V4(ip))
3834                    {
3835                        // Skip common virtual interface prefixes
3836                        if name.starts_with("utun")
3837                            || name.starts_with("gif")
3838                            || name.starts_with("stf")
3839                            || name.starts_with("awdl")
3840                            || name.starts_with("llw")
3841                        {
3842                            continue;
3843                        }
3844                        bind_ips.push(IpAddr::V4(ip));
3845                    }
3846                }
3847            }
3848        }
3849
3850        if self.config.ice_udp_mux
3851            && let Err(e) = self.gather_shared_udp_host_candidate().await
3852        {
3853            debug!("Shared UDP mux host candidate failed: {}", e);
3854        }
3855
3856        for ip in &bind_ips {
3857            let ip = *ip;
3858            // When UDP mux is enabled, the shared socket already provides the
3859            // host candidate; skip per-IP UDP socket binding.
3860            if self.config.ice_udp_mux {
3861                continue;
3862            }
3863            match self.bind_socket(ip).await {
3864                Ok(socket) => {
3865                    if let Ok(addr) = socket.local_addr() {
3866                        let socket = Arc::new(socket);
3867                        self.sockets.lock().push(socket.clone());
3868                        let _ = self.socket_tx.send(IceSocketWrapper::Udp(socket));
3869
3870                        if let Some(ext_ip) = &self.config.external_ip
3871                            && let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
3872                        {
3873                            if !ip.is_loopback() {
3874                                let mut ext_addr = addr;
3875                                ext_addr.set_ip(parsed_ip);
3876                                let mut cand = IceCandidate::host(ext_addr, 1);
3877                                cand.related_address = Some(addr);
3878                                self.push_candidate(cand);
3879                            } else {
3880                                self.push_candidate(IceCandidate::host(addr, 1));
3881                            }
3882                        } else if ip.is_unspecified() {
3883                            // If bound to 0.0.0.0 and no external_ip, try to find a reachable local IP for the candidate
3884                            let mut cand_addr = addr;
3885                            if let Ok(local_ip) = get_local_ip() {
3886                                cand_addr.set_ip(local_ip);
3887                            }
3888                            let mut cand = IceCandidate::host(cand_addr, 1);
3889                            cand.related_address = Some(addr);
3890                            self.push_candidate(cand);
3891                        } else {
3892                            self.push_candidate(IceCandidate::host(addr, 1));
3893                        }
3894                    }
3895                }
3896                Err(e) => {
3897                    if self.config.bind_ip.is_some() {
3898                        debug!("Failed to bind to requested bind_ip {}: {}", ip, e);
3899                    } else if !ip.is_loopback() && !ip.is_unspecified() {
3900                        debug!("Failed to bind socket on {}: {}", ip, e);
3901                    }
3902                }
3903            }
3904        }
3905
3906        // Gather TCP host candidates if TCP is enabled
3907        if self.config.ice_tcp_policy != crate::config::IceTcpPolicy::Disabled {
3908            for ip in &bind_ips {
3909                let ip = *ip;
3910                match TcpListener::bind(SocketAddr::new(ip, 0)).await {
3911                    Ok(listener) => {
3912                        if let Ok(addr) = listener.local_addr() {
3913                            let listener = Arc::new(listener);
3914                            self.tcp_listeners.lock().push(listener.clone());
3915                            let _ = self.socket_tx.send(IceSocketWrapper::TcpListener(listener));
3916
3917                            let tcp_type = TcpType::Passive;
3918                            let mut cand = IceCandidate::host_tcp(addr, 1, tcp_type);
3919                            if ip.is_unspecified()
3920                                && let Ok(local_ip) = get_local_ip()
3921                            {
3922                                let mut cand_addr = addr;
3923                                cand_addr.set_ip(local_ip);
3924                                let mut ext_cand = IceCandidate::host_tcp(cand_addr, 1, tcp_type);
3925                                ext_cand.related_address = Some(addr);
3926                                cand = ext_cand;
3927                            }
3928                            self.push_candidate(cand);
3929                        }
3930                    }
3931                    Err(e) => {
3932                        debug!("Failed to bind TCP listener on {}: {}", ip, e);
3933                    }
3934                }
3935            }
3936        }
3937
3938        Ok(())
3939    }
3940
3941    /// Advertise ICE-TCP active host candidates for controlling clients (no UDP gather).
3942    ///
3943    /// RFC 6544 uses port 9 in SDP for active candidates (not 0 — browsers reject port 0).
3944    async fn gather_tcp_active_candidates(&self) -> Result<()> {
3945        use std::net::{IpAddr, Ipv4Addr};
3946
3947        const ACTIVE_PLACEHOLDER_PORT: u16 = 9;
3948
3949        let mut bind_ips = Vec::new();
3950        if self.config.ice_include_loopback_candidates {
3951            bind_ips.push(IpAddr::V4(Ipv4Addr::LOCALHOST));
3952        }
3953        if let Ok(local_ip) = get_local_ip()
3954            && !bind_ips.contains(&local_ip)
3955        {
3956            bind_ips.push(local_ip);
3957        }
3958
3959        for ip in bind_ips {
3960            self.push_candidate(IceCandidate::tcp(
3961                SocketAddr::new(ip, ACTIVE_PLACEHOLDER_PORT),
3962                1,
3963                "active",
3964            ));
3965        }
3966        self.push_candidate(IceCandidate::tcp(
3967            SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), ACTIVE_PLACEHOLDER_PORT),
3968            1,
3969            "active",
3970        ));
3971        Ok(())
3972    }
3973
3974    async fn gather_tcp_host_candidates(&self) -> Result<()> {
3975        let start = self.config.tcp_port_range_start.unwrap_or(0);
3976        let end = self.config.tcp_port_range_end.unwrap_or(0);
3977
3978        if start == 0 || end == 0 || start > end {
3979            return Ok(());
3980        }
3981
3982        let bind_ips = if let Some(bind_ip_str) = &self.config.bind_ip {
3983            if let Ok(ip) = bind_ip_str.parse::<IpAddr>() {
3984                vec![ip]
3985            } else {
3986                return Ok(());
3987            }
3988        } else {
3989            let mut ips = Vec::new();
3990            if self.config.ice_include_loopback_candidates {
3991                ips.push(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST));
3992            }
3993            use local_ip_address::list_afinet_netifas;
3994            if let Ok(interfaces) = list_afinet_netifas() {
3995                for (name, addr) in interfaces {
3996                    if let IpAddr::V4(ip) = addr
3997                        && !ip.is_loopback()
3998                        && !ips.contains(&IpAddr::V4(ip))
3999                    {
4000                        if name.starts_with("utun")
4001                            || name.starts_with("gif")
4002                            || name.starts_with("stf")
4003                            || name.starts_with("awdl")
4004                            || name.starts_with("llw")
4005                        {
4006                            continue;
4007                        }
4008                        ips.push(IpAddr::V4(ip));
4009                    }
4010                }
4011            }
4012            ips
4013        };
4014
4015        // One passive TCP listener per local IP (first free port in range). Binding the
4016        // entire range per PeerConnection exhausts the pool after a single session.
4017        // When start == end, share one listener process-wide and demux by ICE ufrag.
4018        let use_shared_listener = start == end;
4019
4020        for ip in bind_ips {
4021            if use_shared_listener {
4022                let addr = SocketAddr::new(ip, start);
4023                let inner = self
4024                    .transport_inner
4025                    .lock()
4026                    .as_ref()
4027                    .and_then(|weak| weak.upgrade())
4028                    .context("ICE transport unavailable during TCP gather")?;
4029                let ufrag = inner.local_parameters.lock().username_fragment.clone();
4030                match shared_tcp::acquire(addr, ufrag, Arc::downgrade(&inner)).await {
4031                    Ok((local_addr, registration)) => {
4032                        self.shared_tcp_regs.lock().push(registration);
4033                        self.push_tcp_passive_candidate(local_addr, ip);
4034                        break;
4035                    }
4036                    Err(e) => {
4037                        debug!("shared TCP listener acquire on {} failed: {}", addr, e);
4038                    }
4039                }
4040                continue;
4041            }
4042
4043            for port in start..=end {
4044                let addr = SocketAddr::new(ip, port);
4045                match TcpListener::bind(addr).await {
4046                    Ok(listener) => {
4047                        let local_addr = match listener.local_addr() {
4048                            Ok(a) => a,
4049                            Err(_) => continue,
4050                        };
4051                        let listener = Arc::new(listener);
4052                        self.tcp_listeners.lock().push(listener.clone());
4053                        let _ = self.socket_tx.send(IceSocketWrapper::TcpListener(listener));
4054
4055                        self.push_tcp_passive_candidate(local_addr, ip);
4056                        break;
4057                    }
4058                    Err(_) => continue,
4059                }
4060            }
4061        }
4062
4063        Ok(())
4064    }
4065
4066    async fn gather_upnp_candidates(&self, stun_public_ip: Option<IpAddr>) -> Result<()> {
4067        let sockets = self.sockets.lock().clone();
4068        let timeout = self.config.upnp_discovery_timeout;
4069        let mut tasks = FuturesUnordered::new();
4070
4071        for socket in sockets {
4072            let local_addr = match socket.local_addr() {
4073                Ok(addr) => addr,
4074                Err(_) => continue,
4075            };
4076
4077            // Skip loopback addresses
4078            if local_addr.ip().is_loopback() {
4079                continue;
4080            }
4081
4082            // Skip IPv6 (UPnP IGD doesn't support IPv6 well)
4083            if local_addr.is_ipv6() {
4084                continue;
4085            }
4086
4087            let this = self.clone();
4088            let stun_ip = stun_public_ip;
4089            tasks.push(async move {
4090                // Create mapper with configured lease duration
4091                let mut mapper = UpnpPortMapper::with_lease_duration(
4092                    local_addr,
4093                    this.config.upnp_lease_duration,
4094                );
4095
4096                // Try to discover gateway
4097                if let Err(e) = mapper.discover_with_timeout(timeout).await {
4098                    trace!("UPnP discovery failed for {}: {}", local_addr, e);
4099                    return;
4100                }
4101
4102                // Try to add port mapping (0 = use same port as local)
4103                match mapper.add_mapping(0).await {
4104                    Ok(external_addr) => {
4105                        // Check if UPnP returned a private IP (double-NAT scenario)
4106                        let is_private = is_private_ip(&external_addr.ip());
4107
4108                        // Final address for the candidate
4109                        let candidate_addr = if is_private {
4110                            if let Some(public_ip) = stun_ip {
4111                                let mut addr = external_addr;
4112                                addr.set_ip(public_ip);
4113                                debug!(
4114                                    "UPnP double-NAT detected: {} is private, using STUN public IP {} -> {}",
4115                                    external_addr.ip(),
4116                                    public_ip,
4117                                    addr
4118                                );
4119                                addr
4120                            } else {
4121                                debug!(
4122                                    "UPnP returned private IP {} but no STUN public IP available",
4123                                    external_addr.ip()
4124                                );
4125                                external_addr
4126                            }
4127                        } else {
4128                            external_addr
4129                        };
4130
4131                        // Create server reflexive candidate for the mapping
4132                        let candidate =
4133                            IceCandidate::server_reflexive(local_addr, candidate_addr, 1);
4134                        this.push_candidate(candidate);
4135
4136                        // Store mapper for later cleanup
4137                        this.upnp_mappers.lock().push(mapper);
4138
4139                        debug!(
4140                            "UPnP candidate gathered: {} -> {}",
4141                            local_addr, candidate_addr
4142                        );
4143                    }
4144                    Err(e) => {
4145                        debug!("UPnP mapping failed for {}: {}", local_addr, e);
4146                    }
4147                }
4148            });
4149        }
4150
4151        while tasks.next().await.is_some() {}
4152
4153        Ok(())
4154    }
4155
4156    async fn gather_servers(&self) -> Result<()> {
4157        let mut tasks = FuturesUnordered::new();
4158
4159        for server in &self.config.ice_servers {
4160            for url in &server.urls {
4161                let server = server.clone();
4162                let url = url.clone();
4163                let this = self.clone();
4164
4165                tasks.push(async move {
4166                    let uri = match IceServerUri::parse(&url) {
4167                        Ok(uri) => uri,
4168                        Err(err) => {
4169                            debug!("invalid ICE server URI {}: {}", url, err);
4170                            return;
4171                        }
4172                    };
4173
4174                    match uri.kind {
4175                        IceUriKind::Stun => {
4176                            if this.config.ice_transport_policy == IceTransportPolicy::All {
4177                                match this.probe_stun(&uri).await {
4178                                    Ok(Some(candidate)) => this.push_candidate(candidate),
4179                                    Ok(None) => {}
4180                                    Err(e) => debug!("STUN probe failed for {}: {}", url, e),
4181                                }
4182                            }
4183                        }
4184                        IceUriKind::Turn => match this.probe_turn(&uri, &server).await {
4185                            Ok(Some(candidate)) => this.push_candidate(candidate),
4186                            Ok(None) => {}
4187                            Err(e) => debug!("TURN probe failed for {}: {}", url, e),
4188                        },
4189                    }
4190                });
4191            }
4192        }
4193
4194        while tasks.next().await.is_some() {}
4195        Ok(())
4196    }
4197
4198    /// Gather server candidates and return the first public IP discovered via STUN.
4199    /// This is used to detect and fix double-NAT scenarios for UPnP.
4200    async fn gather_servers_and_get_public_ip(&self) -> Option<IpAddr> {
4201        let mut tasks = FuturesUnordered::new();
4202        let public_ip: Arc<parking_lot::Mutex<Option<IpAddr>>> =
4203            Arc::new(parking_lot::Mutex::new(None));
4204
4205        for server in &self.config.ice_servers {
4206            for url in &server.urls {
4207                let server = server.clone();
4208                let url = url.clone();
4209                let this = self.clone();
4210                let public_ip_clone = public_ip.clone();
4211
4212                tasks.push(async move {
4213                    let uri = match IceServerUri::parse(&url) {
4214                        Ok(uri) => uri,
4215                        Err(err) => {
4216                            debug!("invalid ICE server URI {}: {}", url, err);
4217                            return;
4218                        }
4219                    };
4220
4221                    match uri.kind {
4222                        IceUriKind::Stun => {
4223                            if this.config.ice_transport_policy == IceTransportPolicy::All {
4224                                match this.probe_stun(&uri).await {
4225                                    Ok(Some(candidate)) => {
4226                                        // Capture public IP if it's not private
4227                                        if !is_private_ip(&candidate.address.ip()) {
4228                                            let mut ip = public_ip_clone.lock();
4229                                            if ip.is_none() {
4230                                                *ip = Some(candidate.address.ip());
4231                                            }
4232                                        }
4233                                        this.push_candidate(candidate);
4234                                    }
4235                                    Ok(None) => {}
4236                                    Err(e) => debug!("STUN probe failed for {}: {}", url, e),
4237                                }
4238                            }
4239                        }
4240                        IceUriKind::Turn => match this.probe_turn(&uri, &server).await {
4241                            Ok(Some(candidate)) => this.push_candidate(candidate),
4242                            Ok(None) => {}
4243                            Err(e) => debug!("TURN probe failed for {}: {}", url, e),
4244                        },
4245                    }
4246                });
4247            }
4248        }
4249
4250        while tasks.next().await.is_some() {}
4251        let ip = *public_ip.lock();
4252        if let Some(ip) = &ip {
4253            debug!("STUN public IP for UPnP double-NAT detection: {}", ip);
4254        } else {
4255            debug!("No STUN public IP available for UPnP double-NAT detection");
4256        }
4257        ip
4258    }
4259
4260    async fn probe_stun(&self, uri: &IceServerUri) -> Result<Option<IceCandidate>> {
4261        let addr = uri.resolve(self.config.disable_ipv6).await?;
4262
4263        // Find a suitable host address to bind to (prefer non-loopback IPv4)
4264        let bind_ip = if addr.is_ipv6() {
4265            self.local_candidates
4266                .lock()
4267                .iter()
4268                .filter(|c| c.typ == IceCandidateType::Host)
4269                .filter_map(|c| match c.address.ip() {
4270                    IpAddr::V6(ip) if !ip.is_loopback() && !ip.is_unspecified() => {
4271                        Some(IpAddr::V6(ip))
4272                    }
4273                    _ => None,
4274                })
4275                .next()
4276                .unwrap_or(IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED))
4277        } else {
4278            self.local_candidates
4279                .lock()
4280                .iter()
4281                .filter(|c| c.typ == IceCandidateType::Host)
4282                .filter_map(|c| match c.address.ip() {
4283                    IpAddr::V4(ip) if !ip.is_loopback() && !ip.is_unspecified() => {
4284                        Some(IpAddr::V4(ip))
4285                    }
4286                    _ => None,
4287                })
4288                .next()
4289                .unwrap_or(IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)))
4290        };
4291
4292        let socket = match uri.transport {
4293            IceTransportProtocol::Udp => self.bind_socket(bind_ip).await?,
4294            IceTransportProtocol::Tcp => self.bind_socket(bind_ip).await?,
4295        };
4296        let local_addr = socket.local_addr()?;
4297        let tx_id = random_bytes::<12>();
4298        let message = StunMessage::binding_request(tx_id, Some("rustrtc"));
4299        let bytes = message.encode(None, true)?;
4300        socket.send_to(&bytes, addr).await?;
4301        let mut buf = [0u8; MAX_STUN_MESSAGE];
4302        let (len, from) = timeout(self.config.stun_timeout, socket.recv_from(&mut buf)).await??;
4303        if from.ip() != addr.ip() {
4304            return Ok(None);
4305        }
4306        let parsed = StunMessage::decode(&buf[..len])?;
4307        if let Some(mapped) = parsed.xor_mapped_address {
4308            let socket = Arc::new(socket);
4309            self.sockets.lock().push(socket.clone());
4310            let _ = self.socket_tx.send(IceSocketWrapper::Udp(socket));
4311            return Ok(Some(IceCandidate::server_reflexive(local_addr, mapped, 1)));
4312        }
4313        Ok(None)
4314    }
4315
4316    async fn probe_turn(
4317        &self,
4318        uri: &IceServerUri,
4319        server: &IceServer,
4320    ) -> Result<Option<IceCandidate>> {
4321        let credentials = TurnCredentials::from_server(server)?;
4322        let client = TurnClient::connect(uri, self.config.disable_ipv6).await?;
4323        let allocation = client.allocate(credentials).await?;
4324        let relayed_addr = allocation.relayed_address;
4325        debug!(
4326            "TURN allocation granted: relayed={}, lifetime={}s",
4327            relayed_addr, allocation.lifetime_secs
4328        );
4329
4330        let client = Arc::new(client);
4331        self.turn_clients
4332            .lock()
4333            .insert(relayed_addr, client.clone());
4334        let _ = self
4335            .socket_tx
4336            .send(IceSocketWrapper::Turn(client, relayed_addr));
4337
4338        Ok(Some(IceCandidate::relay(
4339            relayed_addr,
4340            1,
4341            allocation.transport.as_str(),
4342        )))
4343    }
4344
4345    fn push_candidate(&self, candidate: IceCandidate) {
4346        if self.config.disable_ipv6 && candidate.address.is_ipv6() {
4347            return;
4348        }
4349        let mut candidates = self.local_candidates.lock();
4350        if candidates.iter().any(|c| c.address == candidate.address) {
4351            return;
4352        }
4353        tracing::debug!(
4354            "Gathered local candidate: {} type={:?}",
4355            candidate.address,
4356            candidate.typ
4357        );
4358        candidates.push(candidate.clone());
4359        drop(candidates);
4360        let _ = self.candidate_tx.send(candidate);
4361    }
4362}
4363
4364#[derive(Debug, Clone, PartialEq, Eq)]
4365pub(crate) struct IceServerUri {
4366    kind: IceUriKind,
4367    host: String,
4368    port: u16,
4369    transport: IceTransportProtocol,
4370}
4371
4372impl IceServerUri {
4373    fn parse(input: &str) -> Result<Self> {
4374        let (scheme, rest) = input
4375            .split_once(':')
4376            .ok_or_else(|| anyhow!("missing scheme"))?;
4377        let (host_part, query) = match rest.split_once('?') {
4378            Some(parts) => parts,
4379            None => (rest, ""),
4380        };
4381        let (host, port) = if let Some((h, p)) = host_part.rsplit_once(':') {
4382            let port = p.parse::<u16>().context("invalid port")?;
4383            (h.to_string(), port)
4384        } else {
4385            (host_part.to_string(), default_port_for_scheme(scheme)?)
4386        };
4387        let mut transport = default_transport_for_scheme(scheme)?;
4388        if !query.is_empty() {
4389            for pair in query.split('&') {
4390                if let Some((k, v)) = pair.split_once('=')
4391                    && k == "transport"
4392                {
4393                    transport = match v.to_ascii_lowercase().as_str() {
4394                        "udp" => IceTransportProtocol::Udp,
4395                        "tcp" => IceTransportProtocol::Tcp,
4396                        other => bail!("unsupported transport {}", other),
4397                    };
4398                }
4399            }
4400        }
4401        if scheme.starts_with("stun") && query.contains("transport") {
4402            bail!("stun URI must not include transport parameter");
4403        }
4404        let kind = match scheme {
4405            "stun" | "stuns" => IceUriKind::Stun,
4406            "turn" | "turns" => IceUriKind::Turn,
4407            other => bail!("unsupported scheme {}", other),
4408        };
4409        Ok(Self {
4410            kind,
4411            host,
4412            port,
4413            transport,
4414        })
4415    }
4416
4417    async fn resolve(&self, disable_ipv6: bool) -> Result<SocketAddr> {
4418        let target = format!("{}:{}", self.host, self.port);
4419        let addrs = timeout(Duration::from_secs(5), lookup_host(&target))
4420            .await
4421            .map_err(|_| anyhow!("DNS lookup timed out for {}", target))??;
4422
4423        for addr in addrs {
4424            if disable_ipv6 && addr.is_ipv6() {
4425                continue;
4426            }
4427            return Ok(addr);
4428        }
4429        Err(anyhow!(
4430            "{} unresolved (disable_ipv6={})",
4431            self.host,
4432            disable_ipv6
4433        ))
4434    }
4435}
4436
4437#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4438enum IceUriKind {
4439    Stun,
4440    Turn,
4441}
4442
4443#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4444pub(crate) enum IceTransportProtocol {
4445    Udp,
4446    Tcp,
4447}
4448
4449impl IceTransportProtocol {
4450    fn as_str(&self) -> &'static str {
4451        match self {
4452            IceTransportProtocol::Udp => "udp",
4453            IceTransportProtocol::Tcp => "tcp",
4454        }
4455    }
4456}
4457
4458fn default_port_for_scheme(scheme: &str) -> Result<u16> {
4459    Ok(match scheme {
4460        "stun" | "turn" => 3478,
4461        "stuns" | "turns" => 5349,
4462        other => bail!("unsupported scheme {}", other),
4463    })
4464}
4465
4466fn default_transport_for_scheme(scheme: &str) -> Result<IceTransportProtocol> {
4467    Ok(match scheme {
4468        "stun" | "turn" => IceTransportProtocol::Udp,
4469        "stuns" | "turns" => IceTransportProtocol::Tcp,
4470        other => bail!("unsupported scheme {}", other),
4471    })
4472}
4473
4474/// Check if an IP address is a private/internal address (not publicly routable)
4475fn is_private_ip(ip: &IpAddr) -> bool {
4476    match ip {
4477        IpAddr::V4(ipv4) => {
4478            let octets = ipv4.octets();
4479            // 10.0.0.0/8
4480            octets[0] == 10
4481                // 172.16.0.0/12
4482                || (octets[0] == 172 && (16..=31).contains(&octets[1]))
4483                // 192.168.0.0/16
4484                || (octets[0] == 192 && octets[1] == 168)
4485                // 169.254.0.0/16 (link-local)
4486                || (octets[0] == 169 && octets[1] == 254)
4487                // 127.0.0.0/8 (loopback)
4488                || octets[0] == 127
4489        }
4490        IpAddr::V6(ipv6) => {
4491            // IPv6 unique local fc00::/7
4492            ipv6.segments()[0] & 0xfe00 == 0xfc00
4493                // IPv6 link-local fe80::/10
4494                || ipv6.segments()[0] & 0xffc0 == 0xfe80
4495                // IPv6 loopback ::1
4496                || *ipv6 == std::net::Ipv6Addr::LOCALHOST
4497        }
4498    }
4499}
4500
4501fn hex_encode(bytes: &[u8]) -> String {
4502    const TABLE: &[u8; 16] = b"0123456789abcdef";
4503    let mut out = String::with_capacity(bytes.len() * 2);
4504    for byte in bytes {
4505        out.push(TABLE[(byte >> 4) as usize] as char);
4506        out.push(TABLE[(byte & 0x0f) as usize] as char);
4507    }
4508    out
4509}
4510
4511#[derive(Debug, Clone)]
4512pub enum IceSocketWrapper {
4513    Udp(Arc<UdpSocket>),
4514    /// Shared (muxed) UDP socket. Incoming packets arrive via the handle's
4515    /// receiver (fed by the shared demux loop); outbound packets go out through
4516    /// the handle, which also records the destination for reverse routing.
4517    SharedUdp(Arc<shared_udp::SharedUdpHandle>),
4518    TcpListener(Arc<TcpListener>),
4519    TcpStream(
4520        Arc<Mutex<TcpReadHalf>>,
4521        Arc<Mutex<TcpWriteHalf>>,
4522        SocketAddr,
4523    ),
4524    Turn(Arc<TurnClient>, SocketAddr),
4525}
4526
4527impl IceSocketWrapper {
4528    /// Short description for diagnostic logs (no async I/O).
4529    pub fn diag(&self) -> String {
4530        match self {
4531            IceSocketWrapper::Udp(s) => format!(
4532                "udp:{}",
4533                s.local_addr()
4534                    .map(|a| a.to_string())
4535                    .unwrap_or_else(|_| "?".into())
4536            ),
4537            IceSocketWrapper::SharedUdp(h) => format!(
4538                "udp-mux:{}",
4539                h.local_addr()
4540                    .map(|a| a.to_string())
4541                    .unwrap_or_else(|_| "?".into())
4542            ),
4543            IceSocketWrapper::TcpListener(l) => format!(
4544                "tcp-listen:{}",
4545                l.local_addr()
4546                    .map(|a| a.to_string())
4547                    .unwrap_or_else(|_| "?".into())
4548            ),
4549            IceSocketWrapper::TcpStream(_, _, peer) => format!("tcp-stream:peer={peer}"),
4550            IceSocketWrapper::Turn(_, addr) => format!("turn:{addr}"),
4551        }
4552    }
4553
4554    /// Non-blocking variant of `send_to`: calls `try_send_to` once and returns
4555    /// immediately on `WouldBlock` / `ENOBUFS` instead of parking on
4556    /// `writable()`. Used by the RTP bridge fast-path.
4557    pub fn try_send_to(&self, data: &[u8], addr: SocketAddr) -> Result<usize> {
4558        match self {
4559            IceSocketWrapper::Udp(s) => match s.try_send_to(data, addr) {
4560                Ok(len) => Ok(len),
4561                Err(e) => {
4562                    let reason = match s.local_addr() {
4563                        Ok(local) => format!("UDP {} -> {} failed: {}", local, addr, e),
4564                        Err(_) => format!("UDP -> {} failed: {}", addr, e),
4565                    };
4566                    Err(anyhow!(reason))
4567                }
4568            },
4569            IceSocketWrapper::SharedUdp(h) => {
4570                // Shared (muxed) UDP is still a synchronous datagram socket:
4571                // record the peer for reverse routing, then write without
4572                // parking (same contract as the `Udp` arm above).
4573                h.register_peer(addr);
4574                match h.socket().try_send_to(data, addr) {
4575                    Ok(len) => Ok(len),
4576                    Err(e) => {
4577                        let reason = match h.local_addr() {
4578                            Ok(local) => format!("shared UDP {} -> {} failed: {}", local, addr, e),
4579                            Err(_) => format!("shared UDP -> {} failed: {}", addr, e),
4580                        };
4581                        Err(anyhow!(reason))
4582                    }
4583                }
4584            }
4585            // TURN / TCP / TLS have no synchronous send: callers must use the
4586            // async `send_to` (the bridge fast-path queues via `IceConn`).
4587            _ => Err(anyhow::anyhow!(
4588                "IceSocketWrapper::try_send_to not supported for this transport variant"
4589            )),
4590        }
4591    }
4592
4593    pub async fn send_to(&self, data: &[u8], addr: SocketAddr) -> Result<usize> {
4594        match self {
4595            IceSocketWrapper::Udp(s) => loop {
4596                match s.try_send_to(data, addr) {
4597                    Ok(len) => return Ok(len),
4598                    Err(e) if e.kind() == ErrorKind::WouldBlock => {
4599                        s.writable().await?;
4600                        continue;
4601                    }
4602                    Err(e) => {
4603                        if let Some(code) = e.raw_os_error()
4604                            && code == 55
4605                        {
4606                            s.writable().await?;
4607                            continue;
4608                        }
4609                        let reason = anyhow!("UDP {} -> {} failed: {}", s.local_addr()?, addr, e);
4610                        return Err(reason);
4611                    }
4612                }
4613            },
4614            IceSocketWrapper::SharedUdp(h) => {
4615                let dest = addr;
4616                h.send_to(data, dest).await.map_err(anyhow::Error::from)
4617            }
4618            IceSocketWrapper::TcpListener(_) => {
4619                bail!("send_to not supported on TcpListener")
4620            }
4621            IceSocketWrapper::TcpStream(_, write, _) => {
4622                let len = data.len();
4623                if len > 0xFFFF {
4624                    bail!("STUN message too large for TCP framing");
4625                }
4626                let header = (len as u16).to_be_bytes();
4627                let mut framed = Vec::with_capacity(2 + len);
4628                framed.extend_from_slice(&header);
4629                framed.extend_from_slice(data);
4630                tcp_write_all(write, &framed).await?;
4631                Ok(data.len())
4632            }
4633            IceSocketWrapper::Turn(c, _) => {
4634                if let Some(channel) = c.get_channel(addr).await {
4635                    c.send_channel_data(channel, data).await?;
4636                } else {
4637                    c.send_indication(addr, data).await?;
4638                }
4639                Ok(data.len())
4640            }
4641        }
4642    }
4643
4644    pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr)> {
4645        match self {
4646            IceSocketWrapper::Udp(s) => s.recv_from(buf).await.map_err(|e| e.into()),
4647            IceSocketWrapper::SharedUdp(h) => match h.recv().await {
4648                Some((data, addr)) => {
4649                    if data.len() > buf.len() {
4650                        return Err(anyhow::anyhow!(
4651                            "shared UDP packet too large: {} > {}",
4652                            data.len(),
4653                            buf.len()
4654                        ));
4655                    }
4656                    let len = data.len();
4657                    buf[..len].copy_from_slice(&data);
4658                    Ok((len, addr))
4659                }
4660                None => Err(anyhow::anyhow!("shared UDP channel closed")),
4661            },
4662            IceSocketWrapper::TcpStream(read, _, peer) => {
4663                use tokio::io::AsyncReadExt;
4664                let mut stream = read.lock().await;
4665                let mut len_buf = [0u8; 2];
4666                stream.read_exact(&mut len_buf).await?;
4667                let len = u16::from_be_bytes(len_buf) as usize;
4668                if len > buf.len() {
4669                    return Err(anyhow::anyhow!(
4670                        "TCP STUN message too large: {} > {}",
4671                        len,
4672                        buf.len()
4673                    ));
4674                }
4675                stream.read_exact(&mut buf[..len]).await?;
4676                Ok((len, *peer))
4677            }
4678            IceSocketWrapper::TcpListener(_) => Err(anyhow::anyhow!(
4679                "recv_from not supported on TcpListener wrapper directly"
4680            )),
4681            IceSocketWrapper::Turn(_, _) => Err(anyhow::anyhow!(
4682                "recv_from not supported on TURN wrapper directly"
4683            )),
4684        }
4685    }
4686}