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 libp2p_core::multiaddr::Protocol;
91    use once_cell::sync::Lazy;
92    use rand::Rng;
93
94    use super::*;
95
96    #[test]
97    fn new_external_addr_returns_correct_changed_value() {
98        let mut addresses = ExternalAddresses::default();
99
100        let changed = addresses.on_swarm_event(&new_external_addr1());
101        assert!(changed);
102
103        let changed = addresses.on_swarm_event(&new_external_addr1());
104        assert!(!changed)
105    }
106
107    #[test]
108    fn expired_external_addr_returns_correct_changed_value() {
109        let mut addresses = ExternalAddresses::default();
110        addresses.on_swarm_event(&new_external_addr1());
111
112        let changed = addresses.on_swarm_event(&expired_external_addr1());
113        assert!(changed);
114
115        let changed = addresses.on_swarm_event(&expired_external_addr1());
116        assert!(!changed)
117    }
118
119    #[test]
120    fn more_recent_external_addresses_are_prioritized() {
121        let mut addresses = ExternalAddresses::default();
122
123        addresses.on_swarm_event(&new_external_addr1());
124        addresses.on_swarm_event(&new_external_addr2());
125
126        assert_eq!(
127            addresses.as_slice(),
128            &[(*MEMORY_ADDR_2000).clone(), (*MEMORY_ADDR_1000).clone()]
129        );
130    }
131
132    #[test]
133    fn when_pushing_more_than_max_addresses_oldest_is_evicted() {
134        let mut addresses = ExternalAddresses::default();
135
136        while addresses.as_slice().len() < MAX_LOCAL_EXTERNAL_ADDRS {
137            let random_address =
138                Multiaddr::empty().with(Protocol::Memory(rand::thread_rng().gen_range(0..1000)));
139            addresses.on_swarm_event(&FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
140                addr: &random_address,
141            }));
142        }
143
144        addresses.on_swarm_event(&new_external_addr2());
145
146        assert_eq!(addresses.as_slice().len(), 20);
147        assert_eq!(addresses.as_slice()[0], (*MEMORY_ADDR_2000).clone());
148    }
149
150    #[test]
151    fn reporting_existing_external_address_moves_it_to_the_front() {
152        let mut addresses = ExternalAddresses::default();
153
154        addresses.on_swarm_event(&new_external_addr1());
155        addresses.on_swarm_event(&new_external_addr2());
156        addresses.on_swarm_event(&new_external_addr1());
157
158        assert_eq!(
159            addresses.as_slice(),
160            &[(*MEMORY_ADDR_1000).clone(), (*MEMORY_ADDR_2000).clone()]
161        );
162    }
163
164    fn new_external_addr1() -> FromSwarm<'static> {
165        FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
166            addr: &MEMORY_ADDR_1000,
167        })
168    }
169
170    fn new_external_addr2() -> FromSwarm<'static> {
171        FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
172            addr: &MEMORY_ADDR_2000,
173        })
174    }
175
176    fn expired_external_addr1() -> FromSwarm<'static> {
177        FromSwarm::ExternalAddrExpired(ExternalAddrExpired {
178            addr: &MEMORY_ADDR_1000,
179        })
180    }
181
182    static MEMORY_ADDR_1000: Lazy<Multiaddr> =
183        Lazy::new(|| Multiaddr::empty().with(Protocol::Memory(1000)));
184    static MEMORY_ADDR_2000: Lazy<Multiaddr> =
185        Lazy::new(|| Multiaddr::empty().with(Protocol::Memory(2000)));
186}