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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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.
|
/// connected peer.
|
||||||
pub const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(60);
|
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.
|
/// The User-Agent string provided by the node.
|
||||||
pub const USER_AGENT: &'static str = "🦓Zebra v2.0.0-alpha.0🦓";
|
pub const USER_AGENT: &'static str = "🦓Zebra v2.0.0-alpha.0🦓";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,16 @@ pub struct MetaAddr {
|
||||||
pub last_seen: DateTime<Utc>,
|
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 {
|
impl Ord for MetaAddr {
|
||||||
/// `MetaAddr`s are sorted newest-first, and then in an arbitrary
|
/// `MetaAddr`s are sorted newest-first, and then in an arbitrary
|
||||||
/// but determinate total order.
|
/// 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"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rand = "0.7"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
abscissa_core = "0.3.0"
|
abscissa_core = "0.3.0"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ use std::{
|
||||||
use abscissa_core::{config, Command, FrameworkError, Options, Runnable};
|
use abscissa_core::{config, Command, FrameworkError, Options, Runnable};
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use tower::{buffer::Buffer, Service, ServiceExt};
|
use tower::{buffer::Buffer, Service, ServiceExt};
|
||||||
|
use tracing::{span, Level};
|
||||||
|
|
||||||
use zebra_network::{AddressBook, BoxedStdError, Request, Response};
|
use zebra_network::{AddressBook, BoxedStdError, Request, Response};
|
||||||
|
|
||||||
use crate::{config::ZebradConfig, prelude::*};
|
use crate::{config::ZebradConfig, prelude::*};
|
||||||
|
|
@ -61,7 +63,8 @@ impl Service<Request> for SeedService {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
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 {
|
let address_book = if let SeederState::Ready(address_book) = &self.state {
|
||||||
address_book
|
address_book
|
||||||
|
|
@ -71,16 +74,28 @@ impl Service<Request> for SeedService {
|
||||||
|
|
||||||
let response = match req {
|
let response = match req {
|
||||||
Request::GetPeers => {
|
Request::GetPeers => {
|
||||||
debug!(address_book.len = address_book.lock().unwrap().len());
|
// Collect a list of known peers from the address book
|
||||||
info!("SeedService responding to GetPeers");
|
// and sanitize their timestamps.
|
||||||
Ok::<Response, Self::Error>(Response::Peers(
|
let mut peers = address_book
|
||||||
address_book.lock().unwrap().peers().collect(),
|
.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));
|
return Box::pin(futures::future::ready(response));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue