zebrad/components/inbound/
cached_peer_addr_response.rs

1//! Periodically-refreshed GetAddr response for the inbound service.
2//!
3//! Used to avoid giving out Zebra's entire address book over a short duration.
4
5use std::{
6    sync::{Mutex, TryLockError},
7    time::Instant,
8};
9
10use super::*;
11
12/// The minimum duration that a `CachedPeerAddrResponse` is considered fresh before the inbound service
13/// should get new peer addresses from the address book to send as a `GetAddr` response.
14///
15/// Cached responses are considered stale and should be cleared after twice this duration.
16pub const CACHED_ADDRS_REFRESH_INTERVAL: Duration = Duration::from_secs(10 * 60);
17
18/// Caches and refreshes a partial list of peer addresses to be returned as a `GetAddr` response.
19pub struct CachedPeerAddrResponse {
20    /// A shared list of peer addresses.
21    address_book: Arc<Mutex<zn::AddressBook>>,
22
23    /// An owned list of peer addresses used as a `GetAddr` response.
24    value: zn::Response,
25
26    /// Instant after which `cached_addrs` should be refreshed.
27    refresh_time: Instant,
28}
29
30impl CachedPeerAddrResponse {
31    /// Creates a new empty [`CachedPeerAddrResponse`].
32    pub(super) fn new(address_book: Arc<Mutex<AddressBook>>) -> Self {
33        Self {
34            address_book,
35            value: zn::Response::Nil,
36            refresh_time: Instant::now(),
37        }
38    }
39
40    pub(super) fn value(&self) -> zn::Response {
41        self.value.clone()
42    }
43
44    /// Refreshes the `cached_addrs` if the time has past `refresh_time` or the cache is empty
45    pub(super) fn try_refresh(&mut self) {
46        let now = Instant::now();
47
48        // return early if there are some cached addresses, and they are still fresh
49        if now < self.refresh_time {
50            return;
51        }
52
53        let cache_expiry = self.refresh_time + CACHED_ADDRS_REFRESH_INTERVAL;
54
55        // try getting a lock on the address book if it's time to refresh the cached addresses
56        match self
57            .address_book
58            .try_lock()
59            .map(|book| book.fresh_get_addr_response())
60        {
61            // Update cached value and refresh_time if there are some gossipable peers in the address book.
62            //
63            // Security: this avoids outdated gossiped peers. Outdated Zebra binaries will gradually lose all their peers,
64            // because those peers refuse to connect to outdated versions. So we don't want those outdated Zebra
65            // versions to keep gossiping old peer information either.
66            Ok(peers) if !peers.is_empty() => {
67                self.refresh_time = now + CACHED_ADDRS_REFRESH_INTERVAL;
68                self.value = zn::Response::Peers(peers);
69            }
70
71            // Clear the cached response if the time has past the cache expiry time.
72            Ok(_) if now > cache_expiry => {
73                self.value = zn::Response::Nil;
74            }
75
76            Err(TryLockError::WouldBlock) if now > cache_expiry => {
77                warn!("getaddrs response hasn't been refreshed in some time");
78                self.value = zn::Response::Nil;
79            }
80
81            // Don't update the cached response or refresh time if unable to get new peer addresses
82            // from the address book and `now` is before the cache expiry.
83            Ok(_) => {
84                debug!(
85                    "could not refresh cached response because our address \
86                     book has no available peers"
87                );
88            }
89
90            Err(TryLockError::WouldBlock) => {}
91
92            // Panic if the address book lock is poisoned
93            Err(TryLockError::Poisoned(_)) => {
94                panic!("previous thread panicked while holding the address book lock")
95            }
96        };
97    }
98}