libp2p_swarm/behaviour/
external_addresses.rs

1use libp2p_core::Multiaddr;
2
3use crate::behaviour::{ExternalAddrConfirmed, ExternalAddrExpired, FromSwarm};
4
5/// The maximum number of local external addresses. When reached any
6/// further externally reported addresses are ignored. The behaviour always
7/// tracks all its listen addresses.
8const MAX_LOCAL_EXTERNAL_ADDRS: usize = 20;
9
10/// Utility struct for tracking the external addresses of a [`Swarm`](crate::Swarm).
11#[derive(Debug, Clone, Default)]
12pub struct ExternalAddresses {
13    addresses: Vec<Multiaddr>,
14}
15
16impl ExternalAddresses {
17    /// Returns an [`Iterator`] over all external addresses.
18    pub fn iter(&self) -> impl ExactSizeIterator<Item = &Multiaddr> {
19        self.addresses.iter()
20    }
21
22    pub fn as_slice(&self) -> &[Multiaddr] {
23        self.addresses.as_slice()
24    }
25
26    /// Feed a [`FromSwarm`] event to this struct.
27    ///
28    /// Returns whether the event changed our set of external addresses.
29    pub fn on_swarm_event(&mut self, event: &FromSwarm) -> bool {
30        match event {
31            FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed { addr }) => {
32                if let Some(pos) = self
33                    .addresses
34                    .iter()
35                    .position(|candidate| candidate == *addr)
36                {
37                    // Refresh the existing confirmed address.
38                    self.addresses.remove(pos);
39                    self.push_front(addr);
40
41                    tracing::debug!(address=%addr, "Refreshed external address");
42
43                    return false; // No changes to our external addresses.
44                }
45
46                self.push_front(addr);
47
48                if self.addresses.len() > MAX_LOCAL_EXTERNAL_ADDRS {
49                    let expired = self.addresses.pop().expect("list to be not empty");
50
51                    tracing::debug!(
52                        external_address=%expired,
53                        address_limit=%MAX_LOCAL_EXTERNAL_ADDRS,
54                        "Removing previously confirmed external address because we reached the address limit"
55                    );
56                }
57
58                return true;
59            }
60            FromSwarm::ExternalAddrExpired(ExternalAddrExpired {
61                addr: expired_addr, ..
62            }) => {
63                let pos = match self
64                    .addresses
65                    .iter()
66                    .position(|candidate| candidate == *expired_addr)
67                {
68                    None => return false,
69                    Some(p) => p,
70                };
71
72                self.addresses.remove(pos);
73                return true;
74            }
75            _ => {}
76        }
77
78        false
79    }
80
81    fn push_front(&mut self, addr: &Multiaddr) {
82        // We have at most `MAX_LOCAL_EXTERNAL_ADDRS` so
83        // this isn't very expensive.
84        self.addresses.insert(0, addr.clone());
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use std::sync::LazyLock;
91
92    use libp2p_core::multiaddr::Protocol;
93    use rand::Rng;
94
95    use super::*;
96
97    #[test]
98    fn new_external_addr_returns_correct_changed_value() {
99        let mut addresses = ExternalAddresses::default();
100
101        let changed = addresses.on_swarm_event(&new_external_addr1());
102        assert!(changed);
103
104        let changed = addresses.on_swarm_event(&new_external_addr1());
105        assert!(!changed)
106    }
107
108    #[test]
109    fn expired_external_addr_returns_correct_changed_value() {
110        let mut addresses = ExternalAddresses::default();
111        addresses.on_swarm_event(&new_external_addr1());
112
113        let changed = addresses.on_swarm_event(&expired_external_addr1());
114        assert!(changed);
115
116        let changed = addresses.on_swarm_event(&expired_external_addr1());
117        assert!(!changed)
118    }
119
120    #[test]
121    fn more_recent_external_addresses_are_prioritized() {
122        let mut addresses = ExternalAddresses::default();
123
124        addresses.on_swarm_event(&new_external_addr1());
125        addresses.on_swarm_event(&new_external_addr2());
126
127        assert_eq!(
128            addresses.as_slice(),
129            &[(*MEMORY_ADDR_2000).clone(), (*MEMORY_ADDR_1000).clone()]
130        );
131    }
132
133    #[test]
134    fn when_pushing_more_than_max_addresses_oldest_is_evicted() {
135        let mut addresses = ExternalAddresses::default();
136
137        while addresses.as_slice().len() < MAX_LOCAL_EXTERNAL_ADDRS {
138            let random_address =
139                Multiaddr::empty().with(Protocol::Memory(rand::thread_rng().gen_range(0..1000)));
140            addresses.on_swarm_event(&FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
141                addr: &random_address,
142            }));
143        }
144
145        addresses.on_swarm_event(&new_external_addr2());
146
147        assert_eq!(addresses.as_slice().len(), 20);
148        assert_eq!(addresses.as_slice()[0], (*MEMORY_ADDR_2000).clone());
149    }
150
151    #[test]
152    fn reporting_existing_external_address_moves_it_to_the_front() {
153        let mut addresses = ExternalAddresses::default();
154
155        addresses.on_swarm_event(&new_external_addr1());
156        addresses.on_swarm_event(&new_external_addr2());
157        addresses.on_swarm_event(&new_external_addr1());
158
159        assert_eq!(
160            addresses.as_slice(),
161            &[(*MEMORY_ADDR_1000).clone(), (*MEMORY_ADDR_2000).clone()]
162        );
163    }
164
165    fn new_external_addr1() -> FromSwarm<'static> {
166        FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
167            addr: &MEMORY_ADDR_1000,
168        })
169    }
170
171    fn new_external_addr2() -> FromSwarm<'static> {
172        FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
173            addr: &MEMORY_ADDR_2000,
174        })
175    }
176
177    fn expired_external_addr1() -> FromSwarm<'static> {
178        FromSwarm::ExternalAddrExpired(ExternalAddrExpired {
179            addr: &MEMORY_ADDR_1000,
180        })
181    }
182
183    static MEMORY_ADDR_1000: LazyLock<Multiaddr> =
184        LazyLock::new(|| Multiaddr::empty().with(Protocol::Memory(1000)));
185    static MEMORY_ADDR_2000: LazyLock<Multiaddr> =
186        LazyLock::new(|| Multiaddr::empty().with(Protocol::Memory(2000)));
187}