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}