Replace the one-intermediate-peer relay with protocol-scoped connectivity graphs and precomputed shortest-path/ECMP forwarding snapshots. Independent transport readers forward opaque transit frames without a plugin or TUN round trip. Carry source, destination, a bounded hop limit and stable flow tags; preserve end-to-end WireGuard links across topology changes. Classify IP flows before encryption and preserve their tags through the WireGuard pending queue. Add offline four-agent path-change coverage, loop and isolation tests, and an opt-in release forwarding microbenchmark. Bump control/data ALPNs while preserving persistent network identities and state. Also include the pending Windows Mainline idle-timeout fix and its regression test, using a reproducible vendored dependency patch. Validation: fmt, workspace Clippy with warnings denied, and 307 release tests passed. Two pre-existing Windows SQLite wipe failures were excluded; public DHT and the manual benchmark remain ignored by default. The forwarding microbenchmark measured 103 ns (64 B) and 202 ns (1280 B) per transit packet, excluding encryption and socket I/O.
143 lines
4.2 KiB
Rust
143 lines
4.2 KiB
Rust
//! Struct and implementation of the Node entry in the Kademlia routing table
|
|
use std::{
|
|
fmt::{self, Debug, Formatter},
|
|
net::SocketAddrV4,
|
|
sync::Arc,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
use crate::common::Id;
|
|
|
|
/// The age of a node's last_seen time before it is considered stale and removed from a full bucket
|
|
/// on inserting a new node.
|
|
pub const STALE_TIME: Duration = Duration::from_secs(15 * 60);
|
|
const MIN_PING_BACKOFF_INTERVAL: Duration = Duration::from_secs(10);
|
|
pub const TOKEN_ROTATE_INTERVAL: Duration = Duration::from_secs(60 * 5);
|
|
|
|
#[derive(PartialEq)]
|
|
pub(crate) struct NodeInner {
|
|
pub(crate) id: Id,
|
|
pub(crate) address: SocketAddrV4,
|
|
pub(crate) token: Option<Box<[u8]>>,
|
|
pub(crate) last_seen: Instant,
|
|
}
|
|
|
|
impl NodeInner {
|
|
pub fn random() -> Self {
|
|
Self {
|
|
id: Id::random(),
|
|
address: SocketAddrV4::new(0.into(), 0),
|
|
token: None,
|
|
last_seen: Instant::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq)]
|
|
/// Node entry in Kademlia routing table
|
|
pub struct Node(pub(crate) Arc<NodeInner>);
|
|
|
|
impl Debug for Node {
|
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
|
fmt.debug_struct("Node")
|
|
.field("id", &self.0.id)
|
|
.field("address", &self.0.address)
|
|
.field("last_seen", &self.0.last_seen.elapsed().as_secs())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Node {
|
|
/// Creates a new Node from an id and socket address.
|
|
pub fn new(id: Id, address: SocketAddrV4) -> Node {
|
|
Node(Arc::new(NodeInner {
|
|
id,
|
|
address,
|
|
token: None,
|
|
last_seen: Instant::now(),
|
|
}))
|
|
}
|
|
|
|
pub(crate) fn new_with_token(id: Id, address: SocketAddrV4, token: Box<[u8]>) -> Self {
|
|
Node(Arc::new(NodeInner {
|
|
id,
|
|
address,
|
|
token: Some(token),
|
|
last_seen: Instant::now(),
|
|
}))
|
|
}
|
|
|
|
/// Creates a node with random Id for testing purposes.
|
|
pub fn random() -> Node {
|
|
Node(Arc::new(NodeInner::random()))
|
|
}
|
|
|
|
/// Create a node that is unique per `i` as it has a random Id and sets IP and port to `i`
|
|
#[cfg(test)]
|
|
pub fn unique(i: usize) -> Node {
|
|
Node::new(Id::random(), SocketAddrV4::new((i as u32).into(), i as u16))
|
|
}
|
|
|
|
// === Getters ===
|
|
|
|
/// Returns the id of this node
|
|
pub fn id(&self) -> &Id {
|
|
&self.0.id
|
|
}
|
|
|
|
/// Returns the address of this node
|
|
pub fn address(&self) -> SocketAddrV4 {
|
|
self.0.address
|
|
}
|
|
|
|
/// Returns the token we received from this node if any.
|
|
pub fn token(&self) -> Option<Box<[u8]>> {
|
|
self.0.token.clone()
|
|
}
|
|
|
|
/// Node is last seen more than a threshold ago.
|
|
pub fn is_stale(&self) -> bool {
|
|
self.0.last_seen.elapsed() > STALE_TIME
|
|
}
|
|
|
|
/// Node's token was received 5 minutes ago or less
|
|
pub fn valid_token(&self) -> bool {
|
|
self.0.last_seen.elapsed() <= TOKEN_ROTATE_INTERVAL
|
|
}
|
|
|
|
pub(crate) fn should_ping(&self) -> bool {
|
|
self.0.last_seen.elapsed() > MIN_PING_BACKOFF_INTERVAL
|
|
}
|
|
|
|
/// Returns true if both nodes have the same ip and port
|
|
pub fn same_address(&self, other: &Self) -> bool {
|
|
self.0.address == other.0.address
|
|
}
|
|
|
|
/// Returns true if both nodes have the same ip
|
|
pub fn same_ip(&self, other: &Self) -> bool {
|
|
self.0.address.ip() == other.0.address.ip()
|
|
}
|
|
|
|
/// Node [Id] is valid for its IP address.
|
|
///
|
|
/// Check [BEP_0042](https://www.bittorrent.org/beps/bep_0042.html).
|
|
pub fn is_secure(&self) -> bool {
|
|
self.0.id.is_valid_for_ip(*self.0.address.ip())
|
|
}
|
|
|
|
/// Returns true if Any of the existing nodes:
|
|
/// - Have the same IP as this node, And:
|
|
/// = The existing nodes is Not secure.
|
|
/// = The existing nodes is secure And shares the same first 21 bits.
|
|
///
|
|
/// Effectively, allows only One non-secure node or Eight secure nodes from the same IP, in the routing table or ClosestNodes.
|
|
pub(crate) fn already_exists(&self, nodes: &[Self]) -> bool {
|
|
nodes.iter().any(|existing| {
|
|
self.same_ip(existing)
|
|
&& (!existing.is_secure()
|
|
|| self.id().first_21_bits() == existing.id().first_21_bits())
|
|
})
|
|
}
|
|
}
|