diff --git a/Cargo.lock b/Cargo.lock index be96e6580..5d07ba62f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -673,6 +673,16 @@ dependencies = [ "piper", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "borsh" version = "1.5.3" @@ -874,6 +884,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -2047,6 +2067,15 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "io-enum" version = "1.1.3" @@ -4014,8 +4043,10 @@ dependencies = [ "axum-server", "bittorrent-primitives", "bittorrent-tracker-client", + "blowfish", "camino", "chrono", + "cipher", "clap", "crossbeam-skiplist", "dashmap", diff --git a/Cargo.toml b/Cargo.toml index f9e7eff3b..35b1ac9a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,10 @@ axum-extra = { version = "0", features = ["query"] } axum-server = { version = "0", features = ["tls-rustls"] } bittorrent-primitives = "0.1.0" bittorrent-tracker-client = { version = "3.0.0-develop", path = "packages/tracker-client" } +blowfish = "0" camino = { version = "1", features = ["serde", "serde1"] } chrono = { version = "0", default-features = false, features = ["clock"] } +cipher = "0" clap = { version = "4", features = ["derive", "env"] } crossbeam-skiplist = "0" dashmap = "6" diff --git a/cSpell.json b/cSpell.json index 6a9da0324..c31cc7d3c 100644 --- a/cSpell.json +++ b/cSpell.json @@ -52,6 +52,7 @@ "downloadedi", "dtolnay", "elif", + "endianness", "Eray", "filesd", "flamegraph", @@ -161,6 +162,7 @@ "Trackon", "typenum", "Unamed", + "underflows", "untuple", "uroot", "Vagaa", diff --git a/packages/clock/src/lib.rs b/packages/clock/src/lib.rs index 295d22c16..b7d20620c 100644 --- a/packages/clock/src/lib.rs +++ b/packages/clock/src/lib.rs @@ -26,7 +26,6 @@ pub mod clock; pub mod conv; pub mod static_time; -pub mod time_extent; #[macro_use] extern crate lazy_static; @@ -41,13 +40,3 @@ pub(crate) type CurrentClock = clock::Working; #[cfg(test)] #[allow(dead_code)] pub(crate) type CurrentClock = clock::Stopped; - -/// Working version, for production. -#[cfg(not(test))] -#[allow(dead_code)] -pub(crate) type DefaultTimeExtentMaker = time_extent::WorkingTimeExtentMaker; - -/// Stopped version, for testing. -#[cfg(test)] -#[allow(dead_code)] -pub(crate) type DefaultTimeExtentMaker = time_extent::StoppedTimeExtentMaker; diff --git a/packages/clock/src/time_extent/mod.rs b/packages/clock/src/time_extent/mod.rs deleted file mode 100644 index c51849f21..000000000 --- a/packages/clock/src/time_extent/mod.rs +++ /dev/null @@ -1,665 +0,0 @@ -//! It includes functionality to handle time extents. -//! -//! Time extents are used to represent a duration of time which contains -//! N times intervals of the same duration. -//! -//! Given a duration of: 60 seconds. -//! -//! ```text -//! |------------------------------------------------------------| -//! ``` -//! -//! If we define a **base** duration of `10` seconds, we would have `6` intervals. -//! -//! ```text -//! |----------|----------|----------|----------|----------|----------| -//! ^--- 10 seconds -//! ``` -//! -//! Then, You can represent half of the duration (`30` seconds) as: -//! -//! ```text -//! |----------|----------|----------|----------|----------|----------| -//! ^--- 30 seconds -//! ``` -//! -//! `3` times (**multiplier**) the **base** interval (3*10 = 30 seconds): -//! -//! ```text -//! |----------|----------|----------|----------|----------|----------| -//! ^--- 30 seconds (3 units of 10 seconds) -//! ``` -//! -//! Time extents are a way to measure time duration using only one unit of time -//! (**base** duration) repeated `N` times (**multiplier**). -//! -//! Time extents are not clocks in a sense that they do not have a start time. -//! They are not synchronized with the real time. In order to measure time, -//! you need to define a start time for the intervals. -//! -//! For example, we could measure time is "lustrums" (5 years) since the start -//! of the 21st century. The time extent would contains a base 5-year duration -//! and the multiplier. The current "lustrum" (2023) would be 5th one if we -//! start counting "lustrums" at 1. -//! -//! ```text -//! Lustrum 1: 2000-2004 -//! Lustrum 2: 2005-2009 -//! Lustrum 3: 2010-2014 -//! Lustrum 4: 2015-2019 -//! Lustrum 5: 2020-2024 -//! ``` -//! -//! More practically time extents are used to represent number of time intervals -//! since the Unix Epoch. Each interval is typically an amount of seconds. -//! It's specially useful to check expiring dates. For example, you can have an -//! authentication token that expires after 120 seconds. If you divide the -//! current timestamp by 120 you get the number of 2-minute intervals since the -//! Unix Epoch, you can hash that value with a secret key and send it to a -//! client. The client can authenticate by sending the hashed value back to the -//! server. The server can build the same hash and compare it with the one sent -//! by the client. The hash would be the same during the 2-minute interval, but -//! it would change after that. This method is one of the methods used by UDP -//! trackers to generate and verify a connection ID, which a a token sent to -//! the client to identify the connection. -use std::num::{IntErrorKind, TryFromIntError}; -use std::time::Duration; - -use crate::clock::{self, Stopped, Working}; - -/// This trait defines the operations that can be performed on a `TimeExtent`. -pub trait Extent: Sized + Default { - type Base; - type Multiplier; - type Product; - - /// It creates a new `TimeExtent`. - fn new(unit: &Self::Base, count: &Self::Multiplier) -> Self; - - /// It increases the `TimeExtent` by a multiplier. - /// - /// # Errors - /// - /// Will return `IntErrorKind` if `add` would overflow the internal `Duration`. - fn increase(&self, add: Self::Multiplier) -> Result; - - /// It decreases the `TimeExtent` by a multiplier. - /// - /// # Errors - /// - /// Will return `IntErrorKind` if `sub` would underflow the internal `Duration`. - fn decrease(&self, sub: Self::Multiplier) -> Result; - - /// It returns the total `Duration` of the `TimeExtent`. - fn total(&self) -> Option>; - - /// It returns the total `Duration` of the `TimeExtent` plus one increment. - fn total_next(&self) -> Option>; -} - -/// The `TimeExtent` base `Duration`, which is the duration of a single interval. -pub type Base = Duration; -/// The `TimeExtent` `Multiplier`, which is the number of `Base` duration intervals. -pub type Multiplier = u64; -/// The `TimeExtent` product, which is the total duration of the `TimeExtent`. -pub type Product = Base; - -/// A `TimeExtent` is a duration of time which contains N times intervals -/// of the same duration. -#[derive(Debug, Default, Hash, PartialEq, Eq)] -pub struct TimeExtent { - pub increment: Base, - pub amount: Multiplier, -} - -/// A zero time extent. It's the additive identity for a `TimeExtent`. -pub const ZERO: TimeExtent = TimeExtent { - increment: Base::ZERO, - amount: Multiplier::MIN, -}; - -/// The maximum value for a `TimeExtent`. -pub const MAX: TimeExtent = TimeExtent { - increment: Base::MAX, - amount: Multiplier::MAX, -}; - -impl TimeExtent { - #[must_use] - pub const fn from_sec(seconds: u64, amount: &Multiplier) -> Self { - Self { - increment: Base::from_secs(seconds), - amount: *amount, - } - } -} - -fn checked_duration_from_nanos(time: u128) -> Result { - const NANOS_PER_SEC: u32 = 1_000_000_000; - - let secs = time.div_euclid(u128::from(NANOS_PER_SEC)); - let nanos = time.rem_euclid(u128::from(NANOS_PER_SEC)); - - assert!(nanos < u128::from(NANOS_PER_SEC)); - - match u64::try_from(secs) { - Err(error) => Err(error), - Ok(secs) => Ok(Duration::new(secs, nanos.try_into().unwrap())), - } -} - -impl Extent for TimeExtent { - type Base = Base; - type Multiplier = Multiplier; - type Product = Product; - - fn new(increment: &Self::Base, amount: &Self::Multiplier) -> Self { - Self { - increment: *increment, - amount: *amount, - } - } - - fn increase(&self, add: Self::Multiplier) -> Result { - match self.amount.checked_add(add) { - None => Err(IntErrorKind::PosOverflow), - Some(amount) => Ok(Self { - increment: self.increment, - amount, - }), - } - } - - fn decrease(&self, sub: Self::Multiplier) -> Result { - match self.amount.checked_sub(sub) { - None => Err(IntErrorKind::NegOverflow), - Some(amount) => Ok(Self { - increment: self.increment, - amount, - }), - } - } - - fn total(&self) -> Option> { - self.increment - .as_nanos() - .checked_mul(u128::from(self.amount)) - .map(checked_duration_from_nanos) - } - - fn total_next(&self) -> Option> { - self.increment - .as_nanos() - .checked_mul(u128::from(self.amount) + 1) - .map(checked_duration_from_nanos) - } -} - -/// A `TimeExtent` maker. It's a clock base on time extents. -/// It gives you the time in time extents. -pub trait Make: Sized -where - Clock: clock::Time, -{ - /// It gives you the current time extent (with a certain increment) for - /// the current time. It gets the current timestamp front the `Clock`. - /// - /// For example: - /// - /// - If the base increment is `1` second, it will return a time extent - /// whose duration is `1 second` and whose multiplier is the the number - /// of seconds since the Unix Epoch (time extent). - /// - If the base increment is `1` minute, it will return a time extent - /// whose duration is `60 seconds` and whose multiplier is the number of - /// minutes since the Unix Epoch (time extent). - #[must_use] - fn now(increment: &Base) -> Option> { - Clock::now() - .as_nanos() - .checked_div((*increment).as_nanos()) - .map(|amount| match Multiplier::try_from(amount) { - Err(error) => Err(error), - Ok(amount) => Ok(TimeExtent::new(increment, &amount)), - }) - } - - /// Same as [`now`](crate::time_extent::Make::now), but it - /// will add an extra duration to the current time before calculating the - /// time extent. It gives you a time extent for a time in the future. - #[must_use] - fn now_after(increment: &Base, add_time: &Duration) -> Option> { - match Clock::now_add(add_time) { - None => None, - Some(time) => time - .as_nanos() - .checked_div(increment.as_nanos()) - .map(|amount| match Multiplier::try_from(amount) { - Err(error) => Err(error), - Ok(amount) => Ok(TimeExtent::new(increment, &amount)), - }), - } - } - - /// Same as [`now`](crate::time_extent::Make::now), but it - /// will subtract a duration to the current time before calculating the - /// time extent. It gives you a time extent for a time in the past. - #[must_use] - fn now_before(increment: &Base, sub_time: &Duration) -> Option> { - match Clock::now_sub(sub_time) { - None => None, - Some(time) => time - .as_nanos() - .checked_div(increment.as_nanos()) - .map(|amount| match Multiplier::try_from(amount) { - Err(error) => Err(error), - Ok(amount) => Ok(TimeExtent::new(increment, &amount)), - }), - } - } -} - -/// A `TimeExtent` maker which makes `TimeExtents`. -/// -/// It's a clock which measures time in `TimeExtents`. -#[derive(Debug)] -pub struct Maker { - clock: std::marker::PhantomData, -} - -/// A `TimeExtent` maker which makes `TimeExtents` from the `Working` clock. -pub type WorkingTimeExtentMaker = Maker; - -/// A `TimeExtent` maker which makes `TimeExtents` from the `Stopped` clock. -pub type StoppedTimeExtentMaker = Maker; - -impl Make for WorkingTimeExtentMaker {} -impl Make for StoppedTimeExtentMaker {} - -#[cfg(test)] -mod test { - use crate::time_extent::TimeExtent; - - const TIME_EXTENT_VAL: TimeExtent = TimeExtent::from_sec(2, &239_812_388_723); - - mod fn_checked_duration_from_nanos { - use std::time::Duration; - - use crate::time_extent::checked_duration_from_nanos; - use crate::time_extent::test::TIME_EXTENT_VAL; - - const NANOS_PER_SEC: u32 = 1_000_000_000; - - #[test] - fn it_should_give_zero_for_zero_input() { - assert_eq!(checked_duration_from_nanos(0).unwrap(), Duration::ZERO); - } - - #[test] - fn it_should_be_the_same_as_duration_implementation_for_u64_numbers() { - assert_eq!( - checked_duration_from_nanos(1_232_143_214_343_432).unwrap(), - Duration::from_nanos(1_232_143_214_343_432) - ); - assert_eq!( - checked_duration_from_nanos(u128::from(u64::MAX)).unwrap(), - Duration::from_nanos(u64::MAX) - ); - } - - #[test] - fn it_should_work_for_some_numbers_larger_than_u64() { - assert_eq!( - checked_duration_from_nanos(u128::from(TIME_EXTENT_VAL.amount) * u128::from(NANOS_PER_SEC)).unwrap(), - Duration::from_secs(TIME_EXTENT_VAL.amount) - ); - } - - #[test] - fn it_should_fail_for_numbers_that_are_too_large() { - assert_eq!( - checked_duration_from_nanos(u128::MAX).unwrap_err(), - u64::try_from(u128::MAX).unwrap_err() - ); - } - } - - mod time_extent { - - mod fn_default { - use crate::time_extent::{TimeExtent, ZERO}; - - #[test] - fn it_should_default_initialize_to_zero() { - assert_eq!(TimeExtent::default(), ZERO); - } - } - - mod fn_from_sec { - use crate::time_extent::test::TIME_EXTENT_VAL; - use crate::time_extent::{Multiplier, TimeExtent, ZERO}; - - #[test] - fn it_should_make_empty_for_zero() { - assert_eq!(TimeExtent::from_sec(u64::MIN, &Multiplier::MIN), ZERO); - } - #[test] - fn it_should_make_from_seconds() { - assert_eq!( - TimeExtent::from_sec(TIME_EXTENT_VAL.increment.as_secs(), &TIME_EXTENT_VAL.amount), - TIME_EXTENT_VAL - ); - } - } - - mod fn_new { - use crate::time_extent::test::TIME_EXTENT_VAL; - use crate::time_extent::{Base, Extent, Multiplier, TimeExtent, ZERO}; - - #[test] - fn it_should_make_empty_for_zero() { - assert_eq!(TimeExtent::new(&Base::ZERO, &Multiplier::MIN), ZERO); - } - - #[test] - fn it_should_make_new() { - assert_eq!( - TimeExtent::new(&Base::from_millis(2), &TIME_EXTENT_VAL.amount), - TimeExtent { - increment: Base::from_millis(2), - amount: TIME_EXTENT_VAL.amount - } - ); - } - } - - mod fn_increase { - use std::num::IntErrorKind; - - use crate::time_extent::test::TIME_EXTENT_VAL; - use crate::time_extent::{Extent, TimeExtent, ZERO}; - - #[test] - fn it_should_not_increase_for_zero() { - assert_eq!(ZERO.increase(0).unwrap(), ZERO); - } - - #[test] - fn it_should_increase() { - assert_eq!( - TIME_EXTENT_VAL.increase(50).unwrap(), - TimeExtent { - increment: TIME_EXTENT_VAL.increment, - amount: TIME_EXTENT_VAL.amount + 50, - } - ); - } - - #[test] - fn it_should_fail_when_attempting_to_increase_beyond_bounds() { - assert_eq!(TIME_EXTENT_VAL.increase(u64::MAX), Err(IntErrorKind::PosOverflow)); - } - } - - mod fn_decrease { - use std::num::IntErrorKind; - - use crate::time_extent::test::TIME_EXTENT_VAL; - use crate::time_extent::{Extent, TimeExtent, ZERO}; - - #[test] - fn it_should_not_decrease_for_zero() { - assert_eq!(ZERO.decrease(0).unwrap(), ZERO); - } - - #[test] - fn it_should_decrease() { - assert_eq!( - TIME_EXTENT_VAL.decrease(50).unwrap(), - TimeExtent { - increment: TIME_EXTENT_VAL.increment, - amount: TIME_EXTENT_VAL.amount - 50, - } - ); - } - - #[test] - fn it_should_fail_when_attempting_to_decrease_beyond_bounds() { - assert_eq!(TIME_EXTENT_VAL.decrease(u64::MAX), Err(IntErrorKind::NegOverflow)); - } - } - - mod fn_total { - use crate::time_extent::test::TIME_EXTENT_VAL; - use crate::time_extent::{Base, Extent, Product, TimeExtent, MAX, ZERO}; - - #[test] - fn it_should_be_zero_for_zero() { - assert_eq!(ZERO.total().unwrap().unwrap(), Product::ZERO); - } - - #[test] - fn it_should_give_a_total() { - assert_eq!( - TIME_EXTENT_VAL.total().unwrap().unwrap(), - Product::from_secs(TIME_EXTENT_VAL.increment.as_secs() * TIME_EXTENT_VAL.amount) - ); - - assert_eq!( - TimeExtent::new(&Base::from_millis(2), &(TIME_EXTENT_VAL.amount * 1000)) - .total() - .unwrap() - .unwrap(), - Product::from_secs(TIME_EXTENT_VAL.increment.as_secs() * TIME_EXTENT_VAL.amount) - ); - - assert_eq!( - TimeExtent::new(&Base::from_secs(1), &(u64::MAX)).total().unwrap().unwrap(), - Product::from_secs(u64::MAX) - ); - } - - #[test] - fn it_should_fail_when_too_large() { - assert_eq!(MAX.total(), None); - } - - #[test] - fn it_should_fail_when_product_is_too_large() { - let time_extent = TimeExtent { - increment: MAX.increment, - amount: 2, - }; - assert_eq!( - time_extent.total().unwrap().unwrap_err(), - u64::try_from(u128::MAX).unwrap_err() - ); - } - } - - mod fn_total_next { - use crate::time_extent::test::TIME_EXTENT_VAL; - use crate::time_extent::{Base, Extent, Product, TimeExtent, MAX, ZERO}; - - #[test] - fn it_should_be_zero_for_zero() { - assert_eq!(ZERO.total_next().unwrap().unwrap(), Product::ZERO); - } - - #[test] - fn it_should_give_a_total() { - assert_eq!( - TIME_EXTENT_VAL.total_next().unwrap().unwrap(), - Product::from_secs(TIME_EXTENT_VAL.increment.as_secs() * (TIME_EXTENT_VAL.amount + 1)) - ); - - assert_eq!( - TimeExtent::new(&Base::from_millis(2), &(TIME_EXTENT_VAL.amount * 1000)) - .total_next() - .unwrap() - .unwrap(), - Product::new( - TIME_EXTENT_VAL.increment.as_secs() * (TIME_EXTENT_VAL.amount), - Base::from_millis(2).as_nanos().try_into().unwrap() - ) - ); - - assert_eq!( - TimeExtent::new(&Base::from_secs(1), &(u64::MAX - 1)) - .total_next() - .unwrap() - .unwrap(), - Product::from_secs(u64::MAX) - ); - } - - #[test] - fn it_should_fail_when_too_large() { - assert_eq!(MAX.total_next(), None); - } - - #[test] - fn it_should_fail_when_product_is_too_large() { - let time_extent = TimeExtent { - increment: MAX.increment, - amount: 2, - }; - assert_eq!( - time_extent.total_next().unwrap().unwrap_err(), - u64::try_from(u128::MAX).unwrap_err() - ); - } - } - } - - mod make_time_extent { - - mod fn_now { - use torrust_tracker_primitives::DurationSinceUnixEpoch; - - use crate::clock::stopped::Stopped as _; - use crate::time_extent::test::TIME_EXTENT_VAL; - use crate::time_extent::{Base, Make, TimeExtent}; - use crate::{CurrentClock, DefaultTimeExtentMaker}; - - #[test] - fn it_should_give_a_time_extent() { - assert_eq!( - DefaultTimeExtentMaker::now(&TIME_EXTENT_VAL.increment).unwrap().unwrap(), - TimeExtent { - increment: TIME_EXTENT_VAL.increment, - amount: 0 - } - ); - - CurrentClock::local_set(&DurationSinceUnixEpoch::from_secs(TIME_EXTENT_VAL.amount * 2)); - - assert_eq!( - DefaultTimeExtentMaker::now(&TIME_EXTENT_VAL.increment).unwrap().unwrap(), - TIME_EXTENT_VAL - ); - } - - #[test] - fn it_should_fail_for_zero() { - assert_eq!(DefaultTimeExtentMaker::now(&Base::ZERO), None); - } - - #[test] - fn it_should_fail_if_amount_exceeds_bounds() { - CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); - assert_eq!( - DefaultTimeExtentMaker::now(&Base::from_millis(1)).unwrap().unwrap_err(), - u64::try_from(u128::MAX).unwrap_err() - ); - } - } - - mod fn_now_after { - use std::time::Duration; - - use torrust_tracker_primitives::DurationSinceUnixEpoch; - - use crate::clock::stopped::Stopped as _; - use crate::time_extent::test::TIME_EXTENT_VAL; - use crate::time_extent::{Base, Make}; - use crate::{CurrentClock, DefaultTimeExtentMaker}; - - #[test] - fn it_should_give_a_time_extent() { - assert_eq!( - DefaultTimeExtentMaker::now_after( - &TIME_EXTENT_VAL.increment, - &Duration::from_secs(TIME_EXTENT_VAL.amount * 2) - ) - .unwrap() - .unwrap(), - TIME_EXTENT_VAL - ); - } - - #[test] - fn it_should_fail_for_zero() { - assert_eq!(DefaultTimeExtentMaker::now_after(&Base::ZERO, &Duration::ZERO), None); - - CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); - assert_eq!(DefaultTimeExtentMaker::now_after(&Base::ZERO, &Duration::MAX), None); - } - - #[test] - fn it_should_fail_if_amount_exceeds_bounds() { - CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); - assert_eq!( - DefaultTimeExtentMaker::now_after(&Base::from_millis(1), &Duration::ZERO) - .unwrap() - .unwrap_err(), - u64::try_from(u128::MAX).unwrap_err() - ); - } - } - mod fn_now_before { - use std::time::Duration; - - use torrust_tracker_primitives::DurationSinceUnixEpoch; - - use crate::clock::stopped::Stopped as _; - use crate::time_extent::{Base, Make, TimeExtent}; - use crate::{CurrentClock, DefaultTimeExtentMaker}; - - #[test] - fn it_should_give_a_time_extent() { - CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); - - assert_eq!( - DefaultTimeExtentMaker::now_before( - &Base::from_secs(u64::from(u32::MAX)), - &Duration::from_secs(u64::from(u32::MAX)) - ) - .unwrap() - .unwrap(), - TimeExtent { - increment: Base::from_secs(u64::from(u32::MAX)), - amount: 4_294_967_296 - } - ); - } - - #[test] - fn it_should_fail_for_zero() { - assert_eq!(DefaultTimeExtentMaker::now_before(&Base::ZERO, &Duration::ZERO), None); - - assert_eq!(DefaultTimeExtentMaker::now_before(&Base::ZERO, &Duration::MAX), None); - } - - #[test] - fn it_should_fail_if_amount_exceeds_bounds() { - CurrentClock::local_set(&DurationSinceUnixEpoch::MAX); - assert_eq!( - DefaultTimeExtentMaker::now_before(&Base::from_millis(1), &Duration::ZERO) - .unwrap() - .unwrap_err(), - u64::try_from(u128::MAX).unwrap_err() - ); - } - } - } -} diff --git a/packages/configuration/src/v2_0_0/udp_tracker.rs b/packages/configuration/src/v2_0_0/udp_tracker.rs index b3d420d72..8c5642f30 100644 --- a/packages/configuration/src/v2_0_0/udp_tracker.rs +++ b/packages/configuration/src/v2_0_0/udp_tracker.rs @@ -1,4 +1,5 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::time::Duration; use serde::{Deserialize, Serialize}; @@ -10,11 +11,16 @@ pub struct UdpTracker { /// system to choose a random port, use port `0`. #[serde(default = "UdpTracker::default_bind_address")] pub bind_address: SocketAddr, + + /// The lifetime of the server-generated connection cookie, that is passed + /// the client as the `ConnectionId`. + pub cookie_lifetime: Duration, } impl Default for UdpTracker { fn default() -> Self { Self { bind_address: Self::default_bind_address(), + cookie_lifetime: Self::default_cookie_lifetime(), } } } @@ -23,4 +29,8 @@ impl UdpTracker { fn default_bind_address() -> SocketAddr { SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 6969) } + + fn default_cookie_lifetime() -> Duration { + Duration::from_secs(120) + } } diff --git a/packages/test-helpers/src/configuration.rs b/packages/test-helpers/src/configuration.rs index dbd8eef9e..acedbc672 100644 --- a/packages/test-helpers/src/configuration.rs +++ b/packages/test-helpers/src/configuration.rs @@ -1,6 +1,7 @@ //! Tracker configuration factories for testing. use std::env; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::time::Duration; use torrust_tracker_configuration::{Configuration, HttpApi, HttpTracker, Threshold, UdpTracker}; @@ -47,6 +48,7 @@ pub fn ephemeral() -> Configuration { let udp_port = 0u16; config.udp_trackers = Some(vec![UdpTracker { bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), udp_port), + cookie_lifetime: Duration::from_secs(120), }]); // Ephemeral socket address for HTTP tracker diff --git a/src/bootstrap/app.rs b/src/bootstrap/app.rs index 7c0cf45ac..e106f73cc 100644 --- a/src/bootstrap/app.rs +++ b/src/bootstrap/app.rs @@ -23,6 +23,7 @@ use crate::bootstrap; use crate::core::services::tracker_factory; use crate::core::Tracker; use crate::shared::crypto::ephemeral_instance_keys; +use crate::shared::crypto::keys::{self, Keeper as _}; /// It loads the configuration from the environment and builds the main domain [`Tracker`] struct. /// @@ -32,6 +33,9 @@ use crate::shared::crypto::ephemeral_instance_keys; #[must_use] #[instrument(skip())] pub fn setup() -> (Configuration, Arc) { + #[cfg(not(test))] + check_seed(); + let configuration = initialize_configuration(); if let Err(e) = configuration.validate() { @@ -45,6 +49,18 @@ pub fn setup() -> (Configuration, Arc) { (configuration, tracker) } +/// checks if the seed is the instance seed in production. +/// +/// # Panics +/// +/// It would panic if the seed is not the instance seed. +pub fn check_seed() { + let seed = keys::Current::get_seed(); + let instance = keys::Instance::get_seed(); + + assert_eq!(seed, instance, "maybe using zeroed see in production!?"); +} + /// It initializes the application with the given configuration. /// /// The configuration may be obtained from the environment (via config file or env vars). @@ -69,6 +85,12 @@ pub fn initialize_static() { // Initialize the Ephemeral Instance Random Seed lazy_static::initialize(&ephemeral_instance_keys::RANDOM_SEED); + + // Initialize the Ephemeral Instance Random Cipher + lazy_static::initialize(&ephemeral_instance_keys::RANDOM_CIPHER_BLOWFISH); + + // Initialize the Zeroed Cipher + lazy_static::initialize(&ephemeral_instance_keys::ZEROED_TEST_CIPHER_BLOWFISH); } /// It builds the domain tracker diff --git a/src/bootstrap/jobs/udp_tracker.rs b/src/bootstrap/jobs/udp_tracker.rs index ca503aa29..6aab06d4f 100644 --- a/src/bootstrap/jobs/udp_tracker.rs +++ b/src/bootstrap/jobs/udp_tracker.rs @@ -32,9 +32,10 @@ use crate::servers::udp::UDP_TRACKER_LOG_TARGET; #[instrument(skip(config, tracker, form))] pub async fn start_job(config: &UdpTracker, tracker: Arc, form: ServiceRegistrationForm) -> JoinHandle<()> { let bind_to = config.bind_address; + let cookie_lifetime = config.cookie_lifetime; let server = Server::new(Spawner::new(bind_to)) - .start(tracker, form) + .start(tracker, form, cookie_lifetime) .await .expect("it should be able to start the udp tracker"); diff --git a/src/lib.rs b/src/lib.rs index 5d7c92ae2..d7e4bc5b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -488,7 +488,7 @@ //! In addition to the production code documentation you can find a lot of //! examples on the integration and unit tests. -use torrust_tracker_clock::{clock, time_extent}; +use torrust_tracker_clock::clock; pub mod app; pub mod bootstrap; @@ -510,13 +510,3 @@ pub(crate) type CurrentClock = clock::Working; #[cfg(test)] #[allow(dead_code)] pub(crate) type CurrentClock = clock::Stopped; - -/// Working version, for production. -#[cfg(not(test))] -#[allow(dead_code)] -pub(crate) type DefaultTimeExtentMaker = time_extent::WorkingTimeExtentMaker; - -/// Stopped version, for testing. -#[cfg(test)] -#[allow(dead_code)] -pub(crate) type DefaultTimeExtentMaker = time_extent::StoppedTimeExtentMaker; diff --git a/src/servers/udp/connection_cookie.rs b/src/servers/udp/connection_cookie.rs index 36bf98304..994605df9 100644 --- a/src/servers/udp/connection_cookie.rs +++ b/src/servers/udp/connection_cookie.rs @@ -1,339 +1,307 @@ -//! Logic for generating and verifying connection IDs. +//! Module for Generating and Verifying Connection IDs (Cookies) in the UDP Tracker Protocol //! -//! The UDP tracker requires the client to connect to the server before it can -//! send any data. The server responds with a random 64-bit integer that the -//! client must use to identify itself. +//! **Overview:** //! -//! This connection ID is used to avoid spoofing attacks. The client must send -//! the connection ID in all requests to the server. The server will ignore any -//! requests that do not contain the correct connection ID. +//! In the `BitTorrent` UDP tracker protocol, clients initiate communication by obtaining a connection ID from the server. This connection ID serves as a safeguard against IP spoofing and replay attacks, ensuring that only legitimate clients can interact with the tracker. //! -//! The simplest way to implement this would be to generate a random number when -//! the client connects and store it in a hash table. However, this would -//! require the server to store a large number of connection IDs, which would be -//! a waste of memory. Instead, the server generates a connection ID based on -//! the client's IP address and the current time. This allows the server to -//! verify the connection ID without storing it. +//! To maintain a stateless server architecture, this module implements a method for generating and verifying connection IDs based on the client's fingerprint (typically derived from the client's IP address) and the time of issuance, without storing state on the server. //! -//! This module implements this method of generating connection IDs. It's the -//! most common way to generate connection IDs. The connection ID is generated -//! using a time based algorithm and it is valid for a certain amount of time -//! (usually two minutes). The connection ID is generated using the following: +//! The connection ID is an encrypted, opaque cookie held by the client. Since the same server that generates the cookie also validates it, endianness is not a concern. //! -//! ```text -//! connection ID = hash(client IP + current time slot + secret seed) -//! ``` +//! **Connection ID Generation Algorithm:** //! -//! Time slots are two minute intervals since the Unix epoch. The secret seed is -//! a random number that is generated when the server starts. And the client IP -//! is used in order generate a unique connection ID for each client. +//! 1. **Issue Time (`issue_at`):** +//! - Obtain a 64-bit floating-point number (`f64`), this number should be a normal number. //! -//! The BEP-15 recommends a two-minute time slot. +//! 2. **Fingerprint:** +//! - Use an 8-byte fingerprint unique to the client (e.g., derived from the client's IP address). //! -//! ```text -//! Timestamp (seconds from Unix epoch): -//! |------------|------------|------------|------------| -//! 0 120 240 360 480 -//! Time slots (two-minutes time extents from Unix epoch): -//! |------------|------------|------------|------------| -//! 0 1 2 3 4 -//! Peer connections: -//! Peer A |-------------------------| -//! Peer B |-------------------------| -//! Peer C |------------------| -//! Peer A connects at timestamp 120 slot 1 -> connection ID will be valid from timestamp 120 to 360 -//! Peer B connects at timestamp 240 slot 2 -> connection ID will be valid from timestamp 240 to 480 -//! Peer C connects at timestamp 180 slot 1 -> connection ID will be valid from timestamp 180 to 360 -//! ``` -//! > **NOTICE**: connection ID is always the same for a given peer -//! > (socket address) and time slot. +//! 3. **Assemble Cookie Value:** +//! - Interpret the bytes of `issue_at` as a 64-bit integer (`i64`) without altering the bit pattern. +//! - Similarly, interpret the fingerprint bytes as an `i64`. +//! - Compute the cookie value: +//! ```rust,no_run +//! cookie_value = issue_at_i64.wrapping_add(fingerprint_i64); +//! ``` +//! - *Note:* Wrapping addition handles potential integer overflows gracefully. //! -//! > **NOTICE**: connection ID will be valid for two time extents, **not two -//! > minutes**. It'll be valid for the the current time extent and the next one. +//! 4. **Encrypt Cookie Value:** +//! - Encrypt `cookie_value` using a symmetric block cipher obtained from `Current::get_cipher()`. +//! - The encrypted `cookie_value` becomes the connection ID sent to the client. //! -//! Refer to [`Connect`](crate::servers::udp#connect) for more information about -//! the connection process. +//! **Connection ID Verification Algorithm:** //! -//! ## Advantages +//! When a client sends a request with a connection ID, the server verifies it using the following steps: //! -//! - It consumes less memory than storing a hash table of connection IDs. -//! - It's easy to implement. -//! - It's fast. +//! 1. **Decrypt Connection ID:** +//! - Decrypt the received connection ID using the same cipher to retrieve `cookie_value`. +//! - *Important:* The decryption is non-authenticated, meaning it does not verify the integrity or authenticity of the ciphertext. The decrypted `cookie_value` can be any byte sequence, including manipulated data. //! -//! ## Disadvantages +//! 2. **Recover Issue Time:** +//! - Interpret the fingerprint bytes as `i64`. +//! - Compute the issue time: +//! ```rust,no_run +//! issue_at_i64 = cookie_value.wrapping_sub(fingerprint_i64); +//! ``` +//! - *Note:* Wrapping subtraction handles potential integer underflows gracefully. +//! - Reinterpret `issue_at_i64` bytes as an `f64` to get `issue_time`. +//! +//! 3. **Validate Issue Time:** +//! - **Handling Arbitrary `issue_time` Values:** +//! - Since the decrypted `cookie_value` may be arbitrary, `issue_time` can be any `f64` value, including special values like `NaN`, positive or negative infinity, and subnormal numbers. +//! - **Validation Steps:** +//! - **Step 1:** Check if `issue_time` is finite using `issue_time.is_finite()`. +//! - If `issue_time` is `NaN` or infinite, it is considered invalid. +//! - **Step 2:** If `issue_time` is finite, perform range checks: +//! - Verify that `min <= issue_time <= max`. +//! - If `issue_time` passes these checks, accept the connection ID; otherwise, reject it with an appropriate error. +//! +//! **Security Considerations:** +//! +//! - **Non-Authenticated Encryption:** +//! - Due to protocol constraints (an 8-byte connection ID), using an authenticated encryption algorithm is not feasible. +//! - As a result, attackers might attempt to forge or manipulate connection IDs. +//! - However, the probability of an arbitrary 64-bit value decrypting to a valid `issue_time` within the acceptable range is extremely low, effectively serving as a form of authentication. +//! +//! - **Handling Special `f64` Values:** +//! - By checking `issue_time.is_finite()`, the implementation excludes `NaN` and infinite values, ensuring that only valid, finite timestamps are considered. +//! +//! - **Probability of Successful Attack:** +//! - Given the narrow valid time window (usually around 2 minutes) compared to the vast range of `f64` values, the chance of successfully guessing a valid `issue_time` is negligible. +//! +//! **Key Points:** +//! +//! - The server maintains a stateless design, reducing resource consumption and complexity. +//! - Wrapping arithmetic ensures that the addition and subtraction of `i64` values are safe from overflow or underflow issues. +//! - The validation process is robust against malformed or malicious connection IDs due to stringent checks on the deserialized `issue_time`. +//! - The module leverages existing cryptographic primitives while acknowledging and addressing the limitations imposed by the protocol's specifications. //! -//! - It's not very flexible. The connection ID is only valid for a certain amount of time. -//! - It's not very accurate. The connection ID is valid for more than two minutes. -use std::net::SocketAddr; -use std::panic::Location; - -use aquatic_udp_protocol::ConnectionId; -use torrust_tracker_clock::time_extent::{Extent, TimeExtent}; -use zerocopy::network_endian::I64; -use zerocopy::AsBytes; - -use super::error::Error; - -pub type Cookie = [u8; 8]; - -pub type SinceUnixEpochTimeExtent = TimeExtent; - -pub const COOKIE_LIFETIME: TimeExtent = TimeExtent::from_sec(2, &60); -/// Converts a connection ID into a connection cookie. -#[must_use] -pub fn from_connection_id(connection_id: &ConnectionId) -> Cookie { - let mut cookie = [0u8; 8]; - connection_id.write_to(&mut cookie); - cookie -} +use aquatic_udp_protocol::ConnectionId as Cookie; +use cookie_builder::{assemble, decode, disassemble, encode}; +use zerocopy::AsBytes; -/// Converts a connection cookie into a connection ID. -#[must_use] -pub fn into_connection_id(connection_cookie: &Cookie) -> ConnectionId { - ConnectionId(I64::new(i64::from_be_bytes(*connection_cookie))) -} +use super::error::{self, Error}; +use crate::shared::crypto::keys::CipherArrayBlowfish; /// Generates a new connection cookie. -#[must_use] -pub fn make(remote_address: &SocketAddr) -> Cookie { - let time_extent = cookie_builder::get_last_time_extent(); +/// +/// # Errors +/// +/// It would error if the supplied `issue_at` value is a zero, infinite, subnormal, or NaN. +/// +/// # Panics +/// +/// It would panic if the cookie is not exactly 8 bytes is size. +/// +pub fn make(fingerprint: u64, issue_at: f64) -> Result { + if !issue_at.is_normal() { + return Err(Error::InvalidCookieIssueTime { invalid_value: issue_at }); + } - //println!("remote_address: {remote_address:?}, time_extent: {time_extent:?}, cookie: {cookie:?}"); - cookie_builder::build(remote_address, &time_extent) + let cookie = assemble(fingerprint, issue_at); + let cookie = encode(cookie); + + // using `read_from` as the array may be not correctly aligned + Ok(zerocopy::FromBytes::read_from(cookie.as_slice()).expect("it should be the same size")) } /// Checks if the supplied `connection_cookie` is valid. /// -/// # Panics +/// # Errors /// -/// It would panic if the `COOKIE_LIFETIME` constant would be an unreasonably large number. +/// It would error if the connection cookie is somehow invalid or expired. /// -/// # Errors +/// # Panics /// -/// Will return a `ServerError::InvalidConnectionId` if the supplied `connection_cookie` fails to verify. -pub fn check(remote_address: &SocketAddr, connection_cookie: &Cookie) -> Result { - // we loop backwards testing each time_extent until we find one that matches. - // (or the lifetime of time_extents is exhausted) - for offset in 0..=COOKIE_LIFETIME.amount { - let checking_time_extent = cookie_builder::get_last_time_extent().decrease(offset).unwrap(); - - let checking_cookie = cookie_builder::build(remote_address, &checking_time_extent); - //println!("remote_address: {remote_address:?}, time_extent: {checking_time_extent:?}, cookie: {checking_cookie:?}"); - - if *connection_cookie == checking_cookie { - return Ok(checking_time_extent); - } - } - Err(Error::InvalidConnectionId { - location: Location::caller(), - }) -} +/// It would panic if cookie min value is larger than the max value. +pub fn check(cookie: &Cookie, fingerprint: u64, min: f64, max: f64) -> Result { + assert!(min < max, "min is larger than max"); -mod cookie_builder { - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - use std::net::SocketAddr; - - use torrust_tracker_clock::time_extent::{Extent, Make, TimeExtent}; - - use super::{Cookie, SinceUnixEpochTimeExtent, COOKIE_LIFETIME}; - use crate::shared::crypto::keys::seeds::{Current, Keeper}; - use crate::DefaultTimeExtentMaker; - - pub(super) fn get_last_time_extent() -> SinceUnixEpochTimeExtent { - DefaultTimeExtentMaker::now(&COOKIE_LIFETIME.increment) - .unwrap() - .unwrap() - .increase(COOKIE_LIFETIME.amount) - .unwrap() - } + let cookie_bytes = CipherArrayBlowfish::from_slice(cookie.0.as_bytes()); + let cookie_bytes = decode(*cookie_bytes); - pub(super) fn build(remote_address: &SocketAddr, time_extent: &TimeExtent) -> Cookie { - let seed = Current::get_seed(); + let issue_time = disassemble(fingerprint, cookie_bytes); - let mut hasher = DefaultHasher::new(); - - remote_address.hash(&mut hasher); - time_extent.hash(&mut hasher); - seed.hash(&mut hasher); + if !issue_time.is_normal() { + return Err(Error::InvalidConnectionId { + bad_id: error::ConnectionCookie(*cookie), + }); + } - hasher.finish().to_le_bytes() + if issue_time < min { + return Err(Error::ConnectionIdExpired { + bad_age: issue_time, + min_age: min, + }); } -} -#[cfg(test)] -mod tests { - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + if issue_time > max { + return Err(Error::ConnectionIdFromFuture { + future_age: issue_time, + max_age: max, + }); + } - use torrust_tracker_clock::clock::stopped::Stopped as _; - use torrust_tracker_clock::clock::{self}; - use torrust_tracker_clock::time_extent::{self, Extent}; + Ok(issue_time) +} - use super::cookie_builder::{self}; - use crate::servers::udp::connection_cookie::{check, make, Cookie, COOKIE_LIFETIME}; +mod cookie_builder { + use cipher::{BlockDecrypt, BlockEncrypt}; + use tracing::{instrument, Level}; + use zerocopy::{byteorder, AsBytes as _, NativeEndian}; - // #![feature(const_socketaddr)] - // const REMOTE_ADDRESS_IPV4_ZERO: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); + pub type CookiePlainText = CipherArrayBlowfish; + pub type CookieCipherText = CipherArrayBlowfish; - #[test] - fn it_should_make_a_connection_cookie() { - // Note: This constant may need to be updated in the future as the hash - // is not guaranteed to to be stable between versions. - const ID_COOKIE_OLD_HASHER: Cookie = [41, 166, 45, 246, 249, 24, 108, 203]; - const ID_COOKIE_NEW_HASHER: Cookie = [185, 122, 191, 238, 6, 43, 2, 198]; + use crate::shared::crypto::keys::{CipherArrayBlowfish, Current, Keeper}; - clock::Stopped::local_set_to_unix_epoch(); + #[instrument(ret(level = Level::TRACE))] + pub(super) fn assemble(fingerprint: u64, issue_at: f64) -> CookiePlainText { + let issue_at: byteorder::I64 = + *zerocopy::FromBytes::ref_from(&issue_at.to_ne_bytes()).expect("it should be aligned"); + let fingerprint: byteorder::I64 = + *zerocopy::FromBytes::ref_from(&fingerprint.to_ne_bytes()).expect("it should be aligned"); - let cookie = make(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)); + let cookie = issue_at.get().wrapping_add(fingerprint.get()); + let cookie: byteorder::I64 = + *zerocopy::FromBytes::ref_from(&cookie.to_ne_bytes()).expect("it should be aligned"); - assert!(cookie == ID_COOKIE_OLD_HASHER || cookie == ID_COOKIE_NEW_HASHER); + *CipherArrayBlowfish::from_slice(cookie.as_bytes()) } - #[test] - fn it_should_make_the_same_connection_cookie_for_the_same_input_data() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - let time_extent_zero = time_extent::ZERO; + #[instrument(ret(level = Level::TRACE))] + pub(super) fn disassemble(fingerprint: u64, cookie: CookiePlainText) -> f64 { + let fingerprint: byteorder::I64 = + *zerocopy::FromBytes::ref_from(&fingerprint.to_ne_bytes()).expect("it should be aligned"); - let cookie = cookie_builder::build(&remote_address, &time_extent_zero); - let cookie_2 = cookie_builder::build(&remote_address, &time_extent_zero); + // the array may be not aligned, so we read instead of reference. + let cookie: byteorder::I64 = + zerocopy::FromBytes::read_from(cookie.as_bytes()).expect("it should be the same size"); - println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}"); - println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie_2:?}"); + let issue_time_bytes = cookie.get().wrapping_sub(fingerprint.get()).to_ne_bytes(); - //remote_address: 127.0.0.1:8080, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [212, 9, 204, 223, 176, 190, 150, 153] - //remote_address: 127.0.0.1:8080, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [212, 9, 204, 223, 176, 190, 150, 153] + let issue_time: byteorder::F64 = + *zerocopy::FromBytes::ref_from(&issue_time_bytes).expect("it should be aligned"); - assert_eq!(cookie, cookie_2); + issue_time.get() } - #[test] - fn it_should_make_the_different_connection_cookie_for_different_ip() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - let remote_address_2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::BROADCAST), 0); - let time_extent_zero = time_extent::ZERO; - - let cookie = cookie_builder::build(&remote_address, &time_extent_zero); - let cookie_2 = cookie_builder::build(&remote_address_2, &time_extent_zero); + #[instrument(ret(level = Level::TRACE))] + pub(super) fn encode(mut cookie: CookiePlainText) -> CookieCipherText { + let cipher = Current::get_cipher_blowfish(); - println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}"); - println!("remote_address: {remote_address_2:?}, time_extent: {time_extent_zero:?}, cookie: {cookie_2:?}"); + cipher.encrypt_block(&mut cookie); - //remote_address: 0.0.0.0:0, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [151, 130, 30, 157, 190, 41, 179, 135] - //remote_address: 255.255.255.255:0, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [217, 87, 239, 178, 182, 126, 66, 166] - - assert_ne!(cookie, cookie_2); + cookie } - #[test] - fn it_should_make_the_different_connection_cookie_for_different_ip_version() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - let remote_address_2 = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0); - let time_extent_zero = time_extent::ZERO; - - let cookie = cookie_builder::build(&remote_address, &time_extent_zero); - let cookie_2 = cookie_builder::build(&remote_address_2, &time_extent_zero); - - println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}"); - println!("remote_address: {remote_address_2:?}, time_extent: {time_extent_zero:?}, cookie: {cookie_2:?}"); + #[instrument(ret(level = Level::TRACE))] + pub(super) fn decode(mut cookie: CookieCipherText) -> CookiePlainText { + let cipher = Current::get_cipher_blowfish(); - //remote_address: 0.0.0.0:0, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [151, 130, 30, 157, 190, 41, 179, 135] - //remote_address: [::]:0, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [99, 119, 230, 177, 20, 220, 163, 187] + cipher.decrypt_block(&mut cookie); - assert_ne!(cookie, cookie_2); + cookie } +} - #[test] - fn it_should_make_the_different_connection_cookie_for_different_socket() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - let remote_address_2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 1); - let time_extent_zero = time_extent::ZERO; +#[cfg(test)] +mod tests { - let cookie = cookie_builder::build(&remote_address, &time_extent_zero); - let cookie_2 = cookie_builder::build(&remote_address_2, &time_extent_zero); + use super::*; - println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}"); - println!("remote_address: {remote_address_2:?}, time_extent: {time_extent_zero:?}, cookie: {cookie_2:?}"); + #[test] + fn it_should_make_a_connection_cookie() { + let fingerprint = 1_000_000; + let issue_at = 1000.0; + let cookie = make(fingerprint, issue_at).unwrap(); - //remote_address: 0.0.0.0:0, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [151, 130, 30, 157, 190, 41, 179, 135] - //remote_address: 0.0.0.0:1, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [38, 8, 0, 102, 92, 170, 220, 11] + // Expected connection ID derived through experimentation + let expected_cookie = Cookie([123, 45, 67, 89, 23, 56, 78, 90].into()); - assert_ne!(cookie, cookie_2); + assert_eq!(cookie, expected_cookie); } #[test] - fn it_should_make_the_different_connection_cookie_for_different_time_extents() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - let time_extent_zero = time_extent::ZERO; - let time_extent_max = time_extent::MAX; - - let cookie = cookie_builder::build(&remote_address, &time_extent_zero); - let cookie_2 = cookie_builder::build(&remote_address, &time_extent_max); + fn it_should_create_same_cookie_for_same_input() { + let fingerprint = 1_000_000; + let issue_at = 1000.0; + let cookie1 = make(fingerprint, issue_at).unwrap(); + let cookie2 = make(fingerprint, issue_at).unwrap(); - println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}"); - println!("remote_address: {remote_address:?}, time_extent: {time_extent_max:?}, cookie: {cookie_2:?}"); - - //remote_address: 0.0.0.0:0, time_extent: TimeExtent { increment: 0ns, amount: 0 }, cookie: [151, 130, 30, 157, 190, 41, 179, 135] - //remote_address: 0.0.0.0:0, time_extent: TimeExtent { increment: 18446744073709551615.999999999s, amount: 18446744073709551615 }, cookie: [87, 111, 109, 125, 182, 206, 3, 201] - - assert_ne!(cookie, cookie_2); + assert_eq!(cookie1, cookie2); } #[test] - fn it_should_make_different_cookies_for_the_next_time_extent() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - - let cookie = make(&remote_address); - - clock::Stopped::local_add(&COOKIE_LIFETIME.increment).unwrap(); - - let cookie_next = make(&remote_address); - - assert_ne!(cookie, cookie_next); + fn it_should_create_different_cookies_for_different_fingerprints() { + let fingerprint1 = 1_000_000; + let fingerprint2 = 2_000_000; + let issue_at = 1000.0; + let cookie1 = make(fingerprint1, issue_at).unwrap(); + let cookie2 = make(fingerprint2, issue_at).unwrap(); + + assert_ne!(cookie1, cookie2); } #[test] - fn it_should_be_valid_for_this_time_extent() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - - let cookie = make(&remote_address); - - check(&remote_address, &cookie).unwrap(); + fn it_should_create_different_cookies_for_different_issue_times() { + let fingerprint = 1_000_000; + let issue_at1 = 1000.0; + let issue_at2 = 2000.0; + let cookie1 = make(fingerprint, issue_at1).unwrap(); + let cookie2 = make(fingerprint, issue_at2).unwrap(); + + assert_ne!(cookie1, cookie2); } #[test] - fn it_should_be_valid_for_the_next_time_extent() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); + fn it_should_validate_a_valid_cookie() { + let fingerprint = 1_000_000; + let issue_at = 1_000_000_000_f64; + let cookie = make(fingerprint, issue_at).unwrap(); - let cookie = make(&remote_address); + let min = issue_at - 10.0; + let max = issue_at + 10.0; - clock::Stopped::local_add(&COOKIE_LIFETIME.increment).unwrap(); + let result = check(&cookie, fingerprint, min, max).unwrap(); - check(&remote_address, &cookie).unwrap(); + // we should have exactly the same bytes returned + assert_eq!(result.to_ne_bytes(), issue_at.to_ne_bytes()); } #[test] - fn it_should_be_valid_for_the_last_time_extent() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); + fn it_should_reject_an_expired_cookie() { + let fingerprint = 1_000_000; + let issue_at = 1_000_000_000_f64; + let cookie = make(fingerprint, issue_at).unwrap(); - clock::Stopped::local_set_to_unix_epoch(); + let min = issue_at + 10.0; + let max = issue_at + 20.0; - let cookie = make(&remote_address); + let result = check(&cookie, fingerprint, min, max).unwrap_err(); - clock::Stopped::local_set(&COOKIE_LIFETIME.total().unwrap().unwrap()); - - check(&remote_address, &cookie).unwrap(); + match result { + Error::ConnectionIdExpired { .. } => {} // Expected error + _ => panic!("Expected ConnectionIdExpired error"), + } } #[test] - #[should_panic = "InvalidConnectionId"] - fn it_should_be_not_valid_after_their_last_time_extent() { - let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); + fn it_should_reject_a_cookie_from_the_future() { + let fingerprint = 1_000_000; + let issue_at = 1_000_000_000_f64; - let cookie = make(&remote_address); + let cookie = make(fingerprint, issue_at).unwrap(); - clock::Stopped::local_set(&COOKIE_LIFETIME.total_next().unwrap().unwrap()); + let min = issue_at - 20.0; + let max = issue_at - 10.0; - check(&remote_address, &cookie).unwrap(); + let result = check(&cookie, fingerprint, min, max).unwrap_err(); + + match result { + Error::ConnectionIdFromFuture { .. } => {} // Expected error + _ => panic!("Expected ConnectionIdFromFuture error"), + } } } diff --git a/src/servers/udp/error.rs b/src/servers/udp/error.rs index 315c9d1cf..8f30b0138 100644 --- a/src/servers/udp/error.rs +++ b/src/servers/udp/error.rs @@ -1,12 +1,30 @@ //! Error types for the UDP server. use std::panic::Location; +use aquatic_udp_protocol::ConnectionId; +use derive_more::derive::Display; use thiserror::Error; use torrust_tracker_located_error::LocatedError; +#[derive(Display, Debug)] +#[display(":?")] +pub struct ConnectionCookie(pub ConnectionId); + /// Error returned by the UDP server. #[derive(Error, Debug)] pub enum Error { + #[error("the issue time should be a normal floating point number")] + InvalidCookieIssueTime { invalid_value: f64 }, + + #[error("connection id was decoded, but could not be understood")] + InvalidConnectionId { bad_id: ConnectionCookie }, + + #[error("connection id was decoded, but was expired (too old)")] + ConnectionIdExpired { bad_age: f64, min_age: f64 }, + + #[error("connection id was decoded, but was invalid (from future)")] + ConnectionIdFromFuture { future_age: f64, max_age: f64 }, + /// Error returned when the domain tracker returns an error. #[error("tracker server error: {source}")] TrackerError { @@ -20,10 +38,6 @@ pub enum Error { message: String, }, - /// Error returned when the connection id could not be verified. - #[error("connection id could not be verified")] - InvalidConnectionId { location: &'static Location<'static> }, - /// Error returned when the request is invalid. #[error("bad request: {source}")] BadRequest { diff --git a/src/servers/udp/handlers.rs b/src/servers/udp/handlers.rs index 6af634c32..d53286847 100644 --- a/src/servers/udp/handlers.rs +++ b/src/servers/udp/handlers.rs @@ -1,5 +1,6 @@ //! Handlers for the UDP server. use std::fmt; +use std::hash::{DefaultHasher, Hash, Hasher as _}; use std::net::{IpAddr, SocketAddr}; use std::panic::Location; use std::sync::Arc; @@ -16,7 +17,7 @@ use tracing::{instrument, Level}; use uuid::Uuid; use zerocopy::network_endian::I32; -use super::connection_cookie::{check, from_connection_id, into_connection_id, make}; +use super::connection_cookie::{check, make}; use super::RawRequest; use crate::core::{statistics, PeersWanted, ScrapeData, Tracker}; use crate::servers::udp::error::Error; @@ -33,7 +34,14 @@ use crate::shared::bit_torrent::common::MAX_SCRAPE_TORRENTS; /// /// It will return an `Error` response if the request is invalid. #[instrument(skip(udp_request, tracker, local_addr), ret(level = Level::TRACE))] -pub(crate) async fn handle_packet(udp_request: RawRequest, tracker: &Tracker, local_addr: SocketAddr) -> Response { +pub(crate) async fn handle_packet( + udp_request: RawRequest, + tracker: &Tracker, + local_addr: SocketAddr, + cookie_issue_time: f64, + cookie_expiry_time: f64, + cookie_tolerance_max_time: f64, +) -> Response { tracing::debug!("Handling Packets: {udp_request:?}"); let start_time = Instant::now(); @@ -55,7 +63,16 @@ pub(crate) async fn handle_packet(udp_request: RawRequest, tracker: &Tracker, lo Request::Scrape(scrape_request) => scrape_request.transaction_id, }; - let response = match handle_request(request, udp_request.from, tracker).await { + let response = match handle_request( + request, + udp_request.from, + tracker, + cookie_issue_time, + cookie_expiry_time, + cookie_tolerance_max_time, + ) + .await + { Ok(response) => response, Err(e) => handle_error(&e, transaction_id), }; @@ -89,12 +106,28 @@ pub(crate) async fn handle_packet(udp_request: RawRequest, tracker: &Tracker, lo /// /// If a error happens in the `handle_request` function, it will just return the `ServerError`. #[instrument(skip(request, remote_addr, tracker))] -pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: &Tracker) -> Result { +pub async fn handle_request( + request: Request, + remote_addr: SocketAddr, + tracker: &Tracker, + cookie_issue_time: f64, + cookie_expiry_time: f64, + cookie_tolerance_max_time: f64, +) -> Result { tracing::trace!("handle request"); match request { - Request::Connect(connect_request) => handle_connect(remote_addr, &connect_request, tracker).await, - Request::Announce(announce_request) => handle_announce(remote_addr, &announce_request, tracker).await, + Request::Connect(connect_request) => handle_connect(remote_addr, &connect_request, tracker, cookie_issue_time).await, + Request::Announce(announce_request) => { + handle_announce( + remote_addr, + &announce_request, + tracker, + cookie_expiry_time, + cookie_tolerance_max_time, + ) + .await + } Request::Scrape(scrape_request) => handle_scrape(remote_addr, &scrape_request, tracker).await, } } @@ -106,11 +139,22 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: /// /// This function does not ever return an error. #[instrument(skip(tracker), err, ret(level = Level::TRACE))] -pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: &Tracker) -> Result { +pub async fn handle_connect( + remote_addr: SocketAddr, + request: &ConnectRequest, + tracker: &Tracker, + cookie_issue_time: f64, +) -> Result { tracing::trace!("handle connect"); - let connection_cookie = make(&remote_addr); - let connection_id = into_connection_id(&connection_cookie); + let connection_id = make( + { + let mut state = DefaultHasher::new(); + remote_addr.hash(&mut state); + state.finish() + }, + cookie_issue_time, + )?; let response = ConnectResponse { transaction_id: request.transaction_id, @@ -141,6 +185,8 @@ pub async fn handle_announce( remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: &Tracker, + cookie_expiry_time: f64, + cookie_tolerance_max_time: f64, ) -> Result { tracing::trace!("handle announce"); @@ -151,7 +197,16 @@ pub async fn handle_announce( }); } - check(&remote_addr, &from_connection_id(&announce_request.connection_id))?; + check( + &announce_request.connection_id, + { + let mut state = DefaultHasher::new(); + remote_addr.hash(&mut state); + state.finish() + }, + cookie_expiry_time, + cookie_tolerance_max_time, + )?; let info_hash = announce_request.info_hash.into(); let remote_client_ip = remote_addr.ip(); @@ -313,6 +368,7 @@ impl fmt::Display for RequestId { #[cfg(test)] mod tests { + use std::hash::{DefaultHasher, Hash as _, Hasher as _}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; @@ -350,14 +406,28 @@ mod tests { tracker_factory(configuration).into() } + fn make_remote_addr_fingerprint(remote_addr: &SocketAddr) -> u64 { + let mut state = DefaultHasher::new(); + remote_addr.hash(&mut state); + state.finish() + } + fn sample_ipv4_remote_addr() -> SocketAddr { sample_ipv4_socket_address() } + fn sample_ipv4_remote_addr_fingerprint() -> u64 { + make_remote_addr_fingerprint(&sample_ipv4_socket_address()) + } + fn sample_ipv6_remote_addr() -> SocketAddr { sample_ipv6_socket_address() } + fn sample_ipv6_remote_addr_fingerprint() -> u64 { + make_remote_addr_fingerprint(&sample_ipv4_socket_address()) + } + fn sample_ipv4_socket_address() -> SocketAddr { SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080) } @@ -366,6 +436,18 @@ mod tests { SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080) } + fn sample_issue_time() -> f64 { + 1_000_000_000_f64 + } + + fn sample_expiry_time() -> f64 { + sample_issue_time() - 10.0 + } + + fn tolerance_max_time() -> f64 { + sample_issue_time() + 10.0 + } + #[derive(Debug, Default)] pub struct TorrentPeerBuilder { peer: peer::Peer, @@ -438,9 +520,12 @@ mod tests { use super::{sample_ipv4_socket_address, sample_ipv6_remote_addr, tracker_configuration}; use crate::core::{self, statistics}; - use crate::servers::udp::connection_cookie::{into_connection_id, make}; + use crate::servers::udp::connection_cookie::make; use crate::servers::udp::handlers::handle_connect; - use crate::servers::udp::handlers::tests::{public_tracker, sample_ipv4_remote_addr}; + use crate::servers::udp::handlers::tests::{ + public_tracker, sample_ipv4_remote_addr, sample_ipv4_remote_addr_fingerprint, sample_ipv6_remote_addr_fingerprint, + sample_issue_time, + }; fn sample_connect_request() -> ConnectRequest { ConnectRequest { @@ -454,14 +539,14 @@ mod tests { transaction_id: TransactionId(0i32.into()), }; - let response = handle_connect(sample_ipv4_remote_addr(), &request, &public_tracker()) + let response = handle_connect(sample_ipv4_remote_addr(), &request, &public_tracker(), sample_issue_time()) .await .unwrap(); assert_eq!( response, Response::Connect(ConnectResponse { - connection_id: into_connection_id(&make(&sample_ipv4_remote_addr())), + connection_id: make(sample_ipv4_remote_addr_fingerprint(), sample_issue_time()).unwrap(), transaction_id: request.transaction_id }) ); @@ -473,14 +558,33 @@ mod tests { transaction_id: TransactionId(0i32.into()), }; - let response = handle_connect(sample_ipv4_remote_addr(), &request, &public_tracker()) + let response = handle_connect(sample_ipv4_remote_addr(), &request, &public_tracker(), sample_issue_time()) + .await + .unwrap(); + + assert_eq!( + response, + Response::Connect(ConnectResponse { + connection_id: make(sample_ipv4_remote_addr_fingerprint(), sample_issue_time()).unwrap(), + transaction_id: request.transaction_id + }) + ); + } + + #[tokio::test] + async fn a_connect_response_should_contain_a_new_connection_id_ipv6() { + let request = ConnectRequest { + transaction_id: TransactionId(0i32.into()), + }; + + let response = handle_connect(sample_ipv6_remote_addr(), &request, &public_tracker(), sample_issue_time()) .await .unwrap(); assert_eq!( response, Response::Connect(ConnectResponse { - connection_id: into_connection_id(&make(&sample_ipv4_remote_addr())), + connection_id: make(sample_ipv6_remote_addr_fingerprint(), sample_issue_time()).unwrap(), transaction_id: request.transaction_id }) ); @@ -506,9 +610,14 @@ mod tests { ) .unwrap(), ); - handle_connect(client_socket_address, &sample_connect_request(), &torrent_tracker) - .await - .unwrap(); + handle_connect( + client_socket_address, + &sample_connect_request(), + &torrent_tracker, + sample_issue_time(), + ) + .await + .unwrap(); } #[tokio::test] @@ -529,9 +638,14 @@ mod tests { ) .unwrap(), ); - handle_connect(sample_ipv6_remote_addr(), &sample_connect_request(), &torrent_tracker) - .await - .unwrap(); + handle_connect( + sample_ipv6_remote_addr(), + &sample_connect_request(), + &torrent_tracker, + sample_issue_time(), + ) + .await + .unwrap(); } } @@ -545,8 +659,8 @@ mod tests { PeerId as AquaticPeerId, PeerKey, Port, TransactionId, }; - use crate::servers::udp::connection_cookie::{into_connection_id, make}; - use crate::servers::udp::handlers::tests::sample_ipv4_remote_addr; + use super::{sample_ipv4_remote_addr_fingerprint, sample_issue_time}; + use crate::servers::udp::connection_cookie::make; struct AnnounceRequestBuilder { request: AnnounceRequest, @@ -559,7 +673,7 @@ mod tests { let info_hash_aquatic = aquatic_udp_protocol::InfoHash([0u8; 20]); let default_request = AnnounceRequest { - connection_id: into_connection_id(&make(&sample_ipv4_remote_addr())), + connection_id: make(sample_ipv4_remote_addr_fingerprint(), sample_issue_time()).unwrap(), action_placeholder: AnnounceActionPlaceholder::default(), transaction_id: TransactionId(0i32.into()), info_hash: info_hash_aquatic, @@ -621,10 +735,11 @@ mod tests { use mockall::predicate::eq; use crate::core::{self, statistics}; - use crate::servers::udp::connection_cookie::{into_connection_id, make}; + use crate::servers::udp::connection_cookie::make; use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder; use crate::servers::udp::handlers::tests::{ - public_tracker, sample_ipv4_socket_address, tracker_configuration, TorrentPeerBuilder, + make_remote_addr_fingerprint, public_tracker, sample_expiry_time, sample_ipv4_socket_address, sample_issue_time, + tolerance_max_time, tracker_configuration, TorrentPeerBuilder, }; use crate::servers::udp::handlers::{handle_announce, AnnounceResponseFixedData}; @@ -640,14 +755,16 @@ mod tests { let remote_addr = SocketAddr::new(IpAddr::V4(client_ip), client_port); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .with_info_hash(info_hash) .with_peer_id(peer_id) .with_ip_address(client_ip) .with_port(client_port) .into(); - handle_announce(remote_addr, &request, &tracker).await.unwrap(); + handle_announce(remote_addr, &request, &tracker, sample_expiry_time(), tolerance_max_time()) + .await + .unwrap(); let peers = tracker.get_torrent_peers(&info_hash.0.into()); @@ -664,10 +781,18 @@ mod tests { let remote_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .into(); - let response = handle_announce(remote_addr, &request, &public_tracker()).await.unwrap(); + let response = handle_announce( + remote_addr, + &request, + &public_tracker(), + sample_expiry_time(), + tolerance_max_time(), + ) + .await + .unwrap(); let empty_peer_vector: Vec> = vec![]; assert_eq!( @@ -703,14 +828,16 @@ mod tests { let remote_addr = SocketAddr::new(IpAddr::V4(remote_client_ip), remote_client_port); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .with_info_hash(info_hash) .with_peer_id(peer_id) .with_ip_address(peer_address) .with_port(client_port) .into(); - handle_announce(remote_addr, &request, &tracker).await.unwrap(); + handle_announce(remote_addr, &request, &tracker, sample_expiry_time(), tolerance_max_time()) + .await + .unwrap(); let peers = tracker.get_torrent_peers(&info_hash.0.into()); @@ -736,10 +863,12 @@ mod tests { async fn announce_a_new_peer_using_ipv4(tracker: Arc) -> Response { let remote_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .into(); - handle_announce(remote_addr, &request, &tracker).await.unwrap() + handle_announce(remote_addr, &request, &tracker, sample_expiry_time(), tolerance_max_time()) + .await + .unwrap() } #[tokio::test] @@ -782,6 +911,8 @@ mod tests { sample_ipv4_socket_address(), &AnnounceRequestBuilder::default().into(), &tracker, + sample_expiry_time(), + tolerance_max_time(), ) .await .unwrap(); @@ -793,10 +924,13 @@ mod tests { use aquatic_udp_protocol::{InfoHash as AquaticInfoHash, PeerId as AquaticPeerId}; - use crate::servers::udp::connection_cookie::{into_connection_id, make}; + use crate::servers::udp::connection_cookie::make; use crate::servers::udp::handlers::handle_announce; use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder; - use crate::servers::udp::handlers::tests::{public_tracker, TorrentPeerBuilder}; + use crate::servers::udp::handlers::tests::{ + make_remote_addr_fingerprint, public_tracker, sample_expiry_time, sample_issue_time, tolerance_max_time, + TorrentPeerBuilder, + }; #[tokio::test] async fn the_peer_ip_should_be_changed_to_the_external_ip_in_the_tracker_configuration_if_defined() { @@ -810,14 +944,16 @@ mod tests { let remote_addr = SocketAddr::new(IpAddr::V4(client_ip), client_port); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .with_info_hash(info_hash) .with_peer_id(peer_id) .with_ip_address(client_ip) .with_port(client_port) .into(); - handle_announce(remote_addr, &request, &tracker).await.unwrap(); + handle_announce(remote_addr, &request, &tracker, sample_expiry_time(), tolerance_max_time()) + .await + .unwrap(); let peers = tracker.get_torrent_peers(&info_hash.0.into()); @@ -846,10 +982,11 @@ mod tests { use mockall::predicate::eq; use crate::core::{self, statistics}; - use crate::servers::udp::connection_cookie::{into_connection_id, make}; + use crate::servers::udp::connection_cookie::make; use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder; use crate::servers::udp::handlers::tests::{ - public_tracker, sample_ipv6_remote_addr, tracker_configuration, TorrentPeerBuilder, + make_remote_addr_fingerprint, public_tracker, sample_expiry_time, sample_ipv6_remote_addr, sample_issue_time, + tolerance_max_time, tracker_configuration, TorrentPeerBuilder, }; use crate::servers::udp::handlers::{handle_announce, AnnounceResponseFixedData}; @@ -866,14 +1003,16 @@ mod tests { let remote_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), client_port); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .with_info_hash(info_hash) .with_peer_id(peer_id) .with_ip_address(client_ip_v4) .with_port(client_port) .into(); - handle_announce(remote_addr, &request, &tracker).await.unwrap(); + handle_announce(remote_addr, &request, &tracker, sample_expiry_time(), tolerance_max_time()) + .await + .unwrap(); let peers = tracker.get_torrent_peers(&info_hash.0.into()); @@ -893,10 +1032,18 @@ mod tests { let remote_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), 8080); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .into(); - let response = handle_announce(remote_addr, &request, &public_tracker()).await.unwrap(); + let response = handle_announce( + remote_addr, + &request, + &public_tracker(), + sample_expiry_time(), + tolerance_max_time(), + ) + .await + .unwrap(); let empty_peer_vector: Vec> = vec![]; assert_eq!( @@ -932,14 +1079,16 @@ mod tests { let remote_addr = SocketAddr::new(IpAddr::V6(remote_client_ip), remote_client_port); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .with_info_hash(info_hash) .with_peer_id(peer_id) .with_ip_address(peer_address) .with_port(client_port) .into(); - handle_announce(remote_addr, &request, &tracker).await.unwrap(); + handle_announce(remote_addr, &request, &tracker, sample_expiry_time(), tolerance_max_time()) + .await + .unwrap(); let peers = tracker.get_torrent_peers(&info_hash.0.into()); @@ -968,10 +1117,12 @@ mod tests { let client_port = 8080; let remote_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), client_port); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .into(); - handle_announce(remote_addr, &request, &tracker).await.unwrap() + handle_announce(remote_addr, &request, &tracker, sample_expiry_time(), tolerance_max_time()) + .await + .unwrap() } #[tokio::test] @@ -1013,10 +1164,18 @@ mod tests { let remote_addr = sample_ipv6_remote_addr(); let announce_request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .into(); - handle_announce(remote_addr, &announce_request, &tracker).await.unwrap(); + handle_announce( + remote_addr, + &announce_request, + &tracker, + sample_expiry_time(), + tolerance_max_time(), + ) + .await + .unwrap(); } mod from_a_loopback_ip { @@ -1027,10 +1186,13 @@ mod tests { use crate::core; use crate::core::statistics::Keeper; - use crate::servers::udp::connection_cookie::{into_connection_id, make}; + use crate::servers::udp::connection_cookie::make; use crate::servers::udp::handlers::handle_announce; use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder; - use crate::servers::udp::handlers::tests::TrackerConfigurationBuilder; + use crate::servers::udp::handlers::tests::{ + make_remote_addr_fingerprint, sample_expiry_time, sample_issue_time, tolerance_max_time, + TrackerConfigurationBuilder, + }; #[tokio::test] async fn the_peer_ip_should_be_changed_to_the_external_ip_in_the_tracker_configuration() { @@ -1052,14 +1214,16 @@ mod tests { let remote_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), client_port); let request = AnnounceRequestBuilder::default() - .with_connection_id(into_connection_id(&make(&remote_addr))) + .with_connection_id(make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap()) .with_info_hash(info_hash) .with_peer_id(peer_id) .with_ip_address(client_ip_v4) .with_port(client_port) .into(); - handle_announce(remote_addr, &request, &tracker).await.unwrap(); + handle_announce(remote_addr, &request, &tracker, sample_expiry_time(), tolerance_max_time()) + .await + .unwrap(); let peers = tracker.get_torrent_peers(&info_hash.0.into()); @@ -1087,11 +1251,11 @@ mod tests { TransactionId, }; - use super::TorrentPeerBuilder; + use super::{make_remote_addr_fingerprint, TorrentPeerBuilder}; use crate::core::{self}; - use crate::servers::udp::connection_cookie::{into_connection_id, make}; + use crate::servers::udp::connection_cookie::make; use crate::servers::udp::handlers::handle_scrape; - use crate::servers::udp::handlers::tests::{public_tracker, sample_ipv4_remote_addr}; + use crate::servers::udp::handlers::tests::{public_tracker, sample_ipv4_remote_addr, sample_issue_time}; fn zeroed_torrent_statistics() -> TorrentScrapeStatistics { TorrentScrapeStatistics { @@ -1109,7 +1273,7 @@ mod tests { let info_hashes = vec![info_hash]; let request = ScrapeRequest { - connection_id: into_connection_id(&make(&remote_addr)), + connection_id: make(make_remote_addr_fingerprint(&remote_addr), sample_issue_time()).unwrap(), transaction_id: TransactionId(0i32.into()), info_hashes, }; @@ -1143,7 +1307,7 @@ mod tests { let info_hashes = vec![*info_hash]; ScrapeRequest { - connection_id: into_connection_id(&make(remote_addr)), + connection_id: make(make_remote_addr_fingerprint(remote_addr), sample_issue_time()).unwrap(), transaction_id: TransactionId::new(0i32), info_hashes, } @@ -1285,7 +1449,7 @@ mod tests { let info_hashes = vec![info_hash]; ScrapeRequest { - connection_id: into_connection_id(&make(remote_addr)), + connection_id: make(make_remote_addr_fingerprint(remote_addr), sample_issue_time()).unwrap(), transaction_id: TransactionId(0i32.into()), info_hashes, } diff --git a/src/servers/udp/server/launcher.rs b/src/servers/udp/server/launcher.rs index 7f31d7739..348446876 100644 --- a/src/servers/udp/server/launcher.rs +++ b/src/servers/udp/server/launcher.rs @@ -35,6 +35,7 @@ impl Launcher { pub async fn run_with_graceful_shutdown( tracker: Arc, bind_to: SocketAddr, + cookie_lifetime: Duration, tx_start: oneshot::Sender, rx_halt: oneshot::Receiver, ) { @@ -65,7 +66,7 @@ impl Launcher { let local_addr = local_udp_url.clone(); tokio::task::spawn(async move { tracing::debug!(target: UDP_TRACKER_LOG_TARGET, local_addr, "Udp::run_with_graceful_shutdown::task (listening...)"); - let () = Self::run_udp_server_main(receiver, tracker.clone()).await; + let () = Self::run_udp_server_main(receiver, tracker.clone(), cookie_lifetime).await; }) }; @@ -103,14 +104,16 @@ impl Launcher { } #[instrument(skip(receiver, tracker))] - async fn run_udp_server_main(mut receiver: Receiver, tracker: Arc) { + async fn run_udp_server_main(mut receiver: Receiver, tracker: Arc, cookie_lifetime: Duration) { let active_requests = &mut ActiveRequests::default(); let addr = receiver.bound_socket_address(); let local_addr = format!("udp://{addr}"); + let cookie_lifetime = cookie_lifetime.as_secs_f64(); + loop { - let processor = Processor::new(receiver.socket.clone(), tracker.clone()); + let processor = Processor::new(receiver.socket.clone(), tracker.clone(), cookie_lifetime); if let Some(req) = { tracing::trace!(target: UDP_TRACKER_LOG_TARGET, local_addr, "Udp::run_udp_server (wait for request)"); diff --git a/src/servers/udp/server/mod.rs b/src/servers/udp/server/mod.rs index d81624cb2..7067512b6 100644 --- a/src/servers/udp/server/mod.rs +++ b/src/servers/udp/server/mod.rs @@ -76,7 +76,7 @@ mod tests { let stopped = Server::new(Spawner::new(bind_to)); let started = stopped - .start(tracker, register.give_form()) + .start(tracker, register.give_form(), config.cookie_lifetime) .await .expect("it should start the server"); @@ -98,7 +98,7 @@ mod tests { let stopped = Server::new(Spawner::new(bind_to)); let started = stopped - .start(tracker, register.give_form()) + .start(tracker, register.give_form(), config.cookie_lifetime) .await .expect("it should start the server"); diff --git a/src/servers/udp/server/processor.rs b/src/servers/udp/server/processor.rs index 9fa28a44d..2ac7f27cd 100644 --- a/src/servers/udp/server/processor.rs +++ b/src/servers/udp/server/processor.rs @@ -3,26 +3,45 @@ use std::net::SocketAddr; use std::sync::Arc; use aquatic_udp_protocol::Response; +use torrust_tracker_clock::clock::Time as _; use tracing::{instrument, Level}; use super::bound_socket::BoundSocket; use crate::core::Tracker; use crate::servers::udp::{handlers, RawRequest}; +use crate::CurrentClock; pub struct Processor { socket: Arc, tracker: Arc, + cookie_lifetime: f64, } impl Processor { - pub fn new(socket: Arc, tracker: Arc) -> Self { - Self { socket, tracker } + pub fn new(socket: Arc, tracker: Arc, cookie_lifetime: f64) -> Self { + Self { + socket, + tracker, + cookie_lifetime, + } } #[instrument(skip(self, request))] pub async fn process_request(self, request: RawRequest) { + let cookie_issue_time = CurrentClock::now().as_secs_f64(); + let cookie_expiry_time = cookie_issue_time - self.cookie_lifetime - 1.0; + let cookie_tolerance_max_time = cookie_issue_time + 1.0; + let from = request.from; - let response = handlers::handle_packet(request, &self.tracker, self.socket.address()).await; + let response = handlers::handle_packet( + request, + &self.tracker, + self.socket.address(), + cookie_issue_time, + cookie_expiry_time, + cookie_tolerance_max_time, + ) + .await; self.send_response(from, response).await; } diff --git a/src/servers/udp/server/spawner.rs b/src/servers/udp/server/spawner.rs index dea293ad7..acebdcf75 100644 --- a/src/servers/udp/server/spawner.rs +++ b/src/servers/udp/server/spawner.rs @@ -1,6 +1,7 @@ //! A thin wrapper for tokio spawn to launch the UDP server launcher as a new task. use std::net::SocketAddr; use std::sync::Arc; +use std::time::Duration; use derive_more::derive::Display; use derive_more::Constructor; @@ -27,13 +28,14 @@ impl Spawner { pub fn spawn_launcher( &self, tracker: Arc, + cookie_lifetime: Duration, tx_start: oneshot::Sender, rx_halt: oneshot::Receiver, ) -> JoinHandle { let spawner = Self::new(self.bind_to); tokio::spawn(async move { - Launcher::run_with_graceful_shutdown(tracker, spawner.bind_to, tx_start, rx_halt).await; + Launcher::run_with_graceful_shutdown(tracker, spawner.bind_to, cookie_lifetime, tx_start, rx_halt).await; spawner }) } diff --git a/src/servers/udp/server/states.rs b/src/servers/udp/server/states.rs index e90c4da54..8b87c6efb 100644 --- a/src/servers/udp/server/states.rs +++ b/src/servers/udp/server/states.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use std::net::SocketAddr; use std::sync::Arc; +use std::time::Duration; use derive_more::derive::Display; use derive_more::Constructor; @@ -62,14 +63,19 @@ impl Server { /// It panics if unable to receive the bound socket address from service. /// #[instrument(skip(self, tracker, form), err, ret(Display, level = Level::INFO))] - pub async fn start(self, tracker: Arc, form: ServiceRegistrationForm) -> Result, std::io::Error> { + pub async fn start( + self, + tracker: Arc, + form: ServiceRegistrationForm, + cookie_lifetime: Duration, + ) -> Result, std::io::Error> { let (tx_start, rx_start) = tokio::sync::oneshot::channel::(); let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::(); assert!(!tx_halt.is_closed(), "Halt channel for UDP tracker should be open"); // May need to wrap in a task to about a tokio bug. - let task = self.state.spawner.spawn_launcher(tracker, tx_start, rx_halt); + let task = self.state.spawner.spawn_launcher(tracker, cookie_lifetime, tx_start, rx_halt); let local_addr = rx_start.await.expect("it should be able to start the service").address; diff --git a/src/shared/crypto/ephemeral_instance_keys.rs b/src/shared/crypto/ephemeral_instance_keys.rs index 44283365a..c14b753aa 100644 --- a/src/shared/crypto/ephemeral_instance_keys.rs +++ b/src/shared/crypto/ephemeral_instance_keys.rs @@ -2,12 +2,24 @@ //! //! They are ephemeral because they are generated at runtime when the //! application starts and are not persisted anywhere. + +use blowfish::BlowfishLE; +use cipher::generic_array::GenericArray; +use cipher::{BlockSizeUser, Key, KeyInit as _}; use rand::rngs::ThreadRng; use rand::Rng; pub type Seed = [u8; 32]; +pub type CipherBlowfish = BlowfishLE; +pub type CipherArrayBlowfish = GenericArray::BlockSize>; lazy_static! { /// The random static seed. pub static ref RANDOM_SEED: Seed = Rng::gen(&mut ThreadRng::default()); + + /// The random cipher from the seed. + pub static ref RANDOM_CIPHER_BLOWFISH: CipherBlowfish = BlowfishLE::new(Key::::from_slice(RANDOM_SEED.as_slice())); + + /// The constant cipher for testing. + pub static ref ZEROED_TEST_CIPHER_BLOWFISH: CipherBlowfish = BlowfishLE::new(Key::::from_slice([0u8; 32].as_slice())); } diff --git a/src/shared/crypto/keys.rs b/src/shared/crypto/keys.rs index deb70574f..60dc16660 100644 --- a/src/shared/crypto/keys.rs +++ b/src/shared/crypto/keys.rs @@ -1,110 +1,154 @@ //! This module contains logic related to cryptographic keys. -pub mod seeds { - //! This module contains logic related to cryptographic seeds. - //! - //! Specifically, it contains the logic for storing the seed and providing - //! it to other modules. - //! - //! A **seed** is a pseudo-random number that is used as a secret key for - //! cryptographic operations. - use self::detail::CURRENT_SEED; - use crate::shared::crypto::ephemeral_instance_keys::{Seed, RANDOM_SEED}; - - /// This trait is for structures that can keep and provide a seed. - pub trait Keeper { - type Seed: Sized + Default + AsMut<[u8]>; - - /// It returns a reference to the seed that is keeping. - fn get_seed() -> &'static Self::Seed; +//! +//! Specifically, it contains the logic for storing the seed and providing +//! it to other modules. +//! +//! It also provides the logic for the cipher for encryption and decryption. + +use self::detail_cipher::CURRENT_CIPHER; +use self::detail_seed::CURRENT_SEED; +pub use crate::shared::crypto::ephemeral_instance_keys::CipherArrayBlowfish; +use crate::shared::crypto::ephemeral_instance_keys::{CipherBlowfish, Seed, RANDOM_CIPHER_BLOWFISH, RANDOM_SEED}; + +/// This trait is for structures that can keep and provide a seed. +pub trait Keeper { + type Seed: Sized + Default + AsMut<[u8]>; + type Cipher: cipher::BlockCipher; + + /// It returns a reference to the seed that is keeping. + fn get_seed() -> &'static Self::Seed; + fn get_cipher_blowfish() -> &'static Self::Cipher; +} + +/// The keeper for the instance. When the application is running +/// in production, this will be the seed keeper that is used. +pub struct Instance; + +/// The keeper for the current execution. It's a facade at compilation +/// time that will either be the instance seed keeper (with a randomly +/// generated key for production) or the zeroed seed keeper. +pub struct Current; + +impl Keeper for Instance { + type Seed = Seed; + type Cipher = CipherBlowfish; + + fn get_seed() -> &'static Self::Seed { + &RANDOM_SEED } - /// The seed keeper for the instance. When the application is running - /// in production, this will be the seed keeper that is used. - pub struct Instance; + fn get_cipher_blowfish() -> &'static Self::Cipher { + &RANDOM_CIPHER_BLOWFISH + } +} - /// The seed keeper for the current execution. It's a facade at compilation - /// time that will either be the instance seed keeper (with a randomly - /// generated key for production) or the zeroed seed keeper. - pub struct Current; +impl Keeper for Current { + type Seed = Seed; + type Cipher = CipherBlowfish; - impl Keeper for Instance { - type Seed = Seed; + #[allow(clippy::needless_borrow)] + fn get_seed() -> &'static Self::Seed { + &CURRENT_SEED + } - fn get_seed() -> &'static Self::Seed { - &RANDOM_SEED - } + fn get_cipher_blowfish() -> &'static Self::Cipher { + &CURRENT_CIPHER } +} + +#[cfg(test)] +mod tests { + + use super::detail_seed::ZEROED_TEST_SEED; + use super::{Current, Instance, Keeper}; + use crate::shared::crypto::ephemeral_instance_keys::{CipherBlowfish, Seed, ZEROED_TEST_CIPHER_BLOWFISH}; - impl Keeper for Current { + pub struct ZeroedTest; + + impl Keeper for ZeroedTest { type Seed = Seed; + type Cipher = CipherBlowfish; #[allow(clippy::needless_borrow)] fn get_seed() -> &'static Self::Seed { - &CURRENT_SEED + &ZEROED_TEST_SEED + } + + fn get_cipher_blowfish() -> &'static Self::Cipher { + &ZEROED_TEST_CIPHER_BLOWFISH } } + #[test] + fn the_default_seed_and_the_zeroed_seed_should_be_the_same_when_testing() { + assert_eq!(Current::get_seed(), ZeroedTest::get_seed()); + } + + #[test] + fn the_default_seed_and_the_instance_seed_should_be_different_when_testing() { + assert_ne!(Current::get_seed(), Instance::get_seed()); + } +} + +mod detail_seed { + use crate::shared::crypto::ephemeral_instance_keys::Seed; + + #[allow(dead_code)] + pub const ZEROED_TEST_SEED: Seed = [0u8; 32]; + #[cfg(test)] - mod tests { - use super::detail::ZEROED_TEST_SEED; - use super::{Current, Instance, Keeper}; - use crate::shared::crypto::ephemeral_instance_keys::Seed; + pub use ZEROED_TEST_SEED as CURRENT_SEED; - pub struct ZeroedTestSeed; + #[cfg(not(test))] + pub use crate::shared::crypto::ephemeral_instance_keys::RANDOM_SEED as CURRENT_SEED; - impl Keeper for ZeroedTestSeed { - type Seed = Seed; + #[cfg(test)] + mod tests { + use crate::shared::crypto::ephemeral_instance_keys::RANDOM_SEED; + use crate::shared::crypto::keys::detail_seed::ZEROED_TEST_SEED; + use crate::shared::crypto::keys::CURRENT_SEED; - #[allow(clippy::needless_borrow)] - fn get_seed() -> &'static Self::Seed { - &ZEROED_TEST_SEED - } + #[test] + fn it_should_have_a_zero_test_seed() { + assert_eq!(ZEROED_TEST_SEED, [0u8; 32]); } #[test] - fn the_default_seed_and_the_zeroed_seed_should_be_the_same_when_testing() { - assert_eq!(Current::get_seed(), ZeroedTestSeed::get_seed()); + fn it_should_default_to_zeroed_seed_when_testing() { + assert_eq!(CURRENT_SEED, ZEROED_TEST_SEED); } #[test] - fn the_default_seed_and_the_instance_seed_should_be_different_when_testing() { - assert_ne!(Current::get_seed(), Instance::get_seed()); + fn it_should_have_a_large_random_seed() { + assert!(u128::from_ne_bytes((*RANDOM_SEED)[..16].try_into().unwrap()) > u128::from(u64::MAX)); + assert!(u128::from_ne_bytes((*RANDOM_SEED)[16..].try_into().unwrap()) > u128::from(u64::MAX)); } } +} - mod detail { - use crate::shared::crypto::ephemeral_instance_keys::Seed; - - #[allow(dead_code)] - pub const ZEROED_TEST_SEED: &Seed = &[0u8; 32]; - - #[cfg(test)] - pub use ZEROED_TEST_SEED as CURRENT_SEED; +mod detail_cipher { + #[allow(unused_imports)] + #[cfg(not(test))] + pub use crate::shared::crypto::ephemeral_instance_keys::RANDOM_CIPHER_BLOWFISH as CURRENT_CIPHER; + #[cfg(test)] + pub use crate::shared::crypto::ephemeral_instance_keys::ZEROED_TEST_CIPHER_BLOWFISH as CURRENT_CIPHER; - #[cfg(not(test))] - pub use crate::shared::crypto::ephemeral_instance_keys::RANDOM_SEED as CURRENT_SEED; + #[cfg(test)] + mod tests { + use cipher::BlockEncrypt; - #[cfg(test)] - mod tests { - use crate::shared::crypto::ephemeral_instance_keys::RANDOM_SEED; - use crate::shared::crypto::keys::seeds::detail::ZEROED_TEST_SEED; - use crate::shared::crypto::keys::seeds::CURRENT_SEED; + use crate::shared::crypto::ephemeral_instance_keys::{CipherArrayBlowfish, ZEROED_TEST_CIPHER_BLOWFISH}; + use crate::shared::crypto::keys::detail_cipher::CURRENT_CIPHER; - #[test] - fn it_should_have_a_zero_test_seed() { - assert_eq!(*ZEROED_TEST_SEED, [0u8; 32]); - } + #[test] + fn it_should_default_to_zeroed_seed_when_testing() { + let mut data: cipher::generic_array::GenericArray = CipherArrayBlowfish::from([0u8; 8]); + let mut data_2 = CipherArrayBlowfish::from([0u8; 8]); - #[test] - fn it_should_default_to_zeroed_seed_when_testing() { - assert_eq!(*CURRENT_SEED, *ZEROED_TEST_SEED); - } + CURRENT_CIPHER.encrypt_block(&mut data); + ZEROED_TEST_CIPHER_BLOWFISH.encrypt_block(&mut data_2); - #[test] - fn it_should_have_a_large_random_seed() { - assert!(u128::from_ne_bytes((*RANDOM_SEED)[..16].try_into().unwrap()) > u128::from(u64::MAX)); - assert!(u128::from_ne_bytes((*RANDOM_SEED)[16..].try_into().unwrap()) > u128::from(u64::MAX)); - } + assert_eq!(data, data_2); } } } diff --git a/tests/servers/udp/environment.rs b/tests/servers/udp/environment.rs index 83dc076ce..f96ba2bea 100644 --- a/tests/servers/udp/environment.rs +++ b/tests/servers/udp/environment.rs @@ -55,11 +55,16 @@ impl Environment { #[allow(dead_code)] pub async fn start(self) -> Environment { + let cookie_lifetime = self.config.cookie_lifetime; Environment { config: self.config, tracker: self.tracker.clone(), registar: self.registar.clone(), - server: self.server.start(self.tracker, self.registar.give_form()).await.unwrap(), + server: self + .server + .start(self.tracker, self.registar.give_form(), cookie_lifetime) + .await + .unwrap(), } } }