diff --git a/Cargo.lock b/Cargo.lock index b67af2a..e00690c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,8 +209,10 @@ dependencies = [ "aquatic_common", "aquatic_toml_config", "aquatic_udp_protocol", + "blake3", "cfg-if", "crossbeam-channel", + "getrandom", "hex", "log", "mimalloc", @@ -350,6 +352,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + [[package]] name = "arrayvec" version = "0.4.12" @@ -359,6 +367,12 @@ dependencies = [ "nodrop", ] +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-trait" version = "0.1.53" @@ -524,6 +538,20 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "303cec55cd9c5fde944b061b902f142b52a8bb5438cc822481ea1e3ebc96bbcb" +[[package]] +name = "blake3" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.3", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -655,6 +683,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "cpufeatures" version = "0.2.2" @@ -854,6 +888,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common", + "subtle", ] [[package]] @@ -1756,7 +1791,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" dependencies = [ - "arrayvec", + "arrayvec 0.4.12", "itoa 0.4.8", ] diff --git a/aquatic_udp/Cargo.toml b/aquatic_udp/Cargo.toml index d4d5922..304806f 100644 --- a/aquatic_udp/Cargo.toml +++ b/aquatic_udp/Cargo.toml @@ -24,8 +24,10 @@ aquatic_toml_config = { version = "0.2.0", path = "../aquatic_toml_config" } aquatic_udp_protocol = { version = "0.2.0", path = "../aquatic_udp_protocol" } anyhow = "1" +blake3 = { version = "1", features = ["traits-preview"] } cfg-if = "1" crossbeam-channel = "0.5" +getrandom = "0.2" hex = "0.4" log = "0.4" mimalloc = { version = "0.1", default-features = false } diff --git a/aquatic_udp/src/common.rs b/aquatic_udp/src/common.rs index 36affe7..082e036 100644 --- a/aquatic_udp/src/common.rs +++ b/aquatic_udp/src/common.rs @@ -1,19 +1,94 @@ use std::collections::BTreeMap; use std::hash::Hash; -use std::net::{Ipv4Addr, Ipv6Addr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::sync::atomic::AtomicUsize; use std::sync::Arc; +use std::time::{Duration, Instant}; -use aquatic_common::CanonicalSocketAddr; use crossbeam_channel::{Sender, TrySendError}; +use getrandom::getrandom; use aquatic_common::access_list::AccessListArcSwap; +use aquatic_common::CanonicalSocketAddr; use aquatic_udp_protocol::*; use crate::config::Config; pub const MAX_PACKET_SIZE: usize = 8192; +pub struct ConnectionIdHandler { + start_time: Instant, + max_connection_age: Duration, + hmac: blake3::Hasher, +} + +impl ConnectionIdHandler { + pub fn new(config: &Config) -> anyhow::Result { + let mut key = [0; 32]; + + getrandom(&mut key)?; + + let hmac = blake3::Hasher::new_keyed(&key); + + let start_time = Instant::now(); + let max_connection_age = Duration::from_secs(config.cleaning.max_connection_age); + + Ok(Self { + hmac, + start_time, + max_connection_age, + }) + } + + pub fn create_connection_id(&mut self, source_ip: IpAddr) -> ConnectionId { + // Seconds elapsed since server start, as bytes + let elapsed_time_bytes = (self.start_time.elapsed().as_secs() as u32).to_ne_bytes(); + + self.create_connection_id_inner(elapsed_time_bytes, source_ip) + } + + pub fn connection_id_valid(&mut self, source_ip: IpAddr, connection_id: ConnectionId) -> bool { + let elapsed_time_bytes = connection_id.0.to_ne_bytes()[..4].try_into().unwrap(); + + // i64 comparison should be constant-time + let hmac_valid = + connection_id == self.create_connection_id_inner(elapsed_time_bytes, source_ip); + + if !hmac_valid { + return false; + } + + let connection_elapsed_since_start = + Duration::from_secs(u32::from_ne_bytes(elapsed_time_bytes) as u64); + + connection_elapsed_since_start + self.max_connection_age > self.start_time.elapsed() + } + + fn create_connection_id_inner( + &mut self, + elapsed_time_bytes: [u8; 4], + source_ip: IpAddr, + ) -> ConnectionId { + // The first 4 bytes is the elapsed time since server start in seconds. The last 4 is a + // truncated message authentication code. + let mut connection_id_bytes = [0u8; 8]; + + (&mut connection_id_bytes[..4]).copy_from_slice(&elapsed_time_bytes); + + self.hmac.update(&elapsed_time_bytes); + + match source_ip { + IpAddr::V4(ip) => self.hmac.update(&ip.octets()), + IpAddr::V6(ip) => self.hmac.update(&ip.octets()), + }; + + self.hmac.finalize_xof().fill(&mut connection_id_bytes[4..]); + self.hmac.reset(); + + ConnectionId(i64::from_ne_bytes(connection_id_bytes)) + } +} + #[derive(Debug)] pub struct PendingScrapeRequest { pub slab_key: usize, diff --git a/aquatic_udp/src/lib.rs b/aquatic_udp/src/lib.rs index db644b9..ef436d0 100644 --- a/aquatic_udp/src/lib.rs +++ b/aquatic_udp/src/lib.rs @@ -2,25 +2,24 @@ pub mod common; pub mod config; pub mod workers; -use aquatic_common::PanicSentinelWatcher; -use config::Config; - use std::collections::BTreeMap; use std::thread::Builder; use anyhow::Context; -#[cfg(feature = "cpu-pinning")] -use aquatic_common::cpu_pinning::{pin_current_if_configured_to, WorkerIndex}; -use aquatic_common::privileges::PrivilegeDropper; use crossbeam_channel::{bounded, unbounded}; - -use aquatic_common::access_list::update_access_list; use signal_hook::consts::{SIGTERM, SIGUSR1}; use signal_hook::iterator::Signals; -use common::{ConnectedRequestSender, ConnectedResponseSender, SocketWorkerIndex, State}; +use aquatic_common::access_list::update_access_list; +#[cfg(feature = "cpu-pinning")] +use aquatic_common::cpu_pinning::{pin_current_if_configured_to, WorkerIndex}; +use aquatic_common::privileges::PrivilegeDropper; +use aquatic_common::PanicSentinelWatcher; -use crate::common::RequestWorkerIndex; +use common::{ + ConnectedRequestSender, ConnectedResponseSender, RequestWorkerIndex, SocketWorkerIndex, State, +}; +use config::Config; pub const APP_NAME: &str = "aquatic_udp: UDP BitTorrent tracker"; pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");