-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
security: Rate limit GetAddr responses (#7955)
* Updates ADDR_RESPONSE_LIMIT_DENOMINATOR to 4 * Moves logic getting a fraction of Zebra's peers to a method in the address book * Adds and uses CachedPeerAddrs struct in inbound service * moves and documents constant * fixes test * Apply suggestions from code review Co-authored-by: teor <[email protected]> * updates docs * renames sanitized_window method * renames CachedPeerAddrs to CachedPeerAddrResponse * updates test * moves try_refresh to per request * Make unused sanitization method pub(crate) * Apply suggestions from code review Co-authored-by: teor <[email protected]> * moves CachedPeerAddrResponse to a module * updates unit test * fixes unit test * removes unnecessary condition * clears cached getaddr response if it can't refresh for over a minute after the refresh time * tests that inbound service gives out the same addresses for every Peers request before the refresh interval * Applies suggestion from code review * fixes doc link * renames constant * Fix docs on new constant * applies suggestion from code review * uses longer cache expiry time * Adds code comments --------- Co-authored-by: teor <[email protected]>
- Loading branch information
Showing
8 changed files
with
276 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
zebrad/src/components/inbound/cached_peer_addr_response.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
//! Periodically-refreshed GetAddr response for the inbound service. | ||
//! | ||
//! Used to avoid giving out Zebra's entire address book over a short duration. | ||
use std::{ | ||
sync::{Mutex, TryLockError}, | ||
time::Instant, | ||
}; | ||
|
||
use super::*; | ||
|
||
/// The minimum duration that a `CachedPeerAddrResponse` is considered fresh before the inbound service | ||
/// should get new peer addresses from the address book to send as a `GetAddr` response. | ||
/// | ||
/// Cached responses are considered stale and should be cleared after twice this duration. | ||
pub const CACHED_ADDRS_REFRESH_INTERVAL: Duration = Duration::from_secs(10 * 60); | ||
|
||
/// Caches and refreshes a partial list of peer addresses to be returned as a `GetAddr` response. | ||
pub struct CachedPeerAddrResponse { | ||
/// A shared list of peer addresses. | ||
address_book: Arc<Mutex<zn::AddressBook>>, | ||
|
||
/// An owned list of peer addresses used as a `GetAddr` response. | ||
value: zn::Response, | ||
|
||
/// Instant after which `cached_addrs` should be refreshed. | ||
refresh_time: Instant, | ||
} | ||
|
||
impl CachedPeerAddrResponse { | ||
/// Creates a new empty [`CachedPeerAddrResponse`]. | ||
pub(super) fn new(address_book: Arc<Mutex<AddressBook>>) -> Self { | ||
Self { | ||
address_book, | ||
value: zn::Response::Nil, | ||
refresh_time: Instant::now(), | ||
} | ||
} | ||
|
||
pub(super) fn value(&self) -> zn::Response { | ||
self.value.clone() | ||
} | ||
|
||
/// Refreshes the `cached_addrs` if the time has past `refresh_time` or the cache is empty | ||
pub(super) fn try_refresh(&mut self) { | ||
let now = Instant::now(); | ||
|
||
// return early if there are some cached addresses, and they are still fresh | ||
if now < self.refresh_time { | ||
return; | ||
} | ||
|
||
let cache_expiry = self.refresh_time + CACHED_ADDRS_REFRESH_INTERVAL; | ||
|
||
// try getting a lock on the address book if it's time to refresh the cached addresses | ||
match self | ||
.address_book | ||
.try_lock() | ||
.map(|book| book.fresh_get_addr_response()) | ||
{ | ||
// Update cached value and refresh_time if there are some gossipable peers in the address book. | ||
// | ||
// Security: this avoids outdated gossiped peers. Outdated Zebra binaries will gradually lose all their peers, | ||
// because those peers refuse to connect to outdated versions. So we don't want those outdated Zebra | ||
// versions to keep gossiping old peer information either. | ||
Ok(peers) if !peers.is_empty() => { | ||
self.refresh_time = now + CACHED_ADDRS_REFRESH_INTERVAL; | ||
self.value = zn::Response::Peers(peers); | ||
} | ||
|
||
// Clear the cached response if the time has past the cache expiry time. | ||
Ok(_) if now > cache_expiry => { | ||
self.value = zn::Response::Nil; | ||
} | ||
|
||
Err(TryLockError::WouldBlock) if now > cache_expiry => { | ||
warn!("getaddrs response hasn't been refreshed in some time"); | ||
self.value = zn::Response::Nil; | ||
} | ||
|
||
// Don't update the cached response or refresh time if unable to get new peer addresses | ||
// from the address book and `now` is before the cache expiry. | ||
Ok(_) => { | ||
debug!( | ||
"could not refresh cached response because our address \ | ||
book has no available peers" | ||
); | ||
} | ||
|
||
Err(TryLockError::WouldBlock) => {} | ||
|
||
// Panic if the address book lock is poisoned | ||
Err(TryLockError::Poisoned(_)) => { | ||
panic!("previous thread panicked while holding the address book lock") | ||
} | ||
}; | ||
} | ||
} |
Oops, something went wrong.