1use crate::{
6 Error,
7 peer_external::{Externals, Verdict},
8 peer_gossip::{is_loopback, is_public},
9 peer_limits::{Class, Classes},
10 peer_nat::{Nat, PeerLink, Reachability},
11 peer_transport::{Behaviour, BehaviourEvent},
12};
13use libp2p::{
14 Multiaddr, PeerId, Swarm, autonat,
15 core::{ConnectedPoint, transport::ListenerId},
16 dcutr,
17 multiaddr::Protocol,
18 relay,
19 swarm::{ConnectionId, SwarmEvent},
20};
21use std::{
22 collections::{HashMap, HashSet},
23 io,
24 sync::{
25 Arc,
26 atomic::{AtomicBool, Ordering},
27 },
28 time::{Duration, Instant},
29};
30
31pub(crate) struct Remote {
33 pub(crate) peer: PeerId,
34 pub(crate) address: Multiaddr,
35 pub(crate) outbound: bool,
36 pub(crate) class: Class,
37 since: Instant,
38 demoted: Option<Instant>,
39}
40
41#[derive(Default)]
44pub(crate) struct Remotes(HashMap<ConnectionId, Remote>);
45
46impl Remotes {
47 pub(crate) fn admit(
49 &mut self,
50 (id, peer): (ConnectionId, PeerId),
51 endpoint: &ConnectedPoint,
52 classes: &Classes,
53 ) -> Class {
54 let class = match classes.of(id) {
55 Some(class) => class,
56 None => {
57 tracing::warn!(%peer, "connection without an admission class; kept off data");
58 Class::Auxiliary
59 }
60 };
61 let remote = Remote {
62 peer,
63 address: endpoint.get_remote_address().clone(),
64 outbound: endpoint.is_dialer(),
65 class,
66 since: Instant::now(),
67 demoted: None,
68 };
69 self.0.insert(id, remote);
70 class
71 }
72
73 pub(crate) fn remove(&mut self, id: ConnectionId) -> Option<Remote> {
74 self.0.remove(&id)
75 }
76
77 pub(crate) fn loopback(&self, id: ConnectionId) -> bool {
78 self.0
79 .get(&id)
80 .is_some_and(|remote| is_loopback(&remote.address))
81 }
82
83 pub(crate) fn outbound_direct(&self, id: ConnectionId) -> bool {
85 self.0
86 .get(&id)
87 .is_some_and(|remote| remote.outbound && remote.class != Class::Relayed)
88 }
89
90 pub(crate) fn superseded(
93 &self,
94 id: ConnectionId,
95 peer: PeerId,
96 class: Class,
97 ) -> Vec<ConnectionId> {
98 let direct = self
99 .0
100 .values()
101 .any(|remote| remote.peer == peer && remote.class == Class::Data);
102 match class {
103 Class::Data => self.relayed_of(peer),
104 Class::Relayed if direct => vec![id],
105 Class::Relayed | Class::Auxiliary | Class::Holder | Class::Demoted => Vec::new(),
106 }
107 }
108
109 fn retirable(&self, grace: Duration) -> Vec<(ConnectionId, PeerId, bool)> {
113 let mut retirable: Vec<(ConnectionId, PeerId, bool)> = self
114 .0
115 .iter()
116 .filter(|(_, remote)| match remote.class {
117 Class::Holder => remote.since.elapsed() >= grace,
118 Class::Demoted => remote.demoted.is_some_and(|at| at.elapsed() >= grace),
119 Class::Data | Class::Auxiliary | Class::Relayed => false,
120 })
121 .map(|(id, remote)| {
122 let seeker = remote.class == Class::Demoted && Self::candidate(remote);
123 (*id, remote.peer, seeker)
124 })
125 .collect();
126 retirable.sort_by_key(|(id, ..)| *id);
127 retirable
128 }
129
130 pub(crate) fn data_of(&self, peer: PeerId) -> Vec<(ConnectionId, bool)> {
132 self.0
133 .iter()
134 .filter(|(_, remote)| remote.peer == peer && remote.class == Class::Data)
135 .map(|(id, remote)| (*id, remote.outbound))
136 .collect()
137 }
138
139 pub(crate) fn reclass(&mut self, id: ConnectionId, class: Class) {
140 if let Some(remote) = self.0.get_mut(&id) {
141 remote.class = class;
142 remote.demoted = (class == Class::Demoted).then(Instant::now);
143 }
144 }
145
146 pub(crate) fn unproven(
149 &self,
150 grace: Duration,
151 proven: impl Fn(PeerId) -> bool,
152 ) -> HashSet<PeerId> {
153 self.0
154 .values()
155 .filter(|remote| remote.class == Class::Data && remote.since.elapsed() >= grace)
156 .map(|remote| remote.peer)
157 .filter(|peer| !proven(*peer))
158 .collect()
159 }
160
161 pub(crate) fn demoted_after(&self, grace: Duration) -> Vec<(ConnectionId, PeerId, bool)> {
163 self.0
164 .iter()
165 .filter(|(_, remote)| remote.demoted.is_some_and(|at| at.elapsed() >= grace))
166 .map(|(id, remote)| (*id, remote.peer, remote.outbound))
167 .collect()
168 }
169
170 fn has_data(&self, peer: PeerId) -> bool {
171 self.0
172 .values()
173 .any(|remote| remote.peer == peer && remote.class == Class::Data)
174 }
175
176 fn relayed_of(&self, peer: PeerId) -> Vec<ConnectionId> {
177 self.0
178 .iter()
179 .filter(|(_, remote)| remote.peer == peer && remote.class == Class::Relayed)
180 .map(|(id, _)| *id)
181 .collect()
182 }
183
184 fn candidate(remote: &Remote) -> bool {
185 remote.outbound
186 && matches!(remote.class, Class::Data | Class::Demoted)
187 && is_public(&remote.address)
188 }
189
190 fn relay_candidates(&self) -> Vec<(PeerId, Multiaddr)> {
193 let mut candidates: Vec<(&ConnectionId, &Remote)> = self
194 .0
195 .iter()
196 .filter(|(_, remote)| Self::candidate(remote))
197 .collect();
198 candidates.sort_by_key(|(id, _)| **id);
199 candidates
200 .into_iter()
201 .map(|(_, remote)| (remote.peer, without_peer(&remote.address)))
202 .collect()
203 }
204}
205
206pub(crate) fn without_peer(address: &Multiaddr) -> Multiaddr {
207 address
208 .iter()
209 .filter(|part| !matches!(part, Protocol::P2p(_)))
210 .collect()
211}
212
213pub(crate) fn direct_shape(address: &Multiaddr) -> bool {
216 let mut parts = address.iter();
217 let ip = matches!(parts.next(), Some(Protocol::Ip4(_) | Protocol::Ip6(_)));
218 let transport = match (parts.next(), parts.next()) {
219 (Some(Protocol::Tcp(port)), None) => port != 0,
220 (Some(Protocol::Udp(port)), Some(Protocol::QuicV1)) => port != 0 && parts.next().is_none(),
221 _ => false,
222 };
223 ip && transport && is_public(address)
224}
225
226pub(crate) fn validate_announce(address: &Multiaddr, local: PeerId) -> Result<Multiaddr, Error> {
230 let mut bare = address.clone();
231 if let Some(Protocol::P2p(named)) = address.iter().last() {
232 if named != local {
233 return Err(Error::Invalid("announce address names another peer".into()));
234 }
235 bare.pop();
236 }
237 if !is_public(&bare) {
238 return Err(Error::Invalid("announce address must be public".into()));
239 }
240 if !direct_shape(&bare) {
241 return Err(Error::Invalid(
242 "announce address must be an IP literal with TCP or UDP/QUIC v1".into(),
243 ));
244 }
245 Ok(bare)
246}
247
248#[derive(Clone, Copy, Debug, PartialEq, Eq)]
251pub enum ReachMode {
252 AutoNat,
253 Forced(Reachability),
254}
255
256pub(crate) struct Reach {
257 forced: ReachMode,
258 externals: Externals,
259 probe_failed: bool,
260 relay_open: Arc<AtomicBool>,
261 local: PeerId,
262 listen: Option<Multiaddr>,
263 quic: bool,
264 reservations: HashMap<ListenerId, PeerId>,
265 backoff: HashMap<PeerId, Instant>,
266 circuits: HashMap<PeerId, u32>,
267}
268
269impl Reach {
270 const RESERVATIONS: usize = 2;
271 const BACKOFF: Duration = Duration::from_secs(10 * 60);
272 const HOLDER_GRACE: Duration = Duration::from_secs(10);
273
274 pub(crate) fn new(
275 forced: ReachMode,
276 announce: Vec<Multiaddr>,
277 relay_open: Arc<AtomicBool>,
278 (local, listen): (PeerId, Option<Multiaddr>),
279 ) -> Self {
280 Self {
281 forced,
282 externals: Externals::new(local, announce),
283 probe_failed: false,
284 relay_open,
285 local,
286 listen,
287 quic: false,
288 reservations: HashMap::new(),
289 backoff: HashMap::new(),
290 circuits: HashMap::new(),
291 }
292 }
293
294 pub(crate) fn update(&self, nat: &Nat) {
297 let observed = match (
298 self.externals.announced() || !nat.external().is_empty(),
299 self.probe_failed,
300 ) {
301 (true, _) => Reachability::Public,
302 (false, true) => Reachability::Private,
303 (false, false) => Reachability::Unknown,
304 };
305 let reachability = match self.forced {
306 ReachMode::Forced(reachability) => reachability,
307 ReachMode::AutoNat => observed,
308 };
309 nat.set_reachability(reachability);
310 self.relay_open
311 .store(reachability == Reachability::Public, Ordering::Release);
312 }
313
314 pub(crate) fn absorb(
316 &mut self,
317 swarm: &mut Swarm<Behaviour>,
318 remotes: &Remotes,
319 nat: &Nat,
320 event: SwarmEvent<BehaviourEvent>,
321 ) -> Option<SwarmEvent<BehaviourEvent>> {
322 match event {
323 SwarmEvent::Behaviour(BehaviourEvent::Autonat(event)) => {
324 self.autonat(swarm, &event, nat)
325 }
326 SwarmEvent::Behaviour(BehaviourEvent::AutonatServer(event)) => {
327 tracing::debug!(?event, "AutoNAT probe served")
328 }
329 SwarmEvent::Behaviour(BehaviourEvent::Relay(event)) => self.relay_server(&event, nat),
330 SwarmEvent::Behaviour(BehaviourEvent::RelayClient(event)) => {
331 Self::relay_client(&event, nat)
332 }
333 SwarmEvent::Behaviour(BehaviourEvent::Dcutr(event)) => {
334 Self::dcutr(swarm, remotes, event, nat)
335 }
336 SwarmEvent::ExternalAddrConfirmed { address } => self.external(address, true, nat),
337 SwarmEvent::ExternalAddrExpired { address } => self.external(address, false, nat),
338 SwarmEvent::NewListenAddr { ref address, .. }
339 if self.listen_addr(swarm, address, nat) => {}
340 SwarmEvent::ExpiredListenAddr { ref address, .. }
341 if self.listen_expired(address, nat) => {}
342 SwarmEvent::ListenerClosed {
343 listener_id,
344 ref addresses,
345 ref reason,
346 } => {
347 for address in addresses {
348 self.listen_expired(address, nat);
349 }
350 self.listener_closed(listener_id, reason, nat);
351 return Some(event);
352 }
353 other => return Some(other),
354 }
355 None
356 }
357
358 fn autonat(
361 &mut self,
362 swarm: &mut Swarm<Behaviour>,
363 event: &autonat::v2::client::Event,
364 nat: &Nat,
365 ) {
366 if let Err(error) = &event.result {
367 tracing::debug!(%error, address = %event.tested_addr, "AutoNAT probe failed");
368 self.probe_failed = true;
369 self.update(nat);
370 return;
371 }
372 match self.externals.verdict(&event.tested_addr, event.server) {
373 Verdict::Confirm(address) => self.advertise(swarm, address, nat),
374 Verdict::Retry(address) => swarm.behaviour_mut().candidates.offer(address),
375 Verdict::Ignore => {
376 tracing::debug!(address = %event.tested_addr, server = %event.server,
377 "AutoNAT confirmation not counted")
378 }
379 }
380 }
381
382 pub(crate) fn observed(
386 &mut self,
387 (swarm, remotes): (&mut Swarm<Behaviour>, &Remotes),
388 (observed, witness): (Multiaddr, PeerId),
389 nat: &Nat,
390 ) {
391 if !direct_shape(&observed) {
392 return;
393 }
394 let reported = (witness, Instant::now());
395 let data = |peer: &PeerId| remotes.has_data(*peer);
396 for stale in self
397 .externals
398 .witnessed(&observed, reported, &nat.external(), data)
399 {
400 tracing::info!(%stale, now = %observed, "external address mapping changed");
401 swarm.remove_external_address(&stale);
402 self.external(stale, false, nat);
403 }
404 if self.externals.flooded(witness) {
405 tracing::debug!(%witness, "witness reports too many addresses; ignored");
406 return;
407 }
408 swarm.behaviour_mut().candidates.offer(observed);
409 }
410
411 pub(crate) fn advertise(&self, swarm: &mut Swarm<Behaviour>, address: Multiaddr, nat: &Nat) {
414 swarm.add_external_address(address.clone());
415 self.external(address, true, nat);
416 }
417
418 pub(crate) fn external(&self, address: Multiaddr, confirmed: bool, nat: &Nat) {
421 if PeerLink::of(&address) == PeerLink::Direct && self.externals.admits(&address) {
422 nat.set_external(address, confirmed);
423 self.update(nat);
424 }
425 }
426
427 fn relay_server(&mut self, event: &relay::Event, nat: &Nat) {
428 match event {
429 relay::Event::ReservationReqAccepted { src_peer_id, .. } => {
430 nat.set_reserved_by(*src_peer_id, true)
431 }
432 relay::Event::ReservationClosed { src_peer_id }
433 | relay::Event::ReservationTimedOut { src_peer_id } => {
434 nat.set_reserved_by(*src_peer_id, false)
435 }
436 relay::Event::CircuitReqAccepted {
437 src_peer_id,
438 dst_peer_id,
439 } => {
440 for peer in [src_peer_id, dst_peer_id] {
441 *self.circuits.entry(*peer).or_default() += 1;
442 }
443 }
444 relay::Event::CircuitClosed {
445 src_peer_id,
446 dst_peer_id,
447 ..
448 } => {
449 for peer in [src_peer_id, dst_peer_id] {
450 self.circuit_closed(*peer);
451 }
452 }
453 _ => (),
454 }
455 }
456
457 fn circuit_closed(&mut self, peer: PeerId) {
458 let Some(count) = self.circuits.get_mut(&peer) else {
459 return;
460 };
461 *count -= 1;
462 if *count == 0 {
463 self.circuits.remove(&peer);
464 }
465 }
466
467 fn retire_holders(&self, swarm: &mut Swarm<Behaviour>, remotes: &Remotes, nat: &Nat) {
474 let relays = nat.relays();
475 let seeking = match nat.reachability() {
476 Reachability::Public => 0,
477 Reachability::Unknown => Self::RESERVATIONS,
478 Reachability::Private => Self::RESERVATIONS.saturating_sub(relays.len()),
479 };
480 let mut holding = nat.reserved_by_others();
481 holding.extend(relays);
482 let mut kept = 0;
483 for (id, peer, seeker) in remotes.retirable(Self::HOLDER_GRACE) {
484 if holding.contains(&peer) || self.circuits.contains_key(&peer) {
485 continue;
486 }
487 if seeker && kept < seeking {
488 kept += 1;
489 continue;
490 }
491 tracing::debug!(%peer, "holder carries no reservation or circuit; closing");
492 swarm.close_connection(id);
493 }
494 }
495
496 pub(crate) fn relay_client(event: &relay::client::Event, nat: &Nat) {
497 if let relay::client::Event::ReservationReqAccepted { relay_peer_id, .. } = event {
498 nat.set_relay(*relay_peer_id, true);
499 }
500 }
501
502 pub(crate) fn dcutr(
505 swarm: &mut Swarm<Behaviour>,
506 remotes: &Remotes,
507 event: dcutr::Event,
508 nat: &Nat,
509 ) {
510 match event.result {
511 Ok(_direct) => {
512 nat.punch_succeeded();
513 for id in remotes.relayed_of(event.remote_peer_id) {
514 swarm.close_connection(id);
515 }
516 }
517 Err(error) => nat.punch_failed(format!("{}: {error}", event.remote_peer_id)),
518 }
519 }
520
521 pub(crate) fn listen_addr(
524 &mut self,
525 swarm: &mut Swarm<Behaviour>,
526 address: &Multiaddr,
527 nat: &Nat,
528 ) -> bool {
529 if PeerLink::of(address) == PeerLink::Relayed {
530 nat.set_circuit(self.with_local(address), true);
531 return true;
532 }
533 self.open_quic(swarm, address);
534 if let Some(candidate) = self.externals.bound(address) {
535 swarm.behaviour_mut().candidates.offer(candidate);
536 }
537 false
538 }
539
540 pub(crate) fn listen_expired(&self, address: &Multiaddr, nat: &Nat) -> bool {
541 let circuit = PeerLink::of(address) == PeerLink::Relayed;
542 if circuit {
543 nat.set_circuit(self.with_local(address), false);
544 }
545 circuit
546 }
547
548 pub(crate) fn listener_closed(
549 &mut self,
550 id: ListenerId,
551 reason: &Result<(), io::Error>,
552 nat: &Nat,
553 ) {
554 let Some(relay) = self.reservations.remove(&id) else {
555 return;
556 };
557 nat.set_relay(relay, false);
558 if reason.is_err() {
559 self.backoff.insert(relay, Instant::now() + Self::BACKOFF);
560 }
561 }
562
563 fn with_local(&self, address: &Multiaddr) -> Multiaddr {
564 match address.iter().last() {
565 Some(Protocol::P2p(_)) => address.clone(),
566 _ => address.clone().with(Protocol::P2p(self.local)),
567 }
568 }
569
570 fn open_quic(&mut self, swarm: &mut Swarm<Behaviour>, bound: &Multiaddr) {
571 let Some(listen) = &self.listen else {
572 return;
573 };
574 let port = bound.iter().find_map(|part| match part {
575 Protocol::Tcp(port) => Some(port),
576 _ => None,
577 });
578 let (Some(port), Some(ip), false) = (port, listen.iter().next(), self.quic) else {
579 return;
580 };
581 self.quic = true;
582 let quic = Multiaddr::empty()
583 .with(ip)
584 .with(Protocol::Udp(port))
585 .with(Protocol::QuicV1);
586 if let Err(error) = swarm.listen_on(quic.clone()) {
587 tracing::warn!(%error, %quic, "QUIC listener unavailable; TCP only");
588 }
589 }
590
591 pub(crate) fn maintain(&mut self, swarm: &mut Swarm<Behaviour>, remotes: &Remotes, nat: &Nat) {
594 self.retire_holders(swarm, remotes, nat);
595 if nat.reachability() != Reachability::Private {
596 for (id, _) in self.reservations.drain() {
597 swarm.remove_listener(id);
598 }
599 return;
600 }
601 let now = Instant::now();
602 self.backoff.retain(|_, until| *until > now);
603 for (relay, address) in remotes.relay_candidates() {
604 if self.reservations.len() >= Self::RESERVATIONS {
605 return;
606 }
607 let held = self.reservations.values().any(|known| *known == relay);
608 if held || relay == self.local || self.backoff.contains_key(&relay) {
609 continue;
610 }
611 let circuit = address
612 .with(Protocol::P2p(relay))
613 .with(Protocol::P2pCircuit);
614 match swarm.listen_on(circuit) {
615 Ok(id) => {
616 self.reservations.insert(id, relay);
617 }
618 Err(error) => {
619 tracing::warn!(%error, %relay, "relay reservation not attempted");
620 self.backoff.insert(relay, now + Self::BACKOFF);
621 }
622 }
623 }
624 }
625}