pub mod conn;
#[cfg(feature = "std")] pub mod mdns;
#[cfg(feature = "std")] pub mod shared_tcp;
#[cfg(not(feature = "std"))]
#[derive(Debug, Clone)]
pub struct SharedTcpRegistration;
pub mod shared_udp;
pub mod stun;
#[cfg(all(test, feature = "std"))]
mod tests;
pub mod turn;
#[cfg(feature = "std")] pub mod upnp;
#[cfg(feature = "std")]
pub use upnp::{
DEFAULT_LEASE_DURATION, DEFAULT_UPNP_DISCOVERY_TIMEOUT, MAX_LEASE_DURATION, MIN_LEASE_DURATION,
UpnpPortMapper,
};
use crate::config::{BufferDropStrategy, IceServer, IceTransportPolicy, RtcConfiguration};
use crate::platform::atomic64::AtomicU64;
use crate::prelude::*;
use crate::transports::ice::turn::{TurnClient, TurnCredentials};
use crate::transports::{PacketReceiver, get_local_ip};
use alloc::collections::{BTreeMap, VecDeque};
use alloc::sync::Arc;
use bytes::Bytes;
use core::net::{IpAddr, SocketAddr};
use core::sync::atomic::AtomicU32;
use core::sync::atomic::Ordering;
use futures::future::BoxFuture;
use futures::stream::{FuturesUnordered, StreamExt};
#[cfg(feature = "std")]
use std::io::ErrorKind;
#[cfg(not(feature = "std"))]
use crate::platform::time::Instant;
use core::time::Duration;
#[cfg(feature = "std")]
use std::time::Instant;
use crate::errors::{RtcError, RtcResult};
#[cfg(feature = "std")]
use crate::platform::net::UdpSocket as NetUdpSocket;
#[cfg(feature = "std")]
use tokio::net::UdpSocket;
#[cfg(feature = "std")]
use tokio::net::{TcpListener, TcpStream};
#[cfg(not(feature = "std"))]
#[derive(Debug, Clone)]
pub struct UdpSocket;
#[cfg(not(feature = "std"))]
impl UdpSocket {
pub async fn bind(_addr: SocketAddr) -> RtcResult<Self> {
unimplemented!("direct UDP sockets are std-only; use Platform sockets (WP3)")
}
pub fn local_addr(&self) -> RtcResult<SocketAddr> {
unimplemented!("direct UDP sockets are std-only")
}
pub async fn send_to(&self, _: &[u8], _: SocketAddr) -> RtcResult<usize> {
unimplemented!("direct UDP sockets are std-only")
}
pub async fn recv_from(&self, _: &mut [u8]) -> RtcResult<(usize, SocketAddr)> {
unimplemented!("direct UDP sockets are std-only")
}
pub fn try_send_to(&self, _: &[u8], _: SocketAddr) -> RtcResult<usize> {
unimplemented!("direct UDP sockets are std-only")
}
pub async fn writable(&self) -> RtcResult<()> {
unimplemented!("direct UDP sockets are std-only")
}
pub fn diag(&self) -> SocketAddr {
unimplemented!("direct UDP sockets are std-only")
}
}
#[cfg(not(feature = "std"))]
#[derive(Debug, Clone)]
pub struct TcpStream;
#[cfg(not(feature = "std"))]
impl TcpStream {
pub async fn connect(_addr: SocketAddr) -> RtcResult<Self> {
unimplemented!("ICE-TCP is excluded from the embedded target")
}
pub fn local_addr(&self) -> RtcResult<SocketAddr> {
unimplemented!("ICE-TCP is excluded from the embedded target")
}
pub fn set_nodelay(&self, _: bool) -> RtcResult<()> {
Ok(())
}
pub async fn write_all(&self, _: &[u8]) -> RtcResult<()> {
unimplemented!("ICE-TCP is excluded from the embedded target")
}
pub async fn read_exact(&self, _: &mut [u8]) -> RtcResult<()> {
unimplemented!("ICE-TCP is excluded from the embedded target")
}
pub fn into_split(self) -> (TcpReadHalf, TcpWriteHalf) {
unimplemented!("ICE-TCP is excluded from the embedded target")
}
}
#[cfg(not(feature = "std"))]
#[derive(Debug, Clone)]
pub struct TcpListener;
#[cfg(not(feature = "std"))]
impl TcpListener {
pub async fn bind(_addr: SocketAddr) -> RtcResult<Self> {
unimplemented!("ICE-TCP is excluded from the embedded target")
}
pub fn local_addr(&self) -> RtcResult<SocketAddr> {
unimplemented!("ICE-TCP is excluded from the embedded target")
}
}
#[cfg(not(feature = "std"))]
pub type TcpReadHalf = TcpStream;
#[cfg(not(feature = "std"))]
pub type TcpWriteHalf = TcpStream;
#[cfg(not(feature = "std"))]
#[derive(Debug, Clone)]
pub struct UpnpPortMapper;
#[cfg(not(feature = "std"))]
impl UpnpPortMapper {
pub fn with_lease_duration(_local_addr: SocketAddr, _lease: u32) -> Self {
unimplemented!("UPnP is excluded from the embedded target")
}
pub async fn discover(&mut self) -> RtcResult<()> {
unimplemented!("UPnP is excluded from the embedded target")
}
pub async fn add_mapping(&mut self, _port: u16) -> RtcResult<SocketAddr> {
unimplemented!("UPnP is excluded from the embedded target")
}
pub async fn renew_all_stale(&self) -> RtcResult<()> {
unimplemented!("UPnP is excluded from the embedded target")
}
pub async fn discover_with_timeout(&mut self, _: core::time::Duration) -> RtcResult<()> {
unimplemented!("UPnP is excluded from the embedded target")
}
pub async fn add_mapping_random_port(&mut self, _: u16) -> RtcResult<u16> {
unimplemented!("UPnP is excluded from the embedded target")
}
pub async fn cleanup(&self) -> RtcResult<()> {
Ok(())
}
pub async fn get_external_ip(&self) -> RtcResult<core::net::Ipv4Addr> {
unimplemented!("UPnP is excluded from the embedded target")
}
}
use crate::platform::sync::{AsyncMutex as Mutex, broadcast, mpsc, oneshot, watch};
use crate::platform::time::with_timeout;
use tracing::{debug, error, instrument, trace, warn};
#[cfg(any(test, feature = "simulator"))]
use self::stun::random_u32;
use self::stun::{
StunAttribute, StunClass, StunDecoded, StunMessage, StunMethod, random_bytes, random_u64,
};
pub(crate) const MAX_STUN_MESSAGE: usize = 1500;
#[cfg(any(test, feature = "simulator"))]
static PACKET_LOSS_RATE: AtomicU32 = AtomicU32::new(u32::MAX);
pub(crate) fn should_drop_packet() -> bool {
#[cfg(not(any(test, feature = "simulator")))]
return false;
#[cfg(any(test, feature = "simulator"))]
{
let mut rate = PACKET_LOSS_RATE.load(Ordering::Relaxed);
if rate == u32::MAX {
#[cfg(feature = "std")]
{
rate = std::env::var("RUSTRTC_PACKET_LOSS")
.ok()
.and_then(|s| s.parse::<f64>().ok())
.map(|f| (f * 100.0) as u32)
.unwrap_or(0);
}
#[cfg(not(feature = "std"))]
{
rate = 0;
}
PACKET_LOSS_RATE.store(rate, Ordering::Relaxed);
}
if rate == 0 {
return false;
}
let rand_val = random_u32() % 10000;
let drop = rand_val < rate;
if drop {
trace!("SIMULATOR: Dropping packet (rate={}%)", rate as f64 / 100.0);
}
drop
}
}
#[cfg(any(test, feature = "simulator"))]
async fn simulate_stun_respond_delay(sender: &IceSocketWrapper) {
#[cfg(not(feature = "std"))]
{
let _ = sender;
}
#[cfg(feature = "std")]
{
let spec = match std::env::var("RUSTRTC_STUN_RESPOND_DELAY_MS").ok() {
Some(s) => s,
None => return,
};
let Ok(ms) = spec.trim().parse::<u64>() else {
return;
};
if ms == 0 {
return;
}
let is_relayed_or_tcp = matches!(sender, IceSocketWrapper::Turn(_, _)) || matches!(
sender,
IceSocketWrapper::TcpListener(_) | IceSocketWrapper::TcpStream(_, _, _)
);
if is_relayed_or_tcp {
return;
}
trace!("SIMULATOR: delaying STUN response by {}ms", ms);
crate::platform::task::sleep(Duration::from_millis(ms)).await;
}
}
#[derive(Debug)]
struct BufferStats {
pub packets_received: AtomicU64,
pub packets_dropped: AtomicU64,
pub current_size: AtomicU32,
pub peak_size: AtomicU32,
pub last_log_time: crate::platform::sync::Mutex<Instant>,
}
impl Default for BufferStats {
fn default() -> Self {
Self {
packets_received: AtomicU64::new(0),
packets_dropped: AtomicU64::new(0),
current_size: AtomicU32::new(0),
peak_size: AtomicU32::new(0),
last_log_time: crate::platform::sync::Mutex::new(Instant::now()),
}
}
}
#[derive(Debug)]
enum IceCommand {
StartGathering,
RunChecks,
}
#[derive(Debug, Clone)]
pub struct IceTransport {
inner: Arc<IceTransportInner>,
}
pub(crate) struct IceTransportInner {
state: watch::Sender<IceTransportState>,
_state_rx_keeper: watch::Receiver<IceTransportState>,
gathering_state: watch::Sender<IceGathererState>,
_gathering_state_rx_keeper: watch::Receiver<IceGathererState>,
role: crate::platform::sync::Mutex<IceRole>,
selected_pair: crate::platform::sync::Mutex<Option<IceCandidatePair>>,
local_candidates: Mutex<Vec<IceCandidate>>,
remote_candidates: crate::platform::sync::Mutex<Vec<IceCandidate>>,
gather_state: crate::platform::sync::Mutex<IceGathererState>,
config: RtcConfiguration,
gatherer: IceGatherer,
local_parameters: crate::platform::sync::Mutex<IceParameters>,
remote_parameters: crate::platform::sync::Mutex<Option<IceParameters>>,
pending_transactions:
crate::platform::sync::Mutex<BTreeMap<[u8; 12], oneshot::Sender<StunDecoded>>>,
data_receiver: crate::platform::sync::Mutex<Option<Arc<dyn PacketReceiver>>>,
buffered_packets: crate::platform::sync::Mutex<VecDeque<(Vec<u8>, SocketAddr)>>,
buffer_stats: Arc<BufferStats>,
selected_socket: watch::Sender<Option<IceSocketWrapper>>,
_socket_rx_keeper: watch::Receiver<Option<IceSocketWrapper>>,
selected_rtcp_socket: watch::Sender<Option<IceSocketWrapper>>,
_rtcp_socket_rx_keeper: watch::Receiver<Option<IceSocketWrapper>>,
selected_pair_notifier: watch::Sender<Option<IceCandidatePair>>,
_selected_pair_rx_keeper: watch::Receiver<Option<IceCandidatePair>>,
created_at: Instant,
last_received_nanos: AtomicU64,
candidate_tx: broadcast::Sender<IceCandidate>,
cmd_tx: mpsc::UnboundedSender<IceCommand>,
checking_pairs: Mutex<alloc::collections::BTreeSet<(SocketAddr, SocketAddr)>>,
nomination_complete: watch::Sender<Option<bool>>,
_nomination_complete_rx: watch::Receiver<Option<bool>>,
restart_requested: core::sync::atomic::AtomicBool,
nomination_generation: crate::platform::atomic64::AtomicU64,
mdns_hostname: Option<String>,
turn_refresh_in_progress: core::sync::atomic::AtomicBool,
upnp_refresh_in_progress: core::sync::atomic::AtomicBool,
}
impl core::fmt::Debug for IceTransportInner {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("IceTransportInner")
.field("state", &self.state)
.field("role", &self.role)
.field("selected_pair", &self.selected_pair)
.field("local_candidates", &self.local_candidates)
.field("remote_candidates", &self.remote_candidates)
.field("gather_state", &self.gather_state)
.field("config", &self.config)
.field("gatherer", &self.gatherer)
.field("local_parameters", &self.local_parameters)
.field("remote_parameters", &self.remote_parameters)
.field("pending_transactions", &self.pending_transactions)
.field("data_receiver", &"PacketReceiver")
.field("buffered_packets", &self.buffered_packets.lock().len())
.field("buffer_stats", &self.buffer_stats)
.field("selected_socket", &self.selected_socket)
.field("selected_rtcp_socket", &self.selected_rtcp_socket)
.field("selected_pair_notifier", &self.selected_pair_notifier)
.field("candidate_tx", &self.candidate_tx)
.field("cmd_tx", &self.cmd_tx)
.field("nomination_complete", &self.nomination_complete)
.finish()
}
}
#[cfg(feature = "std")]
fn start_mdns_responder(
inner: &Arc<IceTransportInner>,
) -> anyhow::Result<crate::transports::ice::mdns::MdnsResponder> {
let mut addresses: Vec<core::net::IpAddr> = Vec::new();
if let Some(bind) = &inner.config.bind_ip
&& let Ok(ip) = bind.parse::<core::net::IpAddr>()
{
addresses.push(ip);
}
#[cfg(feature = "std")]
use local_ip_address::list_afinet_netifas;
if let Ok(interfaces) = list_afinet_netifas() {
for (_name, addr) in interfaces {
if !addr.is_loopback() && !addresses.contains(&addr) {
addresses.push(addr);
}
}
}
let hostname = inner
.mdns_hostname
.clone()
.unwrap_or_else(crate::transports::ice::mdns::MdnsResponder::generate_hostname);
crate::transports::ice::mdns::MdnsResponder::start(hostname, addresses)
}
struct IceTransportRunner {
inner: Arc<IceTransportInner>,
socket_rx: mpsc::UnboundedReceiver<IceSocketWrapper>,
candidate_rx: broadcast::Receiver<IceCandidate>,
cmd_rx: mpsc::UnboundedReceiver<IceCommand>,
state_rx: watch::Receiver<IceTransportState>,
}
impl IceTransportRunner {
async fn run(mut self) {
#[cfg(feature = "std")]
let _mdns: Option<crate::transports::ice::mdns::MdnsResponder> =
if self.inner.config.enable_mdns {
match start_mdns_responder(&self.inner) {
Ok(responder) => {
debug!("mDNS responder started for {}", responder.hostname());
Some(responder)
}
Err(e) => {
debug!("mDNS responder failed to start: {}", e);
None
}
}
} else {
None
};
let mut interval =
crate::platform::task::interval_after(Duration::from_secs(1), Duration::from_secs(1));
let mut turn_refresh_interval =
crate::platform::task::interval_after(Duration::from_secs(25), Duration::from_secs(25));
let mut upnp_refresh_interval = crate::platform::task::interval_after(
self.inner.config.upnp_refresh_interval,
self.inner.config.upnp_refresh_interval,
);
let mut read_futures: FuturesUnordered<BoxFuture<'static, ()>> = FuturesUnordered::new();
let mut gathering_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
let mut turn_refresh_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
let mut upnp_refresh_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
loop {
enum GatherArm {
State(Result<(), watch::RecvError>),
Socket(IceSocketWrapper),
Candidate(Result<(), broadcast::RecvError>),
Cmd(IceCommand),
Keepalive,
TurnRefreshTick,
TurnRefreshDone,
UpnpTick,
UpnpDone,
ReadLoopDone,
GatheringDone,
}
let mut socket_rx_open = true;
let mut cmd_rx_open = true;
let arm = core::future::poll_fn(|cx| {
use core::task::Poll;
let mut state_fut = core::pin::pin!(self.state_rx.changed());
let mut socket_fut = core::pin::pin!(self.socket_rx.recv());
let mut cand_fut = core::pin::pin!(self.candidate_rx.recv());
let mut cmd_fut = core::pin::pin!(self.cmd_rx.recv());
let mut ka_fut = core::pin::pin!(interval.tick());
let mut turn_tick_fut = core::pin::pin!(turn_refresh_interval.tick());
let mut upnp_tick_fut = core::pin::pin!(upnp_refresh_interval.tick());
let mut read_next_fut = core::pin::pin!(read_futures.next());
loop {
if let Poll::Ready(res) = state_fut.as_mut().poll(cx) {
return Poll::Ready(GatherArm::State(res));
}
if socket_rx_open && let Poll::Ready(v) = socket_fut.as_mut().poll(cx) {
match v {
Some(socket) => return Poll::Ready(GatherArm::Socket(socket)),
None => socket_rx_open = false,
}
}
if let Poll::Ready(res) = cand_fut.as_mut().poll(cx) {
return Poll::Ready(GatherArm::Candidate(res.map(|_| ())));
}
if cmd_rx_open && let Poll::Ready(v) = cmd_fut.as_mut().poll(cx) {
match v {
Some(cmd) => return Poll::Ready(GatherArm::Cmd(cmd)),
None => cmd_rx_open = false,
}
}
if ka_fut.as_mut().poll(cx).is_ready() {
return Poll::Ready(GatherArm::Keepalive);
}
if turn_tick_fut.as_mut().poll(cx).is_ready() {
return Poll::Ready(GatherArm::TurnRefreshTick);
}
if core::pin::Pin::new(&mut turn_refresh_future)
.poll(cx)
.is_ready()
{
return Poll::Ready(GatherArm::TurnRefreshDone);
}
if upnp_tick_fut.as_mut().poll(cx).is_ready() {
return Poll::Ready(GatherArm::UpnpTick);
}
if core::pin::Pin::new(&mut upnp_refresh_future)
.poll(cx)
.is_ready()
{
return Poll::Ready(GatherArm::UpnpDone);
}
if let Poll::Ready(Some(_)) = read_next_fut.as_mut().poll(cx) {
return Poll::Ready(GatherArm::ReadLoopDone);
}
if core::pin::Pin::new(&mut gathering_future)
.poll(cx)
.is_ready()
{
return Poll::Ready(GatherArm::GatheringDone);
}
return Poll::Pending;
}
})
.await;
match arm {
GatherArm::State(res) => {
if res.is_err() {
break;
}
if matches!(
*self.state_rx.borrow(),
IceTransportState::Closed | IceTransportState::Failed
) {
break;
}
}
GatherArm::Socket(socket) => {
match socket {
IceSocketWrapper::Udp(s) => {
#[cfg(feature = "std")]
{
read_futures
.push(Box::pin(Self::run_udp_read_loop(s, self.inner.clone())));
}
#[cfg(not(feature = "std"))]
{
let _ = s;
}
}
IceSocketWrapper::SharedUdp(handle) => {
read_futures.push(Box::pin(Self::run_shared_udp_read_loop(
handle,
self.inner.clone(),
)));
}
#[cfg(feature = "std")]
IceSocketWrapper::TcpListener(l) => {
read_futures
.push(Box::pin(Self::run_tcp_listen_loop(l, self.inner.clone())));
}
#[cfg(feature = "std")]
IceSocketWrapper::TcpStream(read, write, peer) => {
read_futures.push(Box::pin(Self::run_tcp_read_loop(
read,
write,
peer,
self.inner.clone(),
)));
}
IceSocketWrapper::Platform(s) => {
read_futures.push(Box::pin(Self::run_platform_read_loop(
s,
self.inner.clone(),
)));
}
IceSocketWrapper::Turn(c, addr) => {
read_futures.push(Box::pin(Self::run_turn_read_loop(
c,
addr,
self.inner.clone(),
)));
}
}
}
GatherArm::Candidate(res) => match res { Ok(_) => {
let inner = self.inner.clone();
read_futures.push(Box::pin(async move {
perform_connectivity_checks_async(inner).await;
}));
}
Err(broadcast::RecvError::Closed) => break,
Err(broadcast::RecvError::Lagged(_)) => continue,
}
GatherArm::Cmd(cmd) => {
trace!("Runner received command: {:?}", cmd);
match cmd {
IceCommand::StartGathering => {
let inner = self.inner.clone();
gathering_future = Box::pin(async move {
if let Err(e) = inner.gatherer.gather().await {
debug!("Gathering failed: {}", e);
}
{
let mut buffer = inner.local_candidates.lock().await;
*buffer = inner.gatherer.local_candidates();
}
*inner.gather_state.lock() = IceGathererState::Complete;
let _ = inner.gathering_state.send(IceGathererState::Complete);
});
}
IceCommand::RunChecks => {
let inner = self.inner.clone();
let rt_handle = inner.config.runtime_handle.clone();
crate::spawn_rtc(
rt_handle.as_ref(),
tracing::Span::current(),
async move {
perform_connectivity_checks_async(inner).await;
},
);
}
}
}
GatherArm::Keepalive => {
if let Some(f) = Self::run_keepalive_tick(&self.inner).await {
read_futures.push(f);
}
}
GatherArm::TurnRefreshTick => {
if !self
.inner
.turn_refresh_in_progress
.load(core::sync::atomic::Ordering::SeqCst)
{
let inner = self.inner.clone();
turn_refresh_future = Box::pin(async move {
Self::run_turn_refresh(&inner).await;
});
}
}
GatherArm::TurnRefreshDone => {
turn_refresh_future = Box::pin(futures::future::pending());
}
GatherArm::UpnpTick => {
if !self
.inner
.upnp_refresh_in_progress
.swap(true, core::sync::atomic::Ordering::SeqCst)
{
let inner = self.inner.clone();
upnp_refresh_future = Box::pin(async move {
#[cfg(feature = "std")]
inner.gatherer.renew_upnp_mappings().await;
#[cfg(not(feature = "std"))]
{
let _ = &inner;
}
inner
.upnp_refresh_in_progress
.store(false, core::sync::atomic::Ordering::SeqCst);
});
}
}
GatherArm::UpnpDone => {
upnp_refresh_future = Box::pin(futures::future::pending());
}
GatherArm::ReadLoopDone => {
}
GatherArm::GatheringDone => {
gathering_future = Box::pin(futures::future::pending());
}
}
}
}
#[cfg(feature = "std")]
async fn run_udp_read_loop(socket: Arc<UdpSocket>, inner: Arc<IceTransportInner>) {
let mut buf = [0u8; 1500];
let mut marshal_buf = Vec::with_capacity(1500);
let mut state_rx = inner.state.subscribe();
let sender = IceSocketWrapper::Udp(socket.clone());
trace!("Read loop started for {:?}", socket.local_addr());
loop {
let which = {
let mut readable_fut = core::pin::pin!(socket.readable());
let mut state_fut = core::pin::pin!(state_rx.changed());
crate::platform::select::select2(&mut readable_fut, &mut state_fut).await
};
match which {
crate::platform::select::Either::A(res) => {
if let Err(e) = res {
debug!("Socket readable wait error: {}", e);
break;
}
loop {
let (len, addr) = match socket.try_recv_from(&mut buf) {
Ok(v) => v,
Err(e) if e.kind() == ErrorKind::WouldBlock => {
break;
}
Err(e) if e.kind() == ErrorKind::ConnectionReset => {
debug!("Socket recv error (connection reset, continuing): {}", e);
break;
}
Err(e) => {
debug!("Socket recv error: {}", e);
return;
}
};
let packet = &buf[..len];
if len > 0 {
handle_packet(
packet,
addr,
inner.clone(),
sender.clone(),
&mut marshal_buf,
)
.await;
}
}
}
crate::platform::select::Either::B(res) => {
if res.is_err()
|| matches!(
*state_rx.borrow(),
IceTransportState::Closed | IceTransportState::Failed
)
{
trace!("Read loop stopping (IceTransport Closed or Failed)");
break;
}
}
}
}
}
async fn run_platform_read_loop(
socket: Arc<dyn crate::platform::net::UdpSocket>,
inner: Arc<IceTransportInner>,
) {
let mut buf = vec![0u8; 1500];
let mut marshal_buf = Vec::with_capacity(1500);
let sender = IceSocketWrapper::Platform(socket.clone());
loop {
let result = crate::platform::time::with_timeout(
core::time::Duration::from_secs(1),
socket.recv_from(&mut buf),
)
.await;
let (len, addr) = match result {
Err(_) => continue,
Ok(Err(_)) => break,
Ok(Ok(v)) => v,
};
let packet = &buf[..len];
if len > 0 {
handle_packet(
packet,
addr,
inner.clone(),
sender.clone(),
&mut marshal_buf,
)
.await;
}
}
}
async fn run_shared_udp_read_loop(
handle: Arc<shared_udp::SharedUdpHandle>,
inner: Arc<IceTransportInner>,
) {
let mut state_rx = inner.state.subscribe();
let mut marshal_buf = Vec::with_capacity(1500);
let sender = IceSocketWrapper::SharedUdp(handle.clone());
trace!("Shared UDP read loop started");
loop {
let which = {
let mut state_fut = core::pin::pin!(state_rx.changed());
let mut recv_fut = core::pin::pin!(handle.recv());
crate::platform::select::select2(&mut state_fut, &mut recv_fut).await
};
let packet_opt = match which {
crate::platform::select::Either::A(res) => {
if res.is_err()
|| matches!(
*state_rx.borrow(),
IceTransportState::Closed | IceTransportState::Failed
)
{
debug!("Shared UDP read loop stopping (IceTransport Closed or Failed)");
break;
}
continue;
}
crate::platform::select::Either::B(pkt) => pkt,
};
match packet_opt {
Some((packet, addr)) => {
handle_packet(
&packet,
addr,
inner.clone(),
sender.clone(),
&mut marshal_buf,
)
.await;
}
None => break,
}
}
}
async fn run_turn_read_loop(
client: Arc<TurnClient>,
relayed_addr: SocketAddr,
inner: Arc<IceTransportInner>,
) {
let mut buf = [0u8; 1500];
let mut marshal_buf = Vec::with_capacity(1500);
let mut state_rx = inner.state.subscribe();
trace!("Read loop started for TURN client {}", relayed_addr);
loop {
let which = {
let mut recv_fut = core::pin::pin!(async { client.recv(&mut buf).await });
let mut state_fut = core::pin::pin!(state_rx.changed());
crate::platform::select::select2(&mut recv_fut, &mut state_fut).await
};
match which {
crate::platform::select::Either::A(result) => match result {
Ok(len) => {
if len > 0 {
IceTransport::handle_turn_packet(
&buf[..len],
&inner,
&client,
relayed_addr,
&mut marshal_buf,
)
.await;
}
}
Err(e) => {
if e.to_string().contains("deadline has elapsed") {
continue;
}
debug!("TURN client recv error: {}", e);
break;
}
},
crate::platform::select::Either::B(res) => {
if res.is_err()
|| matches!(
*state_rx.borrow(),
IceTransportState::Closed | IceTransportState::Failed
)
{
trace!("TURN Read loop stopping (IceTransport Closed or Failed)");
break;
}
}
}
}
}
#[cfg(feature = "std")]
async fn run_tcp_listen_loop(listener: Arc<TcpListener>, inner: Arc<IceTransportInner>) {
let mut state_rx = inner.state.subscribe();
let local_addr = match listener.local_addr() {
Ok(a) => a,
Err(e) => {
debug!("TCP listener local_addr error: {}", e);
return;
}
};
trace!("TCP listen loop started for {:?}", local_addr);
loop {
let which = {
let mut accept_fut = core::pin::pin!(listener.accept());
let mut state_fut = core::pin::pin!(state_rx.changed());
crate::platform::select::select2(&mut accept_fut, &mut state_fut).await
};
match which {
crate::platform::select::Either::A(accept_res) => match accept_res {
Ok((stream, peer_addr)) => {
trace!("TCP accepted connection from {}", peer_addr);
let wrapper = split_tcp_stream(stream, peer_addr);
inner.gatherer.store_tcp_stream(local_addr, wrapper.clone());
let _ = inner.gatherer.socket_tx.send(wrapper);
}
Err(e) => {
debug!("TCP accept error: {}", e);
break;
}
},
crate::platform::select::Either::B(res) => {
if res.is_err()
|| matches!(
*state_rx.borrow(),
IceTransportState::Closed | IceTransportState::Failed
)
{
debug!("TCP listen loop stopping (IceTransport Closed or Failed)");
break;
}
}
}
}
}
#[cfg(feature = "std")]
async fn run_tcp_read_loop(
read: Arc<Mutex<TcpReadHalf>>,
write: Arc<Mutex<TcpWriteHalf>>,
peer_addr: SocketAddr,
inner: Arc<IceTransportInner>,
) {
let mut buf = [0u8; 65_535];
let mut marshal_buf = Vec::with_capacity(1500);
let mut state_rx = inner.state.subscribe();
#[cfg(feature = "std")]
let sender = IceSocketWrapper::TcpStream(read, write, peer_addr);
trace!("TCP read loop started for peer {}", peer_addr);
loop {
let which = {
let mut recv_fut = core::pin::pin!(sender.recv_from(&mut buf));
let mut state_fut = core::pin::pin!(state_rx.changed());
crate::platform::select::select2(&mut recv_fut, &mut state_fut).await
};
match which {
crate::platform::select::Either::A(result) => match result {
Ok((len, addr)) => {
if len > 0 {
handle_packet(
&buf[..len],
addr,
inner.clone(),
sender.clone(),
&mut marshal_buf,
)
.await;
}
}
Err(e) => {
debug!("TCP recv error from {}: {}", peer_addr, e);
break;
}
},
crate::platform::select::Either::B(res) => {
if res.is_err()
|| matches!(
*state_rx.borrow(),
IceTransportState::Closed | IceTransportState::Failed
)
{
debug!("TCP read loop stopping (IceTransport Closed or Failed)");
break;
}
}
}
}
}
async fn run_keepalive_tick(inner: &Arc<IceTransportInner>) -> Option<BoxFuture<'static, ()>> {
let state = *inner.state.borrow();
if state == IceTransportState::Connected || state == IceTransportState::Disconnected {
if inner.config.transport_mode == crate::TransportMode::WebRtc {
let last_nanos = inner.last_received_nanos.load(Ordering::Relaxed);
let now_nanos = inner.created_at.elapsed().as_nanos() as u64;
let elapsed = Duration::from_nanos(now_nanos.saturating_sub(last_nanos));
let ice_conn_timeout = inner.config.ice_connection_timeout;
let tcp_selected = inner
.selected_pair
.lock()
.as_ref()
.map(|pair| pair.local.transport == "tcp")
.unwrap_or(false);
let disconnect_threshold = if tcp_selected {
ice_conn_timeout.saturating_sub(Duration::from_secs(1))
} else {
inner.config.ice_disconnect_threshold
};
if elapsed > ice_conn_timeout {
let _ = inner.set_state(IceTransportState::Failed);
} else if elapsed > disconnect_threshold {
if state != IceTransportState::Disconnected {
let _ = inner.set_state(IceTransportState::Disconnected);
}
} else if state == IceTransportState::Disconnected {
let _ = inner.set_state(IceTransportState::Connected);
}
}
let pair_opt = inner.selected_pair.lock().clone();
if let Some(pair) = pair_opt {
let socket = inner
._socket_rx_keeper
.borrow()
.clone()
.or_else(|| resolve_socket(inner, &pair));
if let Some(socket) = socket {
let tx_id = random_bytes::<12>();
let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
let remote_params = inner.remote_parameters.lock().clone();
if let Some(params) = remote_params {
let username = format!(
"{}:{}",
params.username_fragment,
inner.local_parameters.lock().username_fragment
);
msg.attributes.push(StunAttribute::Username(username));
msg.attributes
.push(StunAttribute::Priority(pair.local.priority));
if let Ok(bytes) = msg.encode(Some(params.password.as_bytes()), true) {
let (tx, rx) = oneshot::channel();
{
let mut map = inner.pending_transactions.lock();
map.insert(tx_id, tx);
}
let inner_weak = Arc::downgrade(inner);
let cleanup: BoxFuture<'static, ()> = Box::pin(async move {
let _ = with_timeout(Duration::from_secs(5), rx).await;
if let Some(inner) = inner_weak.upgrade() {
let mut map = inner.pending_transactions.lock();
map.remove(&tx_id);
}
});
let _ = socket.send_to(&bytes, pair.remote.address).await;
return Some(cleanup);
}
} else if inner.config.transport_mode != crate::TransportMode::WebRtc
&& let Ok(bytes) = msg.encode(None, false)
{
let _ = socket.send_to(&bytes, pair.remote.address).await;
}
}
}
}
None
}
async fn run_turn_refresh(inner: &Arc<IceTransportInner>) {
if inner
.turn_refresh_in_progress
.swap(true, core::sync::atomic::Ordering::SeqCst)
{
return;
}
struct RefreshGuard<'a>(&'a core::sync::atomic::AtomicBool);
impl Drop for RefreshGuard<'_> {
fn drop(&mut self) {
self.0.store(false, core::sync::atomic::Ordering::SeqCst);
}
}
let _guard = RefreshGuard(&inner.turn_refresh_in_progress);
let state = *inner.state.borrow();
if state != IceTransportState::Connected && state != IceTransportState::Disconnected {
return;
}
let all_clients: Vec<(SocketAddr, Arc<TurnClient>)> = {
let clients = inner.gatherer.turn_clients.lock();
clients.iter().map(|(k, v)| (*k, v.clone())).collect()
};
if all_clients.is_empty() {
return;
}
let pair_opt = inner.selected_pair.lock().clone();
let remote_addr_for_perm = pair_opt.as_ref().map(|p| p.remote.address);
for (relay_local_addr, client) in all_clients {
Self::refresh_one_turn_client(inner, relay_local_addr, &client, remote_addr_for_perm)
.await;
}
}
async fn refresh_one_turn_client(
inner: &Arc<IceTransportInner>,
_relay_local_addr: SocketAddr,
client: &Arc<TurnClient>,
remote_addr_opt: Option<SocketAddr>,
) {
async fn send_and_await_inner(
client: &Arc<TurnClient>,
inner: &Arc<IceTransportInner>,
bytes: Vec<u8>,
tx_id: [u8; 12],
) -> Option<StunDecoded> {
let (tx, rx) = oneshot::channel();
inner.pending_transactions.lock().insert(tx_id, tx);
if let Err(e) = client.send(&bytes).await {
debug!("TURN refresh send failed: {}", e);
inner.pending_transactions.lock().remove(&tx_id);
return None;
}
match with_timeout(Duration::from_secs(5), rx).await {
Ok(Ok(msg)) => Some(msg),
_ => {
inner.pending_transactions.lock().remove(&tx_id);
None
}
}
}
'alloc: for attempt in 0..2u8 {
match client.create_refresh_packet().await {
Ok((bytes, tx_id)) => {
match send_and_await_inner(client, inner, bytes, tx_id).await {
Some(msg) if msg.class == StunClass::SuccessResponse => {
trace!("TURN allocation refreshed successfully");
break 'alloc;
}
Some(msg)
if matches!(msg.error_code, Some(401) | Some(438)) && attempt == 0 =>
{
if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
debug!(
"TURN Refresh got {}: updating nonce, retrying",
msg.error_code.unwrap_or(0)
);
client.update_nonce(realm, nonce).await;
}
continue 'alloc;
}
Some(msg) => {
debug!("TURN Refresh failed: error={:?}", msg.error_code);
}
None => {
debug!("TURN Refresh timeout or send error");
}
}
}
Err(e) => debug!("TURN Refresh packet creation failed: {}", e),
}
break;
}
if let Some(remote_addr) = remote_addr_opt {
'perm: for attempt in 0..2u8 {
match client.create_permission_packet(remote_addr).await {
Ok((bytes, tx_id)) => {
match send_and_await_inner(client, inner, bytes, tx_id).await {
Some(msg) if msg.class == StunClass::SuccessResponse => {
trace!("TURN permission refreshed for {}", remote_addr);
break 'perm;
}
Some(msg)
if matches!(msg.error_code, Some(401) | Some(438))
&& attempt == 0 =>
{
if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
debug!(
"TURN CreatePermission got {}: updating nonce, retrying",
msg.error_code.unwrap_or(0)
);
client.update_nonce(realm, nonce).await;
}
continue 'perm;
}
Some(msg) => {
debug!(
"TURN CreatePermission refresh failed: error={:?}",
msg.error_code
);
}
None => {
debug!("TURN CreatePermission refresh timeout or send error");
}
}
}
Err(e) => debug!("TURN CreatePermission packet creation failed: {}", e),
}
break;
}
}
let bound_peers = client.bound_peers().await;
let num_bindings = bound_peers.len();
for peer in bound_peers {
if let Some(channel) = client.get_channel(peer).await {
'chan: for attempt in 0..2u8 {
match client.create_channel_rebind_packet(peer, channel).await {
Ok((bytes, tx_id)) => {
match send_and_await_inner(client, inner, bytes, tx_id).await {
Some(msg) if msg.class == StunClass::SuccessResponse => {
trace!(
"TURN ChannelBind refreshed: {} -> ch {}",
peer, channel
);
break 'chan;
}
Some(msg)
if matches!(msg.error_code, Some(401) | Some(438))
&& attempt == 0 =>
{
if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
debug!(
"TURN ChannelBind got {}: updating nonce, retrying ch {}",
msg.error_code.unwrap_or(0),
channel
);
client.update_nonce(realm, nonce).await;
}
continue 'chan;
}
Some(msg) => {
debug!(
"TURN ChannelBind refresh failed: ch={} error={:?}",
channel, msg.error_code
);
}
None => {
debug!(
"TURN ChannelBind refresh timeout or send error: ch={}",
channel
);
}
}
}
Err(e) => {
debug!("TURN ChannelBind refresh packet creation failed: {}", e);
}
}
break;
}
}
}
debug!(
"TURN refresh done: allocation + {} permission + {} channel bindings",
if remote_addr_opt.is_some() { 1 } else { 0 },
num_bindings
);
}
}
impl IceTransport {
pub fn new(config: RtcConfiguration) -> (Self, impl core::future::Future<Output = ()> + Send) {
let (candidate_tx, _) = broadcast::channel(100);
let (socket_tx, socket_rx) = crate::platform::sync::mpsc::unbounded_channel();
let gatherer = IceGatherer::new(config.clone(), candidate_tx.clone(), socket_tx);
let (state_tx, state_rx) = watch::channel(IceTransportState::New);
let runner_state_rx = state_tx.subscribe();
let (gathering_state_tx, gathering_state_rx) = watch::channel(IceGathererState::New);
let (selected_socket_tx, selected_socket_rx) = watch::channel(None);
let (selected_rtcp_socket_tx, selected_rtcp_socket_rx) = watch::channel(None);
let (selected_pair_tx, selected_pair_rx) = watch::channel(None);
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
let (nomination_complete_tx, nomination_complete_rx) = watch::channel(None);
let inner = IceTransportInner {
state: state_tx,
_state_rx_keeper: state_rx,
gathering_state: gathering_state_tx,
_gathering_state_rx_keeper: gathering_state_rx,
role: crate::platform::sync::Mutex::new(IceRole::Controlled),
selected_pair: crate::platform::sync::Mutex::new(None),
local_candidates: Mutex::new(Vec::new()),
remote_candidates: crate::platform::sync::Mutex::new(Vec::new()),
gather_state: crate::platform::sync::Mutex::new(IceGathererState::New),
config: config.clone(),
gatherer,
local_parameters: crate::platform::sync::Mutex::new(IceParameters::generate()),
remote_parameters: crate::platform::sync::Mutex::new(None),
pending_transactions: crate::platform::sync::Mutex::new(BTreeMap::new()),
data_receiver: crate::platform::sync::Mutex::new(None),
buffered_packets: crate::platform::sync::Mutex::new(VecDeque::new()),
selected_socket: selected_socket_tx,
_socket_rx_keeper: selected_socket_rx,
selected_rtcp_socket: selected_rtcp_socket_tx,
_rtcp_socket_rx_keeper: selected_rtcp_socket_rx,
selected_pair_notifier: selected_pair_tx,
_selected_pair_rx_keeper: selected_pair_rx,
created_at: Instant::now(),
last_received_nanos: AtomicU64::new(0),
candidate_tx: candidate_tx.clone(),
cmd_tx,
checking_pairs: Mutex::new(alloc::collections::BTreeSet::new()),
nomination_complete: nomination_complete_tx,
_nomination_complete_rx: nomination_complete_rx,
restart_requested: core::sync::atomic::AtomicBool::new(false),
nomination_generation: crate::platform::atomic64::AtomicU64::new(0),
mdns_hostname: {
#[cfg(feature = "std")]
{
config
.enable_mdns
.then(crate::transports::ice::mdns::MdnsResponder::generate_hostname)
}
#[cfg(not(feature = "std"))]
{
None
}
},
turn_refresh_in_progress: core::sync::atomic::AtomicBool::new(false),
upnp_refresh_in_progress: core::sync::atomic::AtomicBool::new(false),
buffer_stats: Arc::new(BufferStats::default()),
};
let inner = Arc::new(inner);
inner.gatherer.set_transport(Arc::downgrade(&inner));
let runner = IceTransportRunner {
inner: inner.clone(),
socket_rx,
candidate_rx: candidate_tx.subscribe(),
cmd_rx,
state_rx: runner_state_rx,
};
(Self { inner }, runner.run())
}
pub fn state(&self) -> IceTransportState {
*self.inner.state.borrow()
}
pub fn subscribe_state(&self) -> watch::Receiver<IceTransportState> {
self.inner.state.subscribe()
}
pub fn subscribe_gathering_state(&self) -> watch::Receiver<IceGathererState> {
self.inner.gathering_state.subscribe()
}
pub fn subscribe_candidates(&self) -> broadcast::Receiver<IceCandidate> {
self.inner.candidate_tx.subscribe()
}
pub fn subscribe_selected_socket(&self) -> watch::Receiver<Option<IceSocketWrapper>> {
self.inner.selected_socket.subscribe()
}
pub(crate) fn subscribe_selected_rtcp_socket(
&self,
) -> watch::Receiver<Option<IceSocketWrapper>> {
self.inner.selected_rtcp_socket.subscribe()
}
pub fn subscribe_selected_pair(&self) -> watch::Receiver<Option<IceCandidatePair>> {
self.inner.selected_pair_notifier.subscribe()
}
pub fn subscribe_nomination_complete(&self) -> watch::Receiver<Option<bool>> {
self.inner.nomination_complete.subscribe()
}
pub fn nudge_passive_tcp_nomination(&self) {
if *self.inner.role.lock() != IceRole::Controlled {
return;
}
if self.inner.nomination_complete.borrow().is_some() {
return;
}
let inner = self.inner.clone();
debug!("ICE: nudging passive TCP nomination (controlled, awaiting inbound TCP)");
let rt_handle = inner.config.runtime_handle.clone();
crate::spawn_rtc(rt_handle.as_ref(), tracing::Span::current(), async move {
let streams: Vec<_> = inner
.gatherer
.tcp_streams
.lock()
.values()
.cloned()
.collect();
for wrapper in streams {
#[cfg(feature = "std")]
if let IceSocketWrapper::TcpStream(_, _, peer) = wrapper {
#[cfg(feature = "std")]
complete_controlled_inbound_tcp_nomination(&wrapper, peer, inner).await;
#[cfg(not(feature = "std"))]
{
let _ = (&wrapper, &peer, &inner);
}
return;
}
}
});
}
pub fn gather_state(&self) -> IceGathererState {
self.inner.gatherer.state()
}
pub fn role(&self) -> IceRole {
*self.inner.role.lock()
}
pub fn local_candidates(&self) -> Vec<IceCandidate> {
self.inner.gatherer.local_candidates()
}
pub(crate) fn local_rtcp_addr(&self) -> Option<SocketAddr> {
self.inner
.gatherer
.local_candidates()
.into_iter()
.find(|candidate| candidate.component == 2)
.map(|candidate| candidate.address)
}
pub fn remote_candidates(&self) -> Vec<IceCandidate> {
self.inner.remote_candidates.lock().clone()
}
pub fn local_parameters(&self) -> IceParameters {
self.inner.local_parameters.lock().clone()
}
pub fn set_remote_parameters(&self, params: IceParameters) {
*self.inner.remote_parameters.lock() = Some(params);
}
pub async fn restart(&self) -> RtcResult<()> {
self.inner
.restart_requested
.store(true, core::sync::atomic::Ordering::SeqCst);
self.restart_internal().await
}
async fn restart_internal(&self) -> RtcResult<()> {
*self.inner.local_parameters.lock() = IceParameters::generate();
self.inner.checking_pairs.lock().await.clear();
self.inner.pending_transactions.lock().clear();
*self.inner.selected_pair.lock() = None;
let _ = self.inner.selected_pair_notifier.send(None);
self.inner
.nomination_generation
.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
let _ = self.inner.nomination_complete.send(None);
let _ = self.inner.set_state(IceTransportState::Checking);
self.rebind_shared_udp_mux().await?;
debug!(
label = self.inner.config.label.as_deref().unwrap_or("-"),
"ICE restart: new ufrag={}, pwd=<redacted>",
self.inner.local_parameters.lock().username_fragment
);
Ok(())
}
async fn rebind_shared_udp_mux(&self) -> RtcResult<()> {
let gatherer = &self.inner.gatherer;
let listen_key = {
let regs = gatherer.shared_udp_regs.lock();
let Some(reg) = regs.first() else {
return Ok(()); };
reg.listen_key()
};
gatherer.shared_udp_regs.lock().clear();
let ufrag = self.inner.local_parameters.lock().username_fragment.clone();
let (_local_addr, handle, registration) =
shared_udp::acquire(listen_key, ufrag).await.map_err(|e| {
RtcError::Internal(format!("shared UDP mux rebind failed: {e:#}"))
})?;
gatherer.shared_udp_regs.lock().push(registration);
let wrapper = IceSocketWrapper::SharedUdp(Arc::new(handle));
*gatherer.shared_udp_socket.lock() = Some(wrapper.clone());
gatherer.socket_tx.send(wrapper).ok();
Ok(())
}
fn start_keepalive(&self) {
}
pub fn start_gathering(&self) -> RtcResult<()> {
{
let mut state = self.inner.gather_state.lock();
if *state == IceGathererState::Complete || *state == IceGathererState::Gathering {
return Ok(());
}
*state = IceGathererState::Gathering;
let _ = self.inner.gathering_state.send(IceGathererState::Gathering);
}
let _ = self.inner.cmd_tx.send(IceCommand::StartGathering);
Ok(())
}
pub async fn start(&self, remote: IceParameters) -> RtcResult<()> {
let previous_remote = self.inner.remote_parameters.lock().clone();
if let Some(prev) = previous_remote
&& (prev.username_fragment != remote.username_fragment
|| prev.password != remote.password)
{
if self
.inner
.restart_requested
.swap(false, core::sync::atomic::Ordering::SeqCst)
{
debug!(
label = self.inner.config.label.as_deref().unwrap_or("-"),
"ICE restart completing: peer re-signalled credentials"
);
} else {
debug!(
label = self.inner.config.label.as_deref().unwrap_or("-"),
"remote ICE restart detected (ufrag/pwd changed): restarting locally"
);
self.restart_internal().await?;
}
} else {
self.inner
.restart_requested
.store(false, core::sync::atomic::Ordering::SeqCst);
}
self.start_gathering()?;
self.start_keepalive();
{
let mut params = self.inner.remote_parameters.lock();
*params = Some(remote);
}
self.inner.set_state(IceTransportState::Checking);
self.try_connectivity_checks();
Ok(())
}
pub async fn start_direct(&self, remote_addr: SocketAddr) -> RtcResult<()> {
self.start_gathering()?;
self.start_keepalive();
let mut rx = self.subscribe_candidates();
let start = Instant::now();
let timeout_dur = Duration::from_secs(2);
let is_suitable = |c: &IceCandidate| -> bool {
if !remote_addr.ip().is_loopback() && c.address.ip().is_loopback() {
return false;
}
true
};
let mut best_local: Option<IceCandidate> = None;
{
let candidates = self.inner.gatherer.local_candidates();
for c in candidates {
if is_suitable(&c) {
best_local = Some(c);
break;
}
}
}
if best_local.is_none() {
loop {
let remaining = timeout_dur
.checked_sub(start.elapsed())
.unwrap_or(Duration::ZERO);
if remaining.is_zero() {
break;
}
match with_timeout(remaining, rx.recv()).await {
Ok(Ok(c)) => {
if is_suitable(&c) {
best_local = Some(c);
break;
}
}
_ => break,
}
}
}
let local = if let Some(best) = best_local {
best
} else if let Some(first) = self.inner.gatherer.local_candidates().first() {
first.clone()
} else {
return Err(RtcError::Internal(format!(
"No local candidates gathered for direct connection"
)));
};
let remote = IceCandidate::host(remote_addr, 1);
let pair = IceCandidatePair::new(local, remote);
*self.inner.selected_pair.lock() = Some(pair.clone());
let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
if let Some(socket) = resolve_socket(&self.inner, &pair) {
let _ = self.inner.selected_socket.send(Some(socket.clone()));
publish_selected_rtcp_socket(&self.inner, Some(socket));
}
let _ = self.inner.set_state(IceTransportState::Connected);
Ok(())
}
pub async fn setup_direct_rtp(&self, remote_addr: SocketAddr) -> RtcResult<SocketAddr> {
self.setup_direct_rtp_with_rtcp(remote_addr, false).await
}
pub(crate) async fn setup_direct_rtp_with_rtcp(
&self,
remote_addr: SocketAddr,
bind_rtcp: bool,
) -> RtcResult<SocketAddr> {
let bind_ip = if let Some(bind_ip_str) = &self.inner.config.bind_ip {
bind_ip_str.parse::<IpAddr>().unwrap_or_else(|_| {
get_local_ip().unwrap_or(IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED))
})
} else if let Ok(ip) = get_local_ip() {
ip
} else {
IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED)
};
let socket = self.inner.gatherer.bind_socket(bind_ip).await?;
let local_addr = socket.local_addr()?;
self.inner.gatherer.register_bound(&socket);
let mut cand_addr = local_addr;
let mut upnp_external_addr: Option<SocketAddr> = None;
#[cfg(feature = "std")]
if self.inner.config.enable_upnp && !local_addr.ip().is_loopback() && !local_addr.is_ipv6()
{
let mut mapper = UpnpPortMapper::with_lease_duration(
local_addr,
self.inner.config.upnp_lease_duration,
);
if let Err(e) = mapper.discover().await {
trace!("UPnP discovery failed for RTP mode: {}", e);
} else if let Ok(ext_addr) = mapper.add_mapping(0).await {
debug!(
"UPnP mapping created for RTP mode: {} -> {}",
local_addr, ext_addr
);
cand_addr.set_ip(ext_addr.ip());
cand_addr.set_port(ext_addr.port());
upnp_external_addr = Some(ext_addr);
self.inner.gatherer.upnp_mappers.lock().push(mapper);
} else {
debug!("UPnP mapping failed for RTP mode, using local address");
}
}
if upnp_external_addr.is_none() {
if let Some(ext_ip) = &self.inner.config.external_ip {
if let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
&& !bind_ip.is_loopback()
{
cand_addr.set_ip(parsed_ip);
}
} else if bind_ip.is_unspecified()
&& let Ok(local_ip) = get_local_ip()
{
cand_addr.set_ip(local_ip);
}
}
if upnp_external_addr.is_none()
&& let Some(ext_port) = self.inner.config.external_port
&& !bind_ip.is_loopback()
{
cand_addr.set_port(ext_port);
}
let mut local_candidate = IceCandidate::host(cand_addr, 1);
if cand_addr != local_addr {
local_candidate.related_address = Some(local_addr);
}
let mut rtcp_socket = None;
let mut rtcp_candidate = None;
if bind_rtcp {
let (rtcp, candidate) =
bind_direct_rtcp_socket(&self.inner, local_addr, cand_addr.ip()).await?;
rtcp_socket = Some(rtcp);
rtcp_candidate = Some(candidate);
}
self.inner.gatherer.push_candidate(local_candidate.clone());
if let Some(candidate) = rtcp_candidate {
self.inner.gatherer.push_candidate(candidate);
}
*self.inner.gatherer.state.lock() = IceGathererState::Complete;
let _ = self.inner.gathering_state.send(IceGathererState::Complete);
let remote_candidate = IceCandidate::host(remote_addr, 1);
let pair = IceCandidatePair::new(local_candidate, remote_candidate);
*self.inner.selected_pair.lock() = Some(pair.clone());
let _ = self.inner.selected_pair_notifier.send(Some(pair));
let _ = self
.inner
.selected_socket
.send(Some(socket.clone()));
let rtcp_socket = rtcp_socket.unwrap_or_else(|| socket.clone());
let _ = self
.inner
.selected_rtcp_socket
.send(Some(rtcp_socket));
let _ = self.inner.set_state(IceTransportState::Connected);
Ok(cand_addr)
}
pub async fn setup_direct_rtp_offer(&self) -> RtcResult<SocketAddr> {
self.setup_direct_rtp_offer_with_rtcp(false).await
}
pub(crate) async fn setup_direct_rtp_offer_with_rtcp(
&self,
bind_rtcp: bool,
) -> RtcResult<SocketAddr> {
let bind_ip = if let Some(bind_ip_str) = &self.inner.config.bind_ip {
bind_ip_str.parse::<IpAddr>().unwrap_or_else(|_| {
get_local_ip().unwrap_or(IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED))
})
} else if let Ok(ip) = get_local_ip() {
ip
} else {
IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED)
};
let socket = self.inner.gatherer.bind_socket(bind_ip).await?;
let local_addr = socket.local_addr()?;
self.inner.gatherer.register_bound(&socket);
let mut cand_addr = local_addr;
let mut upnp_external_addr: Option<SocketAddr> = None;
#[cfg(feature = "std")]
if self.inner.config.enable_upnp && !local_addr.ip().is_loopback() && !local_addr.is_ipv6()
{
let mut mapper = UpnpPortMapper::with_lease_duration(
local_addr,
self.inner.config.upnp_lease_duration,
);
if let Err(e) = mapper.discover().await {
trace!("UPnP discovery failed for RTP offer mode: {}", e);
} else if let Ok(ext_addr) = mapper.add_mapping(0).await {
debug!(
"UPnP mapping created for RTP offer mode: {} -> {}",
local_addr, ext_addr
);
cand_addr.set_ip(ext_addr.ip());
cand_addr.set_port(ext_addr.port());
upnp_external_addr = Some(ext_addr);
self.inner.gatherer.upnp_mappers.lock().push(mapper);
} else {
debug!("UPnP mapping failed for RTP offer mode, using local address");
}
}
if upnp_external_addr.is_none() {
if let Some(ext_ip) = &self.inner.config.external_ip {
if let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
&& !bind_ip.is_loopback()
{
cand_addr.set_ip(parsed_ip);
}
} else if bind_ip.is_unspecified()
&& let Ok(local_ip) = get_local_ip()
{
cand_addr.set_ip(local_ip);
}
}
if upnp_external_addr.is_none()
&& let Some(ext_port) = self.inner.config.external_port
&& !bind_ip.is_loopback()
{
cand_addr.set_port(ext_port);
}
let mut local_candidate = IceCandidate::host(cand_addr, 1);
if cand_addr != local_addr {
local_candidate.related_address = Some(local_addr);
}
let mut rtcp_socket = None;
let mut rtcp_candidate = None;
if bind_rtcp {
let (rtcp, candidate) =
bind_direct_rtcp_socket(&self.inner, local_addr, cand_addr.ip()).await?;
rtcp_socket = Some(rtcp);
rtcp_candidate = Some(candidate);
}
self.inner.gatherer.push_candidate(local_candidate);
if let Some(candidate) = rtcp_candidate {
self.inner.gatherer.push_candidate(candidate);
}
if let Some(rtcp_socket) = rtcp_socket {
let _ = self
.inner
.selected_rtcp_socket
.send(Some(rtcp_socket));
}
*self.inner.gatherer.state.lock() = IceGathererState::Complete;
let _ = self.inner.gathering_state.send(IceGathererState::Complete);
Ok(cand_addr)
}
pub fn complete_direct_rtp(&self, remote_addr: SocketAddr) {
let remote_candidate = IceCandidate::host(remote_addr, 1);
let local_candidate = self
.inner
.gatherer
.local_candidates()
.into_iter()
.find(|candidate| candidate.component == 1)
.unwrap_or_else(|| {
IceCandidate::host(
SocketAddr::new(IpAddr::V4(core::net::Ipv4Addr::LOCALHOST), 0),
1,
)
});
let pair = IceCandidatePair::new(local_candidate, remote_candidate);
*self.inner.selected_pair.lock() = Some(pair.clone());
let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
if let Some(socket) = resolve_socket(&self.inner, &pair) {
let _ = self.inner.selected_socket.send(Some(socket.clone()));
publish_selected_rtcp_socket(&self.inner, Some(socket));
}
let _ = self.inner.set_state(IceTransportState::Connected);
}
fn destroy_turn_allocations_best_effort(&self) {
let clients: Vec<Arc<TurnClient>> = {
let map = self.inner.gatherer.turn_clients.lock();
map.values().cloned().collect()
};
for client in &clients {
if let Ok((bytes, _tx_id)) = client.create_destroy_packet_sync()
&& client.try_send_sync(&bytes)
{
trace!("TURN allocation destroy Refresh(LIFETIME=0) sent (best-effort)");
}
}
}
pub fn stop(&self) {
self.destroy_turn_allocations_best_effort();
#[cfg(feature = "std")]
if let Ok(handle) = tokio::runtime::Handle::try_current() {
let upnp_clone = self.inner.gatherer.clone();
handle.spawn(async move {
upnp_clone.cleanup_upnp_mappings().await;
});
}
let _ = self.inner.set_state(IceTransportState::Closed);
let _ = self.inner.selected_socket.send(None);
let _ = self.inner.selected_rtcp_socket.send(None);
let _ = self.inner.selected_pair_notifier.send(None);
*self.inner.selected_pair.lock() = None;
self.inner.gatherer.sockets.lock().clear();
self.inner.gatherer.tcp_listeners.lock().clear();
self.inner.gatherer.tcp_streams.lock().clear();
self.inner.gatherer.shared_tcp_regs.lock().clear();
self.inner.gatherer.shared_udp_regs.lock().clear();
self.inner.gatherer.turn_clients.lock().clear();
*self.inner.gatherer.shared_udp_socket.lock() = None;
}
#[cfg(test)]
pub fn force_state_for_test(&self, state: IceTransportState) {
let _ = self.inner.set_state(state);
}
pub fn set_role(&self, role: IceRole) {
*self.inner.role.lock() = role;
}
pub fn add_remote_candidate(&self, candidate: IceCandidate) {
let mut list = self.inner.remote_candidates.lock();
list.push(candidate);
drop(list);
self.try_connectivity_checks();
}
pub fn select_pair(&self, pair: IceCandidatePair) {
*self.inner.selected_pair.lock() = Some(pair.clone());
let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
if let Some(socket) = resolve_socket(&self.inner, &pair) {
let _ = self.inner.selected_socket.send(Some(socket.clone()));
publish_selected_rtcp_socket(&self.inner, Some(socket));
}
let _ = self.inner.set_state(IceTransportState::Connected);
}
pub fn config(&self) -> &RtcConfiguration {
&self.inner.config
}
pub fn get_selected_socket(&self) -> Option<IceSocketWrapper> {
if let Some(socket) = self.inner._socket_rx_keeper.borrow().clone() {
return Some(socket);
}
let pair = self.inner.selected_pair.lock().clone()?;
resolve_socket(&self.inner, &pair)
}
pub fn get_selected_pair(&self) -> Option<IceCandidatePair> {
self.inner.selected_pair.lock().clone()
}
pub async fn set_data_receiver(&self, receiver: Arc<dyn PacketReceiver>) {
{
let mut rx_lock = self.inner.data_receiver.lock();
*rx_lock = Some(receiver.clone());
}
let packets: Vec<_> = {
let mut buffer = self.inner.buffered_packets.lock();
if buffer.is_empty() {
return;
}
debug!(
count = buffer.len(),
"Flushing buffered RTP packets to newly registered data_receiver"
);
buffer.drain(..).collect()
};
let mut marshal_buf = Vec::new();
for (packet, addr) in packets {
receiver
.receive(Bytes::from(packet), addr, &mut marshal_buf)
.await;
}
}
fn try_connectivity_checks(&self) {
let _ = self.inner.cmd_tx.send(IceCommand::RunChecks);
}
async fn handle_turn_packet(
packet: &[u8],
inner: &Arc<IceTransportInner>,
client: &Arc<TurnClient>,
relayed_addr: SocketAddr,
marshal_buf: &mut Vec<u8>,
) {
if packet.len() >= 4 {
let channel_num = u16::from_be_bytes([packet[0], packet[1]]);
if (0x4000..=0x7FFF).contains(&channel_num) {
let len = u16::from_be_bytes([packet[2], packet[3]]) as usize;
if packet.len() >= 4 + len {
let data = &packet[4..4 + len];
if let Some(peer_addr) = client.get_peer(channel_num).await {
handle_packet(
data,
peer_addr,
inner.clone(),
IceSocketWrapper::Turn(client.clone(), relayed_addr),
marshal_buf,
)
.await;
}
}
return;
}
}
if let Ok(msg) = StunMessage::decode(packet) {
if msg.class == StunClass::Indication && msg.method == StunMethod::Data {
if let Some(data) = &msg.data
&& let Some(peer_addr) = msg.xor_peer_address
{
handle_packet(
data,
peer_addr,
inner.clone(),
IceSocketWrapper::Turn(client.clone(), relayed_addr),
marshal_buf,
)
.await;
}
} else {
handle_packet(
packet,
relayed_addr,
inner.clone(),
IceSocketWrapper::Turn(client.clone(), relayed_addr),
marshal_buf,
)
.await;
}
}
}
}
async fn perform_connectivity_checks_async(inner: Arc<IceTransportInner>) {
let state = *inner.state.borrow();
if state != IceTransportState::Checking {
return;
}
if inner.selected_pair.lock().is_some() {
return;
}
let remotes = inner.remote_candidates.lock().clone();
let role = *inner.role.lock();
if remotes.is_empty() {
return;
}
let mut locals = inner.gatherer.local_candidates();
if locals.is_empty() && role == IceRole::Controlling {
use core::net::{IpAddr, Ipv4Addr};
for remote in &remotes {
if remote.transport == "tcp" && remote.tcp_type == Some(TcpType::Passive) {
locals.push(IceCandidate::tcp(
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
remote.component,
"active",
));
}
}
}
if locals.is_empty() {
return;
}
let mut pairs = Vec::new();
for local in &locals {
for remote in &remotes {
if local.transport != remote.transport {
continue;
}
if local.component != remote.component {
continue;
}
if local.address.ip().is_loopback() && !remote.address.ip().is_loopback() {
continue;
}
if local.address.is_ipv4() != remote.address.is_ipv4() {
continue;
}
if role == IceRole::Controlled
&& local.transport == "tcp"
&& local.tcp_type == Some(TcpType::Passive)
{
continue;
}
pairs.push(IceCandidatePair::new(local.clone(), remote.clone()));
}
}
pairs.sort_by_key(|p| core::cmp::Reverse(p.priority(role)));
if inner.config.prefer_srflx_over_natted_host {
let is_private_ip = |ip: core::net::IpAddr| -> bool {
match ip {
core::net::IpAddr::V4(v4) => v4.is_private(),
core::net::IpAddr::V6(v6) => v6.is_unique_local(),
}
};
let is_behind_nat = |pair: &IceCandidatePair| -> bool {
pair.local.typ == IceCandidateType::Host
&& is_private_ip(pair.local.address.ip())
&& !(pair.remote.typ == IceCandidateType::Host
&& is_private_ip(pair.remote.address.ip()))
};
pairs.sort_by(|a, b| {
let a_natted = is_behind_nat(a);
let b_natted = is_behind_nat(b);
if a_natted != b_natted {
return a_natted.cmp(&b_natted);
}
b.priority(role).cmp(&a.priority(role))
});
}
let mut pairs_to_check = Vec::new();
{
let mut checking = inner.checking_pairs.lock().await;
for pair in pairs {
let key = (pair.local.address, pair.remote.address);
if !checking.contains(&key) {
checking.insert(key);
pairs_to_check.push(pair);
}
}
}
if pairs_to_check.is_empty() {
return;
}
let mut checks = futures::stream::FuturesUnordered::new();
for pair in pairs_to_check {
let inner = inner.clone();
let local = pair.local.clone();
let remote = pair.remote.clone();
checks.push(async move {
let key = (local.address, remote.address);
let res = perform_binding_check(&local, &remote, &inner, role, false).await;
{
let mut checking = inner.checking_pairs.lock().await;
checking.remove(&key);
}
match res {
Ok(_) => Some(IceCandidatePair::new(local, remote)),
Err(e) => {
trace!(
label = inner.config.label.as_deref().unwrap_or("-"),
"ICE connectivity check failed: {} -> {}: {}",
local.address,
remote.address,
e
);
None
}
}
});
}
if checks.is_empty() {
return;
}
#[cfg(feature = "std")]
use futures::stream::StreamExt;
let mut successful_pairs: Vec<IceCandidatePair> = Vec::new();
const NOMINATION_GRACE: Duration = Duration::from_millis(200);
loop {
let next = if successful_pairs.is_empty() {
checks.next().await
} else {
let mut checks_fut = core::pin::pin!(checks.next());
let mut grace_fut = core::pin::pin!(crate::platform::task::sleep(NOMINATION_GRACE));
match crate::platform::select::select2(&mut checks_fut, &mut grace_fut).await {
crate::platform::select::Either::A(res) => res,
crate::platform::select::Either::B(_) => break,
}
};
match next {
Some(pair) => {
if let Some(pair) = pair {
let key = (pair.local.address, pair.remote.address);
if !successful_pairs
.iter()
.any(|p| (p.local.address, p.remote.address) == key)
{
successful_pairs.push(pair);
}
}
}
None => break,
}
}
if successful_pairs.is_empty() {
return;
}
successful_pairs.sort_by_key(|p| core::cmp::Reverse(p.priority(role)));
for p in &successful_pairs {
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"ICE successful pair ({}): local {} {:?} -> remote {} {:?}",
if role == IceRole::Controlling {
"controlling"
} else {
"controlled"
},
p.local.address,
p.local.typ,
p.remote.address,
p.remote.typ
);
}
if role == IceRole::Controlling {
let _ = inner.set_state(IceTransportState::Connected);
if inner.nomination_complete.borrow().is_some() {
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"ICE checks complete (controlling): already nominated, keeping selected pair"
);
return;
}
let nomination_start = crate::platform::time::Instant::now();
let mut nominated_pair: Option<IceCandidatePair> = None;
for (idx, pair) in successful_pairs.iter().enumerate() {
if idx > 0
&& crate::platform::time::Instant::now().duration_since(nomination_start)
>= inner.config.nomination_timeout
{
debug!("Nomination deadline reached before trying all candidate pairs");
break;
}
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"Controlling agent nominating pair: {} -> {}",
pair.local.address,
pair.remote.address
);
match perform_binding_check(&pair.local, &pair.remote, &inner, role, true).await {
Ok(_) => {
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"Nomination succeeded: {} -> {}", pair.local.address, pair.remote.address
);
nominated_pair = Some(pair.clone());
break;
}
Err(e) => {
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"Nomination failed for {} -> {}: {}",
pair.local.address,
pair.remote.address,
e
);
}
}
}
let final_pair = nominated_pair
.clone()
.unwrap_or_else(|| successful_pairs[0].clone());
let nominated = nominated_pair.is_some();
*inner.selected_pair.lock() = Some(final_pair.clone());
let _ = inner.selected_pair_notifier.send(Some(final_pair.clone()));
if let Some(socket) = resolve_socket(&inner, &final_pair) {
let _ = inner.selected_socket.send(Some(socket.clone()));
publish_selected_rtcp_socket(&inner, Some(socket));
}
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"ICE checks complete. Selected pair: {} -> {}",
final_pair.local.address,
final_pair.remote.address
);
if nominated {
let _ = inner.nomination_complete.send(Some(true));
} else {
let _ = inner.nomination_complete.send(Some(false));
let _ = inner.set_state(IceTransportState::Failed);
}
} else {
if inner.nomination_complete.borrow().is_some() {
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"ICE checks complete (controlled): keeping peer-nominated pair"
);
return;
}
let pair = &successful_pairs[0];
*inner.selected_pair.lock() = Some(pair.clone());
let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
if let Some(socket) = resolve_socket(&inner, pair) {
let _ = inner.selected_socket.send(Some(socket.clone()));
publish_selected_rtcp_socket(&inner, Some(socket));
}
let _ = inner.set_state(IceTransportState::Connected);
if pair.local.transport == "tcp" {
let _ = inner.nomination_complete.send(Some(true));
}
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"ICE checks complete. Selected pair: {} -> {}", pair.local.address, pair.remote.address
);
if pair.local.transport != "tcp"
&& inner.nomination_complete.borrow().is_none()
{
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"Controlled checks complete without nomination — waiting for USE-CANDIDATE"
);
let deadline = Instant::now() + inner.config.nomination_timeout;
while Instant::now() < deadline
&& inner.nomination_complete.borrow().is_none()
&& inner.state.borrow().clone() == IceTransportState::Connected
{
crate::platform::task::sleep(core::time::Duration::from_millis(20)).await;
}
if inner.nomination_complete.borrow().is_none() {
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"Nomination did not arrive in time; keeping best-effort selected pair"
);
let _ = inner.nomination_complete.send(Some(true));
}
}
}
}
fn resolve_socket(inner: &IceTransportInner, pair: &IceCandidatePair) -> Option<IceSocketWrapper> {
if pair.local.typ == IceCandidateType::Relay {
let clients = inner.gatherer.turn_clients.lock();
clients
.get(&pair.local.address)
.map(|c| IceSocketWrapper::Turn(c.clone(), pair.local.address))
} else if pair.local.transport == "tcp" {
let streams = inner.gatherer.tcp_streams.lock();
for wrapper in streams.values() {
#[cfg(feature = "std")]
if let IceSocketWrapper::TcpStream(_, _, peer) = wrapper
&& *peer == pair.remote.address
{
return Some(wrapper.clone());
}
}
drop(streams);
inner.gatherer.get_tcp_socket(pair.local.base_address())
} else {
if pair.local.typ == IceCandidateType::Host
&& let Some(shared) = inner.gatherer.shared_udp_socket.lock().clone()
{
return Some(shared);
}
if let Some(platform) = inner.gatherer.get_platform_socket(pair.local.base_address()) {
return Some(IceSocketWrapper::Platform(platform));
}
let socket = inner.gatherer.get_socket(pair.local.base_address());
if socket.is_none() {
debug!(
"resolve_socket: failed to find socket for {}",
pair.local.base_address()
);
}
socket.map(IceSocketWrapper::Udp)
}
}
fn publish_selected_socket(
inner: &IceTransportInner,
pair: &IceCandidatePair,
inbound: Option<&IceSocketWrapper>,
) {
let socket = match inbound {
#[cfg(feature = "std")]
Some(s @ IceSocketWrapper::TcpStream(_, _, _)) => Some(s.clone()),
_ => resolve_socket(inner, pair),
};
if let Some(socket) = socket {
#[cfg(feature = "std")]
let inbound_tcp = matches!(inbound, Some(IceSocketWrapper::TcpStream(_, _, _)));
#[cfg(not(feature = "std"))]
let inbound_tcp = false;
debug!(
pair_local = %pair.local.address,
pair_remote = %pair.remote.address,
socket = %socket.diag(),
inbound_tcp,
"ICE: published selected socket"
);
let _ = inner.selected_socket.send(Some(socket.clone()));
publish_selected_rtcp_socket(inner, Some(socket));
}
}
fn commit_verified_nomination(
inner: &IceTransportInner,
generation: u64,
pair: &IceCandidatePair,
sender: &IceSocketWrapper,
) -> bool {
if inner
.nomination_generation
.load(core::sync::atomic::Ordering::SeqCst)
!= generation
{
return false;
}
*inner.selected_pair.lock() = Some(pair.clone());
let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
publish_selected_socket(inner, pair, Some(sender));
true
}
#[cfg(feature = "std")]
async fn complete_controlled_inbound_tcp_nomination(
sender: &IceSocketWrapper,
addr: SocketAddr,
inner: Arc<IceTransportInner>,
) {
if *inner.role.lock() != IceRole::Controlled {
return;
}
#[cfg(feature = "std")]
let IceSocketWrapper::TcpStream(read, _, _) = sender else {
return;
};
if inner.nomination_complete.borrow().is_some() {
if let Some(pair) = inner.selected_pair.lock().clone() {
publish_selected_socket(&inner, &pair, Some(sender));
}
return;
}
let local_addr: SocketAddr = {
let s = read.lock().await;
s.local_addr()
.unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
};
let locals = inner.gatherer.local_candidates();
let local_cand = locals.iter().find(|c| {
c.base_address() == local_addr
|| (c.transport == "tcp"
&& c.base_address().port() == local_addr.port()
&& (c.base_address().ip().is_unspecified() || local_addr.ip().is_unspecified()))
});
let pair = {
let remotes = inner.remote_candidates.lock();
let remote_cand = remotes.iter().find(|c| c.address == addr);
if let (Some(l), Some(r)) = (local_cand, remote_cand) {
Some(IceCandidatePair::new(l.clone(), r.clone()))
} else {
None
}
};
if let Some(pair) = pair {
trace!(
"Controlled agent selected pair via inbound TCP nomination: {} -> {}",
pair.local.address, pair.remote.address
);
inner
.nomination_generation
.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
*inner.selected_pair.lock() = Some(pair.clone());
let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
publish_selected_socket(&inner, &pair, Some(sender));
let _ = inner.set_state(IceTransportState::Connected);
} else {
debug!(
"Inbound TCP nomination: synthesizing pair for {} -> {}",
local_addr, addr
);
let local_cand = locals.iter().find(|c| {
c.transport == "tcp"
&& c.tcp_type == Some(TcpType::Passive)
&& (c.base_address().port() == local_addr.port()
|| c.address.port() == local_addr.port())
});
let remote_cand = {
let remotes = inner.remote_candidates.lock();
remotes.iter().find(|c| c.address == addr).cloned()
};
if let (Some(l), Some(r)) = (local_cand, remote_cand) {
let pair = IceCandidatePair::new(l.clone(), r.clone());
*inner.selected_pair.lock() = Some(pair.clone());
let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
publish_selected_socket(&inner, &pair, Some(sender));
let _ = inner.set_state(IceTransportState::Connected);
} else {
let _ = inner.selected_socket.send(Some(sender.clone()));
publish_selected_rtcp_socket(&inner, Some(sender.clone()));
}
}
let _ = inner.nomination_complete.send(Some(true));
let pair_summary = inner
.selected_pair
.lock()
.as_ref()
.map(|p| format!("{} -> {}", p.local.address, p.remote.address))
.unwrap_or_else(|| format!("(no pair) peer={addr}"));
debug!(
peer = %addr,
local_bind = %local_addr,
pair = %pair_summary,
socket = %sender.diag(),
"ICE: passive TCP nomination complete"
);
}
fn resolve_rtcp_socket(inner: &IceTransportInner) -> Option<IceSocketWrapper> {
let candidate = inner
.gatherer
.local_candidates()
.into_iter()
.find(|candidate| candidate.component == 2)?;
if candidate.typ == IceCandidateType::Relay {
let clients = inner.gatherer.turn_clients.lock();
clients
.get(&candidate.address)
.map(|client| IceSocketWrapper::Turn(client.clone(), candidate.address))
} else if candidate.transport == "tcp" {
inner.gatherer.get_tcp_socket(candidate.base_address())
} else {
if let Some(platform) = inner.gatherer.get_platform_socket(candidate.base_address()) {
return Some(IceSocketWrapper::Platform(platform));
}
let socket = inner.gatherer.get_socket(candidate.base_address());
if socket.is_none() {
debug!(
"resolve_rtcp_socket: failed to find socket for {}",
candidate.base_address()
);
}
socket.map(IceSocketWrapper::Udp)
}
}
fn publish_selected_rtcp_socket(inner: &IceTransportInner, fallback: Option<IceSocketWrapper>) {
if let Some(socket) = resolve_rtcp_socket(inner).or(fallback) {
let _ = inner.selected_rtcp_socket.send(Some(socket));
}
}
async fn bind_direct_rtcp_socket(
inner: &IceTransportInner,
rtp_base: SocketAddr,
advertised_ip: IpAddr,
) -> RtcResult<(IceSocketWrapper, IceCandidate)> {
let rtcp_bind_addr = rtp_base
.port()
.checked_add(1)
.map(|port| SocketAddr::new(rtp_base.ip(), port));
let rtcp = if let Some(addr) = rtcp_bind_addr {
match inner.gatherer.bind_one(addr).await {
Ok(socket) => socket,
Err(err) => {
debug!(
"Failed to bind RTCP socket on {}, falling back to ephemeral port: {}",
addr, err
);
inner
.gatherer
.bind_one(SocketAddr::new(rtp_base.ip(), 0))
.await?
}
}
} else {
inner
.gatherer
.bind_one(SocketAddr::new(rtp_base.ip(), 0))
.await?
};
let local_rtcp_addr = rtcp.local_addr()?;
inner.gatherer.register_bound(&rtcp);
let mut rtcp_cand_addr = local_rtcp_addr;
rtcp_cand_addr.set_ip(advertised_ip);
let mut candidate = IceCandidate::host(rtcp_cand_addr, 2);
if rtcp_cand_addr != local_rtcp_addr {
candidate.related_address = Some(local_rtcp_addr);
}
Ok((rtcp, candidate))
}
async fn handle_packet(
packet: &[u8],
addr: SocketAddr,
inner: Arc<IceTransportInner>,
sender: IceSocketWrapper,
marshal_buf: &mut Vec<u8>,
) { if should_drop_packet() {
return;
}
inner.last_received_nanos.store(
inner.created_at.elapsed().as_nanos() as u64,
Ordering::Relaxed,
);
let b = packet[0];
if b < 2 {
match StunMessage::decode(packet) {
Ok(msg) => {
if msg.class == StunClass::Request {
handle_stun_request(&sender, &msg, addr, inner).await;
} else if msg.class == StunClass::SuccessResponse {
let mut map = inner.pending_transactions.lock();
if let Some(tx) = map.remove(&msg.transaction_id) {
let _ = tx.send(msg);
} else {
trace!(
"Unmatched transaction {:?} Pending transactions: {:?}",
msg.transaction_id,
map.keys()
);
}
} else if msg.class == StunClass::ErrorResponse {
trace!("Received STUN Error Response from {}", addr);
debug!(
"Received STUN Error Response from {}: {:?}",
addr, msg.error_code
);
if let Some(code) = msg.error_code {
if code == 401 {
let remote_params = inner.remote_parameters.lock().clone();
debug!(
"STUN 401 received. Current remote params: {:?}",
remote_params
);
}
trace!("Error code: {}", code);
}
let mut map = inner.pending_transactions.lock();
if let Some(tx) = map.remove(&msg.transaction_id) {
let _ = tx.send(msg);
}
}
}
Err(e) => {
debug!("Failed to decode STUN packet from {}: {}", addr, e);
}
}
} else {
let receiver = inner.data_receiver.lock().clone();
if let Some(rx) = receiver {
rx.receive(Bytes::copy_from_slice(packet), addr, marshal_buf)
.await;
} else {
let mut buffer = inner.buffered_packets.lock();
let stats = inner.buffer_stats.clone();
let capacity = inner.config.rtp_buffer_capacity;
stats.packets_received.fetch_add(1, Ordering::Relaxed);
if buffer.len() >= capacity {
match inner.config.buffer_drop_strategy {
BufferDropStrategy::DropOldest => {
buffer.pop_front();
buffer.push_back((packet.to_vec(), addr));
}
BufferDropStrategy::DropNew => {
let dropped = stats.packets_dropped.load(Ordering::Relaxed);
if dropped == 0 || dropped.is_multiple_of(1000) {
tracing::warn!(src = %addr, capacity, "RTP buffer full — dropping inbound packet (DropNew strategy)");
}
}
}
stats.packets_dropped.fetch_add(1, Ordering::Relaxed);
} else {
buffer.push_back((packet.to_vec(), addr));
}
let current_size = buffer.len() as u32;
stats.current_size.store(current_size, Ordering::Relaxed);
let mut peak = stats.peak_size.load(Ordering::Relaxed);
while current_size > peak {
match stats.peak_size.compare_exchange_weak(
peak,
current_size,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(current) => peak = current,
}
}
let mut last_log = stats.last_log_time.lock();
if last_log.elapsed() >= inner.config.buffer_stats_log_interval {
let received = stats.packets_received.load(Ordering::Relaxed);
let dropped = stats.packets_dropped.load(Ordering::Relaxed);
let peak_size = stats.peak_size.load(Ordering::Relaxed);
trace!(
"Buffer stats: received={}, dropped={}, current={}, peak={}, capacity={}",
received, dropped, current_size, peak_size, capacity
);
*last_log = Instant::now();
}
}
}
}
async fn verify_nominated_path(
sender: &IceSocketWrapper,
addr: SocketAddr,
inner: Arc<IceTransportInner>,
) -> bool {
let remote_params = inner.remote_parameters.lock().clone();
let Some(rp) = remote_params else {
return false;
};
let local_params = inner.local_parameters.lock().clone();
let tx_id = random_bytes::<12>();
let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
msg.attributes.push(StunAttribute::Username(format!(
"{}:{}",
rp.username_fragment, local_params.username_fragment
)));
let bytes = match msg.encode(Some(rp.password.as_bytes()), true) {
Ok(b) => b,
Err(_) => return false,
};
let (tx, rx) = oneshot::channel();
{
let mut map = inner.pending_transactions.lock();
map.insert(tx_id, tx);
}
if let Err(e) = sender.send_to(&bytes, addr).await {
inner.pending_transactions.lock().remove(&tx_id);
debug!("Path verification send to {} failed: {}", addr, e);
return false;
}
let verified = with_timeout(inner.config.stun_timeout, rx)
.await
.map(|res| res.is_ok())
.unwrap_or(false);
if !verified {
let mut map = inner.pending_transactions.lock();
map.remove(&tx_id);
}
verified
}
async fn handle_stun_request(
sender: &IceSocketWrapper,
msg: &StunDecoded,
addr: SocketAddr,
inner: Arc<IceTransportInner>,
) {
let response = StunMessage::binding_success_response(msg.transaction_id, addr);
#[cfg(any(test, feature = "simulator"))]
simulate_stun_respond_delay(sender).await;
let password = inner.local_parameters.lock().password.clone();
if let Ok(bytes) = response.encode(Some(password.as_bytes()), true) {
match sender.send_to(&bytes, addr).await {
Ok(_) => trace!("Sent STUN Response to {}", addr),
Err(e) => {
debug!("Failed to send STUN Response to {}: {}", addr, e);
}
}
} else {
debug!("Failed to encode STUN Response");
}
let mut known = false;
{
let remotes = inner.remote_candidates.lock();
for cand in remotes.iter() {
if cand.address == addr {
known = true;
break;
}
}
}
if !known {
debug!("Discovered peer reflexive candidate: {}", addr);
let transport = match sender {
IceSocketWrapper::Platform(_)
| IceSocketWrapper::Udp(_)
| IceSocketWrapper::SharedUdp(_) => "udp",
#[cfg(feature = "std")]
IceSocketWrapper::TcpListener(_) | IceSocketWrapper::TcpStream(_, _, _) => "tcp",
IceSocketWrapper::Turn(_, _) => "udp",
};
let mut candidate = IceCandidate::host(addr, 1); candidate.typ = IceCandidateType::PeerReflexive;
candidate.transport = transport.to_string();
candidate.foundation = IceCandidate::compute_foundation(
IceCandidateType::PeerReflexive,
candidate.base_address(),
transport,
);
candidate.priority = if transport == "tcp" {
IceCandidate::priority_for_tcp(IceCandidateType::PeerReflexive, 1, TcpType::Passive)
} else {
IceCandidate::priority_for(IceCandidateType::PeerReflexive, 1)
};
let mut list = inner.remote_candidates.lock();
list.push(candidate);
drop(list);
let _ = inner.cmd_tx.send(IceCommand::RunChecks);
}
if inner.config.enable_latching && inner.config.transport_mode != crate::TransportMode::WebRtc {
let current_pair = inner.selected_pair.lock().clone();
if let Some(pair) = current_pair
&& pair.remote.address.port() == addr.port()
&& pair.remote.address.ip() != addr.ip()
{
debug!(
"RTP latching: updating remote address from {} to {}",
pair.remote.address, addr
);
let mut new_remote = pair.remote.clone();
new_remote.address = addr;
let new_pair = IceCandidatePair::new(pair.local.clone(), new_remote);
*inner.selected_pair.lock() = Some(new_pair.clone());
let _ = inner.selected_pair_notifier.send(Some(new_pair.clone()));
publish_selected_socket(&inner, &new_pair, Some(sender));
}
}
#[cfg(feature = "std")]
complete_controlled_inbound_tcp_nomination(sender, addr, inner.clone()).await;
#[cfg(not(feature = "std"))]
{
let _ = (sender, &addr, &inner);
}
if msg.use_candidate {
let role = *inner.role.lock();
if role == IceRole::Controlled {
#[cfg(feature = "std")]
if matches!(sender, IceSocketWrapper::TcpStream(_, _, _)) {
#[allow(unused_mut, unused_variables)]
let sender = &sender;
return;
}
let use_candidate_generation = inner
.nomination_generation
.fetch_add(1, core::sync::atomic::Ordering::SeqCst)
+ 1;
let local_addr: SocketAddr = match sender {
IceSocketWrapper::Platform(s) => s
.local_addr()
.unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
IceSocketWrapper::Udp(s) => s
.local_addr()
.unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
IceSocketWrapper::SharedUdp(h) => h
.local_addr()
.unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
#[cfg(feature = "std")]
IceSocketWrapper::TcpListener(l) => l
.local_addr()
.unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
#[cfg(feature = "std")]
IceSocketWrapper::TcpStream(read, _, _) => {
let s = read.lock().await;
s.local_addr()
.unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
}
IceSocketWrapper::Turn(_, addr) => *addr,
};
let locals = inner.gatherer.local_candidates();
let local_cand = locals.iter().find(|c| c.base_address() == local_addr);
let pair = {
let remotes = inner.remote_candidates.lock();
let remote_cand = remotes.iter().find(|c| c.address == addr);
if let (Some(l), Some(r)) = (local_cand, remote_cand) {
Some(IceCandidatePair::new(l.clone(), r.clone()))
} else {
None
}
};
if let Some(pair) = pair {
let same_pair = {
let selected = inner.selected_pair.lock();
selected
.as_ref()
.map(|cur| {
cur.local.address == pair.local.address
&& cur.remote.address == pair.remote.address
})
.unwrap_or(false)
};
let has_selected = inner.selected_pair.lock().is_some();
if same_pair {
trace!(
"Controlled agent keeping current pair (UseCandidate {} -> {})",
pair.local.address, pair.remote.address
);
} else if !has_selected {
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"Controlled agent following UseCandidate (first nomination): {} -> {}",
pair.local.address,
pair.remote.address
);
*inner.selected_pair.lock() = Some(pair.clone());
let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
publish_selected_socket(&inner, &pair, Some(sender));
} else {
debug!(
label = inner.config.label.as_deref().unwrap_or("-"),
"Controlled agent deferring switch to newly nominated path {} -> {} until verified",
pair.local.address,
pair.remote.address
);
let inner2 = inner.clone();
let sender2 = sender.clone();
let pair2 = pair.clone();
crate::platform::task::spawn(async move {
let verified = verify_nominated_path(&sender2, addr, inner2.clone()).await;
if !verified {
debug!(
label = inner2.config.label.as_deref().unwrap_or("-"),
"New nominated path {} -> {} failed verification; keeping current pair",
pair2.local.address,
pair2.remote.address
);
} else if commit_verified_nomination(
&inner2,
use_candidate_generation,
&pair2,
&sender2,
) {
debug!(
label = inner2.config.label.as_deref().unwrap_or("-"),
"New nominated path verified, switching: {} -> {}",
pair2.local.address,
pair2.remote.address
);
} else {
debug!(
label = inner2.config.label.as_deref().unwrap_or("-"),
"New nominated path {} -> {} verified but superseded by a newer nomination; keeping current pair",
pair2.local.address,
pair2.remote.address
);
}
});
}
let _ = inner.set_state(IceTransportState::Connected);
let _ = inner.nomination_complete.send(Some(true));
} else {
debug!(
"Received UseCandidate but could not find UDP pair for {} -> {}",
local_addr, addr
);
let _ = inner.nomination_complete.send(Some(true));
}
}
}
}
struct TransactionGuard<'a> {
map: &'a crate::platform::sync::Mutex<BTreeMap<[u8; 12], oneshot::Sender<StunDecoded>>>,
tx_id: [u8; 12],
}
impl<'a> Drop for TransactionGuard<'a> {
fn drop(&mut self) {
let mut map = self.map.lock();
map.remove(&self.tx_id);
}
}
async fn perform_binding_check(
local: &IceCandidate,
remote: &IceCandidate,
inner: &Arc<IceTransportInner>,
role: IceRole,
nominated: bool,
) -> RtcResult<()> {
#[cfg(feature = "std")]
if local.transport == "tcp" && remote.transport == "tcp" {
return perform_tcp_binding_check(local, remote, inner, role, nominated).await;
}
#[cfg(not(feature = "std"))]
if local.transport == "tcp" && remote.transport == "tcp" {
return Err(RtcError::Internal(
"ICE-TCP is excluded from the embedded target".into(),
));
}
if role == IceRole::Controlled && local.transport == "tcp" {
return Ok(());
}
if remote.transport != "udp" {
return Err(RtcError::Internal(format!(
"only UDP connectivity checks are supported"
)));
}
let local_params = inner.local_parameters.lock().clone();
let remote_params = match inner.remote_parameters.lock().clone() {
Some(p) => p,
None => return Err(RtcError::Internal(format!("no remote params"))),
};
let tx_id = random_bytes::<12>();
let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
let username = format!(
"{}:{}",
remote_params.username_fragment, local_params.username_fragment
);
msg.attributes.push(StunAttribute::Username(username));
msg.attributes.push(StunAttribute::Priority(local.priority));
match role {
IceRole::Controlling => {
msg.attributes
.push(StunAttribute::IceControlling(local_params.tie_breaker));
if nominated {
msg.attributes.push(StunAttribute::UseCandidate);
}
}
IceRole::Controlled => msg
.attributes
.push(StunAttribute::IceControlled(local_params.tie_breaker)),
}
let bytes = msg.encode(Some(remote_params.password.as_bytes()), true)?;
let (tx, mut rx) = oneshot::channel();
{
let mut map = inner.pending_transactions.lock();
map.insert(tx_id, tx);
}
let _guard = TransactionGuard {
map: &inner.pending_transactions,
tx_id,
};
let (socket, platform_socket, turn_client) = if local.typ == IceCandidateType::Relay {
let gatherer = &inner.gatherer;
let clients = gatherer.turn_clients.lock();
let client = clients.get(&local.address).cloned();
(None, None, client)
} else {
let socket = inner.gatherer.get_socket(local.base_address());
let platform_socket = inner.gatherer.get_platform_socket(local.base_address());
(socket, platform_socket, None)
};
if local.typ == IceCandidateType::Relay {
let client = turn_client.as_ref().ok_or_else(|| {
RtcError::Internal(format!("TURN client not found for relay candidate"))
})?;
let (perm_bytes, perm_tx_id) = client.create_permission_packet(remote.address).await?;
let (perm_tx, perm_rx) = oneshot::channel();
{
let mut map = inner.pending_transactions.lock();
map.insert(perm_tx_id, perm_tx);
}
trace!("Sending CreatePermission to TURN server");
if let Err(e) = client.send(&perm_bytes).await {
debug!("CreatePermission send failed: {}", e);
return Err(e);
}
match with_timeout(inner.config.stun_timeout, perm_rx).await {
Ok(Ok(msg)) => {
if msg.class == StunClass::ErrorResponse {
return Err(RtcError::Internal(format!(
"CreatePermission failed: {:?}",
msg.error_code
)));
}
if client.get_channel(remote.address).await.is_none()
&& let Ok((bind_bytes, bind_tx_id, channel_num)) =
client.create_channel_bind_packet(remote.address).await
{
let (bind_tx, bind_rx) = oneshot::channel();
{
let mut map = inner.pending_transactions.lock();
map.insert(bind_tx_id, bind_tx);
}
if client.send(&bind_bytes).await.is_ok() {
let client_clone = client.clone();
let remote_addr = remote.address;
let inner_weak = Arc::downgrade(inner);
let timeout_dur = inner.config.stun_timeout;
match with_timeout(timeout_dur, bind_rx).await {
Ok(Ok(msg)) => {
if msg.class == StunClass::SuccessResponse {
client_clone.add_channel(remote_addr, channel_num).await;
}
}
_ => {
if let Some(inner) = inner_weak.upgrade() {
let mut map = inner.pending_transactions.lock();
map.remove(&bind_tx_id);
}
}
}
}
}
}
_ => {
let mut map = inner.pending_transactions.lock();
map.remove(&perm_tx_id);
return Err(RtcError::Internal(format!("CreatePermission timeout")));
}
}
} else if socket.is_none() && platform_socket.is_none() {
return Err(RtcError::Internal(format!(
"no socket found for local candidate"
)));
}
let start = Instant::now();
let mut rto = Duration::from_millis(500);
let max_timeout = if nominated {
inner.config.nomination_timeout
} else {
inner.config.stun_timeout
};
loop {
if let Some(client) = &turn_client {
let sent = if let Some(channel) = client.get_channel(remote.address).await {
client.send_channel_data(channel, &bytes).await
} else {
client.send_indication(remote.address, &bytes).await
};
if let Err(e) = sent {
debug!("TURN send failed: {}", e);
return Err(e);
}
} else if local.transport == "tcp" {
let tcp_stream = match TcpStream::connect(remote.address).await {
Ok(stream) => {
stream.set_nodelay(true).ok();
stream
}
Err(e) => {
debug!("TCP connect to {} failed: {}", remote.address, e);
return Err(e.into());
}
};
#[cfg(feature = "std")]
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut tcp_stream = tcp_stream;
let mut framed = Vec::with_capacity(2 + bytes.len());
let flen = bytes.len() as u16;
framed.extend_from_slice(&flen.to_be_bytes());
framed.extend_from_slice(&bytes);
tcp_stream.write_all(&framed).await?;
match with_timeout(inner.config.stun_timeout, async {
let mut len_buf = [0u8; 2];
tcp_stream.read_exact(&mut len_buf).await?;
let resp_len = u16::from_be_bytes(len_buf) as usize;
let mut resp_buf = vec![0u8; resp_len];
tcp_stream.read_exact(&mut resp_buf).await?;
StunMessage::decode(&resp_buf)
.map_err(|e| RtcError::Internal(format!("stun decode: {e}")))
})
.await
{
Ok(Ok(parsed)) => {
if parsed.class == StunClass::SuccessResponse {
return Ok(());
}
return Err(RtcError::Internal(format!(
"TCP binding check failed: unexpected response"
)));
}
Ok(Err(e)) => return Err(e),
Err(_) => return Err(RtcError::Internal(format!("TCP binding check timeout"))),
}
} else if let Some(platform_socket) = &platform_socket {
if let Err(e) = platform_socket.send_to(&bytes, remote.address).await {
debug!(
"platform socket send_to {} failed (transient): {}",
remote.address, e
);
}
} else if let Some(socket) = &socket
&& let Err(e) = socket.send_to(&bytes, remote.address).await
{
let is_fatal = {
#[cfg(feature = "std")]
{
matches!(
e.kind(),
std::io::ErrorKind::BrokenPipe
| std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::NotConnected
)
}
#[cfg(not(feature = "std"))]
{
false
}
};
if is_fatal {
debug!(
"socket.send_to {} fatal error, aborting nomination: {}",
remote.address, e
);
return Err(e.into());
}
}
let timeout_fut = crate::platform::task::sleep(max_timeout.saturating_sub(start.elapsed()));
let rto_fut = crate::platform::task::sleep(rto);
let mut timeout_fut = core::pin::pin!(timeout_fut);
let mut rto_fut = core::pin::pin!(rto_fut);
match crate::platform::select::select3(&mut rx, &mut timeout_fut, &mut rto_fut).await {
crate::platform::select::Which3::A(res) => {
let parsed = match res {
Ok(msg) => msg,
Err(_) => return Err(RtcError::Internal(format!("channel closed"))),
};
if parsed.transaction_id != tx_id {
return Err(RtcError::Internal(format!(
"binding response transaction mismatch"
)));
}
if parsed.method != StunMethod::Binding {
return Err(RtcError::Internal(format!(
"unexpected STUN method in binding response"
)));
}
if parsed.class != StunClass::SuccessResponse {
return Err(RtcError::Internal(format!("binding request failed")));
}
return Ok(());
}
crate::platform::select::Which3::B(_) => {
return Err(RtcError::Internal(format!("timeout")));
}
crate::platform::select::Which3::C(_) => {
if start.elapsed() >= max_timeout {
continue;
}
trace!(
"Retransmitting STUN Request to {} tx={:?}",
remote.address, tx_id
);
rto = core::cmp::min(rto * 2, Duration::from_millis(1600));
}
}
}
}
#[cfg(feature = "std")]
fn frame_stun_for_tcp(data: &[u8]) -> Vec<u8> {
let len = data.len() as u16;
let mut framed = Vec::with_capacity(2 + data.len());
framed.extend_from_slice(&len.to_be_bytes());
framed.extend_from_slice(data);
framed
}
#[cfg(feature = "std")]
type TcpReadHalf = tokio::net::tcp::OwnedReadHalf;
#[cfg(feature = "std")]
type TcpWriteHalf = tokio::net::tcp::OwnedWriteHalf;
#[cfg(feature = "std")]
fn split_tcp_stream(stream: TcpStream, peer: SocketAddr) -> IceSocketWrapper {
if let Err(e) = stream.set_nodelay(true) {
debug!("TCP set_nodelay failed: {}", e);
}
let (read, write) = stream.into_split();
#[cfg(feature = "std")]
IceSocketWrapper::TcpStream(
Arc::new(Mutex::new(read)),
Arc::new(Mutex::new(write)),
peer,
)
}
#[cfg(feature = "std")]
pub(crate) async fn attach_demuxed_tcp_stream(
inner: Arc<IceTransportInner>,
stream: TcpStream,
peer_addr: SocketAddr,
listen_addr: SocketAddr,
first_packet: Vec<u8>,
) {
let wrapper = split_tcp_stream(stream, peer_addr);
inner
.gatherer
.store_tcp_stream(listen_addr, wrapper.clone());
let _ = inner.gatherer.socket_tx.send(wrapper.clone());
let mut marshal_buf = Vec::new();
handle_packet(&first_packet, peer_addr, inner, wrapper, &mut marshal_buf).await;
}
#[cfg(feature = "std")]
pub(crate) async fn tcp_write_all(write: &Arc<Mutex<TcpWriteHalf>>, data: &[u8]) -> RtcResult<()> {
let mut offset = 0;
while offset < data.len() {
let guard = write.lock().await;
loop {
match guard.try_write(&data[offset..]) {
Ok(0) => guard.writable().await?,
Ok(n) => {
offset += n;
break;
}
Err(e) if e.kind() == ErrorKind::WouldBlock => guard.writable().await?,
Err(e) => return Err(RtcError::Internal(format!("TCP write failed: {}", e))),
}
}
}
Ok(())
}
#[cfg(feature = "std")]
async fn perform_tcp_binding_check(
local: &IceCandidate,
remote: &IceCandidate,
inner: &Arc<IceTransportInner>,
role: IceRole,
nominated: bool,
) -> RtcResult<()> {
debug!(
"perform_tcp_binding_check: {} -> {}",
local.address, remote.address
);
let local_params = inner.local_parameters.lock().clone();
let remote_params = match inner.remote_parameters.lock().clone() {
Some(p) => p,
None => return Err(RtcError::Internal(format!("no remote params"))),
};
let tx_id = random_bytes::<12>();
let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
let username = format!(
"{}:{}",
remote_params.username_fragment, local_params.username_fragment
);
msg.attributes.push(StunAttribute::Username(username));
msg.attributes.push(StunAttribute::Priority(local.priority));
match role {
IceRole::Controlling => {
msg.attributes
.push(StunAttribute::IceControlling(local_params.tie_breaker));
if nominated {
msg.attributes.push(StunAttribute::UseCandidate);
}
}
IceRole::Controlled => msg
.attributes
.push(StunAttribute::IceControlled(local_params.tie_breaker)),
}
let bytes = msg.encode(Some(remote_params.password.as_bytes()), true)?;
let connect_timeout = inner.config.stun_timeout;
let stream = with_timeout(connect_timeout, TcpStream::connect(remote.address))
.await
.map_err(|_| RtcError::Internal(format!("TCP connect timeout to {}", remote.address)))?
.map_err(|e| {
RtcError::Internal(format!("TCP connect to {} failed: {}", remote.address, e))
})?;
let local_addr = stream.local_addr()?;
let wrapper = split_tcp_stream(stream, remote.address);
let write = match &wrapper {
#[cfg(feature = "std")]
IceSocketWrapper::TcpStream(_, write, _) => write.clone(),
_ => return Err(RtcError::Internal(format!("split_tcp_stream invariant"))),
};
inner.gatherer.store_tcp_stream(local_addr, wrapper.clone());
let _ = inner.gatherer.socket_tx.send(wrapper);
let (tx, mut rx) = oneshot::channel();
{
let mut map = inner.pending_transactions.lock();
map.insert(tx_id, tx);
}
let _guard = TransactionGuard {
map: &inner.pending_transactions,
tx_id,
};
{
let framed = frame_stun_for_tcp(&bytes);
#[cfg(feature = "std")]
tcp_write_all(&write, &framed).await?;
}
let start = Instant::now();
let mut rto = Duration::from_millis(500);
let max_timeout = if nominated {
inner.config.nomination_timeout
} else {
inner.config.stun_timeout
};
loop {
let timeout_fut = crate::platform::task::sleep(max_timeout.saturating_sub(start.elapsed()));
let rto_fut = crate::platform::task::sleep(rto);
let mut timeout_fut = core::pin::pin!(timeout_fut);
let mut rto_fut = core::pin::pin!(rto_fut);
match crate::platform::select::select3(&mut rx, &mut timeout_fut, &mut rto_fut).await {
crate::platform::select::Which3::A(res) => {
let parsed = match res {
Ok(msg) => msg,
Err(_) => return Err(RtcError::Internal(format!("channel closed"))),
};
if parsed.transaction_id != tx_id {
return Err(RtcError::Internal(format!(
"binding response transaction mismatch"
)));
}
if parsed.method != StunMethod::Binding {
return Err(RtcError::Internal(format!(
"unexpected STUN method in binding response"
)));
}
if parsed.class != StunClass::SuccessResponse {
return Err(RtcError::Internal(format!("binding request failed")));
}
return Ok(());
}
crate::platform::select::Which3::B(_) => {
return Err(RtcError::Internal(format!("timeout")));
}
crate::platform::select::Which3::C(_) => {
if start.elapsed() >= max_timeout {
continue;
}
trace!(
"TCP Retransmitting STUN Request to {} tx={:?}",
remote.address, tx_id
);
rto = core::cmp::min(rto * 2, Duration::from_millis(1600));
let framed = frame_stun_for_tcp(&bytes);
#[cfg(feature = "std")]
let _ = tcp_write_all(&write, &framed).await;
}
}
}
}
fn store_ice_state(state: &watch::Sender<IceTransportState>, new: IceTransportState) {
if *state.borrow() != new {
let _ = state.send(new);
}
}
impl IceTransportInner {
fn set_state(&self, new: IceTransportState) {
store_ice_state(&self.state, new);
}
}
#[cfg(all(test, feature = "std"))]
mod state_tests {
use super::{IceTransportState, store_ice_state};
use tokio::sync::watch;
#[test]
fn store_ice_state_dedupes_equal_values() {
let (tx, mut rx) = watch::channel(IceTransportState::Checking);
store_ice_state(&tx, IceTransportState::Connected);
assert!(rx.has_changed().unwrap());
let _ = rx.borrow_and_update();
store_ice_state(&tx, IceTransportState::Connected);
assert!(
!rx.has_changed().unwrap(),
"re-sending an equal state must not notify (ICE recovered spam)"
);
store_ice_state(&tx, IceTransportState::Disconnected);
assert!(rx.has_changed().unwrap());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IceTransportState {
New,
Checking,
Connected,
Completed,
Failed,
Disconnected,
Closed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IceGathererState {
New,
Gathering,
Complete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IceRole {
Controlling,
Controlled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TcpType {
Active,
Passive,
So,
}
impl TcpType {
fn as_str(&self) -> &'static str {
match self {
TcpType::Active => "active",
TcpType::Passive => "passive",
TcpType::So => "so",
}
}
fn from_str(s: &str) -> Option<Self> {
match s {
"active" => Some(TcpType::Active),
"passive" => Some(TcpType::Passive),
"so" => Some(TcpType::So),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IceCandidate {
pub foundation: String,
pub priority: u32,
pub address: SocketAddr,
pub typ: IceCandidateType,
pub transport: String,
pub tcp_type: Option<TcpType>,
pub related_address: Option<SocketAddr>,
pub component: u16,
pub hostname: Option<String>,
}
impl IceCandidate {
fn compute_foundation(typ: IceCandidateType, base_addr: SocketAddr, transport: &str) -> String {
fn fnv1a(parts: &[&[u8]]) -> u64 {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for part in parts {
for b in *part {
hash ^= u64::from(*b);
hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
}
hash ^= 0xff; hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
}
hash
}
let typ_label = alloc::format!("{typ:?}");
let ip_label = base_addr.ip().to_string();
format!(
"{:x}",
fnv1a(&[
typ_label.as_bytes(),
ip_label.as_bytes(),
transport.as_bytes(),
])
)
}
pub fn with_tcp_type(mut self, tcp_type: TcpType) -> Self {
self.tcp_type = Some(tcp_type);
self.transport = "tcp".into();
self
}
pub fn host(address: SocketAddr, component: u16) -> Self {
Self {
foundation: Self::compute_foundation(IceCandidateType::Host, address, "udp"),
priority: IceCandidate::priority_for(IceCandidateType::Host, component),
address,
typ: IceCandidateType::Host,
transport: "udp".into(),
tcp_type: None,
related_address: None,
component,
hostname: None,
}
}
pub fn host_tcp(address: SocketAddr, component: u16, tcp_type: TcpType) -> Self {
Self {
foundation: Self::compute_foundation(IceCandidateType::Host, address, "tcp"),
priority: IceCandidate::priority_for_tcp(IceCandidateType::Host, component, tcp_type),
address,
typ: IceCandidateType::Host,
transport: "tcp".into(),
tcp_type: Some(tcp_type),
related_address: None,
component,
hostname: None,
}
}
pub fn tcp(address: SocketAddr, component: u16, tcptype_str: &str) -> Self {
let transport = "tcp";
let tcp_type = TcpType::from_str(tcptype_str).unwrap_or(TcpType::Passive);
Self {
foundation: Self::compute_foundation(IceCandidateType::Host, address, transport),
priority: IceCandidate::priority_for_tcp(IceCandidateType::Host, component, tcp_type),
address,
typ: IceCandidateType::Host,
transport: transport.into(),
tcp_type: Some(tcp_type),
related_address: None,
component,
hostname: None,
}
}
pub fn with_hostname(mut self, hostname: impl Into<String>) -> Self {
self.hostname = Some(hostname.into());
self
}
pub fn base_address(&self) -> SocketAddr {
if self.typ == IceCandidateType::ServerReflexive || self.typ == IceCandidateType::Host {
self.related_address.unwrap_or(self.address)
} else {
self.address
}
}
fn server_reflexive(base: SocketAddr, mapped: SocketAddr, component: u16) -> Self {
Self {
foundation: Self::compute_foundation(IceCandidateType::ServerReflexive, base, "udp"),
priority: IceCandidate::priority_for(IceCandidateType::ServerReflexive, component),
address: mapped,
typ: IceCandidateType::ServerReflexive,
transport: "udp".into(),
tcp_type: None,
related_address: Some(base),
component,
hostname: None,
}
}
fn relay(mapped: SocketAddr, component: u16, transport: &str) -> Self {
Self {
foundation: Self::compute_foundation(IceCandidateType::Relay, mapped, transport),
priority: IceCandidate::priority_for(IceCandidateType::Relay, component),
address: mapped,
typ: IceCandidateType::Relay,
transport: transport.into(),
tcp_type: None,
related_address: None,
component,
hostname: None,
}
}
fn priority_for(typ: IceCandidateType, component: u16) -> u32 {
let type_pref = match typ {
IceCandidateType::Host => 126u32,
IceCandidateType::PeerReflexive => 110u32,
IceCandidateType::ServerReflexive => 100u32,
IceCandidateType::Relay => 0u32,
};
let local_pref = 65_535u32;
let component = component.min(256) as u32;
(type_pref << 24) | (local_pref << 8) | (256 - component)
}
fn priority_for_tcp(typ: IceCandidateType, component: u16, tcp_type: TcpType) -> u32 {
let type_pref = match typ {
IceCandidateType::Host => 126u32,
IceCandidateType::PeerReflexive => 110u32,
IceCandidateType::ServerReflexive => 100u32,
IceCandidateType::Relay => 0u32,
};
let local_pref = match tcp_type {
TcpType::Passive => 65535u32,
TcpType::Active => 65534u32,
TcpType::So => 65533u32,
};
let component = component.min(256) as u32;
(type_pref << 24) | (local_pref << 8) | (256 - component)
}
pub fn to_sdp(&self) -> String {
let advertised_ip = match &self.hostname {
Some(h) => h.clone(),
None => self.address.ip().to_string(),
};
let mut parts = vec![
self.foundation.clone(),
self.component.to_string(),
self.transport.to_ascii_lowercase(),
self.priority.to_string(),
advertised_ip,
self.address.port().to_string(),
"typ".into(),
self.typ.as_str().into(),
];
if let Some(tcp_type) = self.tcp_type {
parts.push("tcptype".into());
parts.push(tcp_type.as_str().into());
}
if let Some(addr) = self.related_address
&& self.typ != IceCandidateType::Host
{
parts.push("raddr".into());
parts.push(addr.ip().to_string());
parts.push("rport".into());
parts.push(addr.port().to_string());
}
parts.join(" ")
}
pub fn from_sdp(sdp: &str) -> RtcResult<Self> {
let parts: Vec<&str> = sdp.split_whitespace().collect();
if parts.len() < 8 {
return Err(RtcError::Internal(format!("invalid candidate")));
}
let start_idx = 0;
let foundation = parts[start_idx]
.trim_start_matches("candidate:")
.to_string();
let component = parts[start_idx + 1].parse::<u16>()?;
let transport = parts[start_idx + 2].to_ascii_lowercase();
let priority = parts[start_idx + 3].parse::<u32>()?;
let ip_str = parts[start_idx + 4];
let port = parts[start_idx + 5].parse::<u16>()?;
let typ_str = parts[start_idx + 7];
let address = if ip_str.contains(':') {
format!("[{}]:{}", ip_str, port).parse()?
} else {
format!("{}:{}", ip_str, port).parse()?
};
let typ = match typ_str {
"host" => IceCandidateType::Host,
"srflx" => IceCandidateType::ServerReflexive,
"prflx" => IceCandidateType::PeerReflexive,
"relay" => IceCandidateType::Relay,
_ => return Err(RtcError::Internal(format!("unknown type"))),
};
let tcp_type = if transport == "tcp" {
let mut i = 8;
loop {
if i + 1 >= parts.len() {
break None;
}
match parts[i] {
"tcptype" => {
break TcpType::from_str(parts[i + 1]);
}
_ => {
i += 2;
}
}
}
} else {
None
};
Ok(Self {
foundation,
priority,
address,
typ,
transport,
tcp_type,
related_address: None,
component,
hostname: None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IceCandidateType {
Host,
ServerReflexive,
PeerReflexive,
Relay,
}
impl IceCandidateType {
fn as_str(&self) -> &'static str {
match self {
IceCandidateType::Host => "host",
IceCandidateType::ServerReflexive => "srflx",
IceCandidateType::PeerReflexive => "prflx",
IceCandidateType::Relay => "relay",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IceCandidatePair {
pub local: IceCandidate,
pub remote: IceCandidate,
pub nominated: bool,
}
impl IceCandidatePair {
pub fn new(local: IceCandidate, remote: IceCandidate) -> Self {
Self {
local,
remote,
nominated: false,
}
}
pub fn priority(&self, role: IceRole) -> u64 {
let g = self.local.priority as u64;
let d = self.remote.priority as u64;
let (g, d) = match role {
IceRole::Controlling => (g, d),
IceRole::Controlled => (d, g),
};
(1u64 << 32) * core::cmp::min(g, d) + 2 * core::cmp::max(g, d) + if g > d { 1 } else { 0 }
}
}
#[derive(Debug, Clone)]
pub struct IceParameters {
pub username_fragment: String,
pub password: String,
pub ice_lite: bool,
pub tie_breaker: u64,
}
impl IceParameters {
pub fn new(username_fragment: impl Into<String>, password: impl Into<String>) -> Self {
Self {
username_fragment: username_fragment.into(),
password: password.into(),
ice_lite: false,
tie_breaker: random_u64(),
}
}
fn generate() -> Self {
let ufrag = hex_encode(&random_bytes::<8>());
let pwd = hex_encode(&random_bytes::<16>());
Self {
username_fragment: ufrag,
password: pwd,
ice_lite: false,
tie_breaker: random_u64(),
}
}
}
#[derive(Debug, Clone)]
pub struct IceTransportBuilder {
config: RtcConfiguration,
role: IceRole,
servers: Vec<IceServer>,
}
impl IceTransportBuilder {
pub fn new(config: RtcConfiguration) -> Self {
Self {
config,
role: IceRole::Controlled,
servers: Vec::new(),
}
}
pub fn role(mut self, role: IceRole) -> Self {
self.role = role;
self
}
pub fn server(mut self, server: IceServer) -> Self {
self.servers.push(server);
self
}
pub fn build(self) -> (IceTransport, impl core::future::Future<Output = ()> + Send) {
let mut config = self.config.clone();
config.ice_servers.extend(self.servers);
let (transport, runner) = IceTransport::new(config);
transport.set_role(self.role);
if let Err(err) = transport.start_gathering() {
debug!("ICE gather failed: {}", err);
}
(transport, runner)
}
}
#[derive(Debug, Clone)]
struct IceGatherer {
state: Arc<crate::platform::sync::Mutex<IceGathererState>>,
local_candidates: Arc<crate::platform::sync::Mutex<Vec<IceCandidate>>>,
sockets: Arc<crate::platform::sync::Mutex<Vec<Arc<UdpSocket>>>>,
platform_sockets: Arc<
crate::platform::sync::Mutex<Vec<Arc<dyn crate::platform::net::UdpSocket>>>,
>,
#[cfg_attr(not(feature = "std"), allow(dead_code))]
tcp_listeners: Arc<crate::platform::sync::Mutex<Vec<Arc<TcpListener>>>>,
tcp_streams: Arc<crate::platform::sync::Mutex<BTreeMap<SocketAddr, IceSocketWrapper>>>,
#[cfg(feature = "std")]
shared_tcp_regs: Arc<crate::platform::sync::Mutex<Vec<shared_tcp::SharedTcpRegistration>>>,
#[cfg(not(feature = "std"))]
shared_tcp_regs: Arc<crate::platform::sync::Mutex<Vec<SharedTcpRegistration>>>,
shared_udp_regs: Arc<crate::platform::sync::Mutex<Vec<shared_udp::SharedUdpRegistration>>>,
shared_udp_socket: Arc<crate::platform::sync::Mutex<Option<IceSocketWrapper>>>,
transport_inner:
Arc<crate::platform::sync::Mutex<Option<alloc::sync::Weak<IceTransportInner>>>>,
turn_clients: Arc<crate::platform::sync::Mutex<BTreeMap<SocketAddr, Arc<TurnClient>>>>,
upnp_mappers: Arc<crate::platform::sync::Mutex<Vec<UpnpPortMapper>>>,
config: RtcConfiguration,
candidate_tx: broadcast::Sender<IceCandidate>,
socket_tx: crate::platform::sync::mpsc::UnboundedSender<IceSocketWrapper>,
}
impl IceGatherer {
fn new(
config: RtcConfiguration,
candidate_tx: broadcast::Sender<IceCandidate>,
socket_tx: crate::platform::sync::mpsc::UnboundedSender<IceSocketWrapper>,
) -> Self {
Self {
state: Arc::new(crate::platform::sync::Mutex::new(IceGathererState::New)),
local_candidates: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
sockets: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
platform_sockets: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
tcp_listeners: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
tcp_streams: Arc::new(crate::platform::sync::Mutex::new(BTreeMap::new())),
shared_tcp_regs: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
shared_udp_regs: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
shared_udp_socket: Arc::new(crate::platform::sync::Mutex::new(None)),
transport_inner: Arc::new(crate::platform::sync::Mutex::new(None)),
turn_clients: Arc::new(crate::platform::sync::Mutex::new(BTreeMap::new())),
upnp_mappers: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
config,
candidate_tx,
socket_tx,
}
}
fn set_transport(&self, inner: alloc::sync::Weak<IceTransportInner>) {
*self.transport_inner.lock() = Some(inner);
}
fn external_srflx_ip(&self, bind_ip: IpAddr) -> Option<IpAddr> {
if self.config.external_ip_candidate_type
!= crate::config::ExternalIpCandidateType::ServerReflexive
|| self.config.transport_mode != crate::config::TransportMode::WebRtc
|| self.config.ice_transport_policy != IceTransportPolicy::All
|| bind_ip.is_loopback()
{
return None;
}
self.config.external_ip.as_ref()?.parse().ok()
}
fn push_host_with_external_srflx(
&self,
local_addr: SocketAddr,
bind_ip: IpAddr,
tcp_type: Option<TcpType>,
) -> bool {
let Some(external) = self.external_srflx_ip(bind_ip) else {
return false;
};
let mut host_addr = local_addr;
if bind_ip.is_unspecified()
&& let Ok(local_ip) = get_local_ip()
{
host_addr.set_ip(local_ip);
}
let mut host = match tcp_type {
Some(tcp_type) => IceCandidate::host_tcp(host_addr, 1, tcp_type),
None => IceCandidate::host(host_addr, 1),
};
if host_addr != local_addr {
host.related_address = Some(local_addr);
}
let mut srflx = IceCandidate::server_reflexive(
local_addr,
SocketAddr::new(external, local_addr.port()),
1,
);
if let Some(tcp_type) = tcp_type {
srflx = srflx.with_tcp_type(tcp_type);
srflx.foundation = IceCandidate::compute_foundation(
IceCandidateType::ServerReflexive,
local_addr,
"tcp",
);
srflx.priority =
IceCandidate::priority_for_tcp(IceCandidateType::ServerReflexive, 1, tcp_type);
}
self.push_candidate(host);
if external.is_ipv4() == local_addr.is_ipv4() {
self.push_candidate(srflx);
}
true
}
fn push_tcp_passive_candidate(&self, local_addr: SocketAddr, bind_ip: IpAddr) {
if self.push_host_with_external_srflx(local_addr, bind_ip, Some(TcpType::Passive)) {
return;
}
if let Some(ext_ip) = &self.config.external_ip
&& let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
{
if !bind_ip.is_loopback() {
let mut ext_addr = local_addr;
ext_addr.set_ip(parsed_ip);
let mut cand = IceCandidate::tcp(ext_addr, 1, "passive");
cand.related_address = Some(local_addr);
self.push_candidate(cand);
} else {
self.push_candidate(IceCandidate::tcp(local_addr, 1, "passive"));
}
} else if bind_ip.is_unspecified() {
let mut cand_addr = local_addr;
if let Ok(local_ip) = get_local_ip() {
cand_addr.set_ip(local_ip);
}
let mut cand = IceCandidate::tcp(cand_addr, 1, "passive");
cand.related_address = Some(local_addr);
self.push_candidate(cand);
} else {
self.push_candidate(IceCandidate::tcp(local_addr, 1, "passive"));
}
}
#[allow(dead_code)]
#[cfg(feature = "std")]
pub fn upnp_mappers(&self) -> Arc<crate::platform::sync::Mutex<Vec<UpnpPortMapper>>> {
self.upnp_mappers.clone()
}
#[allow(dead_code)]
pub async fn cleanup_upnp_mappings(&self) {
let mappers = self.upnp_mappers.lock().clone();
for mapper in mappers {
if let Err(e) = mapper.cleanup().await {
trace!("Failed to clean up UPnP mappings: {}", e);
}
}
self.upnp_mappers.lock().clear();
}
#[cfg(feature = "std")]
pub async fn renew_upnp_mappings(&self) {
let mappers = self.upnp_mappers.lock().clone();
for mapper in mappers {
if let Err(e) = mapper.renew_all_stale().await {
warn!("Failed to refresh UPnP mappings: {}", e);
}
}
}
fn state(&self) -> IceGathererState {
*self.state.lock()
}
fn local_candidates(&self) -> Vec<IceCandidate> {
self.local_candidates.lock().clone()
}
async fn bind_socket(&self, ip: IpAddr) -> RtcResult<IceSocketWrapper> {
if let (Some(start), Some(end)) = (self.config.rtp_start_port, self.config.rtp_end_port) {
let start = start.saturating_add(start % 2);
let end = end - (end % 2);
if start > end {
return Err(RtcError::Internal(format!(
"No usable even RTP ports in range {}..={}",
start, end
)));
}
let port_count = (((end - start) / 2) + 1) as u64;
let start_index = (random_u64() % port_count) as u16;
let mut port = start + (start_index * 2);
for _ in 0..port_count {
match self.bind_one(SocketAddr::new(ip, port)).await {
Ok(socket) => return Ok(socket),
Err(e) => {
if !e.is_addr_in_use() {
error!(
label = self.config.label.as_deref().unwrap_or("-"),
"binding RTP port {} on {} failed: {}", port, ip, e
);
return Err(e);
}
port = port.saturating_add(2);
if port > end {
port = start;
}
}
}
}
return Err(RtcError::Internal(format!(
"No available even RTP ports in range {}..={} (label={label})",
start,
end,
label = self.config.label.as_deref().unwrap_or("-")
)));
} else {
self.bind_one(SocketAddr::new(ip, 0)).await
}
}
async fn bind_one(&self, addr: SocketAddr) -> RtcResult<IceSocketWrapper> {
if let Some(result) = crate::platform::net::udp_bind_via_factory(addr) {
let socket = result?;
let wrapper = IceSocketWrapper::Platform(socket.clone());
self.platform_sockets.lock().push(socket);
return Ok(wrapper);
}
#[cfg(feature = "std")]
{
let socket = UdpSocket::bind(addr).await?;
let socket = Arc::new(socket);
self.sockets.lock().push(socket.clone());
Ok(IceSocketWrapper::Udp(socket))
}
#[cfg(not(feature = "std"))]
{
let _ = addr;
Err(RtcError::Internal(alloc::string::String::from(
"no UDP bind backend: install one via platform::net::set_udp_bind_fn (rtcembed)",
)))
}
}
pub(crate) fn get_platform_socket(
&self,
addr: SocketAddr,
) -> Option<Arc<dyn crate::platform::net::UdpSocket>> {
let sockets = self.platform_sockets.lock();
sockets.iter().find_map(|socket| {
let local = socket.local_addr().ok()?;
let matches =
local == addr || (local.ip().is_unspecified() && local.port() == addr.port());
matches.then(|| socket.clone())
})
}
pub(crate) fn register_bound(&self, wrapper: &IceSocketWrapper) {
if let IceSocketWrapper::Udp(s) = wrapper {
self.sockets.lock().push(s.clone());
}
let _ = self.socket_tx.send(wrapper.clone());
}
fn get_socket(&self, addr: SocketAddr) -> Option<Arc<UdpSocket>> {
let found = {
let sockets = self.sockets.lock();
sockets.iter().find_map(|socket| {
let local = socket.local_addr().ok()?;
let matches =
local == addr || (local.ip().is_unspecified() && local.port() == addr.port());
matches.then(|| socket.clone())
})
};
if let Some(s) = found {
return Some(s);
}
if let Some(IceSocketWrapper::SharedUdp(handle)) = self.shared_udp_socket.lock().clone()
&& let Ok(local) = handle.local_addr()
&& (local == addr || (local.ip().is_unspecified() && local.port() == addr.port()))
{
return None; }
let available: Vec<String> = self
.sockets
.lock()
.iter()
.map(|s| {
s.local_addr()
.map(|a| a.to_string())
.unwrap_or_else(|_| "error".to_string())
})
.collect();
trace!(
"get_socket: no socket found for {}, available: {:?}",
addr, available
);
None
}
fn get_tcp_socket(&self, addr: SocketAddr) -> Option<IceSocketWrapper> {
let streams = self.tcp_streams.lock();
for (local_addr, wrapper) in streams.iter() {
if *local_addr == addr {
return Some(wrapper.clone());
}
if local_addr.ip().is_unspecified() && local_addr.port() == addr.port() {
return Some(wrapper.clone());
}
}
trace!(
"get_tcp_socket: no TCP stream found for {}, available: {:?}",
addr,
streams.keys().collect::<Vec<_>>()
);
None
}
fn store_tcp_stream(&self, local_addr: SocketAddr, wrapper: IceSocketWrapper) {
self.tcp_streams.lock().insert(local_addr, wrapper);
}
#[instrument(skip(self))]
async fn gather(&self) -> RtcResult<()> {
{
let mut state = self.state.lock();
if *state == IceGathererState::Complete {
return Ok(());
}
*state = IceGathererState::Gathering;
}
let host_fut = async {
if self.config.ice_transport_policy == IceTransportPolicy::All {
if self.config.ice_gather_udp_hosts {
if let Err(e) = self.gather_host_candidates().await {
debug!("Host gathering failed: {}", e);
}
} else if self.config.ice_tcp_policy == crate::config::IceTcpPolicy::Enabled {
let has_tcp_listen_range = match (
self.config.tcp_port_range_start,
self.config.tcp_port_range_end,
) {
(Some(s), Some(e)) => s > 0 && e > 0 && s <= e,
_ => false,
};
if !has_tcp_listen_range
&& let Err(e) = self.gather_tcp_active_candidates().await
{
debug!("TCP active gathering failed: {}", e);
}
}
}
};
host_fut.await;
if (self.config.tcp_port_range_start.is_some() || self.config.tcp_port_range_end.is_some())
&& let Err(e) = {
#[cfg(feature = "std")]
{
self.gather_tcp_host_candidates().await
}
#[cfg(not(feature = "std"))]
{
Ok::<(), RtcError>(())
}
}
{
debug!("TCP host gathering failed: {}", e);
}
let stun_public_ip = if self.config.enable_upnp {
with_timeout(
Duration::from_secs(5),
self.gather_servers_and_get_public_ip(),
)
.await
.unwrap_or_else(|_| {
debug!(
"STUN/TURN gathering timed out after 5s, skipping UPnP double-NAT detection"
);
None
})
} else {
if let Err(e) = self.gather_servers().await {
debug!("Server gathering failed: {}", e);
}
None
};
if self.config.enable_upnp
&& self.config.ice_transport_policy == IceTransportPolicy::All
&& let Err(e) = self.gather_upnp_candidates(stun_public_ip).await
{
debug!("UPnP gathering failed: {}", e);
}
*self.state.lock() = IceGathererState::Complete;
Ok(())
}
async fn gather_shared_udp_host_candidate(&self) -> RtcResult<()> {
let port = self.config.ice_udp_mux_port.ok_or_else(|| {
RtcError::Internal(format!(
"ice_udp_mux is enabled but ice_udp_mux_port is not set"
))
})?;
let bind_ip = if let Some(bind_ip_str) = &self.config.bind_ip {
bind_ip_str
.parse::<IpAddr>()
.map_err(|e| RtcError::Internal(format!("invalid bind_ip {bind_ip_str}: {e}")))?
} else {
IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED)
};
if self.config.disable_ipv6 && bind_ip.is_ipv6() {
return Err(RtcError::Internal(format!(
"disable_ipv6 is set but bind_ip is IPv6"
)));
}
let bind_addr = SocketAddr::new(bind_ip, port);
let inner = self
.transport_inner
.lock()
.as_ref()
.and_then(|weak| weak.upgrade())
.ok_or_else(|| {
RtcError::Internal("ICE transport unavailable during shared UDP gather".into())
})?;
let ufrag = inner.local_parameters.lock().username_fragment.clone();
let (local_addr, handle, registration) = shared_udp::acquire(bind_addr, ufrag)
.await
.map_err(|e| RtcError::Internal(format!("shared udp acquire: {e}")))?;
self.shared_udp_regs.lock().push(registration);
let wrapper = IceSocketWrapper::SharedUdp(Arc::new(handle));
*self.shared_udp_socket.lock() = Some(wrapper.clone());
let _ = self.socket_tx.send(wrapper);
if self.push_host_with_external_srflx(local_addr, bind_ip, None) {
return Ok(());
}
let mut cand_addr = local_addr;
if let Some(ext_ip) = &self.config.external_ip
&& let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
{
if !bind_ip.is_loopback() {
cand_addr.set_ip(parsed_ip);
}
} else if bind_ip.is_unspecified()
&& let Ok(local_ip) = get_local_ip()
{
cand_addr.set_ip(local_ip);
}
let mut cand = IceCandidate::host(cand_addr, 1);
if cand_addr != local_addr {
cand.related_address = Some(local_addr);
}
self.push_candidate(cand);
Ok(())
}
async fn gather_host_candidates(&self) -> RtcResult<()> {
let mut bind_ips = Vec::new();
if let Some(bind_ip_str) = &self.config.bind_ip {
if let Ok(ip) = bind_ip_str.parse::<IpAddr>() {
bind_ips.push(ip);
}
} else if self.config.transport_mode != crate::TransportMode::WebRtc {
if let Ok(ip) = get_local_ip() {
bind_ips.push(ip);
} else {
bind_ips.push(IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED));
}
} else {
if self.config.ice_include_loopback_candidates {
bind_ips.push(IpAddr::V4(core::net::Ipv4Addr::LOCALHOST));
}
#[cfg(feature = "std")]
use local_ip_address::list_afinet_netifas;
#[cfg(feature = "std")]
if let Ok(interfaces) = list_afinet_netifas() {
for (name, addr) in interfaces {
if let IpAddr::V4(ip) = addr
&& !ip.is_loopback()
&& !bind_ips.contains(&IpAddr::V4(ip))
{
if name.starts_with("utun")
|| name.starts_with("gif")
|| name.starts_with("stf")
|| name.starts_with("awdl")
|| name.starts_with("llw")
{
continue;
}
bind_ips.push(IpAddr::V4(ip));
}
}
}
#[cfg(not(feature = "std"))]
if let Ok(ip) = get_local_ip() {
bind_ips.push(ip);
}
}
if self.config.ice_udp_mux
&& let Err(e) = self.gather_shared_udp_host_candidate().await
{
debug!("Shared UDP mux host candidate failed: {}", e);
}
for ip in &bind_ips {
let ip = *ip;
if self.config.ice_udp_mux {
continue;
}
match self.bind_socket(ip).await {
Ok(socket) => {
if let Ok(addr) = socket.local_addr() {
self.register_bound(&socket);
if self.push_host_with_external_srflx(addr, ip, None) {
} else if let Some(ext_ip) = &self.config.external_ip
&& let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
{
if !ip.is_loopback() {
let mut ext_addr = addr;
ext_addr.set_ip(parsed_ip);
let mut cand = IceCandidate::host(ext_addr, 1);
cand.related_address = Some(addr);
self.push_candidate(cand);
} else {
self.push_candidate(IceCandidate::host(addr, 1));
}
} else if ip.is_unspecified() {
let mut cand_addr = addr;
if let Ok(local_ip) = get_local_ip() {
cand_addr.set_ip(local_ip);
}
let mut cand = IceCandidate::host(cand_addr, 1);
cand.related_address = Some(addr);
self.push_candidate(cand);
} else {
self.push_candidate(IceCandidate::host(addr, 1));
}
}
}
Err(e) => {
if self.config.bind_ip.is_some() {
debug!(
label = self.config.label.as_deref().unwrap_or("-"),
"Failed to bind to requested bind_ip {}: {}", ip, e
);
} else if !ip.is_loopback() && !ip.is_unspecified() {
debug!(
label = self.config.label.as_deref().unwrap_or("-"),
"Failed to bind socket on {}: {}", ip, e
);
}
}
}
}
#[cfg(feature = "std")]
if self.config.ice_tcp_policy != crate::config::IceTcpPolicy::Disabled {
for ip in &bind_ips {
let ip = *ip;
match TcpListener::bind(SocketAddr::new(ip, 0)).await {
Ok(listener) => {
if let Ok(addr) = listener.local_addr() {
let listener = Arc::new(listener);
self.tcp_listeners.lock().push(listener.clone());
#[cfg(feature = "std")]
let _ = self.socket_tx.send(IceSocketWrapper::TcpListener(listener));
let tcp_type = TcpType::Passive;
if self.push_host_with_external_srflx(addr, ip, Some(tcp_type)) {
continue;
}
let mut cand = IceCandidate::host_tcp(addr, 1, tcp_type);
if ip.is_unspecified()
&& let Ok(local_ip) = get_local_ip()
{
let mut cand_addr = addr;
cand_addr.set_ip(local_ip);
let mut ext_cand = IceCandidate::host_tcp(cand_addr, 1, tcp_type);
ext_cand.related_address = Some(addr);
cand = ext_cand;
}
self.push_candidate(cand);
}
}
Err(e) => {
debug!("Failed to bind TCP listener on {}: {}", ip, e);
}
}
}
}
Ok(())
}
async fn gather_tcp_active_candidates(&self) -> RtcResult<()> {
use core::net::{IpAddr, Ipv4Addr};
const ACTIVE_PLACEHOLDER_PORT: u16 = 9;
let mut bind_ips = Vec::new();
if self.config.ice_include_loopback_candidates {
bind_ips.push(IpAddr::V4(Ipv4Addr::LOCALHOST));
}
if let Ok(local_ip) = get_local_ip()
&& !bind_ips.contains(&local_ip)
{
bind_ips.push(local_ip);
}
for ip in bind_ips {
self.push_candidate(IceCandidate::tcp(
SocketAddr::new(ip, ACTIVE_PLACEHOLDER_PORT),
1,
"active",
));
}
self.push_candidate(IceCandidate::tcp(
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), ACTIVE_PLACEHOLDER_PORT),
1,
"active",
));
Ok(())
}
#[cfg(feature = "std")]
async fn gather_tcp_host_candidates(&self) -> RtcResult<()> {
let start = self.config.tcp_port_range_start.unwrap_or(0);
let end = self.config.tcp_port_range_end.unwrap_or(0);
if start == 0 || end == 0 || start > end {
return Ok(());
}
let bind_ips = if let Some(bind_ip_str) = &self.config.bind_ip {
if let Ok(ip) = bind_ip_str.parse::<IpAddr>() {
vec![ip]
} else {
return Ok(());
}
} else {
let mut ips = Vec::new();
if self.config.ice_include_loopback_candidates {
ips.push(IpAddr::V4(core::net::Ipv4Addr::LOCALHOST));
}
#[cfg(feature = "std")]
use local_ip_address::list_afinet_netifas;
#[cfg(feature = "std")]
if let Ok(interfaces) = list_afinet_netifas() {
for (name, addr) in interfaces {
if let IpAddr::V4(ip) = addr
&& !ip.is_loopback()
&& !ips.contains(&IpAddr::V4(ip))
{
if name.starts_with("utun")
|| name.starts_with("gif")
|| name.starts_with("stf")
|| name.starts_with("awdl")
|| name.starts_with("llw")
{
continue;
}
ips.push(IpAddr::V4(ip));
}
}
}
ips
};
let use_shared_listener = start == end;
for ip in bind_ips {
if use_shared_listener {
let addr = SocketAddr::new(ip, start);
let inner = self
.transport_inner
.lock()
.as_ref()
.and_then(|weak| weak.upgrade())
.ok_or_else(|| {
RtcError::Internal("ICE transport unavailable during TCP gather".into())
})?;
let ufrag = inner.local_parameters.lock().username_fragment.clone();
match shared_tcp::acquire(addr, ufrag, Arc::downgrade(&inner)).await {
Ok((local_addr, registration)) => {
self.shared_tcp_regs.lock().push(registration);
self.push_tcp_passive_candidate(local_addr, ip);
break;
}
Err(e) => {
debug!("shared TCP listener acquire on {} failed: {}", addr, e);
}
}
continue;
}
for port in start..=end {
let addr = SocketAddr::new(ip, port);
match TcpListener::bind(addr).await {
Ok(listener) => {
let local_addr = match listener.local_addr() {
Ok(a) => a,
Err(_) => continue,
};
let listener = Arc::new(listener);
self.tcp_listeners.lock().push(listener.clone());
#[cfg(feature = "std")]
let _ = self.socket_tx.send(IceSocketWrapper::TcpListener(listener));
self.push_tcp_passive_candidate(local_addr, ip);
break;
}
Err(_) => continue,
}
}
}
Ok(())
}
async fn gather_upnp_candidates(&self, stun_public_ip: Option<IpAddr>) -> RtcResult<()> {
let sockets = self.sockets.lock().clone();
let timeout = self.config.upnp_discovery_timeout;
let mut tasks = FuturesUnordered::new();
for socket in sockets {
let local_addr = match socket.local_addr() {
Ok(addr) => addr,
Err(_) => continue,
};
if local_addr.ip().is_loopback() {
continue;
}
if local_addr.is_ipv6() {
continue;
}
let this = self.clone();
let stun_ip = stun_public_ip;
tasks.push(async move {
let mut mapper = UpnpPortMapper::with_lease_duration(
local_addr,
this.config.upnp_lease_duration,
);
if let Err(e) = mapper.discover_with_timeout(timeout).await {
trace!("UPnP discovery failed for {}: {}", local_addr, e);
return;
}
match mapper.add_mapping(0).await {
Ok(external_addr) => {
let is_private = is_private_ip(&external_addr.ip());
let candidate_addr = if is_private {
if let Some(public_ip) = stun_ip {
let mut addr = external_addr;
addr.set_ip(public_ip);
debug!(
"UPnP double-NAT detected: {} is private, using STUN public IP {} -> {}",
external_addr.ip(),
public_ip,
addr
);
addr
} else {
debug!(
"UPnP returned private IP {} but no STUN public IP available",
external_addr.ip()
);
external_addr
}
} else {
external_addr
};
let candidate =
IceCandidate::server_reflexive(local_addr, candidate_addr, 1);
this.push_candidate(candidate);
this.upnp_mappers.lock().push(mapper);
debug!(
"UPnP candidate gathered: {} -> {}",
local_addr, candidate_addr
);
}
Err(e) => {
debug!("UPnP mapping failed for {}: {}", local_addr, e);
}
}
});
}
while tasks.next().await.is_some() {}
Ok(())
}
async fn gather_servers(&self) -> RtcResult<()> {
let mut tasks = FuturesUnordered::new();
for server in &self.config.ice_servers {
for url in &server.urls {
let server = server.clone();
let url = url.clone();
let this = self.clone();
tasks.push(async move {
let uri = match IceServerUri::parse(&url) {
Ok(uri) => uri,
Err(err) => {
debug!("invalid ICE server URI {}: {}", url, err);
return;
}
};
match uri.kind {
IceUriKind::Stun => {
if this.config.ice_transport_policy == IceTransportPolicy::All {
match this.probe_stun(&uri).await {
Ok(Some(candidate)) => this.push_candidate(candidate),
Ok(None) => {}
Err(e) => debug!(
label = this.config.label.as_deref().unwrap_or("-"),
"STUN probe failed for {}: {}", url, e
),
}
}
}
IceUriKind::Turn => match this.probe_turn(&uri, &server).await {
Ok(Some(candidate)) => this.push_candidate(candidate),
Ok(None) => {}
Err(e) => debug!(
label = this.config.label.as_deref().unwrap_or("-"),
"TURN probe failed for {}: {}", url, e
),
},
}
});
}
}
while tasks.next().await.is_some() {}
Ok(())
}
async fn gather_servers_and_get_public_ip(&self) -> Option<IpAddr> {
let mut tasks = FuturesUnordered::new();
let public_ip: Arc<crate::platform::sync::Mutex<Option<IpAddr>>> =
Arc::new(crate::platform::sync::Mutex::new(None));
for server in &self.config.ice_servers {
for url in &server.urls {
let server = server.clone();
let url = url.clone();
let this = self.clone();
let public_ip_clone = public_ip.clone();
tasks.push(async move {
let uri = match IceServerUri::parse(&url) {
Ok(uri) => uri,
Err(err) => {
debug!("invalid ICE server URI {}: {}", url, err);
return;
}
};
match uri.kind {
IceUriKind::Stun => {
if this.config.ice_transport_policy == IceTransportPolicy::All {
match this.probe_stun(&uri).await {
Ok(Some(candidate)) => {
if !is_private_ip(&candidate.address.ip()) {
let mut ip = public_ip_clone.lock();
if ip.is_none() {
*ip = Some(candidate.address.ip());
}
}
this.push_candidate(candidate);
}
Ok(None) => {}
Err(e) => debug!(
label = this.config.label.as_deref().unwrap_or("-"),
"STUN probe failed for {}: {}", url, e
),
}
}
}
IceUriKind::Turn => match this.probe_turn(&uri, &server).await {
Ok(Some(candidate)) => this.push_candidate(candidate),
Ok(None) => {}
Err(e) => debug!(
label = this.config.label.as_deref().unwrap_or("-"),
"TURN probe failed for {}: {}", url, e
),
},
}
});
}
}
while tasks.next().await.is_some() {}
let ip = *public_ip.lock();
if let Some(ip) = &ip {
debug!("STUN public IP for UPnP double-NAT detection: {}", ip);
} else {
debug!("No STUN public IP available for UPnP double-NAT detection");
}
ip
}
async fn probe_stun(&self, uri: &IceServerUri) -> RtcResult<Option<IceCandidate>> {
let addr = uri.resolve(self.config.disable_ipv6).await?;
let pick_bind_ip = |c: &IceCandidate| -> Option<IpAddr> {
let base = c.related_address.unwrap_or(c.address);
match base.ip() {
IpAddr::V4(ip) if !addr.is_ipv6() && !ip.is_loopback() && !ip.is_unspecified() => {
Some(IpAddr::V4(ip))
}
IpAddr::V6(ip) if addr.is_ipv6() && !ip.is_loopback() && !ip.is_unspecified() => {
Some(IpAddr::V6(ip))
}
_ => None,
}
};
let fallback = if addr.is_ipv6() {
IpAddr::V6(core::net::Ipv6Addr::UNSPECIFIED)
} else {
IpAddr::V4(core::net::Ipv4Addr::new(0, 0, 0, 0))
};
let bind_ip = self
.local_candidates
.lock()
.iter()
.filter(|c| c.typ == IceCandidateType::Host)
.filter_map(pick_bind_ip)
.next()
.unwrap_or(fallback);
let socket = match uri.transport {
IceTransportProtocol::Udp => self.bind_socket(bind_ip).await?,
IceTransportProtocol::Tcp => self.bind_socket(bind_ip).await?,
};
let local_addr = socket.local_addr()?;
let tx_id = random_bytes::<12>();
let message = StunMessage::binding_request(tx_id, Some("rustrtc"));
let bytes = message.encode(None, true)?;
socket.send_to(&bytes, addr).await?;
let mut buf = [0u8; MAX_STUN_MESSAGE];
let (len, from) = with_timeout(self.config.stun_timeout, socket.recv_from(&mut buf))
.await
.map_err(|_| RtcError::Internal("stun recv timeout".into()))??;
if from.ip() != addr.ip() {
return Ok(None);
}
let parsed = StunMessage::decode(&buf[..len])?;
if let Some(mapped) = parsed.xor_mapped_address {
self.register_bound(&socket);
return Ok(Some(IceCandidate::server_reflexive(local_addr, mapped, 1)));
}
Ok(None)
}
async fn probe_turn(
&self,
uri: &IceServerUri,
server: &IceServer,
) -> RtcResult<Option<IceCandidate>> {
let credentials = TurnCredentials::from_server(server)?;
let client = TurnClient::connect(uri, self.config.disable_ipv6).await?;
let allocation = client.allocate(credentials).await?;
let relayed_addr = allocation.relayed_address;
debug!(
"TURN allocation granted: relayed={}, lifetime={}s",
relayed_addr, allocation.lifetime_secs
);
let client = Arc::new(client);
self.turn_clients
.lock()
.insert(relayed_addr, client.clone());
let _ = self
.socket_tx
.send(IceSocketWrapper::Turn(client, relayed_addr));
Ok(Some(IceCandidate::relay(
relayed_addr,
1,
allocation.transport.as_str(),
)))
}
fn push_candidate(&self, mut candidate: IceCandidate) {
if self.config.disable_ipv6 && candidate.address.is_ipv6() {
return;
}
if candidate.typ == IceCandidateType::Host
&& let Some(hostname) = self
.transport_inner
.lock()
.as_ref()
.and_then(|weak| weak.upgrade())
.and_then(|inner| inner.mdns_hostname.clone())
{
candidate = candidate.with_hostname(hostname);
}
let mut candidates = self.local_candidates.lock();
if candidates.iter().any(|c| c.address == candidate.address) {
return;
}
tracing::debug!(
"Gathered local candidate: {} type={:?}",
candidate.address,
candidate.typ
);
candidates.push(candidate.clone());
drop(candidates);
let _ = self.candidate_tx.send(candidate);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct IceServerUri {
kind: IceUriKind,
host: String,
port: u16,
transport: IceTransportProtocol,
}
impl IceServerUri {
fn parse(input: &str) -> RtcResult<Self> {
let (scheme, rest) = input
.split_once(':')
.ok_or_else(|| RtcError::Internal(format!("missing scheme")))?;
let (host_part, query) = match rest.split_once('?') {
Some(parts) => parts,
None => (rest, ""),
};
let (host, port) = if let Some((h, p)) = host_part.rsplit_once(':') {
let port = p
.parse::<u16>()
.map_err(|e| RtcError::Internal(format!("invalid port: {e}")))?;
(h.to_string(), port)
} else {
(host_part.to_string(), default_port_for_scheme(scheme)?)
};
let mut transport = default_transport_for_scheme(scheme)?;
if !query.is_empty() {
for pair in query.split('&') {
if let Some((k, v)) = pair.split_once('=')
&& k == "transport"
{
transport = match v.to_ascii_lowercase().as_str() {
"udp" => IceTransportProtocol::Udp,
"tcp" => IceTransportProtocol::Tcp,
other => {
return Err(RtcError::Internal(format!(
"unsupported transport {}",
other
)));
}
};
}
}
}
if scheme.starts_with("stun") && query.contains("transport") {
return Err(RtcError::Internal(format!(
"stun URI must not include transport parameter"
)));
}
let kind = match scheme {
"stun" | "stuns" => IceUriKind::Stun,
"turn" | "turns" => IceUriKind::Turn,
other => return Err(RtcError::Internal(format!("unsupported scheme {}", other))),
};
Ok(Self {
kind,
host,
port,
transport,
})
}
async fn resolve(&self, disable_ipv6: bool) -> RtcResult<SocketAddr> {
let target = format!("{}:{}", self.host, self.port);
let addrs = with_timeout(
Duration::from_secs(5),
crate::platform::dns::lookup_host(&target),
)
.await
.map_err(|_| RtcError::Internal(format!("DNS lookup timed out for {}", target)))??;
for addr in addrs {
if disable_ipv6 && addr.is_ipv6() {
continue;
}
return Ok(addr);
}
Err(RtcError::Internal(format!(
"{} unresolved (disable_ipv6={})",
self.host, disable_ipv6
)))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum IceUriKind {
Stun,
Turn,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IceTransportProtocol {
Udp,
Tcp,
}
impl IceTransportProtocol {
fn as_str(&self) -> &'static str {
match self {
IceTransportProtocol::Udp => "udp",
IceTransportProtocol::Tcp => "tcp",
}
}
}
fn default_port_for_scheme(scheme: &str) -> RtcResult<u16> {
Ok(match scheme {
"stun" | "turn" => 3478,
"stuns" | "turns" => 5349,
other => return Err(RtcError::Internal(format!("unsupported scheme {}", other))),
})
}
fn default_transport_for_scheme(scheme: &str) -> RtcResult<IceTransportProtocol> {
Ok(match scheme {
"stun" | "turn" => IceTransportProtocol::Udp,
"stuns" | "turns" => IceTransportProtocol::Tcp,
other => return Err(RtcError::Internal(format!("unsupported scheme {}", other))),
})
}
fn is_private_ip(ip: &IpAddr) -> bool {
match ip {
IpAddr::V4(ipv4) => {
let octets = ipv4.octets();
octets[0] == 10
|| (octets[0] == 172 && (16..=31).contains(&octets[1]))
|| (octets[0] == 192 && octets[1] == 168)
|| (octets[0] == 169 && octets[1] == 254)
|| octets[0] == 127
}
IpAddr::V6(ipv6) => {
ipv6.segments()[0] & 0xfe00 == 0xfc00
|| ipv6.segments()[0] & 0xffc0 == 0xfe80
|| *ipv6 == core::net::Ipv6Addr::LOCALHOST
}
}
}
fn hex_encode(bytes: &[u8]) -> String {
const TABLE: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
out.push(TABLE[(byte >> 4) as usize] as char);
out.push(TABLE[(byte & 0x0f) as usize] as char);
}
out
}
#[derive(Clone)]
pub enum IceSocketWrapper {
Platform(Arc<dyn crate::platform::net::UdpSocket>),
Udp(Arc<UdpSocket>),
SharedUdp(Arc<shared_udp::SharedUdpHandle>),
#[cfg(feature = "std")]
TcpListener(Arc<TcpListener>),
#[cfg(feature = "std")]
TcpStream(
Arc<Mutex<TcpReadHalf>>,
Arc<Mutex<TcpWriteHalf>>,
SocketAddr,
),
Turn(Arc<TurnClient>, SocketAddr),
}
impl core::fmt::Debug for IceSocketWrapper {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.diag())
}
}
impl IceSocketWrapper {
pub fn local_addr(&self) -> RtcResult<SocketAddr> {
match self {
IceSocketWrapper::Platform(s) => s.local_addr().map_err(RtcError::from),
IceSocketWrapper::Udp(s) => s.local_addr().map_err(RtcError::from),
IceSocketWrapper::SharedUdp(h) => h.local_addr(),
#[cfg(feature = "std")]
IceSocketWrapper::TcpListener(l) => l.local_addr().map_err(RtcError::from),
#[cfg(feature = "std")]
IceSocketWrapper::TcpStream(_, _, peer) => Ok(*peer),
IceSocketWrapper::Turn(_, addr) => Ok(*addr),
}
}
pub fn diag(&self) -> String {
match self {
IceSocketWrapper::Platform(s) => format!(
"platform-udp:{}",
s.local_addr()
.map(|a| a.to_string())
.unwrap_or_else(|_| "?".into())
),
IceSocketWrapper::Udp(s) => format!(
"udp:{}",
s.local_addr()
.map(|a| a.to_string())
.unwrap_or_else(|_| "?".into())
),
IceSocketWrapper::SharedUdp(h) => format!(
"udp-mux:{}",
h.local_addr()
.map(|a| a.to_string())
.unwrap_or_else(|_| "?".into())
),
#[cfg(feature = "std")]
IceSocketWrapper::TcpListener(l) => format!(
"tcp-listen:{}",
l.local_addr()
.map(|a| a.to_string())
.unwrap_or_else(|_| "?".into())
),
#[cfg(feature = "std")]
IceSocketWrapper::TcpStream(_, _, peer) => format!("tcp-stream:peer={peer}"),
IceSocketWrapper::Turn(_, addr) => format!("turn:{addr}"),
}
}
pub fn try_send_to(&self, data: &[u8], addr: SocketAddr) -> RtcResult<usize> {
match self {
IceSocketWrapper::Platform(s) => {
match <dyn crate::platform::net::UdpSocket>::try_send_to(
s.as_ref(),
data,
addr,
) {
Ok(len) => Ok(len),
Err(e) => Err(RtcError::Internal(alloc::format!(
"platform try_send_to: {e}"
))),
}
}
IceSocketWrapper::Udp(s) => match s.try_send_to(data, addr) {
Ok(len) => Ok(len),
Err(e) => {
let reason = match s.local_addr() {
Ok(local) => format!("UDP {} -> {} failed: {}", local, addr, e),
Err(_) => format!("UDP -> {} failed: {}", addr, e),
};
Err(RtcError::Internal(reason))
}
},
#[cfg(not(feature = "std"))]
IceSocketWrapper::Udp(_) => {
unimplemented!("direct UDP sockets are std-only")
}
IceSocketWrapper::SharedUdp(h) => {
h.register_peer(addr);
match <dyn crate::platform::net::UdpSocket>::try_send_to(
h.socket().as_ref(),
data,
addr,
) {
Ok(len) => Ok(len),
Err(e) => {
let reason = match h.local_addr() {
Ok(local) => format!("shared UDP {} -> {} failed: {}", local, addr, e),
Err(_) => format!("shared UDP -> {} failed: {}", addr, e),
};
Err(RtcError::Internal(reason))
}
}
}
_ => Err(RtcError::Internal(format!(
"IceSocketWrapper::try_send_to not supported for this transport variant"
))),
}
}
pub async fn send_to(&self, data: &[u8], addr: SocketAddr) -> RtcResult<usize> {
match self {
IceSocketWrapper::Platform(s) => s
.send_to(data, addr)
.await
.map_err(|e| RtcError::Internal(format!("platform send_to: {e}"))),
#[cfg(feature = "std")]
IceSocketWrapper::Udp(s) => loop {
match s.try_send_to(data, addr) {
Ok(len) => return Ok(len),
Err(e) if e.kind() == ErrorKind::WouldBlock => {
s.writable().await?;
continue;
}
Err(e) => {
if let Some(code) = e.raw_os_error()
&& code == 55
{
s.writable().await?;
continue;
}
let reason = RtcError::Internal(format!(
"UDP {} -> {} failed: {}",
s.local_addr()?,
addr,
e
));
return Err(reason);
}
}
},
#[cfg(not(feature = "std"))]
IceSocketWrapper::Udp(_) => {
unimplemented!("direct UDP sockets are std-only")
}
IceSocketWrapper::SharedUdp(h) => {
let dest = addr;
h.send_to(data, dest).await.map_err(RtcError::from)
}
#[cfg(feature = "std")]
IceSocketWrapper::TcpListener(_) => {
return Err(RtcError::Internal(format!(
"send_to not supported on TcpListener"
)));
}
#[cfg(feature = "std")]
IceSocketWrapper::TcpStream(_, write, _) => {
let len = data.len();
if len > 0xFFFF {
return Err(RtcError::Internal(format!(
"STUN message too large for TCP framing"
)));
}
let header = (len as u16).to_be_bytes();
let mut framed = Vec::with_capacity(2 + len);
framed.extend_from_slice(&header);
framed.extend_from_slice(data);
tcp_write_all(write, &framed).await?;
Ok(data.len())
}
IceSocketWrapper::Turn(c, _) => {
if let Some(channel) = c.get_channel(addr).await {
c.send_channel_data(channel, data).await?;
} else {
c.send_indication(addr, data).await?;
}
Ok(data.len())
}
}
}
pub async fn recv_from(&self, buf: &mut [u8]) -> RtcResult<(usize, SocketAddr)> {
match self {
IceSocketWrapper::Platform(s) => s.recv_from(buf).await.map_err(|e| e.into()),
IceSocketWrapper::Udp(s) => s.recv_from(buf).await.map_err(|e| e.into()),
IceSocketWrapper::SharedUdp(h) => match h.recv().await {
Some((data, addr)) => {
if data.len() > buf.len() {
return Err(RtcError::Internal(format!(
"shared UDP packet too large: {} > {}",
data.len(),
buf.len()
)));
}
let len = data.len();
buf[..len].copy_from_slice(&data);
Ok((len, addr))
}
None => Err(RtcError::Internal(format!("shared UDP channel closed"))),
},
#[cfg(feature = "std")]
IceSocketWrapper::TcpStream(read, _, peer) => {
#[cfg(feature = "std")]
use tokio::io::AsyncReadExt;
let mut stream = read.lock().await;
let mut len_buf = [0u8; 2];
stream.read_exact(&mut len_buf).await?;
let len = u16::from_be_bytes(len_buf) as usize;
if len > buf.len() {
return Err(RtcError::Internal(format!(
"TCP STUN message too large: {} > {}",
len,
buf.len()
)));
}
stream.read_exact(&mut buf[..len]).await?;
Ok((len, *peer))
}
#[cfg(feature = "std")]
IceSocketWrapper::TcpListener(_) => Err(RtcError::Internal(format!(
"recv_from not supported on TcpListener wrapper directly"
))),
IceSocketWrapper::Turn(_, _) => Err(RtcError::Internal(format!(
"recv_from not supported on TURN wrapper directly"
))),
}
}
}
#[cfg(all(test, feature = "std"))]
mod bind_socket_retry_tests {
use super::*;
fn gatherer(start: u16, end: u16) -> IceGatherer {
let (candidate_tx, _) = broadcast::channel(1);
let (socket_tx, _socket_rx) = crate::platform::sync::mpsc::unbounded_channel();
let mut config = RtcConfiguration::default();
config.rtp_start_port = Some(start);
config.rtp_end_port = Some(end);
IceGatherer::new(config, candidate_tx, socket_tx)
}
async fn occupy_even_port() -> (tokio::net::UdpSocket, u16) {
loop {
let s = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap();
let p = s.local_addr().unwrap().port();
if p % 2 == 0 {
return (s, p);
}
}
}
#[tokio::test]
async fn skips_a_busy_port_and_binds_the_next_one() {
let (_blocker, busy) = occupy_even_port().await;
let other = busy + 2;
let g = gatherer(busy, other);
let socket = g
.bind_socket(IpAddr::from([127, 0, 0, 1]))
.await
.expect("bind_socket must skip the busy port");
assert_eq!(socket.local_addr().unwrap().port(), other);
}
#[tokio::test]
async fn exhausts_the_range_with_the_port_exhaustion_error() {
let (_blocker, busy) = occupy_even_port().await;
let g = gatherer(busy, busy);
let err = g
.bind_socket(IpAddr::from([127, 0, 0, 1]))
.await
.expect_err("a fully busy range must fail");
assert!(
err.to_string().contains("No available even RTP ports"),
"expected port exhaustion, got: {err}"
);
}
}