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