Sanitize outbound address responses.
This aims to prevent a remote peer from inspecting timings of all messages received by this node.
This commit is contained in:
parent
e5aa02bbd4
commit
9a0bffecb8
|
@ -1539,6 +1539,7 @@ dependencies = [
|
|||
"gumdrop 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.13.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -25,6 +25,12 @@ pub const LIVE_PEER_DURATION: Duration = Duration::from_secs(60 + 10 + 10 + 10);
|
|||
/// connected peer.
|
||||
pub const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(60);
|
||||
|
||||
/// Truncate timestamps in outbound address messages to this time interval.
|
||||
///
|
||||
/// This is intended to prevent a peer from learning exactly when we recieved
|
||||
/// messages from each of our peers.
|
||||
pub const TIMESTAMP_TRUNCATION_SECONDS: i64 = 30 * 60;
|
||||
|
||||
/// The User-Agent string provided by the node.
|
||||
pub const USER_AGENT: &'static str = "🦓Zebra v2.0.0-alpha.0🦓";
|
||||
|
||||
|
|
|
@ -31,6 +31,16 @@ pub struct MetaAddr {
|
|||
pub last_seen: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl MetaAddr {
|
||||
/// Sanitize this `MetaAddr` before sending it to a remote peer.
|
||||
pub fn sanitize(mut self) -> MetaAddr {
|
||||
let interval = crate::constants::TIMESTAMP_TRUNCATION_SECONDS;
|
||||
let ts = self.last_seen.timestamp();
|
||||
self.last_seen = Utc.timestamp(ts - ts.rem_euclid(interval), 0);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for MetaAddr {
|
||||
/// `MetaAddr`s are sorted newest-first, and then in an arbitrary
|
||||
/// but determinate total order.
|
||||
|
@ -78,3 +88,23 @@ impl ZcashDeserialize for MetaAddr {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
// XXX remove this test and replace it with a proptest instance.
|
||||
#[test]
|
||||
fn sanitize_truncates_timestamps() {
|
||||
let entry = MetaAddr {
|
||||
services: PeerServices::default(),
|
||||
addr: "127.0.0.1:8233".parse().unwrap(),
|
||||
last_seen: Utc.timestamp(1573680222, 0),
|
||||
}
|
||||
.sanitize();
|
||||
// We want the sanitized timestamp to be a multiple of the truncation interval.
|
||||
assert_eq!(
|
||||
entry.last_seen.timestamp() % crate::constants::TIMESTAMP_TRUNCATION_SECONDS,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ version = "0.1.0"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.7"
|
||||
chrono = "0.4"
|
||||
abscissa_core = "0.3.0"
|
||||
failure = "0.1"
|
||||
|
|
|
@ -10,6 +10,8 @@ use std::{
|
|||
use abscissa_core::{config, Command, FrameworkError, Options, Runnable};
|
||||
use futures::channel::oneshot;
|
||||
use tower::{buffer::Buffer, Service, ServiceExt};
|
||||
use tracing::{span, Level};
|
||||
|
||||
use zebra_network::{AddressBook, BoxedStdError, Request, Response};
|
||||
|
||||
use crate::{config::ZebradConfig, prelude::*};
|
||||
|
@ -61,7 +63,8 @@ impl Service<Request> for SeedService {
|
|||
}
|
||||
|
||||
fn call(&mut self, req: Request) -> Self::Future {
|
||||
info!("SeedService handling a request: {:?}", req);
|
||||
let span = span!(Level::DEBUG, "SeedService::call", req = ?req);
|
||||
let _guard = span.enter();
|
||||
|
||||
let address_book = if let SeederState::Ready(address_book) = &self.state {
|
||||
address_book
|
||||
|
@ -71,16 +74,28 @@ impl Service<Request> for SeedService {
|
|||
|
||||
let response = match req {
|
||||
Request::GetPeers => {
|
||||
debug!(address_book.len = address_book.lock().unwrap().len());
|
||||
info!("SeedService responding to GetPeers");
|
||||
Ok::<Response, Self::Error>(Response::Peers(
|
||||
address_book.lock().unwrap().peers().collect(),
|
||||
))
|
||||
// Collect a list of known peers from the address book
|
||||
// and sanitize their timestamps.
|
||||
let mut peers = address_book
|
||||
.lock()
|
||||
.unwrap()
|
||||
.peers()
|
||||
.map(|addr| addr.sanitize())
|
||||
.collect::<Vec<_>>();
|
||||
// The peers are still ordered by recency, so shuffle them.
|
||||
use rand::seq::SliceRandom;
|
||||
peers.shuffle(&mut rand::thread_rng());
|
||||
// Finally, truncate the list so that we do not trivially
|
||||
// reveal our entire peer set.
|
||||
peers.truncate(50);
|
||||
debug!(peers.len = peers.len(), peers = ?peers);
|
||||
Ok(Response::Peers(peers))
|
||||
}
|
||||
_ => {
|
||||
debug!("ignoring request");
|
||||
Ok::<Response, Self::Error>(Response::Ok)
|
||||
}
|
||||
_ => Ok::<Response, Self::Error>(Response::Ok),
|
||||
};
|
||||
|
||||
info!("SeedService response: {:?}", response);
|
||||
return Box::pin(futures::future::ready(response));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue