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}