1use crate::behaviour::autorelay::handler::Out;
3use crate::multiaddr_ext::MultiaddrExt;
4use crate::prelude::swarm::derive_prelude::{ListenerId, PortUse};
5use crate::prelude::swarm::{
6 ExternalAddresses, ListenOpts, NewListenAddr, NotifyHandler,
7 derive_prelude::{
8 AddressChange, ConnectionClosed, ConnectionDenied, ConnectionEstablished, ConnectionId,
9 DialFailure, ExpiredListenAddr, FromSwarm, ListenerClosed, ListenerError, Multiaddr,
10 NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm,
11 },
12 dial_opts::DialOpts,
13 dummy,
14};
15use crate::prelude::transport::Endpoint;
16use crate::prelude::{PeerId, Protocol};
17use either::Either;
18use std::collections::BTreeMap;
19use std::{
20 collections::{HashMap, HashSet, VecDeque},
21 num::NonZeroU8,
22 task::{Context, Poll, Waker},
23 time::Duration,
24};
25use web_time::{Instant, SystemTime};
26
27mod handler;
28
29#[derive(Debug)]
30pub struct Behaviour {
31 config: Config,
32 status: Status,
33 auto_status_change: bool,
34 external_addresses: ExternalAddresses,
35 events: VecDeque<ToSwarm<<Self as NetworkBehaviour>::ToSwarm, THandlerInEvent<Self>>>,
36
37 connections: HashMap<(PeerId, ConnectionId), Connection>,
38
39 reservations: HashMap<ListenerId, (PeerId, ConnectionId)>,
40
41 external_reservations: HashMap<ListenerId, PeerId>,
42
43 reservation_addrs: HashMap<ListenerId, HashSet<Multiaddr>>,
46
47 static_relays: HashMap<PeerId, Vec<Multiaddr>>,
48
49 static_dial_cooldowns: HashMap<PeerId, Instant>,
50
51 failure_counts: HashMap<PeerId, u32>,
52
53 previous_relays: VecDeque<(PeerId, Multiaddr, SystemTime)>,
54
55 relays_available: bool,
56
57 waker: Option<Waker>,
58}
59
60impl Default for Behaviour {
61 fn default() -> Self {
62 Self {
63 config: Config::default(),
64 status: Status::Enable,
65 auto_status_change: true,
66 external_addresses: ExternalAddresses::default(),
67 events: VecDeque::new(),
68 connections: HashMap::new(),
69 reservations: HashMap::new(),
70 external_reservations: HashMap::new(),
71 reservation_addrs: HashMap::new(),
72 static_relays: HashMap::new(),
73 static_dial_cooldowns: HashMap::new(),
74 failure_counts: HashMap::new(),
75 previous_relays: VecDeque::new(),
76 relays_available: false,
77 waker: None,
78 }
79 }
80}
81
82#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
83pub enum Status {
84 #[default]
85 Enable,
86 Disable,
87}
88
89#[derive(Debug)]
90struct Connection {
91 address: Multiaddr,
92 relay_status: RelayStatus,
93}
94
95impl Connection {
96 pub(crate) fn disqualify_connection_if_relayed(&mut self) {
98 if self.address.is_relayed() {
99 self.relay_status = RelayStatus::NotSupported;
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105enum RelayStatus {
106 Supported { status: ReservationStatus },
107 NotSupported,
108 Pending,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112enum ReservationStatus {
113 Idle,
114 Pending { id: ListenerId },
115 Active { id: ListenerId },
116 Blacklisted,
117}
118
119#[derive(Debug)]
120pub struct Config {
121 max_reservations: NonZeroU8,
122 failure_cooldown: Duration,
123 failure_cooldown_max: Duration,
124 max_previous_relays: usize,
125 static_relays: HashMap<PeerId, Vec<Multiaddr>>,
126}
127
128impl Default for Config {
129 fn default() -> Self {
130 Self {
131 max_reservations: NonZeroU8::new(2).unwrap(),
132 failure_cooldown: Duration::from_secs(30),
133 failure_cooldown_max: Duration::from_secs(10 * 60),
134 max_previous_relays: 16,
135 static_relays: HashMap::new(),
136 }
137 }
138}
139
140impl Config {
141 pub fn set_max_reservations(mut self, max_reservations: NonZeroU8) -> Self {
142 self.max_reservations = max_reservations;
143 self
144 }
145
146 pub fn set_failure_cooldown(mut self, duration: Duration) -> Self {
147 self.failure_cooldown = duration;
148 self
149 }
150
151 pub fn set_failure_cooldown_max(mut self, duration: Duration) -> Self {
152 self.failure_cooldown_max = duration;
153 self
154 }
155
156 pub fn set_max_previous_relays(mut self, max: usize) -> Self {
157 self.max_previous_relays = max;
158 self
159 }
160
161 pub fn add_static_relay(mut self, peer_id: PeerId, addresses: Vec<Multiaddr>) -> Self {
162 let entry = self.static_relays.entry(peer_id).or_default();
163 for addr in addresses {
164 if !entry.contains(&addr) {
165 entry.push(addr);
166 }
167 }
168 self
169 }
170}
171
172#[derive(Debug)]
173#[non_exhaustive]
174pub enum Event {
175 StatusChanged { status: Status },
177 NoRelaysAvailable,
179 RelaysAvailable,
181}
182
183impl Behaviour {
184 pub fn new_with_config(mut config: Config) -> Self {
185 let initial_static_relays = std::mem::take(&mut config.static_relays);
186 let mut behaviour = Self {
187 config,
188 ..Default::default()
189 };
190 for (peer_id, addresses) in initial_static_relays {
191 for address in addresses {
192 behaviour.add_static_relay(peer_id, address);
193 }
194 }
195 behaviour
196 }
197
198 pub fn set_status(&mut self, status: Option<Status>) {
200 match status {
201 Some(status) => {
202 self.auto_status_change = false;
203 if self.status != status {
204 self.status = status;
205 self.events
206 .push_back(ToSwarm::GenerateEvent(Event::StatusChanged { status }));
207 if status == Status::Enable {
208 self.meet_reservation_target();
209 }
210 }
211 }
212 None => {
213 self.auto_status_change = true;
214 self.determine_status_from_external_addresses();
215 }
216 }
217
218 if let Some(waker) = self.waker.take() {
219 waker.wake();
220 }
221 }
222
223 pub fn add_static_relay(&mut self, peer_id: PeerId, address: Multiaddr) -> bool {
229 if address.is_relayed() {
230 tracing::warn!(%peer_id, %address, "static relay address is relayed. ignoring.");
231 return false;
232 }
233
234 let entry = self.static_relays.entry(peer_id).or_default();
235 if entry.contains(&address) {
236 tracing::warn!(%peer_id, %address, "static relay address already exist");
237 } else {
238 entry.push(address);
239 }
240 let combined = entry.clone();
241
242 if self.is_peer_idle(&peer_id) {
243 self.evict_for_static_peer(peer_id);
244 }
245
246 if !self.queue_static_dial(peer_id, combined) {
247 self.meet_reservation_target();
248 }
249
250 if let Some(waker) = self.waker.take() {
251 waker.wake();
252 }
253
254 true
255 }
256
257 pub fn remove_static_relay(&mut self, peer_id: &PeerId) -> bool {
260 self.static_dial_cooldowns.remove(peer_id);
261 self.static_relays.remove(peer_id).is_some()
262 }
263
264 pub fn static_relays(&self) -> impl Iterator<Item = (&PeerId, &[Multiaddr])> {
265 self.static_relays
266 .iter()
267 .map(|(peer, addrs)| (peer, addrs.as_slice()))
268 }
269
270 pub fn previous_relays(&self) -> impl Iterator<Item = (&PeerId, &Multiaddr, &SystemTime)> {
271 self.previous_relays
272 .iter()
273 .map(|(peer, addr, ts)| (peer, addr, ts))
274 }
275
276 fn static_dial_in_cooldown(&self, peer_id: &PeerId) -> bool {
277 self.static_dial_cooldowns
278 .get(peer_id)
279 .is_some_and(|deadline| *deadline > Instant::now())
280 }
281
282 fn queue_static_dial(&mut self, peer_id: PeerId, addresses: Vec<Multiaddr>) -> bool {
283 if addresses.is_empty()
284 || self.has_direct_connection(&peer_id)
285 || self.static_dial_in_cooldown(&peer_id)
286 {
287 return false;
288 }
289 let opts = DialOpts::peer_id(peer_id).addresses(addresses).build();
290 self.events.push_back(ToSwarm::Dial { opts });
291 true
292 }
293
294 fn record_previous_relay(&mut self, peer_id: PeerId, address: Multiaddr) {
295 let max = self.config.max_previous_relays;
296 if max == 0 {
297 return;
298 }
299 self.previous_relays.retain(|(p, _, _)| *p != peer_id);
300 if self.previous_relays.len() >= max {
301 self.previous_relays.pop_front();
302 }
303 self.previous_relays
304 .push_back((peer_id, address, SystemTime::now()));
305 }
306
307 fn forget_previous_relay(&mut self, peer_id: &PeerId) {
308 self.previous_relays.retain(|(p, _, _)| p != peer_id);
309 }
310
311 fn record_failure(&mut self, peer_id: PeerId) -> Duration {
312 let attempts = self.failure_counts.entry(peer_id).or_insert(0);
313 *attempts = attempts.saturating_add(1);
314 let exponent = attempts.saturating_sub(1).min(20);
315 let scale = 1u32 << exponent;
316 self.config
317 .failure_cooldown
318 .saturating_mul(scale)
319 .min(self.config.failure_cooldown_max)
320 }
321
322 fn clear_failure(&mut self, peer_id: &PeerId) {
323 self.failure_counts.remove(peer_id);
324 }
325
326 fn determine_status_from_external_addresses(&mut self) {
327 let has_public_addr = self
328 .external_addresses
329 .iter()
330 .any(|addr| !addr.is_relayed());
331
332 let new_status = match has_public_addr {
333 true => Status::Disable,
334 false => Status::Enable,
335 };
336 if new_status != self.status {
337 self.status = new_status;
338 self.events
339 .push_back(ToSwarm::GenerateEvent(Event::StatusChanged {
340 status: new_status,
341 }));
342 match new_status {
343 Status::Enable => self.meet_reservation_target(),
344 Status::Disable => self.remove_all_reservations(),
345 }
346 }
347 }
348
349 fn is_peer_idle(&self, peer_id: &PeerId) -> bool {
350 self.connections.iter().any(|((pid, _), info)| {
351 pid == peer_id
352 && info.relay_status
353 == RelayStatus::Supported {
354 status: ReservationStatus::Idle,
355 }
356 })
357 }
358
359 fn has_direct_connection(&self, peer_id: &PeerId) -> bool {
360 self.connections
361 .iter()
362 .any(|((pid, _), info)| pid == peer_id && !info.address.is_relayed())
363 }
364
365 fn evict_for_static_peer(&mut self, new_static: PeerId) {
366 let covered = self.covered_peers();
367 if covered.contains(&new_static) {
368 return;
369 }
370 let max = self.config.max_reservations.get() as usize;
371 if covered.len() < max {
372 return;
373 }
374
375 if let Some(listener_id) = self
376 .reservations
377 .iter()
378 .find(|(_, (peer_id, _))| !self.static_relays.contains_key(peer_id))
379 .map(|(listener_id, _)| *listener_id)
380 {
381 self.events
382 .push_back(ToSwarm::RemoveListener { id: listener_id });
383 }
384 }
385
386 fn select_connection_for_reservation(&mut self, peer_id: PeerId, connection_id: ConnectionId) {
387 let info = self
388 .connections
389 .get_mut(&(peer_id, connection_id))
390 .expect("connection is present");
391
392 if info.relay_status
393 != (RelayStatus::Supported {
394 status: ReservationStatus::Idle,
395 })
396 {
397 return;
398 }
399
400 let addr_with_peer_id = match info.address.clone().with_p2p(peer_id) {
401 Ok(addr) => addr,
402 Err(addr) => {
403 tracing::warn!(%addr, "address unexpectedly contains a different peer id than the connection; marking relay connection ineligible");
404 info.relay_status = RelayStatus::NotSupported;
405 return;
406 }
407 };
408
409 let opts = ListenOpts::new(addr_with_peer_id.with(Protocol::P2pCircuit));
410 let id = opts.listener_id();
411
412 info.relay_status = RelayStatus::Supported {
413 status: ReservationStatus::Pending { id },
414 };
415 self.reservations.insert(id, (peer_id, connection_id));
416 self.events.push_back(ToSwarm::ListenOn { opts });
417 }
418
419 pub fn remove_all_reservations(&mut self) {
421 let relay_listeners = self
422 .reservations
423 .iter()
424 .map(|(id, (peer_id, conn_id))| (*id, *peer_id, *conn_id))
425 .collect::<Vec<_>>();
426
427 for (listener_id, peer_id, connection_id) in relay_listeners {
428 let Some(connection) = self.connections.get_mut(&(peer_id, connection_id)) else {
429 continue;
430 };
431
432 if !matches!(
433 connection.relay_status,
434 RelayStatus::Supported {
435 status: ReservationStatus::Active { id } | ReservationStatus::Pending { id }
436 } if id == listener_id
437 ) {
438 continue;
439 }
440
441 connection.relay_status = RelayStatus::Supported {
442 status: ReservationStatus::Idle,
443 };
444
445 self.events
446 .push_back(ToSwarm::RemoveListener { id: listener_id });
447 }
448 }
449
450 fn disable_reservation(&mut self, id: ListenerId, failed: bool) {
451 self.expire_reservation_addrs(id);
452
453 if self.external_reservations.remove(&id).is_some() {
454 self.meet_reservation_target();
455 return;
456 }
457
458 let Some((peer_id, connection_id)) = self.reservations.remove(&id) else {
459 return;
460 };
461
462 let Some(address) = self
463 .connections
464 .get(&(peer_id, connection_id))
465 .filter(|info| {
466 matches!(
467 info.relay_status,
468 RelayStatus::Supported {
469 status: ReservationStatus::Active { .. }
470 | ReservationStatus::Pending { .. }
471 }
472 )
473 })
474 .map(|info| info.address.clone())
475 else {
476 self.meet_reservation_target();
477 return;
478 };
479
480 let blacklist_duration = failed.then(|| self.record_failure(peer_id));
481
482 let connection = self
483 .connections
484 .get_mut(&(peer_id, connection_id))
485 .expect("connection is tracked");
486 match blacklist_duration {
487 Some(duration) => {
488 connection.relay_status = RelayStatus::Supported {
489 status: ReservationStatus::Blacklisted,
490 };
491 self.events.push_back(ToSwarm::NotifyHandler {
492 peer_id,
493 handler: NotifyHandler::One(connection_id),
494 event: Either::Left(handler::In::Blacklist { duration }),
495 });
496 }
497 None => {
498 connection.relay_status = RelayStatus::Supported {
499 status: ReservationStatus::Idle,
500 };
501 }
502 }
503
504 self.record_previous_relay(peer_id, address);
505 self.meet_reservation_target();
506 }
507
508 fn reconcile_reservation_addrs(&mut self) {
509 let confirmed: HashSet<Multiaddr> = self
510 .external_addresses
511 .iter()
512 .filter(|addr| addr.is_relayed())
513 .cloned()
514 .collect();
515
516 for addrs in self.reservation_addrs.values_mut() {
517 addrs.retain(|addr| confirmed.contains(addr));
518 }
519 self.reservation_addrs.retain(|_, addrs| !addrs.is_empty());
520
521 for addr in confirmed {
522 let Some(relay_peer) = addr.relay_peer_id() else {
523 continue;
524 };
525
526 let listeners = self
527 .reservations
528 .iter()
529 .filter(|(_, (peer_id, _))| *peer_id == relay_peer)
530 .map(|(id, _)| *id)
531 .chain(
532 self.external_reservations
533 .iter()
534 .filter(|(_, peer_id)| **peer_id == relay_peer)
535 .map(|(id, _)| *id),
536 )
537 .collect::<Vec<_>>();
538
539 for id in listeners {
540 self.reservation_addrs
541 .entry(id)
542 .or_default()
543 .insert(addr.clone());
544 }
545 }
546 }
547
548 fn expire_reservation_addrs(&mut self, id: ListenerId) {
549 let Some(addrs) = self.reservation_addrs.remove(&id) else {
550 return;
551 };
552
553 for addr in addrs {
554 let still_backed = self
555 .reservation_addrs
556 .values()
557 .any(|other| other.contains(&addr));
558 if !still_backed {
559 self.events.push_back(ToSwarm::ExternalAddrExpired(addr));
560 }
561 }
562 }
563
564 fn covered_peers(&self) -> HashSet<PeerId> {
565 self.reservations
566 .values()
567 .map(|(peer_id, _)| *peer_id)
568 .chain(self.external_reservations.values().copied())
569 .collect()
570 }
571
572 fn meet_reservation_target(&mut self) {
574 if self.status == Status::Disable {
575 return;
576 }
577
578 let max = self.config.max_reservations.get() as usize;
579 let covered = self.covered_peers();
580 let budget = max.saturating_sub(covered.len());
581 if budget == 0 {
582 return;
583 }
584
585 let mut static_candidates = BTreeMap::new();
586 let mut candidates: BTreeMap<_, ConnectionId> = BTreeMap::new();
587 for ((peer_id, connection_id), info) in self.connections.iter() {
588 if covered.contains(peer_id) {
589 continue;
590 }
591 if info.relay_status
592 != (RelayStatus::Supported {
593 status: ReservationStatus::Idle,
594 })
595 {
596 continue;
597 }
598 let bucket = if self.static_relays.contains_key(peer_id) {
599 &mut static_candidates
600 } else {
601 &mut candidates
602 };
603 bucket
604 .entry(*peer_id)
605 .and_modify(|existing| *existing = (*existing).min(*connection_id))
606 .or_insert(*connection_id);
607 }
608
609 let selected_candidates: Vec<(PeerId, ConnectionId)> = static_candidates
610 .into_iter()
611 .chain(candidates)
612 .take(budget)
613 .collect();
614
615 for (peer_id, connection_id) in selected_candidates {
616 self.select_connection_for_reservation(peer_id, connection_id);
617 }
618
619 debug_assert!(self.covered_peers().len() <= max);
620 }
621
622 fn update_relay_availability(&mut self) {
623 let has_hop_peer = self
624 .connections
625 .values()
626 .any(|info| matches!(info.relay_status, RelayStatus::Supported { .. }));
627
628 match (has_hop_peer, self.relays_available) {
629 (true, false) => {
630 self.relays_available = true;
631 self.events
632 .push_back(ToSwarm::GenerateEvent(Event::RelaysAvailable));
633 }
634 (false, true) => {
635 self.relays_available = false;
636 self.events
637 .push_back(ToSwarm::GenerateEvent(Event::NoRelaysAvailable));
638 }
639 _ => {}
640 }
641 }
642}
643
644impl NetworkBehaviour for Behaviour {
645 type ConnectionHandler = Either<handler::Handler, dummy::ConnectionHandler>;
646 type ToSwarm = Event;
647
648 fn handle_established_inbound_connection(
649 &mut self,
650 _connection_id: ConnectionId,
651 _peer: PeerId,
652 local_addr: &Multiaddr,
653 _remote_addr: &Multiaddr,
654 ) -> Result<THandler<Self>, ConnectionDenied> {
655 if local_addr.is_relayed() {
656 Ok(Either::Right(dummy::ConnectionHandler))
657 } else {
658 Ok(Either::Left(handler::Handler::default()))
659 }
660 }
661
662 fn handle_established_outbound_connection(
663 &mut self,
664 _connection_id: ConnectionId,
665 _peer: PeerId,
666 addr: &Multiaddr,
667 _role_override: Endpoint,
668 _port_use: PortUse,
669 ) -> Result<THandler<Self>, ConnectionDenied> {
670 if addr.is_relayed() {
671 Ok(Either::Right(dummy::ConnectionHandler))
672 } else {
673 Ok(Either::Left(handler::Handler::default()))
674 }
675 }
676
677 fn on_swarm_event(&mut self, event: FromSwarm) {
678 let change = self.external_addresses.on_swarm_event(&event);
679
680 if self.auto_status_change && change {
681 self.determine_status_from_external_addresses();
682 }
683
684 if change {
685 self.reconcile_reservation_addrs();
686 }
687
688 match event {
689 FromSwarm::ConnectionEstablished(ConnectionEstablished {
690 peer_id,
691 endpoint,
692 connection_id,
693 ..
694 }) => {
695 let remote_addr = endpoint.get_remote_address().clone();
696
697 let mut connection = Connection {
698 address: remote_addr,
699 relay_status: RelayStatus::Pending,
700 };
701
702 connection.disqualify_connection_if_relayed();
703
704 self.connections
705 .insert((peer_id, connection_id), connection);
706
707 if self.static_relays.contains_key(&peer_id) {
708 self.static_dial_cooldowns.remove(&peer_id);
709 }
710 }
711 FromSwarm::ConnectionClosed(ConnectionClosed {
712 peer_id,
713 connection_id,
714 ..
715 }) => {
716 let Some(connection) = self.connections.remove(&(peer_id, connection_id)) else {
717 return;
718 };
719
720 if !self.connections.keys().any(|(pid, _)| *pid == peer_id) {
721 self.clear_failure(&peer_id);
722 }
723
724 let had_reservation = matches!(
725 connection.relay_status,
726 RelayStatus::Supported {
727 status: ReservationStatus::Active { .. }
728 | ReservationStatus::Pending { .. }
729 | ReservationStatus::Blacklisted
730 }
731 );
732
733 if let RelayStatus::Supported {
734 status: ReservationStatus::Active { id } | ReservationStatus::Pending { id },
735 } = connection.relay_status
736 {
737 self.reservations.remove(&id);
738 self.meet_reservation_target();
739 }
740
741 if had_reservation {
742 self.record_previous_relay(peer_id, connection.address);
743 }
744
745 if let Some(addresses) = self.static_relays.get(&peer_id).cloned() {
746 self.queue_static_dial(peer_id, addresses);
747 }
748
749 self.update_relay_availability();
750 }
751 FromSwarm::AddressChange(AddressChange {
752 peer_id,
753 connection_id,
754 old: _,
755 new,
756 }) => {
757 let Some(connection) = self.connections.get_mut(&(peer_id, connection_id)) else {
758 return;
759 };
760
761 let new_addr = new.get_remote_address();
762
763 connection.address = new_addr.clone();
764 }
765 FromSwarm::NewListenAddr(NewListenAddr { listener_id, addr }) => {
766 if !addr.is_relayed() {
767 return;
768 }
769
770 if let Some((peer_id, connection_id)) = self.reservations.get(&listener_id).copied()
771 {
772 let Some(connection) = self.connections.get_mut(&(peer_id, connection_id))
773 else {
774 return;
775 };
776
777 if matches!(
778 connection.relay_status,
779 RelayStatus::Supported {
780 status: ReservationStatus::Pending { id }
781 } if id == listener_id
782 ) {
783 connection.relay_status = RelayStatus::Supported {
784 status: ReservationStatus::Active { id: listener_id },
785 };
786 self.forget_previous_relay(&peer_id);
787 self.clear_failure(&peer_id);
788 }
789 return;
790 }
791
792 if let Some(relay_peer_id) = addr.relay_peer_id() {
793 self.external_reservations
794 .insert(listener_id, relay_peer_id);
795 self.reconcile_reservation_addrs();
796 }
797 }
798 FromSwarm::ExpiredListenAddr(ExpiredListenAddr { listener_id, .. }) => {
799 self.disable_reservation(listener_id, false);
800 }
801 FromSwarm::ListenerError(ListenerError { listener_id, .. }) => {
802 self.disable_reservation(listener_id, true);
803 }
804 FromSwarm::ListenerClosed(ListenerClosed {
805 listener_id,
806 reason,
807 ..
808 }) => {
809 self.disable_reservation(listener_id, reason.is_err());
810 }
811 FromSwarm::DialFailure(DialFailure {
812 peer_id: Some(peer_id),
813 error,
814 ..
815 }) if self.static_relays.contains_key(&peer_id) => {
816 tracing::warn!(%peer_id, %error, "dial to static relay failed");
817 self.static_dial_cooldowns
818 .insert(peer_id, Instant::now() + self.config.failure_cooldown);
819 }
820 _ => {}
821 }
822 }
823
824 fn on_connection_handler_event(
825 &mut self,
826 peer_id: PeerId,
827 connection_id: ConnectionId,
828 event: THandlerOutEvent<Self>,
829 ) {
830 let Either::Left(event) = event;
831
832 let Some(connection) = self.connections.get_mut(&(peer_id, connection_id)) else {
833 return;
834 };
835
836 match event {
837 Out::Supported => {
838 if matches!(
839 connection.relay_status,
840 RelayStatus::Pending | RelayStatus::NotSupported
841 ) {
842 connection.relay_status = RelayStatus::Supported {
843 status: ReservationStatus::Idle,
844 };
845 if self.static_relays.contains_key(&peer_id) {
846 self.evict_for_static_peer(peer_id);
847 }
848 self.meet_reservation_target();
849 self.update_relay_availability();
850 }
851 }
852 Out::Unsupported => {
853 let drop_listener = match connection.relay_status {
854 RelayStatus::Supported {
855 status: ReservationStatus::Pending { id } | ReservationStatus::Active { id },
856 } => Some(id),
857 _ => None,
858 };
859 let lost_address = drop_listener.map(|_| connection.address.clone());
860 connection.relay_status = RelayStatus::NotSupported;
861 if let Some(id) = drop_listener {
862 self.expire_reservation_addrs(id);
863 self.reservations.remove(&id);
864 self.events.push_back(ToSwarm::RemoveListener { id });
865 self.meet_reservation_target();
866 }
867 if let Some(address) = lost_address {
868 self.record_previous_relay(peer_id, address);
869 }
870 self.update_relay_availability();
871 }
872 Out::BlacklistExpired => {
873 if matches!(
874 connection.relay_status,
875 RelayStatus::Supported {
876 status: ReservationStatus::Blacklisted
877 }
878 ) {
879 connection.relay_status = RelayStatus::Supported {
880 status: ReservationStatus::Idle,
881 };
882 self.meet_reservation_target();
883 }
884 }
885 }
886 }
887
888 fn poll(
889 &mut self,
890 cx: &mut Context<'_>,
891 ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
892 if let Some(event) = self.events.pop_front() {
893 return Poll::Ready(event);
894 }
895
896 self.waker = Some(cx.waker().clone());
897
898 Poll::Pending
899 }
900}
901
902#[cfg(test)]
903mod tests {
904 use crate::behaviour::autorelay;
905 use futures::StreamExt;
906 use futures::{AsyncRead, AsyncWrite};
907 use libp2p::core::muxing::StreamMuxerBox;
908 use libp2p::core::transport::{Boxed, MemoryTransport, OrTransport};
909 use libp2p::core::upgrade;
910 use libp2p::multiaddr::Protocol;
911 use libp2p::swarm::{Config, ConnectionId, NetworkBehaviour, SwarmEvent};
912 use libp2p::{Multiaddr, PeerId, Swarm, Transport, identify, identity, noise, relay, yamux};
913 use std::collections::{HashMap, HashSet};
914 use std::num::NonZeroU8;
915 use std::time::Duration;
916
917 #[tokio::test]
918 async fn autorelay_respects_max_reservations() {
919 init_tracing();
920
921 let (relay_a_peer_id, relay_a_addr) = spawn_relay();
922 let (relay_b_peer_id, relay_b_addr) = spawn_relay();
923
924 let mut client = build_client(
925 autorelay::Config::default().set_max_reservations(NonZeroU8::new(1).unwrap()),
926 );
927 client.dial(relay_a_addr).unwrap();
928 client.dial(relay_b_addr).unwrap();
929
930 let first = wait_until_some(&mut client, Duration::from_secs(20), |event| {
931 if let SwarmEvent::Behaviour(ClientEvent::RelayClient(
932 relay::client::Event::ReservationReqAccepted {
933 relay_peer_id,
934 renewal: false,
935 ..
936 },
937 )) = event
938 {
939 Some(*relay_peer_id)
940 } else {
941 None
942 }
943 })
944 .await;
945 assert!(first == relay_a_peer_id || first == relay_b_peer_id);
946
947 let mut extra = 0usize;
948 let mut settle = futures_timer::Delay::new(Duration::from_secs(5));
949 loop {
950 tokio::select! {
951 _ = &mut settle => break,
952 ev = client.select_next_some() => {
953 if matches!(
954 ev,
955 SwarmEvent::Behaviour(ClientEvent::RelayClient(
956 relay::client::Event::ReservationReqAccepted { renewal: false, .. }
957 ))
958 ) {
959 extra += 1;
960 }
961 }
962 }
963 }
964
965 assert_eq!(
966 extra, 0,
967 "autorelay opened {extra} reservation(s) beyond max_reservations=1"
968 );
969 }
970
971 #[tokio::test]
972 async fn autorelay_with_two_reservations_among_five_relays() {
973 init_tracing();
974
975 let relay_addrs: Vec<(PeerId, Multiaddr)> = (0..5).map(|_| spawn_relay()).collect();
976 let relay_peers: HashSet<PeerId> = relay_addrs.iter().map(|(p, _)| *p).collect();
977
978 let mut client = build_client(
979 autorelay::Config::default().set_max_reservations(NonZeroU8::new(2).unwrap()),
980 );
981 for (_, addr) in &relay_addrs {
982 client.dial(addr.clone()).unwrap();
983 }
984
985 let mut direct_conns: HashMap<PeerId, ConnectionId> = HashMap::new();
986 let mut reservations: HashSet<PeerId> = HashSet::new();
987
988 let mut sleep = futures_timer::Delay::new(Duration::from_secs(30));
989 loop {
990 tokio::select! {
991 _ = &mut sleep => panic!(
992 "timeout: got {} reservations, expected 2",
993 reservations.len()
994 ),
995 ev = client.select_next_some() => match ev {
996 SwarmEvent::ConnectionEstablished {
997 peer_id, connection_id, endpoint, ..
998 } if !endpoint.is_relayed() && relay_peers.contains(&peer_id) => {
999 direct_conns.insert(peer_id, connection_id);
1000 }
1001 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1002 relay::client::Event::ReservationReqAccepted {
1003 relay_peer_id,
1004 renewal: false,
1005 ..
1006 },
1007 )) => {
1008 reservations.insert(relay_peer_id);
1009 }
1010 _ => {}
1011 }
1012 }
1013 if reservations.len() == 2 {
1014 break;
1015 }
1016 }
1017
1018 let drop_peer = *reservations.iter().next().expect("two reservations held");
1019 let keep_peer = reservations
1020 .iter()
1021 .find(|p| **p != drop_peer)
1022 .copied()
1023 .expect("two reservations held");
1024 let drop_conn = *direct_conns
1025 .get(&drop_peer)
1026 .expect("direct connection observed");
1027
1028 assert!(
1029 client.close_connection(drop_conn),
1030 "should close the relay connection holding a reservation"
1031 );
1032
1033 let mut sleep = futures_timer::Delay::new(Duration::from_secs(30));
1034
1035 loop {
1036 tokio::select! {
1037 _ = &mut sleep => panic!("timeout waiting for replacement reservation"),
1038 ev = client.select_next_some() => {
1039 if let SwarmEvent::Behaviour(ClientEvent::RelayClient(
1040 relay::client::Event::ReservationReqAccepted {
1041 relay_peer_id,
1042 renewal: false,
1043 ..
1044 },
1045 )) = ev
1046 && relay_peer_id != keep_peer
1047 && relay_peer_id != drop_peer
1048 {
1049 return;
1050 }
1051 }
1052 }
1053 }
1054 }
1055
1056 #[tokio::test]
1057 async fn autorelay_drops_reservations_when_public_address_appears() {
1058 init_tracing();
1059
1060 let (_, relay_a_addr) = spawn_relay();
1061 let (_, relay_b_addr) = spawn_relay();
1062
1063 let mut client = build_client(
1064 autorelay::Config::default().set_max_reservations(NonZeroU8::new(2).unwrap()),
1065 );
1066 client.dial(relay_a_addr).unwrap();
1067 client.dial(relay_b_addr).unwrap();
1068
1069 let mut confirmed: HashSet<Multiaddr> = HashSet::new();
1070 let mut sleep = futures_timer::Delay::new(Duration::from_secs(30));
1071
1072 loop {
1073 tokio::select! {
1074 _ = &mut sleep => panic!(
1075 "timeout: got {} confirmed external addresses, expected 2",
1076 confirmed.len()
1077 ),
1078 ev = client.select_next_some() => {
1079 if let SwarmEvent::ExternalAddrConfirmed { address } = ev
1080 && address.iter().any(|p| p == Protocol::P2pCircuit)
1081 {
1082 confirmed.insert(address);
1083 }
1084 }
1085 }
1086 if confirmed.len() == 2 {
1087 break;
1088 }
1089 }
1090
1091 let public_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::<u64>()));
1092 client.add_external_address(public_addr);
1093
1094 let mut expired: HashSet<Multiaddr> = HashSet::new();
1095 let mut sleep = futures_timer::Delay::new(Duration::from_secs(15));
1096
1097 loop {
1098 tokio::select! {
1099 _ = &mut sleep => panic!(
1100 "timeout: only {}/{} relayed addresses expired",
1101 expired.len(),
1102 confirmed.len()
1103 ),
1104 ev = client.select_next_some() => {
1105 if let SwarmEvent::ExternalAddrExpired { address } = ev
1106 && confirmed.contains(&address)
1107 {
1108 expired.insert(address);
1109 }
1110 }
1111 }
1112 if expired == confirmed {
1113 break;
1114 }
1115 }
1116 }
1117
1118 #[tokio::test]
1119 async fn autorelay_expires_circuit_address_once_on_connection_close() {
1120 init_tracing();
1121
1122 let (relay_peer, relay_addr) = spawn_relay();
1123
1124 let mut client = build_client(autorelay::Config::default());
1125 client.dial(relay_addr).unwrap();
1126
1127 let (conn, circuit) = wait_until_some(&mut client, Duration::from_secs(15), {
1128 let mut direct: Option<ConnectionId> = None;
1129 move |event| match event {
1130 SwarmEvent::ConnectionEstablished {
1131 peer_id,
1132 connection_id,
1133 endpoint,
1134 ..
1135 } if *peer_id == relay_peer && !endpoint.is_relayed() => {
1136 direct = Some(*connection_id);
1137 None
1138 }
1139 SwarmEvent::ExternalAddrConfirmed { address }
1140 if address.iter().any(|p| p == Protocol::P2pCircuit) =>
1141 {
1142 direct.map(|c| (c, address.clone()))
1143 }
1144 _ => None,
1145 }
1146 })
1147 .await;
1148
1149 assert!(client.close_connection(conn));
1150
1151 let mut expired = 0usize;
1152 let mut settle = futures_timer::Delay::new(Duration::from_secs(5));
1153 loop {
1154 tokio::select! {
1155 _ = &mut settle => break,
1156 ev = client.select_next_some() => {
1157 if let SwarmEvent::ExternalAddrExpired { address } = &ev
1158 && *address == circuit
1159 {
1160 expired += 1;
1161 }
1162 }
1163 }
1164 }
1165
1166 assert_eq!(
1167 expired, 1,
1168 "expected exactly one expiry of the circuit address, got {expired}"
1169 );
1170 }
1171
1172 #[tokio::test]
1173 async fn autorelay_expires_user_circuit_listen_on_removal() {
1174 init_tracing();
1175
1176 let (relay_peer, relay_addr) = spawn_relay();
1177
1178 let mut client = build_client(autorelay::Config::default());
1179 client
1180 .behaviour_mut()
1181 .autorelay
1182 .set_status(Some(autorelay::Status::Disable));
1183
1184 let circuit_listen = relay_addr
1185 .with(Protocol::P2p(relay_peer))
1186 .with(Protocol::P2pCircuit);
1187 let listener_id = client.listen_on(circuit_listen).unwrap();
1188
1189 let circuit = wait_until_some(&mut client, Duration::from_secs(15), |event| {
1190 if let SwarmEvent::ExternalAddrConfirmed { address } = event
1191 && address.iter().any(|p| p == Protocol::P2pCircuit)
1192 {
1193 Some(address.clone())
1194 } else {
1195 None
1196 }
1197 })
1198 .await;
1199
1200 wait_until(&mut client, Duration::from_secs(10), |event| {
1201 matches!(
1202 event,
1203 SwarmEvent::NewListenAddr { address, .. } if *address == circuit
1204 )
1205 })
1206 .await;
1207
1208 assert!(client.remove_listener(listener_id));
1209
1210 wait_until(&mut client, Duration::from_secs(10), |event| {
1211 matches!(
1212 event,
1213 SwarmEvent::ExternalAddrExpired { address } if *address == circuit
1214 )
1215 })
1216 .await;
1217 }
1218
1219 #[tokio::test]
1220 async fn autorelay_blacklists_failing_relay_and_retries_after_cooldown() {
1221 init_tracing();
1222
1223 let (_, relay_addr) = spawn_rejecting_relay();
1224
1225 let cooldown = Duration::from_secs(1);
1226 let mut client = build_client(
1227 autorelay::Config::default()
1228 .set_max_reservations(NonZeroU8::new(1).unwrap())
1229 .set_failure_cooldown(cooldown),
1230 );
1231 client.dial(relay_addr).unwrap();
1232
1233 let first_failure_at =
1234 wait_for_listener_failure(&mut client, Duration::from_secs(10)).await;
1235
1236 let early_retry = with_timeout(
1237 wait_for_listener_failure(&mut client, cooldown * 5),
1238 cooldown / 2,
1239 )
1240 .await;
1241 assert!(
1242 early_retry.is_none(),
1243 "autorelay retried during the cooldown window"
1244 );
1245
1246 let second_failure_at = wait_for_listener_failure(&mut client, cooldown * 5).await;
1247 let elapsed = second_failure_at.duration_since(first_failure_at);
1248 assert!(
1249 elapsed >= cooldown,
1250 "retry should respect cooldown (elapsed {elapsed:?}, cooldown {cooldown:?})"
1251 );
1252 }
1253
1254 async fn wait_for_listener_failure(
1255 client: &mut Swarm<Client>,
1256 timeout: Duration,
1257 ) -> std::time::Instant {
1258 let mut sleep = futures_timer::Delay::new(timeout);
1259
1260 loop {
1261 tokio::select! {
1262 _ = &mut sleep => panic!("timeout waiting for listener failure"),
1263 ev = client.select_next_some() => {
1264 if let SwarmEvent::ListenerClosed { reason: Err(_), .. } = ev {
1265 return std::time::Instant::now();
1266 }
1267 }
1268 }
1269 }
1270 }
1271
1272 #[tokio::test]
1273 async fn autorelay_disabled_does_not_reserve() {
1274 init_tracing();
1275
1276 let (_, relay_addr) = spawn_relay();
1277
1278 let mut client = build_client(autorelay::Config::default());
1279 client
1280 .behaviour_mut()
1281 .autorelay
1282 .set_status(Some(autorelay::Status::Disable));
1283 client.dial(relay_addr).unwrap();
1284
1285 let observed = with_timeout(
1286 wait_until(&mut client, Duration::from_secs(5), |event| {
1287 matches!(
1288 event,
1289 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1290 relay::client::Event::ReservationReqAccepted { .. }
1291 ))
1292 )
1293 }),
1294 Duration::from_secs(3),
1295 )
1296 .await;
1297
1298 assert!(
1299 observed.is_none(),
1300 "autorelay opened a reservation while disabled"
1301 );
1302 }
1303
1304 #[tokio::test]
1305 async fn autorelay_re_enable_triggers_reservation() {
1306 init_tracing();
1307
1308 let (_, relay_addr) = spawn_relay();
1309
1310 let mut client = build_client(autorelay::Config::default());
1311 client
1312 .behaviour_mut()
1313 .autorelay
1314 .set_status(Some(autorelay::Status::Disable));
1315 client.dial(relay_addr).unwrap();
1316
1317 let mut sleep = futures_timer::Delay::new(Duration::from_secs(3));
1318
1319 loop {
1320 tokio::select! {
1321 _ = &mut sleep => break,
1322 ev = client.select_next_some() => {
1323 if matches!(
1324 ev,
1325 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1326 relay::client::Event::ReservationReqAccepted { .. }
1327 ))
1328 ) {
1329 panic!("autorelay reserved while disabled");
1330 }
1331 }
1332 }
1333 }
1334
1335 client
1336 .behaviour_mut()
1337 .autorelay
1338 .set_status(Some(autorelay::Status::Enable));
1339
1340 wait_until(&mut client, Duration::from_secs(10), |event| {
1341 matches!(
1342 event,
1343 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1344 relay::client::Event::ReservationReqAccepted { .. }
1345 ))
1346 )
1347 })
1348 .await;
1349 }
1350
1351 #[tokio::test]
1352 async fn autorelay_disable_preserves_active_reservation() {
1353 init_tracing();
1354
1355 let (_, relay_addr) = spawn_relay();
1356
1357 let mut client = build_client(autorelay::Config::default());
1358 client.dial(relay_addr).unwrap();
1359
1360 wait_until(&mut client, Duration::from_secs(20), |event| {
1361 matches!(
1362 event,
1363 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1364 relay::client::Event::ReservationReqAccepted { .. }
1365 ))
1366 )
1367 })
1368 .await;
1369
1370 client
1371 .behaviour_mut()
1372 .autorelay
1373 .set_status(Some(autorelay::Status::Disable));
1374
1375 let mut sleep = futures_timer::Delay::new(Duration::from_secs(3));
1376
1377 loop {
1378 tokio::select! {
1379 _ = &mut sleep => break,
1380 ev = client.select_next_some() => {
1381 if let SwarmEvent::ListenerClosed { reason: Err(_), .. } = ev {
1382 panic!("disabling autorelay dropped an active reservation");
1383 }
1384 if let SwarmEvent::ExternalAddrExpired { .. } = ev {
1385 panic!("disabling autorelay expired an external address");
1386 }
1387 }
1388 }
1389 }
1390 }
1391
1392 #[tokio::test]
1393 async fn autorelay_prefers_static_relay() {
1394 init_tracing();
1395
1396 let (opportunistic_peer, opportunistic_addr) = spawn_relay();
1397 let (static_peer, static_addr) = spawn_relay();
1398
1399 let mut client = build_client(
1400 autorelay::Config::default().set_max_reservations(NonZeroU8::new(1).unwrap()),
1401 );
1402 client
1403 .behaviour_mut()
1404 .autorelay
1405 .set_status(Some(autorelay::Status::Disable));
1406
1407 client.dial(opportunistic_addr).unwrap();
1408 client
1409 .behaviour_mut()
1410 .autorelay
1411 .add_static_relay(static_peer, static_addr);
1412
1413 let mut warmup = futures_timer::Delay::new(Duration::from_secs(3));
1415 loop {
1416 tokio::select! {
1417 _ = &mut warmup => break,
1418 _ = client.select_next_some() => {}
1419 }
1420 }
1421
1422 client
1423 .behaviour_mut()
1424 .autorelay
1425 .set_status(Some(autorelay::Status::Enable));
1426
1427 let accepted_peer = wait_until_some(&mut client, Duration::from_secs(15), |event| {
1428 if let SwarmEvent::Behaviour(ClientEvent::RelayClient(
1429 relay::client::Event::ReservationReqAccepted { relay_peer_id, .. },
1430 )) = event
1431 {
1432 Some(*relay_peer_id)
1433 } else {
1434 None
1435 }
1436 })
1437 .await;
1438
1439 assert_eq!(
1440 accepted_peer, static_peer,
1441 "autorelay should pick the static relay over the opportunistic one"
1442 );
1443 assert_ne!(accepted_peer, opportunistic_peer);
1444 }
1445
1446 #[tokio::test]
1447 async fn remove_static_relay_preserves_active_reservation() {
1448 init_tracing();
1449
1450 let (relay_peer, relay_addr) = spawn_relay();
1451
1452 let mut client = build_client(autorelay::Config::default());
1453 client
1454 .behaviour_mut()
1455 .autorelay
1456 .add_static_relay(relay_peer, relay_addr);
1457
1458 wait_until(&mut client, Duration::from_secs(15), |event| {
1459 matches!(
1460 event,
1461 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1462 relay::client::Event::ReservationReqAccepted { .. }
1463 ))
1464 )
1465 })
1466 .await;
1467
1468 assert!(
1469 client
1470 .behaviour_mut()
1471 .autorelay
1472 .remove_static_relay(&relay_peer)
1473 );
1474
1475 let mut sleep = futures_timer::Delay::new(Duration::from_secs(3));
1476
1477 loop {
1478 tokio::select! {
1479 _ = &mut sleep => break,
1480 ev = client.select_next_some() => {
1481 if let SwarmEvent::ListenerClosed { reason: Err(_), .. } = ev {
1482 panic!("removing static relay dropped an active reservation");
1483 }
1484 if let SwarmEvent::ExternalAddrExpired { .. } = ev {
1485 panic!("removing static relay expired an external address");
1486 }
1487 }
1488 }
1489 }
1490 }
1491
1492 #[tokio::test]
1493 async fn static_relay_redials_after_connection_drop() {
1494 init_tracing();
1495
1496 let (relay_peer, relay_addr) = spawn_relay();
1497
1498 let mut client = build_client(autorelay::Config::default());
1499 client
1500 .behaviour_mut()
1501 .autorelay
1502 .add_static_relay(relay_peer, relay_addr);
1503
1504 let conn_id =
1505 wait_for_reservation_with_conn(&mut client, relay_peer, Duration::from_secs(15)).await;
1506
1507 assert!(client.close_connection(conn_id));
1508
1509 wait_until(&mut client, Duration::from_secs(20), {
1510 let mut redialed = false;
1511 let mut reserved_again = false;
1512 move |event| {
1513 match event {
1514 SwarmEvent::ConnectionEstablished {
1515 peer_id, endpoint, ..
1516 } if *peer_id == relay_peer && !endpoint.is_relayed() => {
1517 redialed = true;
1518 }
1519 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1520 relay::client::Event::ReservationReqAccepted { relay_peer_id, .. },
1521 )) if *relay_peer_id == relay_peer => {
1522 reserved_again = true;
1523 }
1524 _ => {}
1525 }
1526 redialed && reserved_again
1527 }
1528 })
1529 .await;
1530 }
1531
1532 async fn wait_until_some<F, T>(
1533 client: &mut Swarm<Client>,
1534 timeout: Duration,
1535 mut extract: F,
1536 ) -> T
1537 where
1538 F: FnMut(&SwarmEvent<ClientEvent>) -> Option<T>,
1539 {
1540 let mut sleep = futures_timer::Delay::new(timeout);
1541
1542 loop {
1543 tokio::select! {
1544 _ = &mut sleep => panic!("timeout waiting on predicate"),
1545 ev = client.select_next_some() => {
1546 if let Some(value) = extract(&ev) {
1547 return value;
1548 }
1549 }
1550 }
1551 }
1552 }
1553
1554 #[tokio::test]
1555 async fn autorelay_emits_relay_available_after_recovery() {
1556 init_tracing();
1557
1558 let (relay_peer, relay_addr) = spawn_relay();
1559
1560 let mut client = build_client(autorelay::Config::default());
1561 client.dial(relay_addr.clone()).unwrap();
1562
1563 let conn_id =
1564 wait_for_reservation_with_conn(&mut client, relay_peer, Duration::from_secs(15)).await;
1565
1566 assert!(client.close_connection(conn_id));
1567
1568 wait_until(&mut client, Duration::from_secs(10), |event| {
1569 matches!(
1570 event,
1571 SwarmEvent::Behaviour(ClientEvent::Autorelay(autorelay::Event::NoRelaysAvailable))
1572 )
1573 })
1574 .await;
1575
1576 client.dial(relay_addr).unwrap();
1577
1578 wait_until(&mut client, Duration::from_secs(15), |event| {
1579 matches!(
1580 event,
1581 SwarmEvent::Behaviour(ClientEvent::Autorelay(autorelay::Event::RelaysAvailable))
1582 )
1583 })
1584 .await;
1585 }
1586
1587 #[tokio::test]
1588 async fn autorelay_no_relays_available_is_edge_triggered() {
1589 init_tracing();
1590
1591 let (relay_a_peer, relay_a_addr) = spawn_relay();
1592 let (relay_b_peer, relay_b_addr) = spawn_relay();
1593
1594 let mut client = build_client(autorelay::Config::default());
1595 client.dial(relay_a_addr).unwrap();
1596 client.dial(relay_b_addr).unwrap();
1597
1598 let mut conns: HashMap<PeerId, ConnectionId> = HashMap::new();
1599 let mut reserved: HashSet<PeerId> = HashSet::new();
1600 let mut sleep = futures_timer::Delay::new(Duration::from_secs(20));
1601
1602 loop {
1603 tokio::select! {
1604 _ = &mut sleep => panic!("did not get both reservations in time"),
1605 ev = client.select_next_some() => match ev {
1606 SwarmEvent::ConnectionEstablished {
1607 peer_id, connection_id, endpoint, ..
1608 } if !endpoint.is_relayed()
1609 && (peer_id == relay_a_peer || peer_id == relay_b_peer) =>
1610 {
1611 conns.insert(peer_id, connection_id);
1612 }
1613 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1614 relay::client::Event::ReservationReqAccepted { relay_peer_id, .. }
1615 )) if relay_peer_id == relay_a_peer || relay_peer_id == relay_b_peer => {
1616 reserved.insert(relay_peer_id);
1617 }
1618 _ => {}
1619 }
1620 }
1621 if reserved.len() == 2 {
1622 break;
1623 }
1624 }
1625
1626 let conn_a = *conns.get(&relay_a_peer).unwrap();
1627 let conn_b = *conns.get(&relay_b_peer).unwrap();
1628
1629 assert!(client.close_connection(conn_a));
1630 assert!(client.close_connection(conn_b));
1631
1632 let mut starved_count = 0usize;
1633 let mut sleep = futures_timer::Delay::new(Duration::from_secs(5));
1634
1635 loop {
1636 tokio::select! {
1637 _ = &mut sleep => break,
1638 ev = client.select_next_some() => {
1639 if matches!(
1640 ev,
1641 SwarmEvent::Behaviour(ClientEvent::Autorelay(
1642 autorelay::Event::NoRelaysAvailable
1643 ))
1644 ) {
1645 starved_count += 1;
1646 }
1647 }
1648 }
1649 }
1650
1651 assert_eq!(
1652 starved_count, 1,
1653 "NoRelaysAvailable should fire exactly once across multiple meet_reservation_target invocations"
1654 );
1655 }
1656
1657 #[tokio::test]
1658 async fn autorelay_resumes_after_public_address_removed() {
1659 init_tracing();
1660
1661 let (relay_peer, relay_addr) = spawn_relay();
1662
1663 let mut client = build_client(autorelay::Config::default());
1664 client.dial(relay_addr).unwrap();
1665
1666 wait_for_reservation_from(&mut client, relay_peer, Duration::from_secs(15)).await;
1667
1668 let public_addr = memory_addr();
1669 client.add_external_address(public_addr.clone());
1670
1671 wait_until(&mut client, Duration::from_secs(10), |event| {
1672 matches!(event, SwarmEvent::ExternalAddrExpired { .. })
1673 })
1674 .await;
1675
1676 client.remove_external_address(&public_addr);
1677
1678 wait_for_reservation_from(&mut client, relay_peer, Duration::from_secs(15)).await;
1679 }
1680
1681 #[tokio::test]
1682 async fn autorelay_manual_enable_ignores_public_address() {
1683 init_tracing();
1684
1685 let (relay_peer, relay_addr) = spawn_relay();
1686
1687 let mut client = build_client(autorelay::Config::default());
1688 client
1689 .behaviour_mut()
1690 .autorelay
1691 .set_status(Some(autorelay::Status::Enable));
1692 client.dial(relay_addr).unwrap();
1693
1694 wait_for_reservation_from(&mut client, relay_peer, Duration::from_secs(15)).await;
1695
1696 client.add_external_address(memory_addr());
1697
1698 let mut sleep = futures_timer::Delay::new(Duration::from_secs(3));
1699
1700 loop {
1701 tokio::select! {
1702 _ = &mut sleep => break,
1703 ev = client.select_next_some() => {
1704 if let SwarmEvent::ListenerClosed { reason: Err(_), .. } = ev {
1705 panic!("manual-Enable autorelay dropped reservation after public addr appeared");
1706 }
1707 if let SwarmEvent::ExternalAddrExpired { address } = &ev
1708 && address.iter().any(|p| p == Protocol::P2pCircuit)
1709 {
1710 panic!("manual-Enable autorelay expired the relayed external address");
1711 }
1712 if let SwarmEvent::Behaviour(ClientEvent::Autorelay(
1713 autorelay::Event::StatusChanged { status: autorelay::Status::Disable },
1714 )) = ev
1715 {
1716 panic!("manual-Enable autorelay flipped to Disable on public addr");
1717 }
1718 }
1719 }
1720 }
1721 }
1722
1723 #[tokio::test]
1724 async fn autorelay_forgets_previous_relay_on_reacquire() {
1725 init_tracing();
1726
1727 let (relay_peer, relay_addr) = spawn_relay();
1728
1729 let mut client = build_client(autorelay::Config::default());
1730 client.dial(relay_addr.clone()).unwrap();
1731
1732 let conn_id =
1733 wait_for_reservation_with_conn(&mut client, relay_peer, Duration::from_secs(15)).await;
1734
1735 assert!(client.close_connection(conn_id));
1736
1737 wait_until(&mut client, Duration::from_secs(10), |event| {
1738 matches!(
1739 event,
1740 SwarmEvent::Behaviour(ClientEvent::Autorelay(autorelay::Event::NoRelaysAvailable))
1741 )
1742 })
1743 .await;
1744
1745 assert!(
1746 client
1747 .behaviour()
1748 .autorelay
1749 .previous_relays()
1750 .any(|(p, _, _)| *p == relay_peer),
1751 "expected {relay_peer} in previous_relays after loss"
1752 );
1753
1754 client.dial(relay_addr).unwrap();
1755
1756 wait_until(&mut client, Duration::from_secs(15), |event| {
1757 matches!(
1758 event,
1759 SwarmEvent::NewListenAddr { address, .. } if address.iter().any(|p| p == Protocol::P2pCircuit)
1760 )
1761 })
1762 .await;
1763
1764 let previous: Vec<PeerId> = client
1765 .behaviour()
1766 .autorelay
1767 .previous_relays()
1768 .map(|(p, _, _)| *p)
1769 .collect();
1770 assert!(
1771 !previous.contains(&relay_peer),
1772 "expected {relay_peer} to be removed from previous_relays after re-acquire, got {previous:?}"
1773 );
1774 }
1775
1776 #[tokio::test]
1777 async fn autorelay_previous_relays_is_bounded() {
1778 init_tracing();
1779
1780 let peers_and_addrs: Vec<(PeerId, Multiaddr)> = (0..3).map(|_| spawn_relay()).collect();
1781
1782 let mut client = build_client(
1783 autorelay::Config::default()
1784 .set_max_reservations(NonZeroU8::new(1).unwrap())
1785 .set_max_previous_relays(2),
1786 );
1787
1788 for (peer, addr) in &peers_and_addrs {
1789 client.dial(addr.clone()).unwrap();
1790
1791 let conn_id =
1792 wait_for_reservation_with_conn(&mut client, *peer, Duration::from_secs(15)).await;
1793
1794 assert!(client.close_connection(conn_id));
1795
1796 wait_until(&mut client, Duration::from_secs(10), |event| {
1797 matches!(
1798 event,
1799 SwarmEvent::Behaviour(ClientEvent::Autorelay(
1800 autorelay::Event::NoRelaysAvailable
1801 ))
1802 )
1803 })
1804 .await;
1805 }
1806
1807 let previous: Vec<PeerId> = client
1808 .behaviour()
1809 .autorelay
1810 .previous_relays()
1811 .map(|(p, _, _)| *p)
1812 .collect();
1813
1814 assert_eq!(
1815 previous.len(),
1816 2,
1817 "expected previous_relays to be bounded to 2, got {previous:?}"
1818 );
1819 assert!(
1820 !previous.contains(&peers_and_addrs[0].0),
1821 "oldest relay should have been evicted: {previous:?}"
1822 );
1823 assert!(previous.contains(&peers_and_addrs[1].0));
1824 assert!(previous.contains(&peers_and_addrs[2].0));
1825 }
1826
1827 #[tokio::test]
1828 async fn autorelay_static_relay_dial_cooldown_after_failure() {
1829 init_tracing();
1830
1831 let cooldown = Duration::from_secs(2);
1832 let mut client = build_client(autorelay::Config::default().set_failure_cooldown(cooldown));
1833
1834 let unreachable_peer = PeerId::random();
1835 let unreachable_addr = memory_addr();
1836
1837 client
1838 .behaviour_mut()
1839 .autorelay
1840 .add_static_relay(unreachable_peer, unreachable_addr.clone());
1841
1842 wait_until(&mut client, Duration::from_secs(5), |event| {
1843 matches!(
1844 event,
1845 SwarmEvent::OutgoingConnectionError { peer_id: Some(p), .. } if *p == unreachable_peer
1846 )
1847 })
1848 .await;
1849
1850 let first_failure_at = std::time::Instant::now();
1851
1852 client
1853 .behaviour_mut()
1854 .autorelay
1855 .add_static_relay(unreachable_peer, unreachable_addr.clone());
1856
1857 let mut redialed = false;
1858 let mut watch = futures_timer::Delay::new(cooldown / 2);
1859 loop {
1860 tokio::select! {
1861 _ = &mut watch => break,
1862 ev = client.select_next_some() => {
1863 if matches!(
1864 ev,
1865 SwarmEvent::OutgoingConnectionError { peer_id: Some(p), .. } if p == unreachable_peer
1866 ) {
1867 redialed = true;
1868 break;
1869 }
1870 }
1871 }
1872 }
1873 assert!(!redialed, "autorelay redialed within cooldown");
1874
1875 let remaining = cooldown
1876 .checked_sub(first_failure_at.elapsed())
1877 .unwrap_or_default();
1878 if !remaining.is_zero() {
1879 futures_timer::Delay::new(remaining + Duration::from_millis(200)).await;
1880 }
1881
1882 client
1883 .behaviour_mut()
1884 .autorelay
1885 .add_static_relay(unreachable_peer, unreachable_addr);
1886
1887 wait_until(&mut client, Duration::from_secs(5), |event| {
1888 matches!(
1889 event,
1890 SwarmEvent::OutgoingConnectionError { peer_id: Some(p), .. } if *p == unreachable_peer
1891 )
1892 })
1893 .await;
1894 }
1895
1896 #[tokio::test]
1897 async fn autorelay_evicts_discovered_peers_for_static() {
1898 init_tracing();
1899
1900 let (opp_a_peer, opp_a_addr) = spawn_relay();
1901 let (opp_b_peer, opp_b_addr) = spawn_relay();
1902 let (static_peer, static_addr) = spawn_relay();
1903
1904 let mut client = build_client(
1905 autorelay::Config::default().set_max_reservations(NonZeroU8::new(1).unwrap()),
1906 );
1907
1908 client.dial(opp_a_addr).unwrap();
1909 client.dial(opp_b_addr).unwrap();
1910
1911 wait_until_some(&mut client, Duration::from_secs(20), |event| {
1912 if let SwarmEvent::Behaviour(ClientEvent::RelayClient(
1913 relay::client::Event::ReservationReqAccepted { relay_peer_id, .. },
1914 )) = event
1915 && (*relay_peer_id == opp_a_peer || *relay_peer_id == opp_b_peer)
1916 {
1917 Some(*relay_peer_id)
1918 } else {
1919 None
1920 }
1921 })
1922 .await;
1923
1924 client
1925 .behaviour_mut()
1926 .autorelay
1927 .add_static_relay(static_peer, static_addr);
1928
1929 wait_for_reservation_from(&mut client, static_peer, Duration::from_secs(20)).await;
1930 }
1931
1932 async fn wait_until<F>(client: &mut Swarm<Client>, timeout: Duration, mut predicate: F)
1933 where
1934 F: FnMut(&SwarmEvent<ClientEvent>) -> bool,
1935 {
1936 let mut sleep = futures_timer::Delay::new(timeout);
1937 loop {
1938 tokio::select! {
1939 _ = &mut sleep => panic!("timeout waiting on predicate"),
1940 ev = client.select_next_some() => {
1941 if predicate(&ev) {
1942 return;
1943 }
1944 }
1945 }
1946 }
1947 }
1948
1949 async fn with_timeout<F: Future>(future: F, timeout: Duration) -> Option<F::Output> {
1950 use futures::future::Either;
1951 let timer = futures_timer::Delay::new(timeout);
1952 futures::pin_mut!(future);
1953 match futures::future::select(future, timer).await {
1954 Either::Left((output, _)) => Some(output),
1955 Either::Right(_) => None,
1956 }
1957 }
1958
1959 async fn wait_for_reservation_from(
1960 client: &mut Swarm<Client>,
1961 peer: PeerId,
1962 timeout: Duration,
1963 ) {
1964 wait_until(client, timeout, |event| {
1965 matches!(
1966 event,
1967 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1968 relay::client::Event::ReservationReqAccepted { relay_peer_id, .. }
1969 )) if *relay_peer_id == peer
1970 )
1971 })
1972 .await;
1973 }
1974
1975 async fn wait_for_reservation_with_conn(
1976 client: &mut Swarm<Client>,
1977 peer: PeerId,
1978 timeout: Duration,
1979 ) -> ConnectionId {
1980 wait_until_some(client, timeout, {
1981 let mut established: Option<ConnectionId> = None;
1982 let mut reserved = false;
1983 move |event| {
1984 match event {
1985 SwarmEvent::ConnectionEstablished {
1986 peer_id,
1987 connection_id,
1988 endpoint,
1989 ..
1990 } if *peer_id == peer && !endpoint.is_relayed() => {
1991 established = Some(*connection_id);
1992 }
1993 SwarmEvent::Behaviour(ClientEvent::RelayClient(
1994 relay::client::Event::ReservationReqAccepted { relay_peer_id, .. },
1995 )) if *relay_peer_id == peer => {
1996 reserved = true;
1997 }
1998 _ => {}
1999 }
2000 if reserved { established } else { None }
2001 }
2002 })
2003 .await
2004 }
2005
2006 fn init_tracing() {
2007 }
2011
2012 fn memory_addr() -> Multiaddr {
2013 Multiaddr::empty().with(Protocol::Memory(rand::random::<u64>()))
2014 }
2015
2016 fn spawn_relay() -> (PeerId, Multiaddr) {
2017 spawn_relay_swarm(build_relay())
2018 }
2019
2020 fn spawn_rejecting_relay() -> (PeerId, Multiaddr) {
2021 spawn_relay_swarm(build_rejecting_relay())
2022 }
2023
2024 fn spawn_relay_swarm(mut relay: Swarm<Relay>) -> (PeerId, Multiaddr) {
2025 let addr = memory_addr();
2026 let peer = *relay.local_peer_id();
2027 relay.listen_on(addr.clone()).unwrap();
2028 relay.add_external_address(addr.clone());
2029 tokio::spawn(relay.collect::<Vec<_>>());
2030 (peer, addr)
2031 }
2032
2033 fn build_relay() -> Swarm<Relay> {
2034 build_relay_with_config(relay::Config {
2035 reservation_duration: Duration::from_secs(60),
2036 ..Default::default()
2037 })
2038 }
2039
2040 fn build_rejecting_relay() -> Swarm<Relay> {
2041 build_relay_with_config(relay::Config {
2042 max_reservations: 0,
2043 ..Default::default()
2044 })
2045 }
2046
2047 fn build_relay_with_config(config: relay::Config) -> Swarm<Relay> {
2048 let local_key = identity::Keypair::generate_ed25519();
2049 let local_peer_id = local_key.public().to_peer_id();
2050 let transport = upgrade_transport(MemoryTransport::default().boxed(), &local_key);
2051
2052 Swarm::new(
2053 transport,
2054 Relay {
2055 relay: relay::Behaviour::new(local_peer_id, config),
2056 identify: identify::Behaviour::new(identify::Config::new(
2057 "/autorelay-test/1.0.0".to_owned(),
2058 local_key.public(),
2059 )),
2060 },
2061 local_peer_id,
2062 Config::with_tokio_executor(),
2063 )
2064 }
2065
2066 fn build_client(autorelay_config: autorelay::Config) -> Swarm<Client> {
2067 let local_key = identity::Keypair::generate_ed25519();
2068 let local_peer_id = local_key.public().to_peer_id();
2069 let (relay_transport, relay_client) = relay::client::new(local_peer_id);
2070
2071 let transport = upgrade_transport(
2072 OrTransport::new(relay_transport, MemoryTransport::default()).boxed(),
2073 &local_key,
2074 );
2075
2076 Swarm::new(
2077 transport,
2078 Client {
2079 relay_client,
2080 autorelay: autorelay::Behaviour::new_with_config(autorelay_config),
2081 identify: identify::Behaviour::new(identify::Config::new(
2082 "/autorelay-test/1.0.0".to_owned(),
2083 local_key.public(),
2084 )),
2085 },
2086 local_peer_id,
2087 Config::with_tokio_executor(),
2088 )
2089 }
2090
2091 fn upgrade_transport<StreamSink>(
2092 transport: Boxed<StreamSink>,
2093 identity: &identity::Keypair,
2094 ) -> Boxed<(PeerId, StreamMuxerBox)>
2095 where
2096 StreamSink: AsyncRead + AsyncWrite + Send + Unpin + 'static,
2097 {
2098 transport
2099 .upgrade(upgrade::Version::V1)
2100 .authenticate(noise::Config::new(identity).unwrap())
2101 .multiplex(yamux::Config::default())
2102 .boxed()
2103 }
2104
2105 #[derive(NetworkBehaviour)]
2106 struct Relay {
2107 relay: relay::Behaviour,
2108 identify: identify::Behaviour,
2109 }
2110
2111 #[derive(NetworkBehaviour)]
2112 struct Client {
2113 relay_client: relay::client::Behaviour,
2114 autorelay: autorelay::Behaviour,
2115 identify: identify::Behaviour,
2116 }
2117}