Skip to main content

rtc_ice/candidate/
mod.rs

1//! ICE candidates: the addresses an agent can be reached at.
2//!
3//! A [`Candidate`](crate::candidate::Candidate) pairs a transport address with a [`CandidateType`](crate::candidate::CandidateType) — host, server-reflexive,
4//! peer-reflexive or relay — and the bookkeeping ICE needs: a priority, a foundation, and
5//! last-sent/last-received times that feed consent freshness.
6//!
7//! Type drives priority, and priority drives check order: host candidates are tried first
8//! because they need no traversal, relay candidates last because they always cost an extra hop.
9//! The [`foundation`](crate::candidate::Candidate::foundation) groups candidates that share a base and transport,
10//! so redundant checks can be skipped.
11//!
12//! Each type has its own constructor module (`candidate_host`, `candidate_relay`, …), all
13//! built on the shared [`CandidateConfig`](crate::candidate::CandidateConfig).
14
15#[cfg(test)]
16mod candidate_pair_test;
17#[cfg(test)]
18mod candidate_test;
19
20//TODO: #[cfg(test)]
21//TODO: mod candidate_relay_test;
22/*TODO: #[cfg(test)]
23TODO: mod candidate_server_reflexive_test;
24*/
25
26/// Host candidates: an address on a local interface.
27pub mod candidate_host;
28/// A local/remote candidate pair and its check state.
29pub mod candidate_pair;
30/// Peer-reflexive candidates, learned from an inbound check's source address.
31pub mod candidate_peer_reflexive;
32/// Relay candidates, allocated on a TURN server.
33pub mod candidate_relay;
34/// Server-reflexive candidates, learned from a STUN Binding response.
35pub mod candidate_server_reflexive;
36
37use crate::network_type::NetworkType;
38use crate::tcp_type::TcpType;
39use crc::{CRC_32_ISCSI, Crc};
40use serde::{Deserialize, Serialize};
41use shared::error::*;
42use std::fmt;
43use std::net::{IpAddr, SocketAddr};
44use std::time::Instant;
45
46use crate::candidate::candidate_host::CandidateHostConfig;
47use crate::candidate::candidate_peer_reflexive::CandidatePeerReflexiveConfig;
48use crate::candidate::candidate_relay::CandidateRelayConfig;
49use crate::candidate::candidate_server_reflexive::CandidateServerReflexiveConfig;
50use crate::network_type::determine_network_type;
51
52pub(crate) const RECEIVE_MTU: usize = 8192;
53pub(crate) const DEFAULT_LOCAL_PREFERENCE: u16 = 65535;
54
55/// Indicates that the candidate is used for RTP.
56pub(crate) const COMPONENT_RTP: u16 = 1;
57/// Indicates that the candidate is used for RTCP.
58pub(crate) const COMPONENT_RTCP: u16 = 0;
59
60/// Represents the type of candidate `CandidateType` enum.
61#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
62#[non_exhaustive]
63pub enum CandidateType {
64    #[default]
65    #[serde(rename = "unspecified")]
66    /// No candidate type was set.
67    Unspecified,
68    #[serde(rename = "host")]
69    /// An address on one of this host's own interfaces.
70    ///
71    /// Highest priority: reachable without traversal when both peers share a network.
72    Host,
73    #[serde(rename = "srflx")]
74    /// This host's address as seen by a STUN server — its public mapping through the NAT.
75    ServerReflexive,
76    #[serde(rename = "prflx")]
77    /// An address learned from a peer's inbound connectivity check.
78    ///
79    /// Discovered during checking rather than gathering, when a NAT maps a different port per
80    /// destination.
81    PeerReflexive,
82    #[serde(rename = "relay")]
83    /// An address allocated on a TURN server, which forwards on this host's behalf.
84    ///
85    /// Lowest priority: it always costs an extra hop.
86    Relay,
87}
88
89// String makes CandidateType printable
90impl fmt::Display for CandidateType {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        let s = match *self {
93            CandidateType::Host => "host",
94            CandidateType::ServerReflexive => "srflx",
95            CandidateType::PeerReflexive => "prflx",
96            CandidateType::Relay => "relay",
97            CandidateType::Unspecified => "Unknown candidate type",
98        };
99        write!(f, "{s}")
100    }
101}
102
103impl CandidateType {
104    /// Returns the preference weight of a `CandidateType`.
105    ///
106    /// 4.1.2.2.  Guidelines for Choosing Type and Local Preferences
107    /// The RECOMMENDED values are 126 for host candidates, 100
108    /// for server reflexive candidates, 110 for peer reflexive candidates,
109    /// and 0 for relayed candidates.
110    #[must_use]
111    pub const fn preference(self) -> u16 {
112        match self {
113            Self::Host => 126,
114            Self::PeerReflexive => 110,
115            Self::ServerReflexive => 100,
116            Self::Relay | CandidateType::Unspecified => 0,
117        }
118    }
119}
120
121pub(crate) fn contains_candidate_type(
122    candidate_type: CandidateType,
123    candidate_type_list: &[CandidateType],
124) -> bool {
125    if candidate_type_list.is_empty() {
126        return false;
127    }
128    for ct in candidate_type_list {
129        if *ct == candidate_type {
130            return true;
131        }
132    }
133    false
134}
135
136/// Convey transport addresses related to the candidate, useful for diagnostics and other purposes.
137#[derive(PartialEq, Eq, Debug, Clone)]
138pub struct CandidateRelatedAddress {
139    /// The address of the related candidate — the base this one was derived from.
140    /// The candidate's address.
141    pub address: String,
142    /// The port of the related candidate.
143    /// The candidate's port.
144    pub port: u16,
145}
146
147// String makes CandidateRelatedAddress printable
148impl fmt::Display for CandidateRelatedAddress {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        write!(f, " related {}:{}", self.address, self.port)
151    }
152}
153
154#[derive(Default)]
155/// The fields common to every candidate type, used when constructing one.
156pub struct CandidateConfig {
157    /// A unique identifier for this candidate; generated when left empty.
158    pub candidate_id: String,
159    /// The transport, `udp` or `tcp`.
160    pub network: String,
161    /// The candidate's address.
162    pub address: String,
163    /// The candidate's port.
164    pub port: u16,
165    /// The RTP component id: `1` for RTP, `2` for RTCP when not multiplexed.
166    pub component: u16,
167    /// The candidate priority; computed from the type and local preference when zero.
168    pub priority: u32,
169    /// The foundation, which groups candidates that share a base and transport.
170    ///
171    /// Pairs with the same foundation are checked together, so redundant checks are avoided.
172    pub foundation: String,
173}
174
175#[derive(Clone, Debug)]
176/// One ICE candidate: a transport address this agent can be reached at, or can reach a peer
177/// at, together with its type, priority and liveness bookkeeping.
178pub struct Candidate {
179    pub(crate) id: String,
180    pub(crate) network_type: NetworkType,
181    pub(crate) candidate_type: CandidateType,
182
183    pub(crate) component: u16,
184    pub(crate) address: String,
185    pub(crate) port: u16,
186    pub(crate) related_address: Option<CandidateRelatedAddress>,
187    pub(crate) tcp_type: TcpType,
188
189    pub(crate) resolved_addr: SocketAddr,
190
191    /// When traffic was last sent on this candidate, or `None` if none ever has been.
192    pub(crate) last_sent: Option<Instant>,
193    /// When traffic was last received on this candidate, or `None` if none ever has been.
194    pub(crate) last_received: Option<Instant>,
195
196    pub(crate) foundation_override: String,
197    pub(crate) priority_override: u32,
198
199    pub(crate) network: String,
200
201    pub(crate) url: Option<String>,
202}
203
204impl Default for Candidate {
205    fn default() -> Self {
206        Self {
207            id: String::new(),
208            network_type: NetworkType::Unspecified,
209            candidate_type: CandidateType::default(),
210
211            component: 0,
212            address: String::new(),
213            port: 0,
214            related_address: None,
215            tcp_type: TcpType::default(),
216
217            resolved_addr: SocketAddr::new(IpAddr::from([0, 0, 0, 0]), 0),
218
219            last_sent: None,
220            last_received: None,
221
222            foundation_override: String::new(),
223            priority_override: 0,
224            network: String::new(),
225
226            url: None,
227        }
228    }
229}
230
231// String makes the candidateBase printable
232impl fmt::Display for Candidate {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        if let Some(related_address) = self.related_address() {
235            write!(
236                f,
237                "{} {} {}:{}{}",
238                self.network_type(),
239                self.candidate_type(),
240                self.address(),
241                self.port(),
242                related_address,
243            )
244        } else {
245            write!(
246                f,
247                "{} {} {}:{}",
248                self.network_type(),
249                self.candidate_type(),
250                self.address(),
251                self.port(),
252            )
253        }
254    }
255}
256
257impl Candidate {
258    /// The candidate's foundation, computed from its type, base address and transport.
259    ///
260    /// A foundation groups equivalent candidates; it is deterministic bookkeeping rather than a
261    /// secret, random identifier. CRC-32C is therefore intentional here.
262    pub fn foundation(&self) -> String {
263        if !self.foundation_override.is_empty() {
264            return self.foundation_override.clone();
265        }
266
267        let mut buf = vec![];
268        buf.extend_from_slice(self.candidate_type().to_string().as_bytes());
269        buf.extend_from_slice(self.address.as_bytes());
270        buf.extend_from_slice(self.network_type().to_string().as_bytes());
271
272        let checksum = Crc::<u32>::new(&CRC_32_ISCSI).checksum(&buf);
273
274        format!("{checksum}")
275    }
276
277    /// Returns Candidate ID.
278    pub fn id(&self) -> &str {
279        self.id.as_str()
280    }
281
282    /// Returns candidate component.
283    pub fn component(&self) -> u16 {
284        self.component
285    }
286
287    /// Sets candidate component.
288    pub fn set_component(&mut self, component: u16) {
289        self.component = component;
290    }
291
292    /// When traffic was last received on this candidate.
293    ///
294    /// `None` until the first packet arrives. A freshly constructed or freshly parsed candidate
295    /// has not received anything, and previously said it had — it was seeded with the
296    /// construction instant, which also meant every candidate constructor needed a clock.
297    pub fn last_received(&self) -> Option<Instant> {
298        self.last_received
299    }
300
301    /// When traffic was last sent on this candidate, or `None` if none ever has been.
302    pub fn last_sent(&self) -> Option<Instant> {
303        self.last_sent
304    }
305
306    /// Returns candidate NetworkType.
307    pub fn network_type(&self) -> NetworkType {
308        self.network_type
309    }
310
311    /// Returns Candidate Address.
312    pub fn address(&self) -> &str {
313        self.address.as_str()
314    }
315
316    /// Returns Candidate Port.
317    pub fn port(&self) -> u16 {
318        self.port
319    }
320
321    /// Computes the priority for this ICE Candidate.
322    pub fn priority(&self) -> u32 {
323        if self.priority_override != 0 {
324            return self.priority_override;
325        }
326
327        // The local preference MUST be an integer from 0 (lowest preference) to
328        // 65535 (highest preference) inclusive.  When there is only a single IP
329        // address, this value SHOULD be set to 65535.  If there are multiple
330        // candidates for a particular component for a particular data stream
331        // that have the same type, the local preference MUST be unique for each
332        // one.
333        (1 << 24) * u32::from(self.candidate_type().preference())
334            + (1 << 8) * u32::from(self.local_preference())
335            + (256 - u32::from(self.component()))
336    }
337
338    /// Returns `Option<CandidateRelatedAddress>`.
339    pub fn related_address(&self) -> Option<CandidateRelatedAddress> {
340        self.related_address.as_ref().cloned()
341    }
342
343    /// Returns candidate type.
344    pub fn candidate_type(&self) -> CandidateType {
345        self.candidate_type
346    }
347
348    /// The TCP role for ICE-TCP candidates; `Unspecified` for UDP.
349    pub fn tcp_type(&self) -> TcpType {
350        self.tcp_type
351    }
352
353    /// The STUN or TURN server this candidate was gathered from, if any.
354    pub fn url(&self) -> Option<&str> {
355        self.url.as_deref()
356    }
357
358    /// Returns the string representation of the ICECandidate.
359    pub fn marshal(&self) -> String {
360        let mut val = format!(
361            "{} {} {} {} {} {} typ {}",
362            self.foundation(),
363            self.component(),
364            self.network_type().network_short(),
365            self.priority(),
366            self.address(),
367            self.port(),
368            self.candidate_type()
369        );
370
371        if self.tcp_type != TcpType::Unspecified {
372            val += format!(" tcptype {}", self.tcp_type()).as_str();
373        }
374
375        if let Some(related_address) = self.related_address() {
376            val += format!(
377                " raddr {} rport {}",
378                related_address.address, related_address.port,
379            )
380            .as_str();
381        }
382
383        val
384    }
385
386    /// The candidate's socket address.
387    pub fn addr(&self) -> SocketAddr {
388        self.resolved_addr
389    }
390
391    /// Returns the candidate's base address: the local transport address the
392    /// candidate was derived from, i.e. the address packets for this candidate
393    /// must be sent from (RFC 8445 §5.1.1).
394    ///
395    /// For server-reflexive and peer-reflexive candidates this is the related
396    /// (host) address; for host and relay candidates the base is the candidate
397    /// address itself.
398    pub fn base_addr(&self) -> SocketAddr {
399        match self.candidate_type {
400            CandidateType::ServerReflexive | CandidateType::PeerReflexive => self
401                .related_address
402                .as_ref()
403                .and_then(|ra| {
404                    ra.address
405                        .parse::<IpAddr>()
406                        .ok()
407                        .map(|ip| SocketAddr::new(ip, ra.port))
408                })
409                .unwrap_or(self.resolved_addr),
410            _ => self.resolved_addr,
411        }
412    }
413
414    /// Records traffic on this candidate, updating its last-sent or last-received time.
415    ///
416    /// Feeds consent freshness — a pair that stops seeing traffic is eventually abandoned.
417    pub fn seen(&mut self, now: Instant, outbound: bool) {
418        if outbound {
419            self.set_last_sent(now);
420        } else {
421            self.set_last_received(now);
422        }
423    }
424
425    /// Used to compare two candidateBases.
426    pub fn equal(&self, other: &Candidate) -> bool {
427        self.network_type() == other.network_type()
428            && self.candidate_type() == other.candidate_type()
429            && self.address() == other.address()
430            && self.port() == other.port()
431            && self.tcp_type() == other.tcp_type()
432            && self.related_address() == other.related_address()
433    }
434
435    /// Returns true if this candidate can be paired with `other` according to
436    /// ICE candidate pairing rules.
437    ///
438    /// Candidates must share the same network type (protocol and address family)
439    /// and have compatible TCP types (RFC 6544). UDP candidates use
440    /// `TcpType::Unspecified`.
441    pub(crate) fn can_pair_with(&self, other: &Candidate) -> bool {
442        if self.network_type() != other.network_type() {
443            return false;
444        }
445
446        match (self.tcp_type(), other.tcp_type()) {
447            (TcpType::Active, TcpType::Passive) => true,
448            (TcpType::Passive, TcpType::Active) => true,
449            (TcpType::SimultaneousOpen, TcpType::SimultaneousOpen) => true,
450            (TcpType::Unspecified, TcpType::Unspecified) => true, // UDP candidates
451            _ => false,
452        }
453    }
454
455    /// Sets the resolved IP, deriving the network type from it.
456    ///
457    /// # Errors
458    ///
459    /// Fails if `ip`'s family does not match this candidate's network type.
460    pub fn set_ip(&mut self, ip: &IpAddr) -> Result<()> {
461        self.network_type = determine_network_type(&self.network, ip)?;
462        self.resolved_addr = SocketAddr::new(*ip, self.port); //TODO:  create_addr(network_type, *ip, self.port);
463        Ok(())
464    }
465}
466
467impl Candidate {
468    /// Records that traffic was received on this candidate at `now`.
469    pub fn set_last_received(&mut self, now: Instant) {
470        self.last_received = Some(now);
471    }
472
473    /// Records that traffic was sent on this candidate at `now`.
474    pub fn set_last_sent(&mut self, now: Instant) {
475        self.last_sent = Some(now);
476    }
477
478    /// Returns the local preference for this candidate.
479    pub fn local_preference(&self) -> u16 {
480        if self.network_type().is_tcp() {
481            // RFC 6544, section 4.2
482            //
483            // In Section 4.1.2.1 of [RFC5245], a recommended formula for UDP ICE
484            // candidate prioritization is defined.  For TCP candidates, the same
485            // formula and candidate type preferences SHOULD be used, and the
486            // RECOMMENDED type preferences for the new candidate types defined in
487            // this document (see Section 5) are 105 for NAT-assisted candidates and
488            // 75 for UDP-tunneled candidates.
489            //
490            // (...)
491            //
492            // With TCP candidates, the local preference part of the recommended
493            // priority formula is updated to also include the directionality
494            // (active, passive, or simultaneous-open) of the TCP connection.  The
495            // RECOMMENDED local preference is then defined as:
496            //
497            //     local preference = (2^13) * direction-pref + other-pref
498            //
499            // The direction-pref MUST be between 0 and 7 (both inclusive), with 7
500            // being the most preferred.  The other-pref MUST be between 0 and 8191
501            // (both inclusive), with 8191 being the most preferred.  It is
502            // RECOMMENDED that the host, UDP-tunneled, and relayed TCP candidates
503            // have the direction-pref assigned as follows: 6 for active, 4 for
504            // passive, and 2 for S-O.  For the NAT-assisted and server reflexive
505            // candidates, the RECOMMENDED values are: 6 for S-O, 4 for active, and
506            // 2 for passive.
507            //
508            // (...)
509            //
510            // If any two candidates have the same type-preference and direction-
511            // pref, they MUST have a unique other-pref.  With this specification,
512            // this usually only happens with multi-homed hosts, in which case
513            // other-pref is the preference for the particular IP address from which
514            // the candidate was obtained.  When there is only a single IP address,
515            // this value SHOULD be set to the maximum allowed value (8191).
516            let other_pref: u16 = 8191;
517
518            let direction_pref: u16 = match self.candidate_type() {
519                CandidateType::Host | CandidateType::Relay => match self.tcp_type() {
520                    TcpType::Active => 6,
521                    TcpType::Passive => 4,
522                    TcpType::SimultaneousOpen => 2,
523                    TcpType::Unspecified => 0,
524                },
525                CandidateType::PeerReflexive | CandidateType::ServerReflexive => {
526                    match self.tcp_type() {
527                        TcpType::SimultaneousOpen => 6,
528                        TcpType::Active => 4,
529                        TcpType::Passive => 2,
530                        TcpType::Unspecified => 0,
531                    }
532                }
533                CandidateType::Unspecified => 0,
534            };
535
536            (1 << 13) * direction_pref + other_pref
537        } else {
538            DEFAULT_LOCAL_PREFERENCE
539        }
540    }
541}
542
543/// Creates a Candidate from its string representation.
544pub fn unmarshal_candidate(raw: &str) -> Result<Candidate> {
545    let split: Vec<&str> = raw.split_whitespace().collect();
546    if split.len() < 8 {
547        return Err(Error::Other(format!(
548            "{:?} ({})",
549            Error::ErrAttributeTooShortIceCandidate,
550            split.len()
551        )));
552    }
553
554    // Foundation
555    let foundation = split[0].to_owned();
556
557    // Component
558    let component: u16 = split[1].parse()?;
559
560    // Network
561    let network = split[2].to_owned();
562
563    // Priority
564    let priority: u32 = split[3].parse()?;
565
566    // Address
567    let address = split[4].to_owned();
568
569    // Port
570    let port: u16 = split[5].parse()?;
571
572    let typ = split[7];
573
574    let mut rel_addr = String::new();
575    let mut rel_port = 0;
576    let mut tcp_type = TcpType::Unspecified;
577
578    if split.len() > 8 {
579        let split2 = &split[8..];
580
581        if split2[0] == "raddr" {
582            if split2.len() < 4 {
583                return Err(Error::Other(format!(
584                    "{:?}: incorrect length",
585                    Error::ErrParseRelatedAddr
586                )));
587            }
588
589            // RelatedAddress
590            split2[1].clone_into(&mut rel_addr);
591
592            // RelatedPort
593            rel_port = split2[3].parse()?;
594        } else if split2[0] == "tcptype" {
595            if split2.len() < 2 {
596                return Err(Error::Other(format!(
597                    "{:?}: incorrect length",
598                    Error::ErrParseType
599                )));
600            }
601
602            tcp_type = TcpType::from(split2[1]);
603        }
604    }
605
606    match typ {
607        "host" => {
608            let config = CandidateHostConfig {
609                base_config: CandidateConfig {
610                    network,
611                    address,
612                    port,
613                    component,
614                    priority,
615                    foundation,
616                    ..CandidateConfig::default()
617                },
618                tcp_type,
619            };
620            config.new_candidate_host()
621        }
622        "srflx" => {
623            let config = CandidateServerReflexiveConfig {
624                base_config: CandidateConfig {
625                    network,
626                    address,
627                    port,
628                    component,
629                    priority,
630                    foundation,
631                    ..CandidateConfig::default()
632                },
633                rel_addr,
634                rel_port,
635                url: None,
636            };
637            config.new_candidate_server_reflexive()
638        }
639        "prflx" => {
640            let config = CandidatePeerReflexiveConfig {
641                base_config: CandidateConfig {
642                    network,
643                    address,
644                    port,
645                    component,
646                    priority,
647                    foundation,
648                    ..CandidateConfig::default()
649                },
650                rel_addr,
651                rel_port,
652            };
653
654            config.new_candidate_peer_reflexive()
655        }
656        "relay" => {
657            let config = CandidateRelayConfig {
658                base_config: CandidateConfig {
659                    network,
660                    address,
661                    port,
662                    component,
663                    priority,
664                    foundation,
665                    ..CandidateConfig::default()
666                },
667                rel_addr,
668                rel_port,
669                url: None,
670            };
671            config.new_candidate_relay()
672        }
673        _ => Err(Error::Other(format!(
674            "{:?} ({})",
675            Error::ErrUnknownCandidateType,
676            typ
677        ))),
678    }
679}