Skip to main content

zakura_network/
address_book.rs

1//! The `AddressBook` manages information about what peers exist, when they were
2//! seen, and what services they provide.
3
4use std::{
5    collections::{BTreeSet, HashMap, HashSet, VecDeque},
6    net::{IpAddr, SocketAddr},
7    sync::{Arc, Mutex, RwLock},
8    time::Instant,
9};
10
11use chrono::Utc;
12use tokio::sync::watch;
13use tracing::Span;
14
15use zakura_chain::{parameters::Network, serialization::DateTime32};
16
17use crate::{
18    constants::{self, ADDR_RESPONSE_LIMIT_DENOMINATOR, MAX_ADDRS_IN_MESSAGE},
19    meta_addr::MetaAddrChange,
20    peer_registry::PeerRegistry,
21    protocol::external::{canonical_ip, canonical_peer_addr, canonical_socket_addr},
22    types::{MetaAddr, PeerServices},
23    AddressBookPeers, PeerAddrState, PeerSocketAddr,
24};
25
26#[cfg(test)]
27mod tests;
28
29/// Read-only access to currently banned peer IP addresses.
30#[derive(Clone, Debug, Default)]
31pub struct BannedIps {
32    inner: Arc<RwLock<BanList>>,
33}
34
35/// A bounded FIFO list of banned peer IP addresses.
36#[derive(Debug, Default)]
37struct BanList {
38    /// Banned IP addresses for fast membership checks.
39    ips: HashSet<IpAddr>,
40
41    /// Banned IP addresses in insertion order for FIFO eviction.
42    insertion_order: VecDeque<IpAddr>,
43}
44
45impl BanList {
46    /// Inserts `ip`, evicting the oldest IP when the list exceeds its bound.
47    fn insert(&mut self, ip: IpAddr) {
48        let ip = canonical_ip(ip);
49
50        if !self.ips.insert(ip) {
51            return;
52        }
53
54        self.insertion_order.push_back(ip);
55
56        if self.ips.len() > constants::MAX_BANNED_IPS {
57            let oldest = self
58                .insertion_order
59                .pop_front()
60                .expect("ban order has an entry for every banned IP");
61
62            self.ips.remove(&oldest);
63        }
64    }
65}
66
67impl BannedIps {
68    /// Returns whether `ip` is currently banned.
69    pub fn contains(&self, ip: IpAddr) -> bool {
70        let ip = canonical_ip(ip);
71
72        self.inner
73            .read()
74            .expect("ban list lock should not be poisoned")
75            .ips
76            .contains(&ip)
77    }
78
79    #[cfg(test)]
80    pub(crate) fn with_banned_ip(ip: IpAddr) -> Self {
81        let bans = Self::default();
82        bans.inner
83            .write()
84            .expect("ban list lock should not be poisoned")
85            .insert(ip);
86        bans
87    }
88}
89
90/// Peer addresses indexed for direct lookup, priority order, and IP removal.
91#[derive(Clone, Debug, Default)]
92struct AddressBookIndex {
93    /// Peer metadata keyed by its canonical listener address.
94    by_addr: HashMap<PeerSocketAddr, MetaAddr>,
95
96    /// Peer metadata in connection-attempt order.
97    by_priority: BTreeSet<MetaAddr>,
98
99    /// Canonical listener addresses grouped by canonical IP.
100    by_ip: HashMap<IpAddr, HashSet<PeerSocketAddr>>,
101}
102
103impl AddressBookIndex {
104    fn len(&self) -> usize {
105        self.by_addr.len()
106    }
107
108    fn get(&self, addr: &PeerSocketAddr) -> Option<MetaAddr> {
109        self.by_addr.get(addr).copied()
110    }
111
112    fn insert(&mut self, meta_addr: MetaAddr) {
113        let addr = meta_addr.addr;
114        let previous = self.by_addr.insert(addr, meta_addr);
115        if let Some(previous) = previous {
116            assert!(
117                self.by_priority.remove(&previous),
118                "replaced peer must exist in the priority index"
119            );
120        } else {
121            assert!(
122                self.by_ip.entry(addr.ip()).or_default().insert(addr),
123                "new peer must not exist in the IP index"
124            );
125        }
126
127        assert!(
128            self.by_priority.insert(meta_addr),
129            "new peer metadata must be unique in the priority index"
130        );
131    }
132
133    fn remove(&mut self, addr: &PeerSocketAddr) -> Option<MetaAddr> {
134        let removed = self.by_addr.remove(addr)?;
135
136        assert!(
137            self.by_priority.remove(&removed),
138            "removed peer must exist in the priority index"
139        );
140
141        let remove_ip = {
142            let Some(same_ip_addrs) = self.by_ip.get_mut(&addr.ip()) else {
143                panic!("removed peer IP must exist in the IP index");
144            };
145            assert!(
146                same_ip_addrs.remove(addr),
147                "removed peer must exist in the IP index"
148            );
149            same_ip_addrs.is_empty()
150        };
151        if remove_ip {
152            self.by_ip.remove(&addr.ip());
153        }
154
155        Some(removed)
156    }
157
158    fn remove_ip(&mut self, ip: IpAddr) {
159        let ip = canonical_ip(ip);
160        let Some(addrs) = self.by_ip.remove(&ip) else {
161            return;
162        };
163
164        for addr in addrs {
165            let removed = self
166                .by_addr
167                .remove(&addr)
168                .expect("IP index peer must exist in the address index");
169            assert!(
170                self.by_priority.remove(&removed),
171                "IP index peer must exist in the priority index"
172            );
173        }
174    }
175
176    fn ordered_values(&self) -> impl DoubleEndedIterator<Item = &MetaAddr> {
177        self.by_priority.iter()
178    }
179
180    #[cfg(test)]
181    fn assert_consistent(&self) {
182        assert_eq!(self.by_addr.len(), self.by_priority.len());
183        assert_eq!(
184            self.by_addr.len(),
185            self.by_ip.values().map(HashSet::len).sum::<usize>()
186        );
187
188        for (addr, meta_addr) in &self.by_addr {
189            assert_eq!(*addr, meta_addr.addr);
190            assert!(self.by_priority.contains(meta_addr));
191            assert!(self
192                .by_ip
193                .get(&addr.ip())
194                .is_some_and(|same_ip_addrs| same_ip_addrs.contains(addr)));
195        }
196
197        for meta_addr in self.ordered_values() {
198            assert_eq!(self.by_addr.get(&meta_addr.addr), Some(meta_addr));
199        }
200
201        for (ip, addrs) in &self.by_ip {
202            for addr in addrs {
203                assert_eq!(&addr.ip(), ip);
204                assert!(self.by_addr.contains_key(addr));
205            }
206        }
207
208        let peers: Vec<_> = self.ordered_values().copied().collect();
209        assert!(peers.windows(2).all(|pair| pair[0] <= pair[1]));
210    }
211}
212
213/// A database of peer listener addresses, their advertised services, and
214/// information on when they were last seen.
215///
216/// # Security
217///
218/// Address book state must be based on outbound connections to peers.
219///
220/// If the address book is updated incorrectly:
221/// - malicious peers can interfere with other peers' `AddressBook` state,
222///   or
223/// - Zebra can advertise unreachable addresses to its own peers.
224///
225/// ## Adding Addresses
226///
227/// The address book should only contain Zcash listener port addresses from peers
228/// on the configured network. These addresses can come from:
229/// - DNS seeders
230/// - addresses gossiped by other peers
231/// - the canonical address (`Version.address_from`) provided by each peer,
232///   particularly peers on inbound connections.
233///
234/// The remote addresses of inbound connections must not be added to the address
235/// book, because they contain ephemeral outbound ports, not listener ports.
236///
237/// Isolated connections must not add addresses or update the address book.
238///
239/// ## Updating Address State
240///
241/// Updates to address state must be based on outbound connections to peers.
242///
243/// Updates must not be based on:
244/// - the remote addresses of inbound connections, or
245/// - the canonical address of any connection.
246#[derive(Debug)]
247pub struct AddressBook {
248    /// Peer listener addresses, suitable for outbound connections,
249    /// in connection attempt order.
250    ///
251    /// Some peers in this list might have open outbound or inbound connections.
252    ///
253    /// Direct, priority-ordered, and per-IP indexes are updated together.
254    peers: AddressBookIndex,
255
256    /// The address with a last_connection_state of [`PeerAddrState::Responded`] and
257    /// the most recent `last_response` time by IP.
258    ///
259    /// This is used to avoid initiating outbound connections past [`Config::max_connections_per_ip`](crate::config::Config), and
260    /// currently only supports a `max_connections_per_ip` of 1, and must be `None` when used with a greater `max_connections_per_ip`.
261    // TODO: Replace with `by_ip: HashMap<IpAddr, BTreeMap<DateTime32, MetaAddr>>` to support configured `max_connections_per_ip` greater than 1
262    most_recent_by_ip: Option<HashMap<IpAddr, MetaAddr>>,
263
264    /// A list of banned addresses, with the time they were banned.
265    bans_by_ip: BannedIps,
266
267    /// The local listener address.
268    local_listener: SocketAddr,
269
270    /// The services advertised for our own [`local_listener`](Self::local_listener)
271    /// address when it is gossiped to peers.
272    ///
273    /// This must match the services advertised during the handshake, so a node
274    /// that does not advertise [`PeerServices::NODE_NETWORK`] (for example, a
275    /// pruned node) does not gossip itself as a full node.
276    local_listener_services: PeerServices,
277
278    /// The configured Zcash network.
279    network: Network,
280
281    /// Active connection metadata, stored outside address discovery state.
282    peer_registry: Option<PeerRegistry>,
283
284    /// The maximum number of addresses in the address book.
285    ///
286    /// Always set to [`MAX_ADDRS_IN_ADDRESS_BOOK`](constants::MAX_ADDRS_IN_ADDRESS_BOOK),
287    /// in release builds. Lower values are used during testing.
288    addr_limit: usize,
289
290    /// The span for operations on this address book.
291    span: Span,
292
293    /// Whether operational log fields expose legacy peer addresses.
294    expose_peer_addresses: bool,
295
296    /// A channel used to send the latest address book metrics.
297    address_metrics_tx: watch::Sender<AddressMetrics>,
298
299    /// Whether the address book changed since metrics were last published.
300    address_metrics_dirty: bool,
301
302    #[cfg(test)]
303    address_metrics_update_count: usize,
304
305    /// The last time we logged a message about the address metrics.
306    last_address_log: Option<Instant>,
307}
308
309/// Metrics about the states of the addresses in an [`AddressBook`].
310#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
311pub struct AddressMetrics {
312    /// The number of addresses in the `Responded` state.
313    pub responded: usize,
314
315    /// The number of addresses in the `NeverAttemptedGossiped` state.
316    pub never_attempted_gossiped: usize,
317
318    /// The number of addresses in the `Failed` state.
319    pub failed: usize,
320
321    /// The number of addresses in the `AttemptPending` state.
322    pub attempt_pending: usize,
323
324    /// The number of `Responded` addresses within the liveness limit.
325    pub recently_live: usize,
326
327    /// The number of `Responded` addresses outside the liveness limit.
328    pub recently_stopped_responding: usize,
329
330    /// The number of addresses in the address book, regardless of their states.
331    pub num_addresses: usize,
332
333    /// The maximum number of addresses in the address book.
334    pub address_limit: usize,
335}
336
337#[allow(clippy::len_without_is_empty)]
338impl AddressBook {
339    /// Construct an [`AddressBook`] with the given `local_listener` on `network`.
340    ///
341    /// Uses the supplied [`tracing::Span`] for address book operations.
342    pub fn new(
343        local_listener: SocketAddr,
344        network: &Network,
345        max_connections_per_ip: usize,
346        span: Span,
347    ) -> AddressBook {
348        let constructor_span = span.clone();
349        let _guard = constructor_span.enter();
350
351        let instant_now = Instant::now();
352        let chrono_now = Utc::now();
353
354        // The default value is correct for an empty address book,
355        // and it gets replaced by `update_metrics` anyway.
356        let (address_metrics_tx, _address_metrics_rx) = watch::channel(AddressMetrics::default());
357
358        // Avoid initiating outbound handshakes when max_connections_per_ip is 1.
359        let should_limit_outbound_conns_per_ip = max_connections_per_ip == 1;
360        let mut new_book = AddressBook {
361            peers: AddressBookIndex::default(),
362            local_listener: canonical_socket_addr(local_listener),
363            // Default to full-node services; callers that advertise different
364            // services set them with `with_local_listener_services`.
365            local_listener_services: PeerServices::NODE_NETWORK,
366            network: network.clone(),
367            peer_registry: None,
368            addr_limit: constants::MAX_ADDRS_IN_ADDRESS_BOOK,
369            span,
370            expose_peer_addresses: false,
371            address_metrics_tx,
372            address_metrics_dirty: false,
373            #[cfg(test)]
374            address_metrics_update_count: 0,
375            last_address_log: None,
376            most_recent_by_ip: should_limit_outbound_conns_per_ip.then(HashMap::new),
377            bans_by_ip: Default::default(),
378        };
379
380        new_book.update_metrics(instant_now, chrono_now);
381        new_book
382    }
383
384    /// Sets the services advertised for our own gossiped listener address.
385    ///
386    /// These must match the services advertised during the handshake.
387    #[must_use]
388    pub fn with_local_listener_services(mut self, services: PeerServices) -> Self {
389        self.local_listener_services = services;
390        self
391    }
392
393    /// Sets whether operational logs expose legacy peer addresses.
394    #[must_use]
395    pub(crate) fn with_expose_peer_addresses(mut self, expose_peer_addresses: bool) -> Self {
396        self.expose_peer_addresses = expose_peer_addresses;
397        self
398    }
399
400    /// Attach the active peer registry used by local RPC diagnostics.
401    #[must_use]
402    pub(crate) fn with_peer_registry(mut self, peer_registry: PeerRegistry) -> Self {
403        self.peer_registry = Some(peer_registry);
404        self
405    }
406
407    /// Construct an [`AddressBook`] with the given `local_listener`, `network`,
408    /// `addr_limit`, [`tracing::Span`], and addresses.
409    ///
410    /// `addr_limit` is enforced by this method, and by [`AddressBook::update`].
411    ///
412    /// If there are multiple [`MetaAddr`]s with the same address,
413    /// an arbitrary address is inserted into the address book,
414    /// and the rest are dropped.
415    ///
416    /// This constructor can be used to break address book invariants,
417    /// so it should only be used in tests.
418    #[cfg(any(test, feature = "proptest-impl"))]
419    pub fn new_with_addrs(
420        local_listener: SocketAddr,
421        network: &Network,
422        max_connections_per_ip: usize,
423        addr_limit: usize,
424        span: Span,
425        addrs: impl IntoIterator<Item = MetaAddr>,
426    ) -> AddressBook {
427        let constructor_span = span.clone();
428        let _guard = constructor_span.enter();
429
430        let instant_now = Instant::now();
431        let chrono_now = Utc::now();
432
433        // The maximum number of addresses should be always greater than 0
434        assert!(addr_limit > 0);
435
436        let mut new_book = AddressBook::new(local_listener, network, max_connections_per_ip, span);
437        new_book.addr_limit = addr_limit;
438
439        let addrs = addrs
440            .into_iter()
441            .map(|mut meta_addr| {
442                meta_addr.addr = canonical_peer_addr(meta_addr.addr);
443                meta_addr
444            })
445            .filter(|meta_addr| meta_addr.address_is_valid_for_outbound(network))
446            .map(|meta_addr| (meta_addr.addr, meta_addr));
447
448        for (socket_addr, meta_addr) in addrs {
449            // overwrite any duplicate addresses
450            new_book.peers.insert(meta_addr);
451            // Add the address to `most_recent_by_ip` if it has responded
452            if new_book.should_update_most_recent_by_ip(meta_addr) {
453                new_book
454                    .most_recent_by_ip
455                    .as_mut()
456                    .expect("should be some when should_update_most_recent_by_ip is true")
457                    .insert(socket_addr.ip(), meta_addr);
458            }
459            // exit as soon as we get enough addresses
460            if new_book.peers.len() >= addr_limit {
461                break;
462            }
463        }
464
465        new_book.update_metrics(instant_now, chrono_now);
466        new_book
467    }
468
469    /// Return a watch channel for the address book metrics.
470    ///
471    /// The metrics in the watch channel are only updated when the address book updates,
472    /// so they can be significantly outdated if Zebra is disconnected or hung.
473    ///
474    /// The current metrics value is marked as seen.
475    /// So `Receiver::changed` will only return after the next address book update.
476    pub fn address_metrics_watcher(&self) -> watch::Receiver<AddressMetrics> {
477        self.address_metrics_tx.subscribe()
478    }
479
480    /// Set the local listener address. Only for use in tests.
481    #[cfg(any(test, feature = "proptest-impl"))]
482    pub fn set_local_listener(&mut self, addr: SocketAddr) {
483        self.local_listener = addr;
484    }
485
486    /// Get the local listener address.
487    ///
488    /// This address contains minimal state, but it is not sanitized.
489    pub fn local_listener_meta_addr(&self, now: chrono::DateTime<Utc>) -> MetaAddr {
490        let now: DateTime32 = now.try_into().expect("will succeed until 2038");
491
492        MetaAddr::new_local_listener_change(self.local_listener, self.local_listener_services)
493            .local_listener_into_new_meta_addr(now)
494    }
495
496    /// Get the local listener [`SocketAddr`].
497    pub fn local_listener_socket_addr(&self) -> SocketAddr {
498        self.local_listener
499    }
500
501    /// Get the active addresses in `self` in random order with sanitized timestamps,
502    /// including our local listener address.
503    ///
504    /// Limited to the number of peer addresses Zebra should give out per `GetAddr` request.
505    pub fn fresh_get_addr_response(&self) -> Vec<MetaAddr> {
506        let now = Utc::now();
507        let mut peers = self.sanitized(now);
508        let address_limit = peers.len().div_ceil(ADDR_RESPONSE_LIMIT_DENOMINATOR);
509        peers.truncate(MAX_ADDRS_IN_MESSAGE.min(address_limit));
510
511        peers
512    }
513
514    /// Get the active addresses in `self` in random order with sanitized timestamps,
515    /// including our local listener address.
516    pub(crate) fn sanitized(&self, now: chrono::DateTime<Utc>) -> Vec<MetaAddr> {
517        use rand::seq::SliceRandom;
518        let _guard = self.span.enter();
519
520        let mut peers = self.peers.clone();
521
522        // Unconditionally add our local listener address to the advertised peers,
523        // to replace any self-connection failures. The address book and change
524        // constructors make sure that the SocketAddr is canonical.
525        let local_listener = self.local_listener_meta_addr(now);
526        peers.insert(local_listener);
527
528        // Then sanitize and shuffle
529        let mut peers: Vec<MetaAddr> = peers
530            .ordered_values()
531            .filter_map(|meta_addr| meta_addr.sanitize(&self.network))
532            // # Security
533            //
534            // Remove peers that:
535            //   - last responded more than three hours ago, or
536            //   - haven't responded yet but were reported last seen more than three hours ago
537            //
538            // This prevents Zebra from gossiping nodes that are likely unreachable. Gossiping such
539            // nodes impacts the network health, because connection attempts end up being wasted on
540            // peers that are less likely to respond.
541            .filter(|addr| addr.is_active_for_gossip(now))
542            .collect();
543
544        peers.shuffle(&mut rand::thread_rng());
545
546        peers
547    }
548
549    /// Get the active addresses in `self`, in preferred caching order,
550    /// excluding our local listener address.
551    pub fn cacheable(&self, now: chrono::DateTime<Utc>) -> Vec<MetaAddr> {
552        let _guard = self.span.enter();
553
554        let peers = self.peers.clone();
555
556        // Get peers in preferred order, then keep the recently active ones
557        peers
558            .ordered_values()
559            // # Security
560            //
561            // Remove peers that:
562            //   - last responded more than three hours ago, or
563            //   - haven't responded yet but were reported last seen more than three hours ago
564            //
565            // This prevents Zebra from caching nodes that are likely unreachable,
566            // which improves startup time and reliability.
567            .filter(|addr| addr.is_active_for_gossip(now))
568            .cloned()
569            .collect()
570    }
571
572    /// Look up `addr` in the address book, and return its [`MetaAddr`].
573    ///
574    /// Converts `addr` to a canonical address before looking it up.
575    pub fn get(&mut self, addr: PeerSocketAddr) -> Option<MetaAddr> {
576        let addr = canonical_peer_addr(*addr);
577        self.peers.get(&addr)
578    }
579
580    /// Returns true if `updated` needs to be applied to the recent outbound peer connection IP cache.
581    ///
582    /// Checks if there are no existing entries in the address book with this IP,
583    /// or if `updated` has a more recent `last_response` requiring the outbound connector to wait
584    /// longer before initiating handshakes with peers at this IP.
585    ///
586    /// This code only needs to check a single cache entry, rather than the entire address book,
587    /// because other code maintains these invariants:
588    /// - `last_response` times for an entry can only increase.
589    /// - this is the only field checked by `has_connection_recently_responded()`
590    ///
591    /// See [`AddressBook::is_ready_for_connection_attempt_with_ip`] for more details.
592    fn should_update_most_recent_by_ip(&self, updated: MetaAddr) -> bool {
593        let Some(most_recent_by_ip) = self.most_recent_by_ip.as_ref() else {
594            return false;
595        };
596
597        if let Some(previous) = most_recent_by_ip.get(&updated.addr.ip()) {
598            updated.last_connection_state == PeerAddrState::Responded
599                && updated.last_response() > previous.last_response()
600        } else {
601            updated.last_connection_state == PeerAddrState::Responded
602        }
603    }
604
605    /// Returns true if `addr` is the latest entry for its IP, which is stored in `most_recent_by_ip`.
606    /// The entry is checked for an exact match to the IP and port of `addr`.
607    fn should_remove_most_recent_by_ip(&self, addr: PeerSocketAddr) -> bool {
608        let Some(most_recent_by_ip) = self.most_recent_by_ip.as_ref() else {
609            return false;
610        };
611
612        if let Some(previous) = most_recent_by_ip.get(&addr.ip()) {
613            previous.addr == addr
614        } else {
615            false
616        }
617    }
618
619    /// Apply `change` to the address book, returning the updated `MetaAddr`,
620    /// if the change was valid.
621    ///
622    /// # Correctness
623    ///
624    /// All changes should go through `update`, so that the address book
625    /// only contains valid outbound addresses.
626    ///
627    /// Change addresses must be canonical `PeerSocketAddr`s. This makes sure that
628    /// each address book entry has a unique IP address.
629    ///
630    /// # Security
631    ///
632    /// This function must apply every attempted, responded, and failed change
633    /// to the address book. This prevents rapid reconnections to the same peer.
634    ///
635    /// As an exception, this function can ignore all changes for specific
636    /// [`PeerSocketAddr`]s. Ignored addresses will never be used to connect to
637    /// peers.
638    pub fn update(&mut self, change: MetaAddrChange) -> Option<MetaAddr> {
639        let updated = self.update_inner(change);
640        self.publish_metrics();
641        updated
642    }
643
644    /// Apply `change` without immediately publishing address book metrics.
645    #[allow(clippy::unwrap_in_result)]
646    fn update_inner(&mut self, change: MetaAddrChange) -> Option<MetaAddr> {
647        let addr_label = change.addr().addr_label(self.expose_peer_addresses);
648
649        if self.bans_by_ip.contains(change.addr().ip()) {
650            tracing::warn!(
651                peer = %addr_label,
652                ?change,
653                "attempted to add a banned peer addr to address book"
654            );
655            return None;
656        }
657
658        let previous = self.get(change.addr());
659
660        let _guard = self.span.enter();
661
662        let instant_now = Instant::now();
663        let chrono_now = Utc::now();
664
665        let updated = change.apply_to_meta_addr(previous, instant_now, chrono_now);
666
667        trace!(
668            peer = %addr_label,
669            ?change,
670            ?updated,
671            ?previous,
672            total_peers = self.peers.len(),
673            recent_peers = self.recently_live_peers(chrono_now).len(),
674            "calculated updated address book entry",
675        );
676
677        if let Some(updated) = updated {
678            if updated.misbehavior() >= constants::MAX_PEER_MISBEHAVIOR_SCORE {
679                // Ban and skip outbound connections with excessively misbehaving peers.
680                let banned_ip = updated.addr.ip();
681                {
682                    let mut bans_by_ip = self
683                        .bans_by_ip
684                        .inner
685                        .write()
686                        .expect("ban list lock should not be poisoned");
687
688                    bans_by_ip.insert(banned_ip);
689                }
690
691                // `most_recent_by_ip` is only populated when
692                // `max_connections_per_ip == 1`. The ban path runs for any
693                // configured value, so we must guard the optional cache rather
694                // than unwrap it.
695                if let Some(most_recent_by_ip) = self.most_recent_by_ip.as_mut() {
696                    most_recent_by_ip.remove(&banned_ip);
697                }
698
699                self.peers.remove_ip(banned_ip);
700                self.address_metrics_dirty = true;
701
702                warn!(
703                    peer = %addr_label,
704                    ?updated,
705                    total_peers = self.peers.len(),
706                    recent_peers = self.recently_live_peers(chrono_now).len(),
707                    "banned ip and removed banned peer addresses from address book",
708                );
709
710                return None;
711            }
712
713            // Ignore invalid outbound addresses.
714            // (Inbound connections can be monitored via Zebra's metrics.)
715            if !updated.address_is_valid_for_outbound(&self.network) {
716                return None;
717            }
718
719            // Ignore invalid outbound services and other info,
720            // but only if the peer has never been attempted.
721            //
722            // Otherwise, if we got the info directly from the peer,
723            // store it in the address book, so we know not to reconnect.
724            if !updated.last_known_info_is_valid_for_outbound(&self.network)
725                && updated.last_connection_state.is_never_attempted()
726            {
727                return None;
728            }
729
730            self.peers.insert(updated);
731            self.address_metrics_dirty = true;
732
733            // Add the address to `most_recent_by_ip` if it sent the most recent
734            // response Zebra has received from this IP.
735            if self.should_update_most_recent_by_ip(updated) {
736                self.most_recent_by_ip
737                    .as_mut()
738                    .expect("should be some when should_update_most_recent_by_ip is true")
739                    .insert(updated.addr.ip(), updated);
740            }
741
742            debug!(
743                peer = %addr_label,
744                ?change,
745                ?updated,
746                ?previous,
747                total_peers = self.peers.len(),
748                recent_peers = self.recently_live_peers(chrono_now).len(),
749                "updated address book entry",
750            );
751
752            // Security: Limit the number of peers in the address book.
753            //
754            // We only delete outdated peers when we have too many peers.
755            // If we deleted them as soon as they became too old,
756            // then other peers could re-insert them into the address book.
757            // And we would start connecting to those outdated peers again,
758            // ignoring the age limit in [`MetaAddr::is_probably_reachable`].
759            while self.peers.len() > self.addr_limit {
760                let surplus_peer = self
761                    .peers()
762                    .next_back()
763                    .expect("just checked there is at least one peer");
764
765                self.peers.remove(&surplus_peer.addr);
766
767                // Check if this surplus peer's addr matches that in `most_recent_by_ip`
768                // for this the surplus peer's ip to remove it there as well.
769                if self.should_remove_most_recent_by_ip(surplus_peer.addr) {
770                    self.most_recent_by_ip
771                        .as_mut()
772                        .expect("should be some when should_remove_most_recent_by_ip is true")
773                        .remove(&surplus_peer.addr.ip());
774                }
775
776                debug!(
777                    surplus = ?surplus_peer,
778                    ?updated,
779                    total_peers = self.peers.len(),
780                    recent_peers = self.recently_live_peers(chrono_now).len(),
781                    "removed surplus address book entry",
782                );
783            }
784
785            assert!(self.len() <= self.addr_limit);
786        }
787
788        updated
789    }
790
791    /// Removes the entry with `addr`, returning it if it exists
792    ///
793    /// # Note
794    ///
795    /// All address removals should go through `take`, so that the address
796    /// book metrics are accurate.
797    #[allow(dead_code)]
798    fn take(&mut self, removed_addr: PeerSocketAddr) -> Option<MetaAddr> {
799        let _guard = self.span.enter();
800
801        let chrono_now = Utc::now();
802
803        trace!(
804            ?removed_addr,
805            total_peers = self.peers.len(),
806            recent_peers = self.recently_live_peers(chrono_now).len(),
807        );
808
809        if let Some(entry) = self.peers.remove(&removed_addr) {
810            self.address_metrics_dirty = true;
811
812            // Check if this surplus peer's addr matches that in `most_recent_by_ip`
813            // for this the surplus peer's ip to remove it there as well.
814            if self.should_remove_most_recent_by_ip(entry.addr) {
815                if let Some(most_recent_by_ip) = self.most_recent_by_ip.as_mut() {
816                    most_recent_by_ip.remove(&entry.addr.ip());
817                }
818            }
819
820            std::mem::drop(_guard);
821            self.publish_metrics();
822            Some(entry)
823        } else {
824            None
825        }
826    }
827
828    /// Returns true if the given [`PeerSocketAddr`] is pending a reconnection
829    /// attempt.
830    pub fn pending_reconnection_addr(&mut self, addr: PeerSocketAddr) -> bool {
831        let meta_addr = self.get(addr);
832
833        let _guard = self.span.enter();
834        match meta_addr {
835            None => false,
836            Some(peer) => peer.last_connection_state == PeerAddrState::AttemptPending,
837        }
838    }
839
840    /// Return an iterator over all peers.
841    ///
842    /// Returns peers in reconnection attempt order, including recently connected peers.
843    pub fn peers(&'_ self) -> impl DoubleEndedIterator<Item = MetaAddr> + '_ {
844        let _guard = self.span.enter();
845        self.peers.ordered_values().cloned()
846    }
847
848    /// Is this IP ready for a new outbound connection attempt?
849    /// Checks if the outbound connection with the most recent response at this IP has recently responded.
850    ///
851    /// Note: last_response times may remain live for a long time if the local clock is changed to an earlier time.
852    fn is_ready_for_connection_attempt_with_ip(
853        &self,
854        ip: &IpAddr,
855        chrono_now: chrono::DateTime<Utc>,
856    ) -> bool {
857        let Some(most_recent_by_ip) = self.most_recent_by_ip.as_ref() else {
858            // if we're not checking IPs, any connection is allowed
859            return true;
860        };
861        let Some(same_ip_peer) = most_recent_by_ip.get(ip) else {
862            // If there's no entry for this IP, any connection is allowed
863            return true;
864        };
865        !same_ip_peer.has_connection_recently_responded(chrono_now)
866    }
867
868    /// Return an iterator over peers that are due for a reconnection attempt,
869    /// in reconnection attempt order.
870    pub fn reconnection_peers(
871        &'_ self,
872        instant_now: Instant,
873        chrono_now: chrono::DateTime<Utc>,
874    ) -> impl DoubleEndedIterator<Item = MetaAddr> + '_ {
875        let _guard = self.span.enter();
876
877        // Skip live peers, and peers pending a reconnect attempt.
878        // The peers are already stored in sorted order.
879        self.peers
880            .ordered_values()
881            .filter(move |peer| {
882                peer.is_ready_for_connection_attempt(instant_now, chrono_now, &self.network)
883                    && self.is_ready_for_connection_attempt_with_ip(&peer.addr.ip(), chrono_now)
884            })
885            .cloned()
886    }
887
888    /// Return an iterator over all the peers in `state`,
889    /// in reconnection attempt order, including recently connected peers.
890    pub fn state_peers(
891        &'_ self,
892        state: PeerAddrState,
893    ) -> impl DoubleEndedIterator<Item = MetaAddr> + '_ {
894        let _guard = self.span.enter();
895
896        self.peers
897            .ordered_values()
898            .filter(move |peer| peer.last_connection_state == state)
899            .cloned()
900    }
901
902    /// Return an iterator over peers that might be connected,
903    /// in reconnection attempt order.
904    pub fn maybe_connected_peers(
905        &'_ self,
906        instant_now: Instant,
907        chrono_now: chrono::DateTime<Utc>,
908    ) -> impl DoubleEndedIterator<Item = MetaAddr> + '_ {
909        let _guard = self.span.enter();
910
911        self.peers
912            .ordered_values()
913            .filter(move |peer| {
914                !peer.is_ready_for_connection_attempt(instant_now, chrono_now, &self.network)
915            })
916            .cloned()
917    }
918
919    /// Returns banned IP addresses.
920    pub fn bans(&self) -> BannedIps {
921        self.bans_by_ip.clone()
922    }
923
924    /// Clones this address book with an independent, empty ban list.
925    #[cfg(feature = "internal-bench")]
926    #[doc(hidden)]
927    pub fn clone_with_fresh_bans_for_benchmark(&self) -> Self {
928        let mut address_book = self.clone();
929        address_book.bans_by_ip = BannedIps::default();
930        address_book
931    }
932
933    /// Returns the number of entries in this address book.
934    pub fn len(&self) -> usize {
935        self.peers.len()
936    }
937
938    /// Returns metrics for the addresses in this address book.
939    /// Only for use in tests.
940    ///
941    /// # Correctness
942    ///
943    /// Use [`AddressBook::address_metrics_watcher`] in production code,
944    /// to avoid deadlocks.
945    #[cfg(test)]
946    pub fn address_metrics(&self, now: chrono::DateTime<Utc>) -> AddressMetrics {
947        self.address_metrics_internal(now)
948    }
949
950    /// Returns metrics for the addresses in this address book.
951    ///
952    /// # Correctness
953    ///
954    /// External callers should use [`AddressBook::address_metrics_watcher`]
955    /// in production code, to avoid deadlocks.
956    /// (Using the watch channel receiver does not lock the address book mutex.)
957    fn address_metrics_internal(&self, now: chrono::DateTime<Utc>) -> AddressMetrics {
958        let responded = self.state_peers(PeerAddrState::Responded).count();
959        let never_attempted_gossiped = self
960            .state_peers(PeerAddrState::NeverAttemptedGossiped)
961            .count();
962        let failed = self.state_peers(PeerAddrState::Failed).count();
963        let attempt_pending = self.state_peers(PeerAddrState::AttemptPending).count();
964
965        let recently_live = self.recently_live_peers(now).len();
966        let recently_stopped_responding = responded
967            .checked_sub(recently_live)
968            .expect("all recently live peers must have responded");
969
970        let num_addresses = self.len();
971
972        AddressMetrics {
973            responded,
974            never_attempted_gossiped,
975            failed,
976            attempt_pending,
977            recently_live,
978            recently_stopped_responding,
979            num_addresses,
980            address_limit: self.addr_limit,
981        }
982    }
983
984    /// Publish metrics if the address book changed since the previous update.
985    fn publish_metrics(&mut self) {
986        if !self.address_metrics_dirty {
987            return;
988        }
989
990        self.address_metrics_dirty = false;
991        self.update_metrics(Instant::now(), Utc::now());
992    }
993
994    /// Update the metrics for this address book.
995    fn update_metrics(&mut self, instant_now: Instant, chrono_now: chrono::DateTime<Utc>) {
996        let _guard = self.span.enter();
997
998        #[cfg(test)]
999        {
1000            self.address_metrics_update_count += 1;
1001        }
1002
1003        let m = self.address_metrics_internal(chrono_now);
1004
1005        // Ignore errors: we don't care if any receivers are listening.
1006        let _ = self.address_metrics_tx.send(m);
1007
1008        // TODO: rename to address_book.[state_name]
1009        metrics::gauge!("candidate_set.responded").set(m.responded as f64);
1010        metrics::gauge!("candidate_set.gossiped").set(m.never_attempted_gossiped as f64);
1011        metrics::gauge!("candidate_set.failed").set(m.failed as f64);
1012        metrics::gauge!("candidate_set.pending").set(m.attempt_pending as f64);
1013
1014        // TODO: rename to address_book.responded.recently_live
1015        metrics::gauge!("candidate_set.recently_live").set(m.recently_live as f64);
1016        // TODO: rename to address_book.responded.stopped_responding
1017        metrics::gauge!("candidate_set.disconnected").set(m.recently_stopped_responding as f64);
1018
1019        std::mem::drop(_guard);
1020        self.log_metrics(&m, instant_now);
1021    }
1022
1023    /// Log metrics for this address book
1024    fn log_metrics(&mut self, m: &AddressMetrics, now: Instant) {
1025        let _guard = self.span.enter();
1026
1027        trace!(
1028            address_metrics = ?m,
1029        );
1030
1031        if m.responded > 0 {
1032            return;
1033        }
1034
1035        // These logs are designed to be human-readable in a terminal, at the
1036        // default Zebra log level. If you need to know address states for
1037        // every request, use the trace-level logs, or the metrics exporter.
1038        if let Some(last_address_log) = self.last_address_log {
1039            // Avoid duplicate address logs
1040            if now.saturating_duration_since(last_address_log).as_secs() < 60 {
1041                return;
1042            }
1043        } else {
1044            // Suppress initial logs until the peer set has started up.
1045            // There can be multiple address changes before the first peer has
1046            // responded.
1047            self.last_address_log = Some(now);
1048            return;
1049        }
1050
1051        self.last_address_log = Some(now);
1052        // if all peers have failed
1053        if m.responded + m.attempt_pending + m.never_attempted_gossiped == 0 {
1054            warn!(
1055                address_metrics = ?m,
1056                "all peer addresses have failed. Hint: check your network connection"
1057            );
1058        } else {
1059            info!(
1060                address_metrics = ?m,
1061                "no active peer connections: trying gossiped addresses"
1062            );
1063        }
1064    }
1065}
1066
1067impl AddressBookPeers for AddressBook {
1068    fn recently_live_peers(&self, now: chrono::DateTime<Utc>) -> Vec<MetaAddr> {
1069        let _guard = self.span.enter();
1070
1071        self.peers
1072            .ordered_values()
1073            .filter(|peer| peer.was_recently_live(now))
1074            .cloned()
1075            .collect()
1076    }
1077
1078    fn connected_peers(&self) -> Option<Vec<crate::ConnectedPeer>> {
1079        self.peer_registry
1080            .as_ref()
1081            .map(PeerRegistry::connected_peers)
1082    }
1083
1084    fn add_peer(&mut self, peer: PeerSocketAddr) -> bool {
1085        if self.get(peer).is_some() {
1086            // Peer already exists in the address book, so we don't need to add it again.
1087            return false;
1088        }
1089        self.update(MetaAddr::new_initial_peer(peer)).is_some()
1090    }
1091}
1092
1093impl AddressBookPeers for Arc<Mutex<AddressBook>> {
1094    fn recently_live_peers(&self, now: chrono::DateTime<Utc>) -> Vec<MetaAddr> {
1095        self.lock()
1096            .expect("panic in a previous thread that was holding the mutex")
1097            .recently_live_peers(now)
1098    }
1099
1100    fn connected_peers(&self) -> Option<Vec<crate::ConnectedPeer>> {
1101        self.lock()
1102            .expect("panic in a previous thread that was holding the mutex")
1103            .connected_peers()
1104    }
1105
1106    fn add_peer(&mut self, peer: PeerSocketAddr) -> bool {
1107        self.lock()
1108            .expect("panic in a previous thread that was holding the mutex")
1109            .add_peer(peer)
1110    }
1111}
1112
1113impl Extend<MetaAddrChange> for AddressBook {
1114    fn extend<T>(&mut self, iter: T)
1115    where
1116        T: IntoIterator<Item = MetaAddrChange>,
1117    {
1118        // Publish once after the full batch instead of scanning the address
1119        // book after every accepted change.
1120        for change in iter.into_iter() {
1121            self.update_inner(change);
1122        }
1123
1124        self.publish_metrics();
1125    }
1126}
1127
1128impl Clone for AddressBook {
1129    /// Clone the addresses, address limit, local listener address, and span.
1130    ///
1131    /// Cloned address books have a separate metrics struct watch channel, and an empty last address log.
1132    ///
1133    /// All address books update the same prometheus metrics.
1134    fn clone(&self) -> AddressBook {
1135        // The existing metrics might be outdated, but we avoid calling `update_metrics`,
1136        // so we don't overwrite the prometheus metrics from the main address book.
1137        let (address_metrics_tx, _address_metrics_rx) =
1138            watch::channel(*self.address_metrics_tx.borrow());
1139
1140        AddressBook {
1141            peers: self.peers.clone(),
1142            local_listener: self.local_listener,
1143            local_listener_services: self.local_listener_services,
1144            network: self.network.clone(),
1145            peer_registry: self.peer_registry.clone(),
1146            addr_limit: self.addr_limit,
1147            span: self.span.clone(),
1148            expose_peer_addresses: self.expose_peer_addresses,
1149            address_metrics_tx,
1150            address_metrics_dirty: false,
1151            #[cfg(test)]
1152            address_metrics_update_count: 0,
1153            last_address_log: None,
1154            most_recent_by_ip: self.most_recent_by_ip.clone(),
1155            bans_by_ip: self.bans_by_ip.clone(),
1156        }
1157    }
1158}