1use std::net::SocketAddr;
19use std::sync::Arc;
20use std::sync::atomic::Ordering;
21use std::time::Duration;
22
23use tokio::net::UdpSocket;
24
25use sipx_sdp::ice::{
26 Candidate, CandidateType, ComponentId, Credentials, Foundation, RelatedAddress, Transport,
27};
28
29use super::agent::{Agent, Config, Input, Output};
30use super::candidate::{Gathered, LocalBase, LocalCandidate};
31use super::negotiate::Negotiation;
32use crate::counters::DiscardMeters;
33
34const STUN_RTO: Duration = Duration::from_millis(500);
36
37#[derive(Debug, Clone)]
39pub struct Gathering {
40 pub credentials: Credentials,
45 pub offerer: bool,
48 pub tiebreaker: u64,
50 pub stun_server: Option<SocketAddr>,
54 pub stun_timeout: Duration,
60 pub agent: Config,
62}
63
64impl Gathering {
65 #[must_use]
71 pub fn new(credentials: Credentials, offerer: bool) -> Self {
72 Self {
73 credentials,
74 offerer,
75 tiebreaker: rand::random(),
76 stun_server: None,
77 stun_timeout: Duration::from_secs(2),
78 agent: Config::default(),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy)]
85pub(crate) struct Base<'a> {
86 pub index: LocalBase,
90 pub component: ComponentId,
92 pub socket: &'a UdpSocket,
94}
95
96#[derive(Debug)]
102pub struct LocalDescription {
103 agent: Agent,
104 pending: Vec<Output>,
110 credentials: Credentials,
111 candidates: Vec<Candidate>,
112 defaults: Vec<(ComponentId, SocketAddr)>,
113}
114
115impl LocalDescription {
116 #[must_use]
118 pub const fn credentials(&self) -> &Credentials {
119 &self.credentials
120 }
121
122 #[must_use]
124 pub fn candidates(&self) -> &[Candidate] {
125 &self.candidates
126 }
127
128 #[must_use]
136 pub fn default_destination(&self, component: ComponentId) -> Option<SocketAddr> {
137 self.defaults
138 .iter()
139 .find(|(id, _)| *id == component)
140 .map(|(_, address)| *address)
141 }
142
143 #[must_use]
151 pub fn attributes(&self) -> Vec<sipx_sdp::Attribute> {
152 let mut attributes = vec![
153 sipx_sdp::Attribute::valued("ice-ufrag", self.credentials.ufrag()),
154 sipx_sdp::Attribute::valued("ice-pwd", self.credentials.pwd()),
155 sipx_sdp::Attribute::valued("ice-options", sipx_sdp::ice::ICE2),
156 ];
157 attributes.extend(
158 self.candidates
159 .iter()
160 .map(|candidate| sipx_sdp::Attribute::valued("candidate", candidate.to_value())),
161 );
162 attributes
163 }
164
165 pub fn accept(&mut self, negotiation: &Negotiation) -> bool {
172 let Negotiation::Ice {
173 credentials,
174 candidates,
175 lite,
176 } = negotiation
177 else {
178 return false;
179 };
180 self.pending
181 .extend(self.agent.handle(Input::RemoteDescription {
182 credentials: credentials.clone(),
183 candidates: candidates.clone(),
184 lite: *lite,
185 }));
186 true
187 }
188
189 #[must_use]
191 pub(crate) fn running(&self) -> bool {
192 !self.agent.remote_candidates().is_empty()
193 }
194
195 pub(crate) fn into_driver_parts(self) -> (Agent, Vec<Output>) {
197 (self.agent, self.pending)
198 }
199}
200
201pub(crate) async fn gather(
207 bases: &[Base<'_>],
208 config: &Gathering,
209 discards: Arc<DiscardMeters>,
210) -> LocalDescription {
211 let mut agent = Agent::new(
212 config.agent,
213 config.offerer,
214 config.credentials.clone(),
215 config.tiebreaker,
216 );
217 let mut pending = Vec::new();
218
219 for base in bases {
220 let Ok(address) = base.socket.local_addr() else {
221 continue;
222 };
223 if address.ip().is_unspecified() {
224 tracing::debug!(%address, "no host candidate for a wildcard bind");
230 continue;
231 }
232 pending.extend(agent.handle(Input::LocalCandidate(Gathered {
233 base: base.index,
234 base_address: address,
235 address,
236 kind: CandidateType::Host,
237 component: base.component,
238 server: None,
239 })));
240
241 let Some(server) = config.stun_server else {
242 continue;
243 };
244 let Some(mapped) = reflexive(base.socket, server, config.stun_timeout, &discards).await
245 else {
246 continue;
247 };
248 if mapped == address {
249 discards
252 .ice_redundant_candidates
253 .fetch_add(1, Ordering::Relaxed);
254 tracing::debug!(%address, "no nat: the reflexive candidate is the host one");
255 continue;
256 }
257 pending.extend(agent.handle(Input::LocalCandidate(Gathered {
258 base: base.index,
259 base_address: address,
260 address: mapped,
261 kind: CandidateType::ServerReflexive,
262 component: base.component,
263 server: Some(server.ip()),
264 })));
265 }
266
267 pending.extend(agent.handle(Input::GatheringDone));
268
269 let candidates = lines(agent.local_candidates());
270 let defaults = defaults(&candidates);
271 LocalDescription {
272 agent,
273 pending,
274 credentials: config.credentials.clone(),
275 candidates,
276 defaults,
277 }
278}
279
280async fn reflexive(
290 socket: &UdpSocket,
291 server: SocketAddr,
292 within: Duration,
293 discards: &DiscardMeters,
294) -> Option<SocketAddr> {
295 let id = sipx_transport::stun::new_transaction_id();
296 let request = sipx_transport::stun::binding_request(&id);
297 let deadline = tokio::time::Instant::now().checked_add(within)?;
298 let mut rto = STUN_RTO;
299 let mut datagram = vec![0u8; 1500];
300
301 while tokio::time::Instant::now() < deadline {
302 if socket.send_to(&request, server).await.is_err() {
303 return None;
304 }
305 let wait = deadline
306 .saturating_duration_since(tokio::time::Instant::now())
307 .min(rto);
308 let until = tokio::time::Instant::now().checked_add(wait)?;
309 loop {
310 let read = tokio::time::timeout_at(until, socket.recv_from(&mut datagram)).await;
311 let Ok(Ok((len, from))) = read else {
312 break;
313 };
314 if from != server {
315 discards
318 .ice_gathering_foreign_datagrams
319 .fetch_add(1, Ordering::Relaxed);
320 tracing::debug!(%from, %server, "dropping a datagram from outside the STUN gathering transaction");
321 continue;
322 }
323 let Some(reply) = sipx_transport::stun::parse_reply(datagram.get(..len)?) else {
324 continue;
325 };
326 if reply.id() != id {
327 continue;
328 }
329 return match reply {
330 sipx_transport::stun::Reply::Bound { mapped, .. } => mapped,
331 sipx_transport::stun::Reply::Failed { .. } => None,
334 };
335 }
336 rto = rto.saturating_mul(2);
337 }
338 None
339}
340
341pub(crate) fn lines(candidates: &[LocalCandidate]) -> Vec<Candidate> {
349 let mut lines: Vec<Candidate> = candidates
350 .iter()
351 .filter_map(|candidate| {
352 Some(Candidate {
353 foundation: Foundation::new(&candidate.foundation.0.to_string())?,
354 component: candidate.gathered.component,
355 transport: Transport::Udp,
356 priority: candidate.priority,
357 address: candidate.gathered.address.ip(),
358 port: candidate.gathered.address.port(),
359 kind: candidate.gathered.kind,
360 related: related(candidate),
363 extensions: Vec::new(),
364 })
365 })
366 .collect();
367 lines.sort_by(|left, right| {
370 right
371 .priority
372 .get()
373 .cmp(&left.priority.get())
374 .then_with(|| left.component.get().cmp(&right.component.get()))
375 });
376 lines
377}
378
379fn related(candidate: &LocalCandidate) -> Option<RelatedAddress> {
381 match candidate.gathered.kind {
382 CandidateType::Host => None,
383 CandidateType::ServerReflexive | CandidateType::PeerReflexive | CandidateType::Relayed => {
384 Some(RelatedAddress {
385 address: candidate.gathered.base_address.ip(),
386 port: candidate.gathered.base_address.port(),
387 })
388 }
389 }
390}
391
392fn defaults(candidates: &[Candidate]) -> Vec<(ComponentId, SocketAddr)> {
396 let mut defaults: Vec<(ComponentId, SocketAddr)> = Vec::new();
397 for candidate in candidates {
398 if defaults.iter().any(|(id, _)| *id == candidate.component) {
399 continue;
400 }
401 defaults.push((
402 candidate.component,
403 SocketAddr::new(candidate.address, candidate.port),
404 ));
405 }
406 defaults
407}
408
409#[cfg(test)]
410#[allow(
411 clippy::unwrap_used,
412 clippy::expect_used,
413 clippy::panic,
414 clippy::indexing_slicing
415)]
416mod tests {
417 use super::*;
418
419 fn credentials() -> Credentials {
420 Credentials::new("8hhY", "asd88fgpdd777uzjYhagZg").expect("valid")
421 }
422
423 async fn bound() -> UdpSocket {
424 UdpSocket::bind("127.0.0.1:0".parse::<SocketAddr>().unwrap())
425 .await
426 .expect("a loopback port")
427 }
428
429 #[tokio::test]
432 async fn host_candidates_come_off_the_bound_sockets() {
433 let (media, control) = (bound().await, bound().await);
434 let description = gather(
435 &[
436 Base {
437 index: LocalBase(0),
438 component: ComponentId::RTP,
439 socket: &media,
440 },
441 Base {
442 index: LocalBase(1),
443 component: ComponentId::RTCP,
444 socket: &control,
445 },
446 ],
447 &Gathering::new(credentials(), true),
448 Arc::new(DiscardMeters::default()),
449 )
450 .await;
451
452 assert_eq!(description.candidates().len(), 2);
453 let first = &description.candidates()[0];
454 assert_eq!(first.component, ComponentId::RTP);
455 assert_eq!(first.kind, CandidateType::Host);
456 assert_eq!(first.priority.get(), 2_130_706_431);
457 assert_eq!(first.related, None, "a host candidate carries no raddr");
458 assert_eq!(description.candidates()[1].priority.get(), 2_130_706_430);
459
460 assert_eq!(
461 description.default_destination(ComponentId::RTP),
462 Some(media.local_addr().unwrap())
463 );
464 assert_eq!(
465 description.default_destination(ComponentId::RTCP),
466 Some(control.local_addr().unwrap())
467 );
468 }
469
470 #[tokio::test]
477 async fn no_control_port_means_no_second_component() {
478 let rtp = bound().await;
479 let description = gather(
480 &[Base {
481 index: LocalBase(0),
482 component: ComponentId::RTP,
483 socket: &rtp,
484 }],
485 &Gathering::new(credentials(), true),
486 Arc::new(DiscardMeters::default()),
487 )
488 .await;
489
490 assert_eq!(description.candidates().len(), 1);
491 assert_eq!(description.candidates()[0].component, ComponentId::RTP);
492 assert_eq!(description.default_destination(ComponentId::RTCP), None);
493 }
494
495 #[tokio::test]
498 async fn a_wildcard_bind_yields_no_host_candidate() {
499 let any = UdpSocket::bind("0.0.0.0:0".parse::<SocketAddr>().unwrap())
500 .await
501 .expect("bound");
502 let description = gather(
503 &[Base {
504 index: LocalBase(0),
505 component: ComponentId::RTP,
506 socket: &any,
507 }],
508 &Gathering::new(credentials(), true),
509 Arc::new(DiscardMeters::default()),
510 )
511 .await;
512 assert!(description.candidates().is_empty());
513 }
514
515 #[tokio::test]
521 async fn a_server_reflexive_candidate_comes_from_the_stun_server() {
522 let server = bound().await;
523 let server_address = server.local_addr().unwrap();
524 let reported: SocketAddr = "198.51.100.7:31337".parse().unwrap();
525 tokio::spawn(async move {
526 let mut datagram = vec![0u8; 1500];
527 let Ok((_len, from)) = server.recv_from(&mut datagram).await else {
528 return;
529 };
530 let id: [u8; 12] = datagram[8..20].try_into().expect("a header");
531 let _ = server.send_to(&binding_response(&id, reported), from).await;
532 });
533
534 let rtp = bound().await;
535 let mut gathering = Gathering::new(credentials(), true);
536 gathering.stun_server = Some(server_address);
537 let description = gather(
538 &[Base {
539 index: LocalBase(0),
540 component: ComponentId::RTP,
541 socket: &rtp,
542 }],
543 &gathering,
544 Arc::new(DiscardMeters::default()),
545 )
546 .await;
547
548 let reflexive = description
549 .candidates()
550 .iter()
551 .find(|candidate| candidate.kind == CandidateType::ServerReflexive)
552 .expect("the server's answer became a candidate");
553 assert_eq!(reflexive.address, reported.ip());
554 assert_eq!(reflexive.port, reported.port());
555 assert_eq!(reflexive.priority.get(), 1_694_498_815);
556 let related = reflexive.related.as_ref().expect("srflx carries raddr");
557 assert_eq!(related.address, rtp.local_addr().unwrap().ip());
558 assert_eq!(related.port, rtp.local_addr().unwrap().port());
559
560 assert_eq!(
562 description.default_destination(ComponentId::RTP),
563 Some(rtp.local_addr().unwrap())
564 );
565 }
566
567 #[tokio::test]
571 async fn a_silent_stun_server_still_yields_the_host_candidates() {
572 let black_hole = bound().await;
574 let rtp = bound().await;
575 let mut gathering = Gathering::new(credentials(), true);
576 gathering.stun_server = Some(black_hole.local_addr().unwrap());
577 gathering.stun_timeout = Duration::from_millis(120);
578
579 let description = gather(
580 &[Base {
581 index: LocalBase(0),
582 component: ComponentId::RTP,
583 socket: &rtp,
584 }],
585 &gathering,
586 Arc::new(DiscardMeters::default()),
587 )
588 .await;
589 assert_eq!(description.candidates().len(), 1);
590 assert_eq!(description.candidates()[0].kind, CandidateType::Host);
591 }
592
593 fn binding_response(id: &[u8; 12], mapped: SocketAddr) -> Vec<u8> {
595 let SocketAddr::V4(v4) = mapped else {
596 panic!("the fixture is IPv4");
597 };
598 let cookie = sipx_transport::stun::MAGIC_COOKIE;
599 let mut value = vec![0u8, 0x01];
600 value.extend_from_slice(&(v4.port() ^ u16::try_from(cookie >> 16).unwrap()).to_be_bytes());
601 let octets = u32::from(*v4.ip()) ^ cookie;
602 value.extend_from_slice(&octets.to_be_bytes());
603
604 let mut message = vec![0x01, 0x01];
605 message.extend_from_slice(&u16::try_from(value.len() + 4).unwrap().to_be_bytes());
606 message.extend_from_slice(&cookie.to_be_bytes());
607 message.extend_from_slice(id);
608 message.extend_from_slice(&0x0020u16.to_be_bytes());
609 message.extend_from_slice(&u16::try_from(value.len()).unwrap().to_be_bytes());
610 message.extend_from_slice(&value);
611 message
612 }
613}