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}