From 9ee34f898e820400e7321e11bfc4dd02493f21e9 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 14:47:09 +0100 Subject: [PATCH 01/90] test: add test for Instant serialization --- src/api/server.rs | 44 ++++++++++++++++++++++++++++++++++++++++++ src/protocol/utils.rs | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/api/server.rs b/src/api/server.rs index 19ceac92a..674c496a9 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,3 +1,47 @@ +//! # API Server +//! +//! HTTP server for the tracker HTTP API. +//! +//! Endpoint example: +//! +//! GET /api/torrent/:info_hash +//! +//! Get torrent details. +//! +//! ```s +//! curl -s http://127.0.0.1:1212/api/torrent/4beb7001cb833968582c67f55cc59dcc6c8d3fe5?token=MyAccessToken | jq +//! ``` +//! +//! ```json +//! { +//! "info_hash": "4beb7001cb833968582c67f55cc59dcc6c8d3fe5", +//! "seeders": 1, +//! "completed": 0, +//! "leechers": 0, +//! "peers": [ +//! { +//! "peer_id": { +//! "id": "2d7142343431302d7358376d33786d2877674179", +//! "client": "qBittorrent" +//! }, +//! "peer_addr": "192.168.1.88:17548", +//! "updated": 385, +//! "uploaded": 0, +//! "downloaded": 0, +//! "left": 0, +//! "event": "None" +//! } +//! ] +//! } +//! ``` +//! +//! | Parameter | Description | +//! |-----------|-------------| +//! | info_hash | The info_hash of the torrent. | +//! +//! The `info_hash.peers.updated` are the number of milliseconds since the last update. +//! + use std::cmp::min; use std::collections::{HashMap, HashSet}; use std::net::SocketAddr; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 30b87b99b..be6fb1258 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -10,12 +10,57 @@ pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId { } } +/// It returns the current time in Unix Epoch. pub fn current_time() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH).unwrap() .as_secs() } +/// Serializer for `std::time::Instant` type. +/// Before serializing, it converts the instant to time elapse since that instant in milliseconds. +/// +/// You can use it like this: +/// +/// ```text +/// #[serde(serialize_with = "ser_instant")] +/// pub updated: std::time::Instant, +/// ``` +/// pub fn ser_instant(inst: &std::time::Instant, ser: S) -> Result { ser.serialize_u64(inst.elapsed().as_millis() as u64) } + +#[cfg(test)] +mod tests { + use std::time::Instant; + use serde::Serialize; + + #[warn(unused_imports)] + use super::ser_instant; + + #[derive(PartialEq, Eq, Debug, Clone, Serialize)] + struct S { + #[serde(serialize_with = "ser_instant")] + pub time: Instant, + } + + #[test] + fn instant_types_can_be_serialized_as_elapsed_time_since_that_instant_in_milliseconds() { + + use std::{thread, time}; + + let t1 = time::Instant::now(); + + let s = S { time: t1 }; + + // Sleep 10 milliseconds + let ten_millis = time::Duration::from_millis(10); + thread::sleep(ten_millis); + + let json_serialized_value = serde_json::to_string(&s).unwrap(); + + // Json contains time duration since t1 instant in milliseconds + assert_eq!(json_serialized_value, r#"{"time":10}"#); + } +} \ No newline at end of file From 27e04e6ddc39372060f9a8871e7a182d0e6a48a6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 17:04:13 +0100 Subject: [PATCH 02/90] docs: mod udp tracker description --- src/udp/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/udp/mod.rs b/src/udp/mod.rs index 25780ba93..a925abc82 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -1,3 +1,9 @@ +//! BitTorrent UDP Tracker Implementation. +//! +//! Protocol Specification: +//! +//! [BEP 15](https://www.bittorrent.org/beps/bep_0015.html) + pub use self::errors::*; pub use self::handlers::*; pub use self::request::*; From d8b13e52ec2c8d6ddc1eeeadd59e7e62b520daaf Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 17:07:31 +0100 Subject: [PATCH 03/90] test: added temp test for udp tracker connetion id I do not know yet requirements for the conenction id. These tests have to be changed once we find them out. --- src/protocol/utils.rs | 46 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index be6fb1258..dd39d2820 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -1,8 +1,8 @@ use std::net::SocketAddr; use std::time::SystemTime; - use aquatic_udp_protocol::ConnectionId; +/// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId { match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { Ok(duration) => ConnectionId(((duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36)) as i64), @@ -33,9 +33,51 @@ pub fn ser_instant(inst: &std::time::Instant, ser: S) -> R #[cfg(test)] mod tests { - use std::time::Instant; + use core::time; + use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}, thread::sleep}; + + #[test] + fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_some_hours() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let connection_id_1 = get_connection_id(&client_addr); + + // TODO: mock time passing + sleep(time::Duration::from_secs(10)); + + let connection_id_2 = get_connection_id(&client_addr); + + assert_eq!(connection_id_1, connection_id_2); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_different_for_each_tracker_client() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080); + + let connection_id_1 = get_connection_id(&client_1_addr); + let connection_id_2 = get_connection_id(&client_2_addr); + + assert_ne!(connection_id_1, connection_id_2); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_different_for_the_same_tracker_client_at_different_times() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let connection_id_1 = get_connection_id(&client_addr); + + sleep(time::Duration::from_secs(2)); + + let connection_id_2 = get_connection_id(&client_addr); + + assert_ne!(connection_id_1, connection_id_2); + } + use serde::Serialize; + use crate::protocol::utils::get_connection_id; + #[warn(unused_imports)] use super::ser_instant; From 6c2f312916bdfda42f029a2c59faa827e2f18664 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 11 Aug 2022 19:06:33 +0100 Subject: [PATCH 04/90] test: fix UDP conn ID generation test by mocking current time The current implementation of `get_connection_id` seems to be OK. I've added some tests. I needed to mock the current time becuase it's used in the ID generation. For now, I only neded to mock the current time (now). Not the clock service. I have also changed the signature of the `handle_connect` method. Now it panics if we cannot get the system time. In the previous behavior it was returning always a hardcoded connection ID: ConnectionId(0x7FFFFFFFFFFFFFFF) --- src/protocol/clock.rs | 20 +++++++++ src/protocol/mod.rs | 1 + src/protocol/utils.rs | 99 +++++++++++++++++++++++++++++++------------ src/udp/handlers.rs | 3 +- 4 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 src/protocol/clock.rs diff --git a/src/protocol/clock.rs b/src/protocol/clock.rs new file mode 100644 index 000000000..184aa8bff --- /dev/null +++ b/src/protocol/clock.rs @@ -0,0 +1,20 @@ +use std::time::{SystemTime}; + +pub trait Clock { + fn now_as_timestamp(&self) -> u64; +} + +/// A [`Clock`] which uses the operating system to determine the time. +struct SystemClock; + +impl Clock for SystemClock { + fn now_as_timestamp(&self) -> u64 { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() + } +} + +/// It returns the current timestamp using the system clock. +pub fn current_timestamp_from_system_clock() -> u64 { + let system_clock = SystemClock; + system_clock.now_as_timestamp() +} \ No newline at end of file diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 99cfd91e4..c3fb56965 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,2 +1,3 @@ pub mod common; pub mod utils; +pub mod clock; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index dd39d2820..feb88c908 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -1,16 +1,11 @@ -use std::net::SocketAddr; -use std::time::SystemTime; +use std::{net::SocketAddr, time::SystemTime}; use aquatic_udp_protocol::ConnectionId; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId { - match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { - Ok(duration) => ConnectionId(((duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36)) as i64), - Err(_) => ConnectionId(0x7FFFFFFFFFFFFFFF), - } +pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { + ConnectionId(((current_timestamp / 3600) | ((remote_address.port() as u64) << 36)) as i64) } -/// It returns the current time in Unix Epoch. pub fn current_time() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH).unwrap() @@ -33,47 +28,99 @@ pub fn ser_instant(inst: &std::time::Instant, ser: S) -> R #[cfg(test)] mod tests { - use core::time; - use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}, thread::sleep}; + use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}}; + + #[test] + fn connection_id_is_generated_based_on_remote_client_port_an_hours_passed_since_unix_epoch() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + + let timestamp = 946684800u64; // GTM: Sat Jan 01 2000 00:00:00 GMT+0000 + + // Timestamp in hours 946684800u64 / 3600 = 262968 = 0x_0000_0000_0004_0338 = 262968 + // Port 0001 = 0x_0000_0000_0000_0001 = 1 + // Port 0001 << 36 = 0x_0000_0010_0000_0000 = 68719476736 + // + // 0x_0000_0000_0004_0338 | 0x_0000_0010_0000_0000 = 0x_0000_0010_0004_0338 = 68719739704 + // + // HEX BIN DEC + // -------------------------------------------------------------------------------- + // 0x_0000_0000_0004_0338 = ... 0000000000000000001000000001100111000 = 262968 + // OR + // 0x_0000_0010_0000_0000 = ... 1000000000000000000000000000000000000 = 68719476736 + // ------------------------------------------------------------------- + // 0x_0000_0010_0004_0338 = ... 1000000000000000001000000001100111000 = 68719739704 + + // Assert intermediary values + assert_eq!(timestamp / 3600, 0x_0000_0000_0004_0338); + assert_eq!((client_addr.port() as u64), 1); + assert_eq!(((client_addr.port() as u64) << 36), 0x_0000_0010_0000_0000); // 68719476736 + assert_eq!((0x_0000_0000_0004_0338u64 | 0x_0000_0010_0000_0000u64), 0x_0000_0010_0004_0338u64); // 68719739704 + assert_eq!(0x_0000_0010_0004_0338u64 as i64, 68719739704); // 68719739704 + + let connection_id = super::get_connection_id(&client_addr, timestamp); + + assert_eq!(connection_id, ConnectionId(68719739704)); + } #[test] - fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_some_hours() { + fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_one_hour() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let connection_id_1 = get_connection_id(&client_addr); + let now = 946684800u64; - // TODO: mock time passing - sleep(time::Duration::from_secs(10)); + let connection_id = get_connection_id(&client_addr, now); - let connection_id_2 = get_connection_id(&client_addr); + let in_one_hour = now + 3600 - 1; - assert_eq!(connection_id_1, connection_id_2); + let connection_id_after_one_hour = get_connection_id(&client_addr, in_one_hour); + + assert_eq!(connection_id, connection_id_after_one_hour); } #[test] - fn connection_id_in_udp_tracker_should_be_different_for_each_tracker_client() { - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080); + fn connection_id_in_udp_tracker_should_change_for_the_same_client_and_port_after_one_hour() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let connection_id_1 = get_connection_id(&client_1_addr); - let connection_id_2 = get_connection_id(&client_2_addr); + let now = 946684800u64; - assert_ne!(connection_id_1, connection_id_2); + let connection_id = get_connection_id(&client_addr, now); + + let after_one_hour = now + 3600; + + let connection_id_after_one_hour = get_connection_id(&client_addr, after_one_hour); + + assert_ne!(connection_id, connection_id_after_one_hour); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); + + let now = 946684800u64; + + let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); + + assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } #[test] - fn connection_id_in_udp_tracker_should_be_different_for_the_same_tracker_client_at_different_times() { + fn connection_id_in_udp_tracker_should_expire_after_one_hour() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let connection_id_1 = get_connection_id(&client_addr); + let now = 946684800u64; + + let connection_id_1 = get_connection_id(&client_addr, now); - sleep(time::Duration::from_secs(2)); + let in_one_hour = now + 3600; - let connection_id_2 = get_connection_id(&client_addr); + let connection_id_2 = get_connection_id(&client_addr, in_one_hour); assert_ne!(connection_id_1, connection_id_2); } + use aquatic_udp_protocol::ConnectionId; use serde::Serialize; use crate::protocol::utils::get_connection_id; diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 860a2fe4b..a93ab219c 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -11,6 +11,7 @@ use crate::udp::request::AnnounceRequestWrapper; use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; use crate::protocol::utils::get_connection_id; +use crate::protocol::clock::current_timestamp_from_system_clock; pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { match tracker.authenticate_request(info_hash, &None).await { @@ -70,7 +71,7 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: } pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let connection_id = get_connection_id(&remote_addr); + let connection_id = get_connection_id(&remote_addr, current_timestamp_from_system_clock()); let response = Response::from(ConnectResponse { transaction_id: request.transaction_id, From bfa803b7f8af5480dd6924415da64c294cdc9510 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 12 Aug 2022 09:16:11 +0100 Subject: [PATCH 05/90] refactor: rename function --- src/protocol/clock.rs | 2 +- src/udp/handlers.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/protocol/clock.rs b/src/protocol/clock.rs index 184aa8bff..7488a8778 100644 --- a/src/protocol/clock.rs +++ b/src/protocol/clock.rs @@ -14,7 +14,7 @@ impl Clock for SystemClock { } /// It returns the current timestamp using the system clock. -pub fn current_timestamp_from_system_clock() -> u64 { +pub fn current_timestamp() -> u64 { let system_clock = SystemClock; system_clock.now_as_timestamp() } \ No newline at end of file diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index a93ab219c..67b398dbc 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -11,7 +11,7 @@ use crate::udp::request::AnnounceRequestWrapper; use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; use crate::protocol::utils::get_connection_id; -use crate::protocol::clock::current_timestamp_from_system_clock; +use crate::protocol::clock::current_timestamp; pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { match tracker.authenticate_request(info_hash, &None).await { @@ -71,7 +71,7 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: } pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let connection_id = get_connection_id(&remote_addr, current_timestamp_from_system_clock()); + let connection_id = get_connection_id(&remote_addr, current_timestamp()); let response = Response::from(ConnectResponse { transaction_id: request.transaction_id, From 33bc38d058a6465759224d6e3ce4fe5d7a99802d Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 12 Aug 2022 01:17:59 +0200 Subject: [PATCH 06/90] test: updated connection id tests to test for BEP-15 requirements --- src/protocol/utils.rs | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index feb88c908..69a554025 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -31,7 +31,7 @@ mod tests { use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}}; #[test] - fn connection_id_is_generated_based_on_remote_client_port_an_hours_passed_since_unix_epoch() { + fn connection_id_is_generated_based_on_remote_client_port_and_hours_passed_since_unix_epoch() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let timestamp = 946684800u64; // GTM: Sat Jan 01 2000 00:00:00 GMT+0000 @@ -57,20 +57,20 @@ mod tests { assert_eq!((0x_0000_0000_0004_0338u64 | 0x_0000_0010_0000_0000u64), 0x_0000_0010_0004_0338u64); // 68719739704 assert_eq!(0x_0000_0010_0004_0338u64 as i64, 68719739704); // 68719739704 - let connection_id = super::get_connection_id(&client_addr, timestamp); + let connection_id = get_connection_id(&client_addr, timestamp); assert_eq!(connection_id, ConnectionId(68719739704)); } #[test] - fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_one_hour() { + fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_two_minutes() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; let connection_id = get_connection_id(&client_addr, now); - let in_one_hour = now + 3600 - 1; + let in_one_hour = now + 120 - 1; let connection_id_after_one_hour = get_connection_id(&client_addr, in_one_hour); @@ -78,24 +78,24 @@ mod tests { } #[test] - fn connection_id_in_udp_tracker_should_change_for_the_same_client_and_port_after_one_hour() { + fn connection_id_in_udp_tracker_should_change_for_the_same_client_ip_and_port_after_two_minutes() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; let connection_id = get_connection_id(&client_addr, now); - let after_one_hour = now + 3600; + let after_two_minutes = now + 120; - let connection_id_after_one_hour = get_connection_id(&client_addr, after_one_hour); + let connection_id_after_two_minutes = get_connection_id(&client_addr, after_two_minutes); - assert_ne!(connection_id, connection_id_after_one_hour); - } + assert_ne!(connection_id, connection_id_after_two_minutes); + } #[test] - fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); + fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let now = 946684800u64; @@ -106,18 +106,16 @@ mod tests { } #[test] - fn connection_id_in_udp_tracker_should_expire_after_one_hour() { - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); let now = 946684800u64; - let connection_id_1 = get_connection_id(&client_addr, now); - - let in_one_hour = now + 3600; - - let connection_id_2 = get_connection_id(&client_addr, in_one_hour); + let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); - assert_ne!(connection_id_1, connection_id_2); + assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } use aquatic_udp_protocol::ConnectionId; @@ -139,7 +137,7 @@ mod tests { use std::{thread, time}; - let t1 = time::Instant::now(); + let t1 = Instant::now(); let s = S { time: t1 }; @@ -152,4 +150,4 @@ mod tests { // Json contains time duration since t1 instant in milliseconds assert_eq!(json_serialized_value, r#"{"time":10}"#); } -} \ No newline at end of file +} From 5c0551e7666a42f84086f4627e6d4196b7f7a7be Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 12 Aug 2022 02:47:16 +0200 Subject: [PATCH 07/90] feat: improved get_connection_id function to conform with the requirements listed in BEP-15 --- Cargo.lock | 34 ++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/lib.rs | 2 ++ src/protocol/utils.rs | 46 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c52f6767b..52366c069 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,12 @@ dependencies = [ "either", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + [[package]] name = "arrayvec" version = "0.5.2" @@ -158,6 +164,20 @@ dependencies = [ "wyz", ] +[[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" @@ -302,6 +322,12 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "convert_case" version = "0.4.0" @@ -428,6 +454,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common", + "subtle", ] [[package]] @@ -2086,6 +2113,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.92" @@ -2341,6 +2374,7 @@ dependencies = [ "aquatic_udp_protocol", "async-trait", "binascii", + "blake3", "chrono", "config", "derive_more", diff --git a/Cargo.toml b/Cargo.toml index 554ba940d..db9043f09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ serde_with = "2.0.0" hex = "0.4.3" percent-encoding = "2.1.0" binascii = "0.1" +blake3 = "1.3.1" warp = { version = "0.3", features = ["tls"] } diff --git a/src/lib.rs b/src/lib.rs index 245f4686c..c3d7c6efc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +extern crate core; + pub use http::server::*; pub use udp::server::*; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 69a554025..764704987 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -1,9 +1,44 @@ use std::{net::SocketAddr, time::SystemTime}; +use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; +// todo: SALT should be randomly generated on startup +const SALT: &str = "SALT"; + /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { - ConnectionId(((current_timestamp / 3600) | ((remote_address.port() as u64) << 36)) as i64) +pub fn get_connection_id(remote_address: &SocketAddr, time_as_seconds: u64) -> ConnectionId { + let peer_ip_as_bytes = match remote_address.ip() { + IpAddr::V4(ip) => ip.octets().to_vec(), + IpAddr::V6(ip) => ip.octets().to_vec(), + }; + + let input: Vec = [ + (time_as_seconds / 120).to_be_bytes().as_slice(), + peer_ip_as_bytes.as_slice(), + remote_address.port().to_be_bytes().as_slice(), + SALT.as_bytes() + ].concat(); + + let hash = blake3::hash(&input); + + let mut truncated_hash: [u8; 8] = [0u8; 8]; + + truncated_hash.copy_from_slice(&hash.as_bytes()[..8]); + + let connection_id = i64::from_le_bytes(truncated_hash); + + ConnectionId(connection_id) +} + +/// Verifies whether a connection id is valid at this time for a given remote address (ip + port) +pub fn verify_connection_id(connection_id: ConnectionId, remote_address: &SocketAddr) -> Result<(), ()> { + let current_time = current_time(); + + match connection_id { + cid if cid == get_connection_id(remote_address, current_time) => Ok(()), + cid if cid == get_connection_id(remote_address, current_time - 120) => Ok(()), + _ => Err(()) + } } pub fn current_time() -> u64 { @@ -29,6 +64,8 @@ pub fn ser_instant(inst: &std::time::Instant, ser: S) -> R #[cfg(test)] mod tests { use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}}; + use serde::Serialize; + use crate::protocol::utils::{ConnectionId, get_connection_id}; #[test] fn connection_id_is_generated_based_on_remote_client_port_and_hours_passed_since_unix_epoch() { @@ -118,11 +155,6 @@ mod tests { assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } - use aquatic_udp_protocol::ConnectionId; - use serde::Serialize; - - use crate::protocol::utils::get_connection_id; - #[warn(unused_imports)] use super::ser_instant; From 6e1689e7085004bbbc5bbf8c766747481c769c75 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 12 Aug 2022 11:19:25 +0200 Subject: [PATCH 08/90] test: added benchmarks to test the speed of generating a connection id --- Cargo.lock | 257 +++++++++++++++++++++++++++++- Cargo.toml | 7 + benches/generate_connection_id.rs | 37 +++++ 3 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 benches/generate_connection_id.rs diff --git a/Cargo.lock b/Cargo.lock index 52366c069..94188a64f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde 1.0.137", +] + [[package]] name = "buf_redux" version = "0.8.4" @@ -230,6 +242,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.73" @@ -368,6 +386,87 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits 0.2.15", + "oorandom", + "plotters", + "rayon", + "regex", + "serde 1.0.137", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "once_cell", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -378,6 +477,28 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde 1.0.137", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.14.1" @@ -761,6 +882,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.11.2" @@ -818,7 +945,7 @@ checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.1", ] [[package]] @@ -865,7 +992,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 1.0.1", "pin-project-lite", "socket2", "tokio", @@ -923,6 +1050,21 @@ dependencies = [ "syn", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.1" @@ -1338,6 +1480,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -1498,6 +1646,34 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "plotters" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" +dependencies = [ + "num-traits 0.2.15", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" +dependencies = [ + "plotters-backend", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1601,6 +1777,30 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1621,6 +1821,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.25" @@ -1733,6 +1939,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "saturating" version = "0.1.0" @@ -1870,6 +2085,16 @@ dependencies = [ "serde 1.0.137", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde 1.0.137", +] + [[package]] name = "serde_derive" version = "1.0.137" @@ -1887,7 +2112,7 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ - "itoa", + "itoa 1.0.1", "ryu", "serde 1.0.137", ] @@ -1899,7 +2124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa", + "itoa 1.0.1", "ryu", "serde 1.0.137", ] @@ -2220,7 +2445,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" dependencies = [ - "itoa", + "itoa 1.0.1", "libc", "num_threads", "serde 1.0.137", @@ -2249,6 +2474,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde 1.0.137", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2377,6 +2612,7 @@ dependencies = [ "blake3", "chrono", "config", + "criterion", "derive_more", "fern", "futures", @@ -2581,6 +2817,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index db9043f09..3aa6298bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,13 @@ opt-level = 3 lto = "fat" strip = true +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "generate_connection_id" +harness = false + [dependencies] tokio = { version = "1.7", features = ["full"] } diff --git a/benches/generate_connection_id.rs b/benches/generate_connection_id.rs new file mode 100644 index 000000000..0e7fd90f4 --- /dev/null +++ b/benches/generate_connection_id.rs @@ -0,0 +1,37 @@ +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use aquatic_udp_protocol::ConnectionId; +use criterion::{Criterion, criterion_group, criterion_main}; +use torrust_tracker::protocol::utils::{current_time, get_connection_id}; + +fn get_connection_id_old(current_time: u64, port: u16) -> ConnectionId { + let time_i64 = (current_time / 3600) as i64; + + ConnectionId((time_i64 | port as i64) << 36) +} + +pub fn benchmark_generate_id_with_time_and_port(bench: &mut Criterion) { + let remote_address = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 117); + let current_time = current_time(); + + bench.bench_function("generate_id_with_time_and_port", |b| { + b.iter(|| { + // Inner closure, the actual test + let _ = get_connection_id_old(current_time, remote_address.port()); + }) + }); +} + +pub fn benchmark_generate_id_with_hashed_time_and_ip_and_port_and_salt(bench: &mut Criterion) { + let remote_address = SocketAddr::from(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 117)); + let current_time = current_time(); + + bench.bench_function("generate_id_with_hashed_time_and_ip_and_port_and_salt", |b| { + b.iter(|| { + // Inner closure, the actual test + let _ = get_connection_id(&remote_address, current_time); + }) + }); +} + +criterion_group!(benches, benchmark_generate_id_with_time_and_port, benchmark_generate_id_with_hashed_time_and_ip_and_port_and_salt); +criterion_main!(benches); From 2f16d13fcb9c64a766faa1c06dc5916a7c2aa8c9 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 12 Aug 2022 13:19:15 +0200 Subject: [PATCH 09/90] refactor: renamed variables and removed unused import --- src/lib.rs | 2 -- src/protocol/utils.rs | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3d7c6efc..245f4686c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -extern crate core; - pub use http::server::*; pub use udp::server::*; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 764704987..63c47bc4d 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -107,11 +107,11 @@ mod tests { let connection_id = get_connection_id(&client_addr, now); - let in_one_hour = now + 120 - 1; + let in_two_minutes = now + 120 - 1; - let connection_id_after_one_hour = get_connection_id(&client_addr, in_one_hour); + let connection_id_after_two_minutes = get_connection_id(&client_addr, in_two_minutes); - assert_eq!(connection_id, connection_id_after_one_hour); + assert_eq!(connection_id, connection_id_after_two_minutes); } #[test] From a10624c866324f57959ee9812bf0b9877c6201be Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 14:47:09 +0100 Subject: [PATCH 10/90] test: add test for Instant serialization --- src/api/server.rs | 44 ++++++++++++++++++++++++++++++++++++++++++ src/protocol/utils.rs | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/api/server.rs b/src/api/server.rs index 19ceac92a..674c496a9 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,3 +1,47 @@ +//! # API Server +//! +//! HTTP server for the tracker HTTP API. +//! +//! Endpoint example: +//! +//! GET /api/torrent/:info_hash +//! +//! Get torrent details. +//! +//! ```s +//! curl -s http://127.0.0.1:1212/api/torrent/4beb7001cb833968582c67f55cc59dcc6c8d3fe5?token=MyAccessToken | jq +//! ``` +//! +//! ```json +//! { +//! "info_hash": "4beb7001cb833968582c67f55cc59dcc6c8d3fe5", +//! "seeders": 1, +//! "completed": 0, +//! "leechers": 0, +//! "peers": [ +//! { +//! "peer_id": { +//! "id": "2d7142343431302d7358376d33786d2877674179", +//! "client": "qBittorrent" +//! }, +//! "peer_addr": "192.168.1.88:17548", +//! "updated": 385, +//! "uploaded": 0, +//! "downloaded": 0, +//! "left": 0, +//! "event": "None" +//! } +//! ] +//! } +//! ``` +//! +//! | Parameter | Description | +//! |-----------|-------------| +//! | info_hash | The info_hash of the torrent. | +//! +//! The `info_hash.peers.updated` are the number of milliseconds since the last update. +//! + use std::cmp::min; use std::collections::{HashMap, HashSet}; use std::net::SocketAddr; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 30b87b99b..be6fb1258 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -10,12 +10,57 @@ pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId { } } +/// It returns the current time in Unix Epoch. pub fn current_time() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH).unwrap() .as_secs() } +/// Serializer for `std::time::Instant` type. +/// Before serializing, it converts the instant to time elapse since that instant in milliseconds. +/// +/// You can use it like this: +/// +/// ```text +/// #[serde(serialize_with = "ser_instant")] +/// pub updated: std::time::Instant, +/// ``` +/// pub fn ser_instant(inst: &std::time::Instant, ser: S) -> Result { ser.serialize_u64(inst.elapsed().as_millis() as u64) } + +#[cfg(test)] +mod tests { + use std::time::Instant; + use serde::Serialize; + + #[warn(unused_imports)] + use super::ser_instant; + + #[derive(PartialEq, Eq, Debug, Clone, Serialize)] + struct S { + #[serde(serialize_with = "ser_instant")] + pub time: Instant, + } + + #[test] + fn instant_types_can_be_serialized_as_elapsed_time_since_that_instant_in_milliseconds() { + + use std::{thread, time}; + + let t1 = time::Instant::now(); + + let s = S { time: t1 }; + + // Sleep 10 milliseconds + let ten_millis = time::Duration::from_millis(10); + thread::sleep(ten_millis); + + let json_serialized_value = serde_json::to_string(&s).unwrap(); + + // Json contains time duration since t1 instant in milliseconds + assert_eq!(json_serialized_value, r#"{"time":10}"#); + } +} \ No newline at end of file From 925344a8260c62ead02c429093c3c62d19bf05d8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 17:04:13 +0100 Subject: [PATCH 11/90] docs: mod udp tracker description --- src/udp/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/udp/mod.rs b/src/udp/mod.rs index 25780ba93..a925abc82 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -1,3 +1,9 @@ +//! BitTorrent UDP Tracker Implementation. +//! +//! Protocol Specification: +//! +//! [BEP 15](https://www.bittorrent.org/beps/bep_0015.html) + pub use self::errors::*; pub use self::handlers::*; pub use self::request::*; From 52390bf1d51534e2304d4791e55cb41a51159740 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 17:07:31 +0100 Subject: [PATCH 12/90] test: added temp test for udp tracker connetion id I do not know yet requirements for the conenction id. These tests have to be changed once we find them out. --- src/protocol/utils.rs | 46 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index be6fb1258..dd39d2820 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -1,8 +1,8 @@ use std::net::SocketAddr; use std::time::SystemTime; - use aquatic_udp_protocol::ConnectionId; +/// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId { match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { Ok(duration) => ConnectionId(((duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36)) as i64), @@ -33,9 +33,51 @@ pub fn ser_instant(inst: &std::time::Instant, ser: S) -> R #[cfg(test)] mod tests { - use std::time::Instant; + use core::time; + use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}, thread::sleep}; + + #[test] + fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_some_hours() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let connection_id_1 = get_connection_id(&client_addr); + + // TODO: mock time passing + sleep(time::Duration::from_secs(10)); + + let connection_id_2 = get_connection_id(&client_addr); + + assert_eq!(connection_id_1, connection_id_2); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_different_for_each_tracker_client() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080); + + let connection_id_1 = get_connection_id(&client_1_addr); + let connection_id_2 = get_connection_id(&client_2_addr); + + assert_ne!(connection_id_1, connection_id_2); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_different_for_the_same_tracker_client_at_different_times() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let connection_id_1 = get_connection_id(&client_addr); + + sleep(time::Duration::from_secs(2)); + + let connection_id_2 = get_connection_id(&client_addr); + + assert_ne!(connection_id_1, connection_id_2); + } + use serde::Serialize; + use crate::protocol::utils::get_connection_id; + #[warn(unused_imports)] use super::ser_instant; From 38e65e6b80c568e8b24879dc59610e521164bcde Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 11 Aug 2022 19:06:33 +0100 Subject: [PATCH 13/90] test: fix UDP conn ID generation test by mocking current time The current implementation of `get_connection_id` seems to be OK. I've added some tests. I needed to mock the current time becuase it's used in the ID generation. For now, I only neded to mock the current time (now). Not the clock service. I have also changed the signature of the `handle_connect` method. Now it panics if we cannot get the system time. In the previous behavior it was returning always a hardcoded connection ID: ConnectionId(0x7FFFFFFFFFFFFFFF) --- src/protocol/clock.rs | 20 +++++++++ src/protocol/mod.rs | 1 + src/protocol/utils.rs | 99 +++++++++++++++++++++++++++++++------------ src/udp/handlers.rs | 3 +- 4 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 src/protocol/clock.rs diff --git a/src/protocol/clock.rs b/src/protocol/clock.rs new file mode 100644 index 000000000..184aa8bff --- /dev/null +++ b/src/protocol/clock.rs @@ -0,0 +1,20 @@ +use std::time::{SystemTime}; + +pub trait Clock { + fn now_as_timestamp(&self) -> u64; +} + +/// A [`Clock`] which uses the operating system to determine the time. +struct SystemClock; + +impl Clock for SystemClock { + fn now_as_timestamp(&self) -> u64 { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() + } +} + +/// It returns the current timestamp using the system clock. +pub fn current_timestamp_from_system_clock() -> u64 { + let system_clock = SystemClock; + system_clock.now_as_timestamp() +} \ No newline at end of file diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 99cfd91e4..c3fb56965 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,2 +1,3 @@ pub mod common; pub mod utils; +pub mod clock; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index dd39d2820..feb88c908 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -1,16 +1,11 @@ -use std::net::SocketAddr; -use std::time::SystemTime; +use std::{net::SocketAddr, time::SystemTime}; use aquatic_udp_protocol::ConnectionId; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId { - match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { - Ok(duration) => ConnectionId(((duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36)) as i64), - Err(_) => ConnectionId(0x7FFFFFFFFFFFFFFF), - } +pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { + ConnectionId(((current_timestamp / 3600) | ((remote_address.port() as u64) << 36)) as i64) } -/// It returns the current time in Unix Epoch. pub fn current_time() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH).unwrap() @@ -33,47 +28,99 @@ pub fn ser_instant(inst: &std::time::Instant, ser: S) -> R #[cfg(test)] mod tests { - use core::time; - use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}, thread::sleep}; + use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}}; + + #[test] + fn connection_id_is_generated_based_on_remote_client_port_an_hours_passed_since_unix_epoch() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + + let timestamp = 946684800u64; // GTM: Sat Jan 01 2000 00:00:00 GMT+0000 + + // Timestamp in hours 946684800u64 / 3600 = 262968 = 0x_0000_0000_0004_0338 = 262968 + // Port 0001 = 0x_0000_0000_0000_0001 = 1 + // Port 0001 << 36 = 0x_0000_0010_0000_0000 = 68719476736 + // + // 0x_0000_0000_0004_0338 | 0x_0000_0010_0000_0000 = 0x_0000_0010_0004_0338 = 68719739704 + // + // HEX BIN DEC + // -------------------------------------------------------------------------------- + // 0x_0000_0000_0004_0338 = ... 0000000000000000001000000001100111000 = 262968 + // OR + // 0x_0000_0010_0000_0000 = ... 1000000000000000000000000000000000000 = 68719476736 + // ------------------------------------------------------------------- + // 0x_0000_0010_0004_0338 = ... 1000000000000000001000000001100111000 = 68719739704 + + // Assert intermediary values + assert_eq!(timestamp / 3600, 0x_0000_0000_0004_0338); + assert_eq!((client_addr.port() as u64), 1); + assert_eq!(((client_addr.port() as u64) << 36), 0x_0000_0010_0000_0000); // 68719476736 + assert_eq!((0x_0000_0000_0004_0338u64 | 0x_0000_0010_0000_0000u64), 0x_0000_0010_0004_0338u64); // 68719739704 + assert_eq!(0x_0000_0010_0004_0338u64 as i64, 68719739704); // 68719739704 + + let connection_id = super::get_connection_id(&client_addr, timestamp); + + assert_eq!(connection_id, ConnectionId(68719739704)); + } #[test] - fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_some_hours() { + fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_one_hour() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let connection_id_1 = get_connection_id(&client_addr); + let now = 946684800u64; - // TODO: mock time passing - sleep(time::Duration::from_secs(10)); + let connection_id = get_connection_id(&client_addr, now); - let connection_id_2 = get_connection_id(&client_addr); + let in_one_hour = now + 3600 - 1; - assert_eq!(connection_id_1, connection_id_2); + let connection_id_after_one_hour = get_connection_id(&client_addr, in_one_hour); + + assert_eq!(connection_id, connection_id_after_one_hour); } #[test] - fn connection_id_in_udp_tracker_should_be_different_for_each_tracker_client() { - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080); + fn connection_id_in_udp_tracker_should_change_for_the_same_client_and_port_after_one_hour() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let connection_id_1 = get_connection_id(&client_1_addr); - let connection_id_2 = get_connection_id(&client_2_addr); + let now = 946684800u64; - assert_ne!(connection_id_1, connection_id_2); + let connection_id = get_connection_id(&client_addr, now); + + let after_one_hour = now + 3600; + + let connection_id_after_one_hour = get_connection_id(&client_addr, after_one_hour); + + assert_ne!(connection_id, connection_id_after_one_hour); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); + + let now = 946684800u64; + + let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); + + assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } #[test] - fn connection_id_in_udp_tracker_should_be_different_for_the_same_tracker_client_at_different_times() { + fn connection_id_in_udp_tracker_should_expire_after_one_hour() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let connection_id_1 = get_connection_id(&client_addr); + let now = 946684800u64; + + let connection_id_1 = get_connection_id(&client_addr, now); - sleep(time::Duration::from_secs(2)); + let in_one_hour = now + 3600; - let connection_id_2 = get_connection_id(&client_addr); + let connection_id_2 = get_connection_id(&client_addr, in_one_hour); assert_ne!(connection_id_1, connection_id_2); } + use aquatic_udp_protocol::ConnectionId; use serde::Serialize; use crate::protocol::utils::get_connection_id; diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 860a2fe4b..a93ab219c 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -11,6 +11,7 @@ use crate::udp::request::AnnounceRequestWrapper; use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; use crate::protocol::utils::get_connection_id; +use crate::protocol::clock::current_timestamp_from_system_clock; pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { match tracker.authenticate_request(info_hash, &None).await { @@ -70,7 +71,7 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: } pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let connection_id = get_connection_id(&remote_addr); + let connection_id = get_connection_id(&remote_addr, current_timestamp_from_system_clock()); let response = Response::from(ConnectResponse { transaction_id: request.transaction_id, From 0fa29b20d56c0b1e7d667ee175f6e985098a2c4e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 12 Aug 2022 09:16:11 +0100 Subject: [PATCH 14/90] refactor: rename function --- src/protocol/clock.rs | 2 +- src/udp/handlers.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/protocol/clock.rs b/src/protocol/clock.rs index 184aa8bff..7488a8778 100644 --- a/src/protocol/clock.rs +++ b/src/protocol/clock.rs @@ -14,7 +14,7 @@ impl Clock for SystemClock { } /// It returns the current timestamp using the system clock. -pub fn current_timestamp_from_system_clock() -> u64 { +pub fn current_timestamp() -> u64 { let system_clock = SystemClock; system_clock.now_as_timestamp() } \ No newline at end of file diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index a93ab219c..67b398dbc 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -11,7 +11,7 @@ use crate::udp::request::AnnounceRequestWrapper; use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; use crate::protocol::utils::get_connection_id; -use crate::protocol::clock::current_timestamp_from_system_clock; +use crate::protocol::clock::current_timestamp; pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { match tracker.authenticate_request(info_hash, &None).await { @@ -71,7 +71,7 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: } pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let connection_id = get_connection_id(&remote_addr, current_timestamp_from_system_clock()); + let connection_id = get_connection_id(&remote_addr, current_timestamp()); let response = Response::from(ConnectResponse { transaction_id: request.transaction_id, From 8667652e01096253c9c69fb44bbad109efdd58cc Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 12 Aug 2022 01:17:59 +0200 Subject: [PATCH 15/90] test: updated connection id tests to test for BEP-15 requirements --- src/protocol/utils.rs | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index feb88c908..69a554025 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -31,7 +31,7 @@ mod tests { use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}}; #[test] - fn connection_id_is_generated_based_on_remote_client_port_an_hours_passed_since_unix_epoch() { + fn connection_id_is_generated_based_on_remote_client_port_and_hours_passed_since_unix_epoch() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let timestamp = 946684800u64; // GTM: Sat Jan 01 2000 00:00:00 GMT+0000 @@ -57,20 +57,20 @@ mod tests { assert_eq!((0x_0000_0000_0004_0338u64 | 0x_0000_0010_0000_0000u64), 0x_0000_0010_0004_0338u64); // 68719739704 assert_eq!(0x_0000_0010_0004_0338u64 as i64, 68719739704); // 68719739704 - let connection_id = super::get_connection_id(&client_addr, timestamp); + let connection_id = get_connection_id(&client_addr, timestamp); assert_eq!(connection_id, ConnectionId(68719739704)); } #[test] - fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_one_hour() { + fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_two_minutes() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; let connection_id = get_connection_id(&client_addr, now); - let in_one_hour = now + 3600 - 1; + let in_one_hour = now + 120 - 1; let connection_id_after_one_hour = get_connection_id(&client_addr, in_one_hour); @@ -78,24 +78,24 @@ mod tests { } #[test] - fn connection_id_in_udp_tracker_should_change_for_the_same_client_and_port_after_one_hour() { + fn connection_id_in_udp_tracker_should_change_for_the_same_client_ip_and_port_after_two_minutes() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; let connection_id = get_connection_id(&client_addr, now); - let after_one_hour = now + 3600; + let after_two_minutes = now + 120; - let connection_id_after_one_hour = get_connection_id(&client_addr, after_one_hour); + let connection_id_after_two_minutes = get_connection_id(&client_addr, after_two_minutes); - assert_ne!(connection_id, connection_id_after_one_hour); - } + assert_ne!(connection_id, connection_id_after_two_minutes); + } #[test] - fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); + fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let now = 946684800u64; @@ -106,18 +106,16 @@ mod tests { } #[test] - fn connection_id_in_udp_tracker_should_expire_after_one_hour() { - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); let now = 946684800u64; - let connection_id_1 = get_connection_id(&client_addr, now); - - let in_one_hour = now + 3600; - - let connection_id_2 = get_connection_id(&client_addr, in_one_hour); + let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); - assert_ne!(connection_id_1, connection_id_2); + assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } use aquatic_udp_protocol::ConnectionId; @@ -139,7 +137,7 @@ mod tests { use std::{thread, time}; - let t1 = time::Instant::now(); + let t1 = Instant::now(); let s = S { time: t1 }; @@ -152,4 +150,4 @@ mod tests { // Json contains time duration since t1 instant in milliseconds assert_eq!(json_serialized_value, r#"{"time":10}"#); } -} \ No newline at end of file +} From fddaaac9fb94ac2368922349bd916e36419ac085 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 12 Aug 2022 02:47:16 +0200 Subject: [PATCH 16/90] feat: improved get_connection_id function to conform with the requirements listed in BEP-15 --- Cargo.lock | 34 ++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/lib.rs | 2 ++ src/protocol/utils.rs | 46 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 279e4a67d..b73d48102 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,12 @@ dependencies = [ "either", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + [[package]] name = "arrayvec" version = "0.5.2" @@ -158,6 +164,20 @@ dependencies = [ "wyz", ] +[[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" @@ -302,6 +322,12 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "convert_case" version = "0.4.0" @@ -428,6 +454,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common", + "subtle", ] [[package]] @@ -2081,6 +2108,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.92" @@ -2335,6 +2368,7 @@ dependencies = [ "aquatic_udp_protocol", "async-trait", "binascii", + "blake3", "chrono", "config", "derive_more", diff --git a/Cargo.toml b/Cargo.toml index 9d21ed7d7..f1d6f1e7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ serde_with = "2.0.0" hex = "0.4.3" percent-encoding = "2.1.0" binascii = "0.1" +blake3 = "1.3.1" openssl = { version = "0.10.41", features = ["vendored"] } diff --git a/src/lib.rs b/src/lib.rs index 245f4686c..c3d7c6efc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +extern crate core; + pub use http::server::*; pub use udp::server::*; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 69a554025..764704987 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -1,9 +1,44 @@ use std::{net::SocketAddr, time::SystemTime}; +use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; +// todo: SALT should be randomly generated on startup +const SALT: &str = "SALT"; + /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { - ConnectionId(((current_timestamp / 3600) | ((remote_address.port() as u64) << 36)) as i64) +pub fn get_connection_id(remote_address: &SocketAddr, time_as_seconds: u64) -> ConnectionId { + let peer_ip_as_bytes = match remote_address.ip() { + IpAddr::V4(ip) => ip.octets().to_vec(), + IpAddr::V6(ip) => ip.octets().to_vec(), + }; + + let input: Vec = [ + (time_as_seconds / 120).to_be_bytes().as_slice(), + peer_ip_as_bytes.as_slice(), + remote_address.port().to_be_bytes().as_slice(), + SALT.as_bytes() + ].concat(); + + let hash = blake3::hash(&input); + + let mut truncated_hash: [u8; 8] = [0u8; 8]; + + truncated_hash.copy_from_slice(&hash.as_bytes()[..8]); + + let connection_id = i64::from_le_bytes(truncated_hash); + + ConnectionId(connection_id) +} + +/// Verifies whether a connection id is valid at this time for a given remote address (ip + port) +pub fn verify_connection_id(connection_id: ConnectionId, remote_address: &SocketAddr) -> Result<(), ()> { + let current_time = current_time(); + + match connection_id { + cid if cid == get_connection_id(remote_address, current_time) => Ok(()), + cid if cid == get_connection_id(remote_address, current_time - 120) => Ok(()), + _ => Err(()) + } } pub fn current_time() -> u64 { @@ -29,6 +64,8 @@ pub fn ser_instant(inst: &std::time::Instant, ser: S) -> R #[cfg(test)] mod tests { use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}}; + use serde::Serialize; + use crate::protocol::utils::{ConnectionId, get_connection_id}; #[test] fn connection_id_is_generated_based_on_remote_client_port_and_hours_passed_since_unix_epoch() { @@ -118,11 +155,6 @@ mod tests { assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } - use aquatic_udp_protocol::ConnectionId; - use serde::Serialize; - - use crate::protocol::utils::get_connection_id; - #[warn(unused_imports)] use super::ser_instant; From f36e673a89ac15b1f8ee975c276c08be001effac Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 12 Aug 2022 11:19:25 +0200 Subject: [PATCH 17/90] test: added benchmarks to test the speed of generating a connection id --- Cargo.lock | 257 +++++++++++++++++++++++++++++- Cargo.toml | 7 + benches/generate_connection_id.rs | 37 +++++ 3 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 benches/generate_connection_id.rs diff --git a/Cargo.lock b/Cargo.lock index b73d48102..2e269ba51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde 1.0.137", +] + [[package]] name = "buf_redux" version = "0.8.4" @@ -230,6 +242,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.73" @@ -368,6 +386,87 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits 0.2.15", + "oorandom", + "plotters", + "rayon", + "regex", + "serde 1.0.137", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "once_cell", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -378,6 +477,28 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde 1.0.137", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.14.1" @@ -761,6 +882,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.11.2" @@ -836,7 +963,7 @@ checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.1", ] [[package]] @@ -883,7 +1010,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 1.0.1", "pin-project-lite", "socket2", "tokio", @@ -941,6 +1068,21 @@ dependencies = [ "syn", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.1" @@ -1348,6 +1490,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -1495,6 +1643,34 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "plotters" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" +dependencies = [ + "num-traits 0.2.15", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" +dependencies = [ + "plotters-backend", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1598,6 +1774,30 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1618,6 +1818,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.25" @@ -1728,6 +1934,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "saturating" version = "0.1.0" @@ -1865,6 +2080,16 @@ dependencies = [ "serde 1.0.137", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde 1.0.137", +] + [[package]] name = "serde_derive" version = "1.0.137" @@ -1882,7 +2107,7 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ - "itoa", + "itoa 1.0.1", "ryu", "serde 1.0.137", ] @@ -1894,7 +2119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa", + "itoa 1.0.1", "ryu", "serde 1.0.137", ] @@ -2215,7 +2440,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" dependencies = [ - "itoa", + "itoa 1.0.1", "libc", "num_threads", "serde 1.0.137", @@ -2244,6 +2469,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde 1.0.137", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2371,6 +2606,7 @@ dependencies = [ "blake3", "chrono", "config", + "criterion", "derive_more", "fern", "futures", @@ -2576,6 +2812,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index f1d6f1e7e..d87d3efd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,13 @@ opt-level = 3 lto = "fat" strip = true +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "generate_connection_id" +harness = false + [dependencies] tokio = { version = "1.7", features = ["rt-multi-thread", "net", "sync", "macros", "signal"] } diff --git a/benches/generate_connection_id.rs b/benches/generate_connection_id.rs new file mode 100644 index 000000000..0e7fd90f4 --- /dev/null +++ b/benches/generate_connection_id.rs @@ -0,0 +1,37 @@ +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use aquatic_udp_protocol::ConnectionId; +use criterion::{Criterion, criterion_group, criterion_main}; +use torrust_tracker::protocol::utils::{current_time, get_connection_id}; + +fn get_connection_id_old(current_time: u64, port: u16) -> ConnectionId { + let time_i64 = (current_time / 3600) as i64; + + ConnectionId((time_i64 | port as i64) << 36) +} + +pub fn benchmark_generate_id_with_time_and_port(bench: &mut Criterion) { + let remote_address = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 117); + let current_time = current_time(); + + bench.bench_function("generate_id_with_time_and_port", |b| { + b.iter(|| { + // Inner closure, the actual test + let _ = get_connection_id_old(current_time, remote_address.port()); + }) + }); +} + +pub fn benchmark_generate_id_with_hashed_time_and_ip_and_port_and_salt(bench: &mut Criterion) { + let remote_address = SocketAddr::from(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 117)); + let current_time = current_time(); + + bench.bench_function("generate_id_with_hashed_time_and_ip_and_port_and_salt", |b| { + b.iter(|| { + // Inner closure, the actual test + let _ = get_connection_id(&remote_address, current_time); + }) + }); +} + +criterion_group!(benches, benchmark_generate_id_with_time_and_port, benchmark_generate_id_with_hashed_time_and_ip_and_port_and_salt); +criterion_main!(benches); From 3880891df764977ab4898833b339c8157c2c4d8f Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 12 Aug 2022 13:19:15 +0200 Subject: [PATCH 18/90] refactor: renamed variables and removed unused import --- src/lib.rs | 2 -- src/protocol/utils.rs | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3d7c6efc..245f4686c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -extern crate core; - pub use http::server::*; pub use udp::server::*; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 764704987..63c47bc4d 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -107,11 +107,11 @@ mod tests { let connection_id = get_connection_id(&client_addr, now); - let in_one_hour = now + 120 - 1; + let in_two_minutes = now + 120 - 1; - let connection_id_after_one_hour = get_connection_id(&client_addr, in_one_hour); + let connection_id_after_two_minutes = get_connection_id(&client_addr, in_two_minutes); - assert_eq!(connection_id, connection_id_after_one_hour); + assert_eq!(connection_id, connection_id_after_two_minutes); } #[test] From 0f2beba979e6874846efd84e33aa6029bfea17c7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 10:32:14 +0100 Subject: [PATCH 19/90] test: fix test for new implementation of get_connection_id --- src/protocol/utils.rs | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 63c47bc4d..e7ef9ffb4 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -68,35 +68,14 @@ mod tests { use crate::protocol::utils::{ConnectionId, get_connection_id}; #[test] - fn connection_id_is_generated_based_on_remote_client_port_and_hours_passed_since_unix_epoch() { - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - - let timestamp = 946684800u64; // GTM: Sat Jan 01 2000 00:00:00 GMT+0000 - - // Timestamp in hours 946684800u64 / 3600 = 262968 = 0x_0000_0000_0004_0338 = 262968 - // Port 0001 = 0x_0000_0000_0000_0001 = 1 - // Port 0001 << 36 = 0x_0000_0010_0000_0000 = 68719476736 - // - // 0x_0000_0000_0004_0338 | 0x_0000_0010_0000_0000 = 0x_0000_0010_0004_0338 = 68719739704 - // - // HEX BIN DEC - // -------------------------------------------------------------------------------- - // 0x_0000_0000_0004_0338 = ... 0000000000000000001000000001100111000 = 262968 - // OR - // 0x_0000_0010_0000_0000 = ... 1000000000000000000000000000000000000 = 68719476736 - // ------------------------------------------------------------------- - // 0x_0000_0010_0004_0338 = ... 1000000000000000001000000001100111000 = 68719739704 - - // Assert intermediary values - assert_eq!(timestamp / 3600, 0x_0000_0000_0004_0338); - assert_eq!((client_addr.port() as u64), 1); - assert_eq!(((client_addr.port() as u64) << 36), 0x_0000_0010_0000_0000); // 68719476736 - assert_eq!((0x_0000_0000_0004_0338u64 | 0x_0000_0010_0000_0000u64), 0x_0000_0010_0004_0338u64); // 68719739704 - assert_eq!(0x_0000_0010_0004_0338u64 as i64, 68719739704); // 68719739704 - - let connection_id = get_connection_id(&client_addr, timestamp); - - assert_eq!(connection_id, ConnectionId(68719739704)); + fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_salt() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let now_as_timestamp = 946684800u64; // GMT/UTC date and time is: 01-01-2000 00:00:00 + + let connection_id = get_connection_id(&client_addr, now_as_timestamp); + + assert_eq!(connection_id, ConnectionId(-6628342936351095906)); } #[test] From 55bb73ca1c36adfca914a7f18e0e5afb39150b6c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 16:53:24 +0100 Subject: [PATCH 20/90] test: add test for UDB connection ID verification --- src/protocol/utils.rs | 51 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 20861019b..1f7948954 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -6,14 +6,14 @@ use aquatic_udp_protocol::ConnectionId; const SALT: &str = "SALT"; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(remote_address: &SocketAddr, time_as_seconds: u64) -> ConnectionId { +pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { let peer_ip_as_bytes = match remote_address.ip() { IpAddr::V4(ip) => ip.octets().to_vec(), IpAddr::V6(ip) => ip.octets().to_vec(), }; let input: Vec = [ - (time_as_seconds / 120).to_be_bytes().as_slice(), + (current_timestamp / 120).to_be_bytes().as_slice(), peer_ip_as_bytes.as_slice(), remote_address.port().to_be_bytes().as_slice(), SALT.as_bytes() @@ -31,12 +31,10 @@ pub fn get_connection_id(remote_address: &SocketAddr, time_as_seconds: u64) -> C } /// Verifies whether a connection id is valid at this time for a given remote address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, remote_address: &SocketAddr) -> Result<(), ()> { - let current_time = current_time(); - +pub fn verify_connection_id(connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: u64) -> Result<(), ()> { match connection_id { - cid if cid == get_connection_id(remote_address, current_time) => Ok(()), - cid if cid == get_connection_id(remote_address, current_time - 120) => Ok(()), + cid if cid == get_connection_id(remote_address, current_timestamp) => Ok(()), + cid if cid == get_connection_id(remote_address, current_timestamp - 120) => Ok(()), _ => Err(()) } } @@ -65,7 +63,7 @@ pub fn ser_instant(inst: &std::time::Instant, ser: S) -> R mod tests { use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}}; use serde::Serialize; - use crate::protocol::utils::{ConnectionId, get_connection_id}; + use crate::protocol::utils::{ConnectionId, get_connection_id, verify_connection_id}; #[test] fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_salt() { @@ -135,6 +133,43 @@ mod tests { assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } + #[test] + fn connection_id_in_udp_tracker_should_be_valid_for_the_current_two_minute_window_since_unix_epoch_and_the_previous_window() { + + // The implementation generates a different connection id for each client and port every two minutes. + // Connection should expire 2 minutes after the generation but we do not store the exact time + // when it was generated. In order to implement a stateless connection ID generation, + // we change it automatically and we approximate it to the 2-minute window. + // + // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | + // |----------------------------------------------------------------------------| + // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | + // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | + // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | + // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | + // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | + // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | + // + // Because of the implementation, the have to verify the current connection id and the previous one. + // If the ID was generated at the end of a 2-minute slot I won't be valid just after some seconds. + // For the worse scenario if the ID was generated at the beginning of a 2-minute slot, + // It will be valid for almost 4 minutes. + + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + + let unix_epoch = 0u64; + + let connection_id = get_connection_id(&client_addr, unix_epoch); + + assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch), Ok(())); + + // X = Y + assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch + 120), Ok(())); + + // X != Z + assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch + 240 + 1), Err(())); + } + #[warn(unused_imports)] use super::ser_instant; From 0feea820e76f08498e342ecd54aa943115419cce Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 17:04:10 +0100 Subject: [PATCH 21/90] refactor: move get_connection_id to its module inside UDP module --- benches/generate_connection_id.rs | 2 +- src/protocol/utils.rs | 149 +---------------------------- src/udp/connection_id.rs | 151 ++++++++++++++++++++++++++++++ src/udp/handlers.rs | 3 +- src/udp/mod.rs | 1 + 5 files changed, 157 insertions(+), 149 deletions(-) create mode 100644 src/udp/connection_id.rs diff --git a/benches/generate_connection_id.rs b/benches/generate_connection_id.rs index 0e7fd90f4..298e8cc92 100644 --- a/benches/generate_connection_id.rs +++ b/benches/generate_connection_id.rs @@ -1,7 +1,7 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use aquatic_udp_protocol::ConnectionId; use criterion::{Criterion, criterion_group, criterion_main}; -use torrust_tracker::protocol::utils::{current_time, get_connection_id}; +use torrust_tracker::{protocol::utils::{current_time}, udp::connection_id::get_connection_id}; fn get_connection_id_old(current_time: u64, port: u16) -> ConnectionId { let time_i64 = (current_time / 3600) as i64; diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index 1f7948954..d7e861daf 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -1,43 +1,4 @@ -use std::{net::SocketAddr, time::SystemTime}; -use std::net::IpAddr; -use aquatic_udp_protocol::ConnectionId; - -// todo: SALT should be randomly generated on startup -const SALT: &str = "SALT"; - -/// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { - let peer_ip_as_bytes = match remote_address.ip() { - IpAddr::V4(ip) => ip.octets().to_vec(), - IpAddr::V6(ip) => ip.octets().to_vec(), - }; - - let input: Vec = [ - (current_timestamp / 120).to_be_bytes().as_slice(), - peer_ip_as_bytes.as_slice(), - remote_address.port().to_be_bytes().as_slice(), - SALT.as_bytes() - ].concat(); - - let hash = blake3::hash(&input); - - let mut truncated_hash: [u8; 8] = [0u8; 8]; - - truncated_hash.copy_from_slice(&hash.as_bytes()[..8]); - - let connection_id = i64::from_le_bytes(truncated_hash); - - ConnectionId(connection_id) -} - -/// Verifies whether a connection id is valid at this time for a given remote address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: u64) -> Result<(), ()> { - match connection_id { - cid if cid == get_connection_id(remote_address, current_timestamp) => Ok(()), - cid if cid == get_connection_id(remote_address, current_timestamp - 120) => Ok(()), - _ => Err(()) - } -} +use std::{time::SystemTime}; pub fn current_time() -> u64 { SystemTime::now() @@ -61,114 +22,8 @@ pub fn ser_instant(inst: &std::time::Instant, ser: S) -> R #[cfg(test)] mod tests { - use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}}; + use std::{time::Instant}; use serde::Serialize; - use crate::protocol::utils::{ConnectionId, get_connection_id, verify_connection_id}; - - #[test] - fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_salt() { - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - - let now_as_timestamp = 946684800u64; // GMT/UTC date and time is: 01-01-2000 00:00:00 - - let connection_id = get_connection_id(&client_addr, now_as_timestamp); - - assert_eq!(connection_id, ConnectionId(-6628342936351095906)); - - } - - #[test] - fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_two_minutes() { - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - - let now = 946684800u64; - - let connection_id = get_connection_id(&client_addr, now); - - let in_two_minutes = now + 120 - 1; - - let connection_id_after_two_minutes = get_connection_id(&client_addr, in_two_minutes); - - assert_eq!(connection_id, connection_id_after_two_minutes); - } - - #[test] - fn connection_id_in_udp_tracker_should_change_for_the_same_client_ip_and_port_after_two_minutes() { - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - - let now = 946684800u64; - - let connection_id = get_connection_id(&client_addr, now); - - let after_two_minutes = now + 120; - - let connection_id_after_two_minutes = get_connection_id(&client_addr, after_two_minutes); - - assert_ne!(connection_id, connection_id_after_two_minutes); - } - - #[test] - fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - - let now = 946684800u64; - - let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); - - assert_ne!(connection_id_for_client_1, connection_id_for_client_2); - } - - #[test] - fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); - - let now = 946684800u64; - - let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); - - assert_ne!(connection_id_for_client_1, connection_id_for_client_2); - } - - #[test] - fn connection_id_in_udp_tracker_should_be_valid_for_the_current_two_minute_window_since_unix_epoch_and_the_previous_window() { - - // The implementation generates a different connection id for each client and port every two minutes. - // Connection should expire 2 minutes after the generation but we do not store the exact time - // when it was generated. In order to implement a stateless connection ID generation, - // we change it automatically and we approximate it to the 2-minute window. - // - // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | - // |----------------------------------------------------------------------------| - // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | - // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | - // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | - // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | - // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | - // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | - // - // Because of the implementation, the have to verify the current connection id and the previous one. - // If the ID was generated at the end of a 2-minute slot I won't be valid just after some seconds. - // For the worse scenario if the ID was generated at the beginning of a 2-minute slot, - // It will be valid for almost 4 minutes. - - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - - let unix_epoch = 0u64; - - let connection_id = get_connection_id(&client_addr, unix_epoch); - - assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch), Ok(())); - - // X = Y - assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch + 120), Ok(())); - - // X != Z - assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch + 240 + 1), Err(())); - } #[warn(unused_imports)] use super::ser_instant; diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs new file mode 100644 index 000000000..3a9853f57 --- /dev/null +++ b/src/udp/connection_id.rs @@ -0,0 +1,151 @@ +use std::{net::SocketAddr}; +use std::net::IpAddr; +use aquatic_udp_protocol::ConnectionId; + +// todo: SALT should be randomly generated on startup +const SALT: &str = "SALT"; + +/// It generates a connection id needed for the BitTorrent UDP Tracker Protocol +pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { + let peer_ip_as_bytes = match remote_address.ip() { + IpAddr::V4(ip) => ip.octets().to_vec(), + IpAddr::V6(ip) => ip.octets().to_vec(), + }; + + let input: Vec = [ + (current_timestamp / 120).to_be_bytes().as_slice(), + peer_ip_as_bytes.as_slice(), + remote_address.port().to_be_bytes().as_slice(), + SALT.as_bytes() + ].concat(); + + let hash = blake3::hash(&input); + + let mut truncated_hash: [u8; 8] = [0u8; 8]; + + truncated_hash.copy_from_slice(&hash.as_bytes()[..8]); + + let connection_id = i64::from_le_bytes(truncated_hash); + + ConnectionId(connection_id) +} + +/// Verifies whether a connection id is valid at this time for a given remote address (ip + port) +pub fn verify_connection_id(connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: u64) -> Result<(), ()> { + match connection_id { + cid if cid == get_connection_id(remote_address, current_timestamp) => Ok(()), + cid if cid == get_connection_id(remote_address, current_timestamp - 120) => Ok(()), + _ => Err(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; + + #[test] + fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_salt() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let now_as_timestamp = 946684800u64; // GMT/UTC date and time is: 01-01-2000 00:00:00 + + let connection_id = get_connection_id(&client_addr, now_as_timestamp); + + assert_eq!(connection_id, ConnectionId(-6628342936351095906)); + + } + + #[test] + fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_two_minutes() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let now = 946684800u64; + + let connection_id = get_connection_id(&client_addr, now); + + let in_two_minutes = now + 120 - 1; + + let connection_id_after_two_minutes = get_connection_id(&client_addr, in_two_minutes); + + assert_eq!(connection_id, connection_id_after_two_minutes); + } + + #[test] + fn connection_id_in_udp_tracker_should_change_for_the_same_client_ip_and_port_after_two_minutes() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let now = 946684800u64; + + let connection_id = get_connection_id(&client_addr, now); + + let after_two_minutes = now + 120; + + let connection_id_after_two_minutes = get_connection_id(&client_addr, after_two_minutes); + + assert_ne!(connection_id, connection_id_after_two_minutes); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + + let now = 946684800u64; + + let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); + + assert_ne!(connection_id_for_client_1, connection_id_for_client_2); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); + + let now = 946684800u64; + + let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); + + assert_ne!(connection_id_for_client_1, connection_id_for_client_2); + } + + #[test] + fn connection_id_in_udp_tracker_should_be_valid_for_the_current_two_minute_window_since_unix_epoch_and_the_previous_window() { + + // The implementation generates a different connection id for each client and port every two minutes. + // Connection should expire 2 minutes after the generation but we do not store the exact time + // when it was generated. In order to implement a stateless connection ID generation, + // we change it automatically and we approximate it to the 2-minute window. + // + // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | + // |----------------------------------------------------------------------------| + // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | + // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | + // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | + // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | + // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | + // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | + // + // Because of the implementation, the have to verify the current connection id and the previous one. + // If the ID was generated at the end of a 2-minute slot I won't be valid just after some seconds. + // For the worse scenario if the ID was generated at the beginning of a 2-minute slot, + // It will be valid for almost 4 minutes. + + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + + let unix_epoch = 0u64; + + let connection_id = get_connection_id(&client_addr, unix_epoch); + + assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch), Ok(())); + + // X = Y + assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch + 120), Ok(())); + + // X != Z + assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch + 240 + 1), Err(())); + } +} \ No newline at end of file diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 67b398dbc..1803e5b0a 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -10,9 +10,10 @@ use crate::udp::errors::ServerError; use crate::udp::request::AnnounceRequestWrapper; use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; -use crate::protocol::utils::get_connection_id; use crate::protocol::clock::current_timestamp; +use super::connection_id::get_connection_id; + pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { match tracker.authenticate_request(info_hash, &None).await { Ok(_) => Ok(()), diff --git a/src/udp/mod.rs b/src/udp/mod.rs index a925abc82..b8d9d5621 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -9,6 +9,7 @@ pub use self::handlers::*; pub use self::request::*; pub use self::server::*; +pub mod connection_id; pub mod errors; pub mod request; pub mod server; From 89d4f708d946e2b1662e8a537254a262914cf92a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 17:15:46 +0100 Subject: [PATCH 22/90] refactor: remove duplciate function --- benches/generate_connection_id.rs | 6 +++--- src/protocol/clock.rs | 2 +- src/protocol/utils.rs | 8 -------- src/tracker/key.rs | 7 +++---- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/benches/generate_connection_id.rs b/benches/generate_connection_id.rs index 298e8cc92..7a3cd8143 100644 --- a/benches/generate_connection_id.rs +++ b/benches/generate_connection_id.rs @@ -1,7 +1,7 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use aquatic_udp_protocol::ConnectionId; use criterion::{Criterion, criterion_group, criterion_main}; -use torrust_tracker::{protocol::utils::{current_time}, udp::connection_id::get_connection_id}; +use torrust_tracker::{udp::connection_id::get_connection_id, protocol::clock::current_timestamp}; fn get_connection_id_old(current_time: u64, port: u16) -> ConnectionId { let time_i64 = (current_time / 3600) as i64; @@ -11,7 +11,7 @@ fn get_connection_id_old(current_time: u64, port: u16) -> ConnectionId { pub fn benchmark_generate_id_with_time_and_port(bench: &mut Criterion) { let remote_address = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 117); - let current_time = current_time(); + let current_time = current_timestamp(); bench.bench_function("generate_id_with_time_and_port", |b| { b.iter(|| { @@ -23,7 +23,7 @@ pub fn benchmark_generate_id_with_time_and_port(bench: &mut Criterion) { pub fn benchmark_generate_id_with_hashed_time_and_ip_and_port_and_salt(bench: &mut Criterion) { let remote_address = SocketAddr::from(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 117)); - let current_time = current_time(); + let current_time = current_timestamp(); bench.bench_function("generate_id_with_hashed_time_and_ip_and_port_and_salt", |b| { b.iter(|| { diff --git a/src/protocol/clock.rs b/src/protocol/clock.rs index 7488a8778..dfef5d45b 100644 --- a/src/protocol/clock.rs +++ b/src/protocol/clock.rs @@ -17,4 +17,4 @@ impl Clock for SystemClock { pub fn current_timestamp() -> u64 { let system_clock = SystemClock; system_clock.now_as_timestamp() -} \ No newline at end of file +} diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index d7e861daf..2e7a84492 100644 --- a/src/protocol/utils.rs +++ b/src/protocol/utils.rs @@ -1,11 +1,3 @@ -use std::{time::SystemTime}; - -pub fn current_time() -> u64 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH).unwrap() - .as_secs() -} - /// Serializer for `std::time::Instant` type. /// Before serializing, it converts the instant to time elapse since that instant in milliseconds. /// diff --git a/src/tracker/key.rs b/src/tracker/key.rs index 2e2ca81f7..da07ebd7d 100644 --- a/src/tracker/key.rs +++ b/src/tracker/key.rs @@ -4,9 +4,8 @@ use rand::{Rng, thread_rng}; use rand::distributions::Alphanumeric; use serde::Serialize; -use crate::protocol::utils::current_time; - use crate::AUTH_KEY_LENGTH; +use crate::protocol::clock::current_timestamp; pub fn generate_auth_key(seconds_valid: u64) -> AuthKey { let key: String = thread_rng() @@ -19,12 +18,12 @@ pub fn generate_auth_key(seconds_valid: u64) -> AuthKey { AuthKey { key, - valid_until: Some(current_time() + seconds_valid), + valid_until: Some(current_timestamp() + seconds_valid), } } pub fn verify_auth_key(auth_key: &AuthKey) -> Result<(), Error> { - let current_time = current_time(); + let current_time = current_timestamp(); if auth_key.valid_until.is_none() { return Err(Error::KeyInvalid); } if auth_key.valid_until.unwrap() < current_time { return Err(Error::KeyExpired); } From 692a92de1cdddd05c5fa7e9f92197ff69250a8cb Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 17:22:58 +0100 Subject: [PATCH 23/90] refactor: move Instant serializer to its own mod in tracker mod --- src/protocol/mod.rs | 1 - src/tracker/mod.rs | 1 + src/tracker/peer.rs | 2 +- src/{protocol/utils.rs => tracker/serializer.rs} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename src/{protocol/utils.rs => tracker/serializer.rs} (100%) diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index c3fb56965..412c3c62c 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,3 +1,2 @@ pub mod common; -pub mod utils; pub mod clock; diff --git a/src/tracker/mod.rs b/src/tracker/mod.rs index 791e2e7d2..636ae33d9 100644 --- a/src/tracker/mod.rs +++ b/src/tracker/mod.rs @@ -1,6 +1,7 @@ pub mod tracker; pub mod statistics; pub mod peer; +pub mod serializer; pub mod torrent; pub mod key; pub mod mode; diff --git a/src/tracker/peer.rs b/src/tracker/peer.rs index ce4e52022..e2701ab65 100644 --- a/src/tracker/peer.rs +++ b/src/tracker/peer.rs @@ -5,9 +5,9 @@ use serde; use serde::{Serialize}; use crate::protocol::common::{NumberOfBytesDef, AnnounceEventDef}; -use crate::protocol::utils::ser_instant; use crate::http::AnnounceRequest; use crate::PeerId; +use crate::tracker::serializer::ser_instant; #[derive(PartialEq, Eq, Debug, Clone, Serialize)] pub struct TorrentPeer { diff --git a/src/protocol/utils.rs b/src/tracker/serializer.rs similarity index 100% rename from src/protocol/utils.rs rename to src/tracker/serializer.rs From bf639b87fda68f4a03fa1efc942d2b34a8b37806 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 17:56:00 +0100 Subject: [PATCH 24/90] refactor: rename variable --- src/udp/connection_id.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 3a9853f57..a66b9b91d 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -2,11 +2,23 @@ use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; -// todo: SALT should be randomly generated on startup -const SALT: &str = "SALT"; +// todo: SERVER_SECRET should be randomly generated on startup +const SERVER_SECRET: &str = "SALT"; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { + + /* WIP: New proposal by @da2ce7 + + Static_Sever_Secret = Random (32-bytes), generated on sever start. + + Time_Bound_Pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) (32-bytes), cached, expires every two minutes. + + Authentication_String = IP_Address || Port || User Token || Etc. (32-bytes), unique for each client. + + ConnectionID = Hash(Time_Bound_Pepper || Authentication_String) (64-bit) + */ + let peer_ip_as_bytes = match remote_address.ip() { IpAddr::V4(ip) => ip.octets().to_vec(), IpAddr::V6(ip) => ip.octets().to_vec(), @@ -16,7 +28,7 @@ pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> (current_timestamp / 120).to_be_bytes().as_slice(), peer_ip_as_bytes.as_slice(), remote_address.port().to_be_bytes().as_slice(), - SALT.as_bytes() + SERVER_SECRET.as_bytes() ].concat(); let hash = blake3::hash(&input); @@ -45,7 +57,7 @@ mod tests { use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; #[test] - fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_salt() { + fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_server_secret() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now_as_timestamp = 946684800u64; // GMT/UTC date and time is: 01-01-2000 00:00:00 From eb679a7233b3fa4a5fd986b1e607d249e5a209e3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 18:03:01 +0100 Subject: [PATCH 25/90] feat: change server secret for udp connection id --- src/udp/connection_id.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index a66b9b91d..602301b24 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -3,7 +3,7 @@ use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; // todo: SERVER_SECRET should be randomly generated on startup -const SERVER_SECRET: &str = "SALT"; +const SERVER_SECRET: [u8; 32] = [0;32]; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { @@ -28,7 +28,7 @@ pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> (current_timestamp / 120).to_be_bytes().as_slice(), peer_ip_as_bytes.as_slice(), remote_address.port().to_be_bytes().as_slice(), - SERVER_SECRET.as_bytes() + SERVER_SECRET.as_slice() ].concat(); let hash = blake3::hash(&input); @@ -64,7 +64,7 @@ mod tests { let connection_id = get_connection_id(&client_addr, now_as_timestamp); - assert_eq!(connection_id, ConnectionId(-6628342936351095906)); + assert_eq!(connection_id, ConnectionId(-7545411207427689958)); } From 897697daad36d3b3e409a4fa7a27ca7c64747c16 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 18:15:22 +0100 Subject: [PATCH 26/90] refactor: change type and inject server secret for hashing connection id --- benches/generate_connection_id.rs | 3 ++- src/udp/connection_id.rs | 41 +++++++++++++++---------------- src/udp/handlers.rs | 3 ++- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/benches/generate_connection_id.rs b/benches/generate_connection_id.rs index 7a3cd8143..1a8dfc4e0 100644 --- a/benches/generate_connection_id.rs +++ b/benches/generate_connection_id.rs @@ -24,11 +24,12 @@ pub fn benchmark_generate_id_with_time_and_port(bench: &mut Criterion) { pub fn benchmark_generate_id_with_hashed_time_and_ip_and_port_and_salt(bench: &mut Criterion) { let remote_address = SocketAddr::from(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 117)); let current_time = current_timestamp(); + let server_secret = [0;32]; bench.bench_function("generate_id_with_hashed_time_and_ip_and_port_and_salt", |b| { b.iter(|| { // Inner closure, the actual test - let _ = get_connection_id(&remote_address, current_time); + let _ = get_connection_id(&server_secret, &remote_address, current_time); }) }); } diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 602301b24..47c81410b 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -2,11 +2,8 @@ use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; -// todo: SERVER_SECRET should be randomly generated on startup -const SERVER_SECRET: [u8; 32] = [0;32]; - /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { +pub fn get_connection_id(server_secret: &[u8; 32], remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { /* WIP: New proposal by @da2ce7 @@ -28,7 +25,7 @@ pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> (current_timestamp / 120).to_be_bytes().as_slice(), peer_ip_as_bytes.as_slice(), remote_address.port().to_be_bytes().as_slice(), - SERVER_SECRET.as_slice() + server_secret.as_slice() ].concat(); let hash = blake3::hash(&input); @@ -43,10 +40,10 @@ pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> } /// Verifies whether a connection id is valid at this time for a given remote address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: u64) -> Result<(), ()> { +pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &[u8; 32], remote_address: &SocketAddr, current_timestamp: u64) -> Result<(), ()> { match connection_id { - cid if cid == get_connection_id(remote_address, current_timestamp) => Ok(()), - cid if cid == get_connection_id(remote_address, current_timestamp - 120) => Ok(()), + cid if cid == get_connection_id(server_secret, remote_address, current_timestamp) => Ok(()), + cid if cid == get_connection_id(server_secret, remote_address, current_timestamp - 120) => Ok(()), _ => Err(()) } } @@ -56,13 +53,15 @@ mod tests { use super::*; use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; + const SERVER_SECRET: [u8;32] = [0;32]; + #[test] fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_server_secret() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now_as_timestamp = 946684800u64; // GMT/UTC date and time is: 01-01-2000 00:00:00 - let connection_id = get_connection_id(&client_addr, now_as_timestamp); + let connection_id = get_connection_id(&SERVER_SECRET, &client_addr, now_as_timestamp); assert_eq!(connection_id, ConnectionId(-7545411207427689958)); @@ -74,11 +73,11 @@ mod tests { let now = 946684800u64; - let connection_id = get_connection_id(&client_addr, now); + let connection_id = get_connection_id(&SERVER_SECRET, &client_addr, now); let in_two_minutes = now + 120 - 1; - let connection_id_after_two_minutes = get_connection_id(&client_addr, in_two_minutes); + let connection_id_after_two_minutes = get_connection_id(&SERVER_SECRET, &client_addr, in_two_minutes); assert_eq!(connection_id, connection_id_after_two_minutes); } @@ -89,11 +88,11 @@ mod tests { let now = 946684800u64; - let connection_id = get_connection_id(&client_addr, now); + let connection_id = get_connection_id(&SERVER_SECRET, &client_addr, now); let after_two_minutes = now + 120; - let connection_id_after_two_minutes = get_connection_id(&client_addr, after_two_minutes); + let connection_id_after_two_minutes = get_connection_id(&SERVER_SECRET, &client_addr, after_two_minutes); assert_ne!(connection_id, connection_id_after_two_minutes); } @@ -105,8 +104,8 @@ mod tests { let now = 946684800u64; - let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); + let connection_id_for_client_1 = get_connection_id(&SERVER_SECRET, &client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&SERVER_SECRET, &client_2_addr, now); assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } @@ -118,8 +117,8 @@ mod tests { let now = 946684800u64; - let connection_id_for_client_1 = get_connection_id(&client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&client_2_addr, now); + let connection_id_for_client_1 = get_connection_id(&SERVER_SECRET, &client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&SERVER_SECRET, &client_2_addr, now); assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } @@ -150,14 +149,14 @@ mod tests { let unix_epoch = 0u64; - let connection_id = get_connection_id(&client_addr, unix_epoch); + let connection_id = get_connection_id(&SERVER_SECRET, &client_addr, unix_epoch); - assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch), Ok(())); + assert_eq!(verify_connection_id(connection_id, &SERVER_SECRET, &client_addr, unix_epoch), Ok(())); // X = Y - assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch + 120), Ok(())); + assert_eq!(verify_connection_id(connection_id, &SERVER_SECRET, &client_addr, unix_epoch + 120), Ok(())); // X != Z - assert_eq!(verify_connection_id(connection_id, &client_addr, unix_epoch + 240 + 1), Err(())); + assert_eq!(verify_connection_id(connection_id, &SERVER_SECRET, &client_addr, unix_epoch + 240 + 1), Err(())); } } \ No newline at end of file diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 1803e5b0a..2459580c1 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -72,7 +72,8 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: } pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let connection_id = get_connection_id(&remote_addr, current_timestamp()); + let server_secret = [0;32]; // todo: server_secret should be randomly generated on startup + let connection_id = get_connection_id(&server_secret, &remote_addr, current_timestamp()); let response = Response::from(ConnectResponse { transaction_id: request.transaction_id, From f2d7d5f2edb6e9e7c8c6f3076076be9ab51fa5ce Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 16 Aug 2022 20:29:48 +0100 Subject: [PATCH 27/90] refactor: wip. extract logic to generate time bound pepper Time bound pepper is a dinamycally-generated hash value which expires after 2 minutes. It's used to generate the UDP tracket connection IDs. --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/udp/connection_id.rs | 97 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2e269ba51..c5a7bf319 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arraytools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5367919938d9d809b2f2f6a3c7d7174c85ebb08beadf0c1d3dd94f64439f437" + [[package]] name = "arrayvec" version = "0.5.2" @@ -2601,6 +2607,7 @@ name = "torrust-tracker" version = "2.3.0" dependencies = [ "aquatic_udp_protocol", + "arraytools", "async-trait", "binascii", "blake3", diff --git a/Cargo.toml b/Cargo.toml index d87d3efd5..f9544bc68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,3 +60,4 @@ async-trait = "0.1.52" aquatic_udp_protocol = "0.2.0" uuid = { version = "1.1.2", features = ["v4"] } +arraytools = "0.1.5" diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 47c81410b..5eff98709 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -1,6 +1,54 @@ +use std::ops::BitOr; use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; +use arraytools::ArrayTools; + +fn generate_time_bound_pepper(server_secret: &[u8; 32], current_timestamp: u64) -> [u8; 32] { + + // todo: use a TimeBoundPepper struct as proposed by @da2ce7, storing also the current timestamp + // used to generate the TimeBoundPepper + // + // #[derive(Default)] + // struct TimeBoundPepper { + // created: SystemTime, + // pepper: [u8; 32], + // } + // + // impl Default for TimeBoundPepper { + // fn default() -> Self { SystemTime::now(), pepper: hash(SERVER_SECRET || self.created ) }; + // } + // + // impl TimeBoundPepper { + // pub fn new() -> Default::default(); + // + // pub fn get_pepper(&mut self, expires: std::time::Duration) -> [u8; 32] { + // if (created.elapsed().unwrap() >= expires) self = self.new(); + // return self.pepper; + // } + + // Time_Bound_Pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) + // (32-bytes), cached, expires every two minutes. + + let unix_time_minutes: u64 = current_timestamp / 60; + + let number_of_two_minute_slots_since_unix_time: u64 = unix_time_minutes / 2; + + let or_right_part: Vec = [ + [0u8; 24].as_slice(), + number_of_two_minute_slots_since_unix_time.to_be_bytes().as_slice(), + ].concat(); + + //println!("OR left : {:?}", [0u8; 32]); + //println!("OR right: {:?}", &or_right_part); + + // todo: extract function for OR operation for [u8; 32] arrays + let server_secret_or_two_minute_counter: [u8; 32] = server_secret.zip_with( or_right_part.try_into().unwrap(), BitOr::bitor); + + let time_bound_pepper = blake3::hash(&server_secret_or_two_minute_counter); + + *time_bound_pepper.as_bytes() +} /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(server_secret: &[u8; 32], remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { @@ -16,6 +64,9 @@ pub fn get_connection_id(server_secret: &[u8; 32], remote_address: &SocketAddr, ConnectionID = Hash(Time_Bound_Pepper || Authentication_String) (64-bit) */ + // todo: not used yet. See `generate_time_bound_pepper` function above. + let _time_bound_pepper = generate_time_bound_pepper(server_secret, current_timestamp); + let peer_ip_as_bytes = match remote_address.ip() { IpAddr::V4(ip) => ip.octets().to_vec(), IpAddr::V6(ip) => ip.octets().to_vec(), @@ -55,6 +106,52 @@ mod tests { const SERVER_SECRET: [u8;32] = [0;32]; + #[test] + fn time_bound_pepper_should_be_the_same_during_each_two_minute_window_since_unix_epoch() { + + // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | + // |----------------------------------------------------------------------------| + // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | + // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | + // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | + // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | + // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | + // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | + + let initial_timestamp = 0u64; + + let time_bound_pepper = generate_time_bound_pepper(&SERVER_SECRET, initial_timestamp); + + assert_eq!(time_bound_pepper, [42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218]); + + let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&SERVER_SECRET, initial_timestamp + 120 - 1); + + assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); + } + + #[test] + fn time_bound_pepper_should_change_after_a_two_minute_window_starting_time_windows_at_unix_epoch() { + + // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | + // |----------------------------------------------------------------------------| + // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | + // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | + // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | + // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | + // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | + // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | + + let initial_timestamp = 0u64; + + let time_bound_pepper = generate_time_bound_pepper(&SERVER_SECRET, initial_timestamp); + + assert_eq!(time_bound_pepper, [42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218]); + + let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&SERVER_SECRET, initial_timestamp + 120 - 1); + + assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); + } + #[test] fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_server_secret() { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); From 4769a49339ce6ea2f3d2b62e4879b1e43bb4f8eb Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 12:57:35 +0100 Subject: [PATCH 28/90] refactor: extract struct ByteArray32 WIP: the goal is to use a TimeBoundPepper struct for the connection ID generation used by the UDP tracker. --- src/udp/byte_array_32.rs | 81 ++++++++++++++++++++++++++++++++++++ src/udp/connection_id.rs | 89 ++++++++++++++++++++++------------------ src/udp/handlers.rs | 9 +++- src/udp/mod.rs | 1 + 4 files changed, 138 insertions(+), 42 deletions(-) create mode 100644 src/udp/byte_array_32.rs diff --git a/src/udp/byte_array_32.rs b/src/udp/byte_array_32.rs new file mode 100644 index 000000000..af7b0f6e4 --- /dev/null +++ b/src/udp/byte_array_32.rs @@ -0,0 +1,81 @@ +use std::ops::BitOr; +use arraytools::ArrayTools; +use std::convert::From; + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct ByteArray32([u8; 32]); + +impl ByteArray32 { + + pub fn new(bytes: [u8; 32]) -> Self { + ByteArray32(bytes) + } + + pub fn as_generic_byte_array(self) -> [u8; 32] { + self.0 + } +} + +impl BitOr for ByteArray32 { + type Output = Self; + + // rhs is the "right-hand side" of the expression `a | b` + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0.zip_with(rhs.0, BitOr::bitor)) + } +} + +impl From for ByteArray32 { + fn from(item: u64) -> Self { + let vec: Vec = [ + [0u8; 24].as_slice(), + item.to_be_bytes().as_slice(), // 8 bytes + ].concat(); + + let bytes: [u8; 32] = match vec.try_into() { + Ok(bytes) => bytes, + Err(_) => panic!("Expected a Vec of length 32"), + }; + + Self(bytes) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_should_be_instantiated_from_an_u64() { + + // Pad numbers with zeros on the left + + assert_eq!(ByteArray32::from(0x00_00_00_00_00_00_00_00_u64), ByteArray32::new([ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].as_slice(), // 24 bytes + [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].as_slice(), // + 8 bytes (64 bits, u64) + ].concat().try_into().unwrap())); // 32 bytes + + assert_eq!(ByteArray32::from(0xFF_FF_FF_FF_FF_FF_FF_00_u64), ByteArray32::new([ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].as_slice(), // 24 bytes + [ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00].as_slice(), // + 8 bytes (64 bits, u64) + ].concat().try_into().unwrap())); // 32 bytes // 32 bytes + } + + #[test] + fn it_should_be_converted_into_a_generic_byte_array() { + + let byte_array_32 = ByteArray32::new([0; 32]); + + assert_eq!(byte_array_32.as_generic_byte_array(), [0u8; 32]); + } + + #[test] + fn it_should_support_bitwise_or_operator() { + assert_eq!(ByteArray32::new([ 0; 32]) | ByteArray32::new([ 0; 32]), ByteArray32::new([ 0; 32])); // 0 | 0 = 0 + assert_eq!(ByteArray32::new([ 0; 32]) | ByteArray32::new([0xFF; 32]), ByteArray32::new([0xFF; 32])); // 0 | 1 = 1 + assert_eq!(ByteArray32::new([0xFF; 32]) | ByteArray32::new([ 0; 32]), ByteArray32::new([0xFF; 32])); // 1 | 0 = 1 + assert_eq!(ByteArray32::new([0xFF; 32]) | ByteArray32::new([0xFF; 32]), ByteArray32::new([0xFF; 32])); // 1 | 1 = 1 + } +} \ No newline at end of file diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 5eff98709..232013c8b 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -1,10 +1,10 @@ -use std::ops::BitOr; use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; -use arraytools::ArrayTools; -fn generate_time_bound_pepper(server_secret: &[u8; 32], current_timestamp: u64) -> [u8; 32] { +use super::byte_array_32::ByteArray32; + +fn generate_time_bound_pepper(server_secret: &ByteArray32, current_timestamp: u64) -> ByteArray32 { // todo: use a TimeBoundPepper struct as proposed by @da2ce7, storing also the current timestamp // used to generate the TimeBoundPepper @@ -32,26 +32,17 @@ fn generate_time_bound_pepper(server_secret: &[u8; 32], current_timestamp: u64) let unix_time_minutes: u64 = current_timestamp / 60; - let number_of_two_minute_slots_since_unix_time: u64 = unix_time_minutes / 2; - - let or_right_part: Vec = [ - [0u8; 24].as_slice(), - number_of_two_minute_slots_since_unix_time.to_be_bytes().as_slice(), - ].concat(); - - //println!("OR left : {:?}", [0u8; 32]); - //println!("OR right: {:?}", &or_right_part); + // Server Secret | Unix_Time_Minutes / 2 + let server_secret_or_two_minute_counter = *server_secret | ByteArray32::from(unix_time_minutes / 2); - // todo: extract function for OR operation for [u8; 32] arrays - let server_secret_or_two_minute_counter: [u8; 32] = server_secret.zip_with( or_right_part.try_into().unwrap(), BitOr::bitor); + // Hash(Server Secret | Unix_Time_Minutes / 2) + let time_bound_pepper = blake3::hash(&server_secret_or_two_minute_counter.as_generic_byte_array()); - let time_bound_pepper = blake3::hash(&server_secret_or_two_minute_counter); - - *time_bound_pepper.as_bytes() + ByteArray32::new(*time_bound_pepper.as_bytes()) } /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(server_secret: &[u8; 32], remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { +pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { /* WIP: New proposal by @da2ce7 @@ -76,7 +67,7 @@ pub fn get_connection_id(server_secret: &[u8; 32], remote_address: &SocketAddr, (current_timestamp / 120).to_be_bytes().as_slice(), peer_ip_as_bytes.as_slice(), remote_address.port().to_be_bytes().as_slice(), - server_secret.as_slice() + server_secret.as_generic_byte_array().as_slice() ].concat(); let hash = blake3::hash(&input); @@ -91,7 +82,7 @@ pub fn get_connection_id(server_secret: &[u8; 32], remote_address: &SocketAddr, } /// Verifies whether a connection id is valid at this time for a given remote address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &[u8; 32], remote_address: &SocketAddr, current_timestamp: u64) -> Result<(), ()> { +pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: u64) -> Result<(), ()> { match connection_id { cid if cid == get_connection_id(server_secret, remote_address, current_timestamp) => Ok(()), cid if cid == get_connection_id(server_secret, remote_address, current_timestamp - 120) => Ok(()), @@ -104,11 +95,15 @@ mod tests { use super::*; use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; - const SERVER_SECRET: [u8;32] = [0;32]; + fn generate_server_secret_for_testing() -> ByteArray32 { + ByteArray32::new([0u8;32]) + } #[test] fn time_bound_pepper_should_be_the_same_during_each_two_minute_window_since_unix_epoch() { + let server_secret = generate_server_secret_for_testing(); + // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | // |----------------------------------------------------------------------------| // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | @@ -120,11 +115,11 @@ mod tests { let initial_timestamp = 0u64; - let time_bound_pepper = generate_time_bound_pepper(&SERVER_SECRET, initial_timestamp); + let time_bound_pepper = generate_time_bound_pepper(&server_secret, initial_timestamp); - assert_eq!(time_bound_pepper, [42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218]); + assert_eq!(time_bound_pepper, ByteArray32::new([42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218])); - let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&SERVER_SECRET, initial_timestamp + 120 - 1); + let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&server_secret, initial_timestamp + 120 - 1); assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); } @@ -132,6 +127,8 @@ mod tests { #[test] fn time_bound_pepper_should_change_after_a_two_minute_window_starting_time_windows_at_unix_epoch() { + let server_secret = generate_server_secret_for_testing(); + // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | // |----------------------------------------------------------------------------| // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | @@ -143,22 +140,24 @@ mod tests { let initial_timestamp = 0u64; - let time_bound_pepper = generate_time_bound_pepper(&SERVER_SECRET, initial_timestamp); + let time_bound_pepper = generate_time_bound_pepper(&server_secret, initial_timestamp); - assert_eq!(time_bound_pepper, [42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218]); + assert_eq!(time_bound_pepper, ByteArray32::new([42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218])); - let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&SERVER_SECRET, initial_timestamp + 120 - 1); + let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&server_secret, initial_timestamp + 120 - 1); assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); } #[test] fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_server_secret() { + let server_secret = generate_server_secret_for_testing(); + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now_as_timestamp = 946684800u64; // GMT/UTC date and time is: 01-01-2000 00:00:00 - let connection_id = get_connection_id(&SERVER_SECRET, &client_addr, now_as_timestamp); + let connection_id = get_connection_id(&server_secret, &client_addr, now_as_timestamp); assert_eq!(connection_id, ConnectionId(-7545411207427689958)); @@ -166,56 +165,64 @@ mod tests { #[test] fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_two_minutes() { + let server_secret = generate_server_secret_for_testing(); + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; - let connection_id = get_connection_id(&SERVER_SECRET, &client_addr, now); + let connection_id = get_connection_id(&server_secret, &client_addr, now); let in_two_minutes = now + 120 - 1; - let connection_id_after_two_minutes = get_connection_id(&SERVER_SECRET, &client_addr, in_two_minutes); + let connection_id_after_two_minutes = get_connection_id(&server_secret, &client_addr, in_two_minutes); assert_eq!(connection_id, connection_id_after_two_minutes); } #[test] fn connection_id_in_udp_tracker_should_change_for_the_same_client_ip_and_port_after_two_minutes() { + let server_secret = generate_server_secret_for_testing(); + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; - let connection_id = get_connection_id(&SERVER_SECRET, &client_addr, now); + let connection_id = get_connection_id(&server_secret, &client_addr, now); let after_two_minutes = now + 120; - let connection_id_after_two_minutes = get_connection_id(&SERVER_SECRET, &client_addr, after_two_minutes); + let connection_id_after_two_minutes = get_connection_id(&server_secret, &client_addr, after_two_minutes); assert_ne!(connection_id, connection_id_after_two_minutes); } #[test] fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { + let server_secret = generate_server_secret_for_testing(); + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let now = 946684800u64; - let connection_id_for_client_1 = get_connection_id(&SERVER_SECRET, &client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&SERVER_SECRET, &client_2_addr, now); + let connection_id_for_client_1 = get_connection_id(&server_secret, &client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&server_secret, &client_2_addr, now); assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } #[test] fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { + let server_secret = generate_server_secret_for_testing(); + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); let now = 946684800u64; - let connection_id_for_client_1 = get_connection_id(&SERVER_SECRET, &client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&SERVER_SECRET, &client_2_addr, now); + let connection_id_for_client_1 = get_connection_id(&server_secret, &client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&server_secret, &client_2_addr, now); assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } @@ -242,18 +249,20 @@ mod tests { // For the worse scenario if the ID was generated at the beginning of a 2-minute slot, // It will be valid for almost 4 minutes. + let server_secret = generate_server_secret_for_testing(); + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let unix_epoch = 0u64; - let connection_id = get_connection_id(&SERVER_SECRET, &client_addr, unix_epoch); + let connection_id = get_connection_id(&server_secret, &client_addr, unix_epoch); - assert_eq!(verify_connection_id(connection_id, &SERVER_SECRET, &client_addr, unix_epoch), Ok(())); + assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, unix_epoch), Ok(())); // X = Y - assert_eq!(verify_connection_id(connection_id, &SERVER_SECRET, &client_addr, unix_epoch + 120), Ok(())); + assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, unix_epoch + 120), Ok(())); // X != Z - assert_eq!(verify_connection_id(connection_id, &SERVER_SECRET, &client_addr, unix_epoch + 240 + 1), Err(())); + assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, unix_epoch + 240 + 1), Err(())); } } \ No newline at end of file diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 2459580c1..d5b34a75d 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -2,6 +2,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId}; +use log::debug; use crate::{InfoHash, MAX_SCRAPE_TORRENTS}; use crate::peer::TorrentPeer; @@ -12,6 +13,7 @@ use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; use crate::protocol::clock::current_timestamp; +use super::byte_array_32::ByteArray32; use super::connection_id::get_connection_id; pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { @@ -72,8 +74,11 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: } pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let server_secret = [0;32]; // todo: server_secret should be randomly generated on startup - let connection_id = get_connection_id(&server_secret, &remote_addr, current_timestamp()); + let server_secret = ByteArray32::new([0;32]); // todo: server_secret should be randomly generated on startup + let current_timestamp = current_timestamp(); + let connection_id = get_connection_id(&server_secret, &remote_addr, current_timestamp); + + debug!("connection_id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); let response = Response::from(ConnectResponse { transaction_id: request.transaction_id, diff --git a/src/udp/mod.rs b/src/udp/mod.rs index b8d9d5621..2b9fc8502 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -9,6 +9,7 @@ pub use self::handlers::*; pub use self::request::*; pub use self::server::*; +pub mod byte_array_32; pub mod connection_id; pub mod errors; pub mod request; From 5b42af28389606d07456ce68ab75b0e108568c9c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 13:09:45 +0100 Subject: [PATCH 29/90] refactor: move time bound pepper to a new mod --- src/udp/connection_id.rs | 88 +------------------------------- src/udp/mod.rs | 1 + src/udp/time_bound_pepper.rs | 98 ++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 87 deletions(-) create mode 100644 src/udp/time_bound_pepper.rs diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 232013c8b..eae676c0c 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -3,43 +3,7 @@ use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; use super::byte_array_32::ByteArray32; - -fn generate_time_bound_pepper(server_secret: &ByteArray32, current_timestamp: u64) -> ByteArray32 { - - // todo: use a TimeBoundPepper struct as proposed by @da2ce7, storing also the current timestamp - // used to generate the TimeBoundPepper - // - // #[derive(Default)] - // struct TimeBoundPepper { - // created: SystemTime, - // pepper: [u8; 32], - // } - // - // impl Default for TimeBoundPepper { - // fn default() -> Self { SystemTime::now(), pepper: hash(SERVER_SECRET || self.created ) }; - // } - // - // impl TimeBoundPepper { - // pub fn new() -> Default::default(); - // - // pub fn get_pepper(&mut self, expires: std::time::Duration) -> [u8; 32] { - // if (created.elapsed().unwrap() >= expires) self = self.new(); - // return self.pepper; - // } - - // Time_Bound_Pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) - // (32-bytes), cached, expires every two minutes. - - let unix_time_minutes: u64 = current_timestamp / 60; - - // Server Secret | Unix_Time_Minutes / 2 - let server_secret_or_two_minute_counter = *server_secret | ByteArray32::from(unix_time_minutes / 2); - - // Hash(Server Secret | Unix_Time_Minutes / 2) - let time_bound_pepper = blake3::hash(&server_secret_or_two_minute_counter.as_generic_byte_array()); - - ByteArray32::new(*time_bound_pepper.as_bytes()) -} +use super::time_bound_pepper::generate_time_bound_pepper; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { @@ -99,56 +63,6 @@ mod tests { ByteArray32::new([0u8;32]) } - #[test] - fn time_bound_pepper_should_be_the_same_during_each_two_minute_window_since_unix_epoch() { - - let server_secret = generate_server_secret_for_testing(); - - // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | - // |----------------------------------------------------------------------------| - // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | - // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | - // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | - // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | - // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | - // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | - - let initial_timestamp = 0u64; - - let time_bound_pepper = generate_time_bound_pepper(&server_secret, initial_timestamp); - - assert_eq!(time_bound_pepper, ByteArray32::new([42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218])); - - let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&server_secret, initial_timestamp + 120 - 1); - - assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); - } - - #[test] - fn time_bound_pepper_should_change_after_a_two_minute_window_starting_time_windows_at_unix_epoch() { - - let server_secret = generate_server_secret_for_testing(); - - // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | - // |----------------------------------------------------------------------------| - // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | - // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | - // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | - // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | - // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | - // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | - - let initial_timestamp = 0u64; - - let time_bound_pepper = generate_time_bound_pepper(&server_secret, initial_timestamp); - - assert_eq!(time_bound_pepper, ByteArray32::new([42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218])); - - let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&server_secret, initial_timestamp + 120 - 1); - - assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); - } - #[test] fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_server_secret() { let server_secret = generate_server_secret_for_testing(); diff --git a/src/udp/mod.rs b/src/udp/mod.rs index 2b9fc8502..aa425ac12 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -15,6 +15,7 @@ pub mod errors; pub mod request; pub mod server; pub mod handlers; +pub mod time_bound_pepper; pub type Bytes = u64; pub type Port = u16; diff --git a/src/udp/time_bound_pepper.rs b/src/udp/time_bound_pepper.rs new file mode 100644 index 000000000..c05375555 --- /dev/null +++ b/src/udp/time_bound_pepper.rs @@ -0,0 +1,98 @@ +use super::byte_array_32::ByteArray32; + +pub fn generate_time_bound_pepper(server_secret: &ByteArray32, current_timestamp: u64) -> ByteArray32 { + + // todo: use a TimeBoundPepper struct as proposed by @da2ce7, storing also the current timestamp + // used to generate the TimeBoundPepper + // + // #[derive(Default)] + // struct TimeBoundPepper { + // created: SystemTime, + // pepper: [u8; 32], + // } + // + // impl Default for TimeBoundPepper { + // fn default() -> Self { SystemTime::now(), pepper: hash(SERVER_SECRET || self.created ) }; + // } + // + // impl TimeBoundPepper { + // pub fn new() -> Default::default(); + // + // pub fn get_pepper(&mut self, expires: std::time::Duration) -> [u8; 32] { + // if (created.elapsed().unwrap() >= expires) self = self.new(); + // return self.pepper; + // } + + // Time_Bound_Pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) + // (32-bytes), cached, expires every two minutes. + + let unix_time_minutes: u64 = current_timestamp / 60; + + // Server Secret | Unix_Time_Minutes / 2 + let server_secret_or_two_minute_counter = *server_secret | ByteArray32::from(unix_time_minutes / 2); + + // Hash(Server Secret | Unix_Time_Minutes / 2) + let time_bound_pepper = blake3::hash(&server_secret_or_two_minute_counter.as_generic_byte_array()); + + ByteArray32::new(*time_bound_pepper.as_bytes()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn generate_server_secret_for_testing() -> ByteArray32 { + ByteArray32::new([0u8;32]) + } + + #[test] + fn time_bound_pepper_should_be_the_same_during_each_two_minute_window_since_unix_epoch() { + + let server_secret = generate_server_secret_for_testing(); + + // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | + // |----------------------------------------------------------------------------| + // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | + // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | + // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | + // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | + // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | + // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | + + let initial_timestamp = 0u64; + + let time_bound_pepper = generate_time_bound_pepper(&server_secret, initial_timestamp); + + assert_eq!(time_bound_pepper, ByteArray32::new([42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218])); + + let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&server_secret, initial_timestamp + 120 - 1); + + assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); + } + + #[test] + fn time_bound_pepper_should_change_after_a_two_minute_window_starting_time_windows_at_unix_epoch() { + + let server_secret = generate_server_secret_for_testing(); + + // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | + // |----------------------------------------------------------------------------| + // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | + // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | + // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | + // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | + // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | + // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | + + let initial_timestamp = 0u64; + + let time_bound_pepper = generate_time_bound_pepper(&server_secret, initial_timestamp); + + assert_eq!(time_bound_pepper, ByteArray32::new([42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218])); + + let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&server_secret, initial_timestamp + 120 - 1); + + assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); + } + +} \ No newline at end of file From a444bcea88c823a216708fcc60aed02a3a445e37 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 13:10:58 +0100 Subject: [PATCH 30/90] refactor: rename tests --- src/udp/time_bound_pepper.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/udp/time_bound_pepper.rs b/src/udp/time_bound_pepper.rs index c05375555..3c466be05 100644 --- a/src/udp/time_bound_pepper.rs +++ b/src/udp/time_bound_pepper.rs @@ -46,7 +46,7 @@ mod tests { } #[test] - fn time_bound_pepper_should_be_the_same_during_each_two_minute_window_since_unix_epoch() { + fn it_should_be_the_same_during_each_two_minute_window_since_unix_epoch() { let server_secret = generate_server_secret_for_testing(); @@ -71,7 +71,7 @@ mod tests { } #[test] - fn time_bound_pepper_should_change_after_a_two_minute_window_starting_time_windows_at_unix_epoch() { + fn it_should_change_after_a_two_minute_window_starting_time_windows_at_unix_epoch() { let server_secret = generate_server_secret_for_testing(); From a8322edd34a49ff594ff20278743f63f07625ed0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 13:48:39 +0100 Subject: [PATCH 31/90] refactor: extract struct TimeBoundPepper --- src/udp/connection_id.rs | 6 +- src/udp/time_bound_pepper.rs | 108 +++++++++++++++-------------------- 2 files changed, 48 insertions(+), 66 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index eae676c0c..9aafb4711 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -3,7 +3,7 @@ use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; use super::byte_array_32::ByteArray32; -use super::time_bound_pepper::generate_time_bound_pepper; +use super::time_bound_pepper::TimeBoundPepper; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { @@ -19,8 +19,8 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd ConnectionID = Hash(Time_Bound_Pepper || Authentication_String) (64-bit) */ - // todo: not used yet. See `generate_time_bound_pepper` function above. - let _time_bound_pepper = generate_time_bound_pepper(server_secret, current_timestamp); + // todo: not used yet. + let _time_bound_pepper = TimeBoundPepper::new(&server_secret, current_timestamp); let peer_ip_as_bytes = match remote_address.ip() { IpAddr::V4(ip) => ip.octets().to_vec(), diff --git a/src/udp/time_bound_pepper.rs b/src/udp/time_bound_pepper.rs index 3c466be05..defe34f4e 100644 --- a/src/udp/time_bound_pepper.rs +++ b/src/udp/time_bound_pepper.rs @@ -1,40 +1,46 @@ +//! A secret hash value bound to time. +//! It changes every two minutes starting at Unix Epoch. +//! +//! | Date | Timestamp | Unix Epoch in minutes | TimeBoundPepper | +//! |-----------------------|-----------|-----------------------|-----------------| +//! | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | +//! | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | +//! | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | +//! | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | +//! | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | +//! | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | + use super::byte_array_32::ByteArray32; -pub fn generate_time_bound_pepper(server_secret: &ByteArray32, current_timestamp: u64) -> ByteArray32 { - - // todo: use a TimeBoundPepper struct as proposed by @da2ce7, storing also the current timestamp - // used to generate the TimeBoundPepper - // - // #[derive(Default)] - // struct TimeBoundPepper { - // created: SystemTime, - // pepper: [u8; 32], - // } - // - // impl Default for TimeBoundPepper { - // fn default() -> Self { SystemTime::now(), pepper: hash(SERVER_SECRET || self.created ) }; - // } - // - // impl TimeBoundPepper { - // pub fn new() -> Default::default(); - // - // pub fn get_pepper(&mut self, expires: std::time::Duration) -> [u8; 32] { - // if (created.elapsed().unwrap() >= expires) self = self.new(); - // return self.pepper; - // } - - // Time_Bound_Pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) - // (32-bytes), cached, expires every two minutes. - - let unix_time_minutes: u64 = current_timestamp / 60; - - // Server Secret | Unix_Time_Minutes / 2 - let server_secret_or_two_minute_counter = *server_secret | ByteArray32::from(unix_time_minutes / 2); - - // Hash(Server Secret | Unix_Time_Minutes / 2) - let time_bound_pepper = blake3::hash(&server_secret_or_two_minute_counter.as_generic_byte_array()); - - ByteArray32::new(*time_bound_pepper.as_bytes()) +#[derive(PartialEq, Debug)] +pub struct TimeBoundPepper { + pepper: ByteArray32, +} + +impl TimeBoundPepper { + pub fn new(server_secret: &ByteArray32, current_timestamp: u64) -> Self { + Self { + pepper: Self::generate_pepper(server_secret, current_timestamp) + } + } + + /// Time_Bound_Pepper = Hash(Server_Secret || Unix_Time_Minutes / 2) + fn generate_pepper(server_secret: &ByteArray32, current_timestamp: u64) -> ByteArray32 { + + let unix_time_minutes: u64 = current_timestamp / 60; + + // Server Secret | Unix_Time_Minutes / 2 + let server_secret_or_two_minute_counter = *server_secret | ByteArray32::from(unix_time_minutes / 2); + + // Hash(Server Secret | Unix_Time_Minutes / 2) + let time_bound_pepper = blake3::hash(&server_secret_or_two_minute_counter.as_generic_byte_array()); + + ByteArray32::new(*time_bound_pepper.as_bytes()) + } + + pub fn get_pepper(&self) -> &ByteArray32 { + &self.pepper + } } #[cfg(test)] @@ -50,22 +56,10 @@ mod tests { let server_secret = generate_server_secret_for_testing(); - // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | - // |----------------------------------------------------------------------------| - // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | - // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | - // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | - // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | - // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | - // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | - let initial_timestamp = 0u64; - let time_bound_pepper = generate_time_bound_pepper(&server_secret, initial_timestamp); - - assert_eq!(time_bound_pepper, ByteArray32::new([42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218])); - - let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&server_secret, initial_timestamp + 120 - 1); + let time_bound_pepper = TimeBoundPepper::new(&server_secret, initial_timestamp); + let time_bound_pepper_after_two_minutes = TimeBoundPepper::new(&server_secret, initial_timestamp + 120 - 1); assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); } @@ -75,22 +69,10 @@ mod tests { let server_secret = generate_server_secret_for_testing(); - // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | - // |----------------------------------------------------------------------------| - // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | - // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | - // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | - // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | - // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | - // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | - let initial_timestamp = 0u64; - let time_bound_pepper = generate_time_bound_pepper(&server_secret, initial_timestamp); - - assert_eq!(time_bound_pepper, ByteArray32::new([42, 218, 131, 193, 129, 154, 83, 114, 218, 225, 35, 143, 193, 222, 209, 35, 200, 16, 79, 218, 161, 88, 98, 170, 238, 105, 66, 138, 24, 32, 252, 218])); - - let time_bound_pepper_after_two_minutes = generate_time_bound_pepper(&server_secret, initial_timestamp + 120 - 1); + let time_bound_pepper = TimeBoundPepper::new(&server_secret, initial_timestamp); + let time_bound_pepper_after_two_minutes = TimeBoundPepper::new(&server_secret, initial_timestamp + 120 - 1); assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); } From e3b7b6e32375cf8667b8744626d37e75d8cad67e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 14:29:30 +0100 Subject: [PATCH 32/90] refactor: create alias type Timestamp --- src/udp/connection_id.rs | 6 +++--- src/udp/time_bound_pepper.rs | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 9aafb4711..c7f401901 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -3,10 +3,10 @@ use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; use super::byte_array_32::ByteArray32; -use super::time_bound_pepper::TimeBoundPepper; +use super::time_bound_pepper::{TimeBoundPepper, Timestamp}; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol -pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId { +pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { /* WIP: New proposal by @da2ce7 @@ -46,7 +46,7 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd } /// Verifies whether a connection id is valid at this time for a given remote address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: u64) -> Result<(), ()> { +pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { match connection_id { cid if cid == get_connection_id(server_secret, remote_address, current_timestamp) => Ok(()), cid if cid == get_connection_id(server_secret, remote_address, current_timestamp - 120) => Ok(()), diff --git a/src/udp/time_bound_pepper.rs b/src/udp/time_bound_pepper.rs index defe34f4e..4d087cc27 100644 --- a/src/udp/time_bound_pepper.rs +++ b/src/udp/time_bound_pepper.rs @@ -12,20 +12,22 @@ use super::byte_array_32::ByteArray32; +pub type Timestamp = u64; + #[derive(PartialEq, Debug)] pub struct TimeBoundPepper { pepper: ByteArray32, } impl TimeBoundPepper { - pub fn new(server_secret: &ByteArray32, current_timestamp: u64) -> Self { + pub fn new(server_secret: &ByteArray32, current_timestamp: Timestamp) -> Self { Self { pepper: Self::generate_pepper(server_secret, current_timestamp) } } /// Time_Bound_Pepper = Hash(Server_Secret || Unix_Time_Minutes / 2) - fn generate_pepper(server_secret: &ByteArray32, current_timestamp: u64) -> ByteArray32 { + fn generate_pepper(server_secret: &ByteArray32, current_timestamp: Timestamp) -> ByteArray32 { let unix_time_minutes: u64 = current_timestamp / 60; From 55982fe681495d159079f502b31e2eb3ca1b68c6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 16:02:34 +0100 Subject: [PATCH 33/90] feat: implemented @da2ce7's proposal for TimeBoundPepper New implementation for the UDP tracker Connection ID. It was prooposed by Cameron Garnham here: https://github.com/torrust/torrust-tracker/pull/60#issuecomment-1214172679 Co-authored-by: Cameron Garnham --- src/udp/connection_id.rs | 67 ++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index c7f401901..d5b963992 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -8,35 +8,49 @@ use super::time_bound_pepper::{TimeBoundPepper, Timestamp}; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { - /* WIP: New proposal by @da2ce7 + let peer_ip_as_bytes = match remote_address.ip() { + IpAddr::V4(ip) => [ + [0u8; 28].as_slice(), + ip.octets().as_slice(), + ].concat(), + IpAddr::V6(ip) => [ + [0u8; 16].as_slice(), + ip.octets().as_slice(), + ].concat(), + }; - Static_Sever_Secret = Random (32-bytes), generated on sever start. - - Time_Bound_Pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) (32-bytes), cached, expires every two minutes. - - Authentication_String = IP_Address || Port || User Token || Etc. (32-bytes), unique for each client. - - ConnectionID = Hash(Time_Bound_Pepper || Authentication_String) (64-bit) - */ + let peer_ip_address_32_bytes: [u8; 32] = match peer_ip_as_bytes.try_into() { + Ok(bytes) => bytes, + Err(_) => panic!("Expected a Vec of length 32"), + }; - // todo: not used yet. - let _time_bound_pepper = TimeBoundPepper::new(&server_secret, current_timestamp); + let port = [ + [0u8; 30].as_slice(), + remote_address.port().to_be_bytes().as_slice(), // 2 bytes + ].concat(); - let peer_ip_as_bytes = match remote_address.ip() { - IpAddr::V4(ip) => ip.octets().to_vec(), - IpAddr::V6(ip) => ip.octets().to_vec(), + let port_32_bytes: [u8; 32] = match port.try_into() { + Ok(bytes) => bytes, + Err(_) => panic!("Expected a Vec of length 32"), }; + // authentication_string = IP_Address || Port + // (32-bytes), unique for each client. + let authentication_string = ByteArray32::new(peer_ip_address_32_bytes) | ByteArray32::new(port_32_bytes); + + // time_bound_pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) + // (32-bytes), cached, expires every two minutes. + let time_bound_pepper = TimeBoundPepper::new(&server_secret, current_timestamp); + + // connection_id = Hash(time_bound_pepper || authentication_string) (64-bit) let input: Vec = [ - (current_timestamp / 120).to_be_bytes().as_slice(), - peer_ip_as_bytes.as_slice(), - remote_address.port().to_be_bytes().as_slice(), - server_secret.as_generic_byte_array().as_slice() + time_bound_pepper.get_pepper().as_generic_byte_array(), + authentication_string.as_generic_byte_array(), ].concat(); let hash = blake3::hash(&input); - let mut truncated_hash: [u8; 8] = [0u8; 8]; + let mut truncated_hash: [u8; 8] = [0u8; 8]; // 8 bytes = 64 bits truncated_hash.copy_from_slice(&hash.as_bytes()[..8]); @@ -64,7 +78,7 @@ mod tests { } #[test] - fn connection_id_is_generated_by_hashing_the_client_ip_and_port_with_a_server_secret() { + fn test_pre_calculate_value() { let server_secret = generate_server_secret_for_testing(); let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); @@ -73,12 +87,11 @@ mod tests { let connection_id = get_connection_id(&server_secret, &client_addr, now_as_timestamp); - assert_eq!(connection_id, ConnectionId(-7545411207427689958)); - + assert_eq!(connection_id, ConnectionId(6587457301375199145)); } #[test] - fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_two_minutes() { + fn it_should_be_the_same_for_one_client_during_two_minutes() { let server_secret = generate_server_secret_for_testing(); let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); @@ -95,7 +108,7 @@ mod tests { } #[test] - fn connection_id_in_udp_tracker_should_change_for_the_same_client_ip_and_port_after_two_minutes() { + fn it_should_change_for_the_same_client_ip_and_port_after_two_minutes() { let server_secret = generate_server_secret_for_testing(); let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); @@ -112,7 +125,7 @@ mod tests { } #[test] - fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { + fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { let server_secret = generate_server_secret_for_testing(); let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); @@ -127,7 +140,7 @@ mod tests { } #[test] - fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { + fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { let server_secret = generate_server_secret_for_testing(); let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); @@ -142,7 +155,7 @@ mod tests { } #[test] - fn connection_id_in_udp_tracker_should_be_valid_for_the_current_two_minute_window_since_unix_epoch_and_the_previous_window() { + fn it_should_be_valid_for_the_current_two_minute_window_since_unix_epoch_and_the_previous_window() { // The implementation generates a different connection id for each client and port every two minutes. // Connection should expire 2 minutes after the generation but we do not store the exact time From 1ba7c39aca73a5e20e42a928c47b3f6d99a1529e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 16:23:47 +0100 Subject: [PATCH 34/90] refactor: extract ByteArray32 constructors --- src/udp/connection_id.rs | 75 +++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index d5b963992..33ddb0bc5 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -1,6 +1,7 @@ use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; +use std::convert::From; use super::byte_array_32::ByteArray32; use super::time_bound_pepper::{TimeBoundPepper, Timestamp}; @@ -8,35 +9,9 @@ use super::time_bound_pepper::{TimeBoundPepper, Timestamp}; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { - let peer_ip_as_bytes = match remote_address.ip() { - IpAddr::V4(ip) => [ - [0u8; 28].as_slice(), - ip.octets().as_slice(), - ].concat(), - IpAddr::V6(ip) => [ - [0u8; 16].as_slice(), - ip.octets().as_slice(), - ].concat(), - }; - - let peer_ip_address_32_bytes: [u8; 32] = match peer_ip_as_bytes.try_into() { - Ok(bytes) => bytes, - Err(_) => panic!("Expected a Vec of length 32"), - }; - - let port = [ - [0u8; 30].as_slice(), - remote_address.port().to_be_bytes().as_slice(), // 2 bytes - ].concat(); - - let port_32_bytes: [u8; 32] = match port.try_into() { - Ok(bytes) => bytes, - Err(_) => panic!("Expected a Vec of length 32"), - }; - // authentication_string = IP_Address || Port // (32-bytes), unique for each client. - let authentication_string = ByteArray32::new(peer_ip_address_32_bytes) | ByteArray32::new(port_32_bytes); + let authentication_string = ByteArray32::from(remote_address.ip()) | ByteArray32::from(remote_address.port()); // time_bound_pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) // (32-bytes), cached, expires every two minutes. @@ -68,6 +43,46 @@ pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArr } } +impl From for ByteArray32 { + //// Converts an IpAddr to a ByteArray32 + fn from(ip: IpAddr) -> Self { + let peer_ip_as_bytes = match ip { + IpAddr::V4(ip) => [ + [0u8; 28].as_slice(), // 28 bytes + ip.octets().as_slice(), // 4 bytes + ].concat(), + IpAddr::V6(ip) => [ + [0u8; 16].as_slice(), // 16 bytes + ip.octets().as_slice(), // 16 bytes + ].concat(), + }; + + let peer_ip_address_32_bytes: [u8; 32] = match peer_ip_as_bytes.try_into() { + Ok(bytes) => bytes, + Err(_) => panic!("Expected a Vec of length 32"), + }; + + ByteArray32::new(peer_ip_address_32_bytes) + } +} + +impl From for ByteArray32 { + /// Converts a u16 to a ByteArray32 + fn from(port: u16) -> Self { + let port = [ + [0u8; 30].as_slice(), // 30 bytes + port.to_be_bytes().as_slice(), // 2 bytes + ].concat(); + + let port_32_bytes: [u8; 32] = match port.try_into() { + Ok(bytes) => bytes, + Err(_) => panic!("Expected a Vec of length 32"), + }; + + ByteArray32::new(port_32_bytes) + } +} + #[cfg(test)] mod tests { use super::*; @@ -77,6 +92,12 @@ mod tests { ByteArray32::new([0u8;32]) } + #[test] + fn ip_address_should_be_converted_to_a_32_bytes_array() { + let ip_address = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + assert_eq!(ByteArray32::from(ip_address), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1])); + } + #[test] fn test_pre_calculate_value() { let server_secret = generate_server_secret_for_testing(); From 3287448bd7030220ae2a4394d7013d8058280808 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 16:39:58 +0100 Subject: [PATCH 35/90] docs: add comments --- src/udp/connection_id.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 33ddb0bc5..55a6e98a9 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -6,31 +6,37 @@ use std::convert::From; use super::byte_array_32::ByteArray32; use super::time_bound_pepper::{TimeBoundPepper, Timestamp}; -/// It generates a connection id needed for the BitTorrent UDP Tracker Protocol +/// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. +/// time_bound_pepper = Hash(Server_Secret || Unix_Time_Minutes / 2) (32 bytes, 256 bits) +/// hash_input = Concat(time_bound_pepper, authentication_string) (64 bytes, 512 bits) +/// connection_id = Truncate(Hash(hash_input)) ( 8 bytes, 64-bits) pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { // authentication_string = IP_Address || Port // (32-bytes), unique for each client. let authentication_string = ByteArray32::from(remote_address.ip()) | ByteArray32::from(remote_address.port()); - // time_bound_pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) + // time_bound_pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) // (32-bytes), cached, expires every two minutes. let time_bound_pepper = TimeBoundPepper::new(&server_secret, current_timestamp); - // connection_id = Hash(time_bound_pepper || authentication_string) (64-bit) + // Contact(time_bound_pepper, authentication_string) (64 bytes) let input: Vec = [ time_bound_pepper.get_pepper().as_generic_byte_array(), authentication_string.as_generic_byte_array(), ].concat(); + // Hash(Contact(...) (32 bytes) let hash = blake3::hash(&input); + // Truncate(Hash(...)) (8 bytes, 64-bits) let mut truncated_hash: [u8; 8] = [0u8; 8]; // 8 bytes = 64 bits truncated_hash.copy_from_slice(&hash.as_bytes()[..8]); let connection_id = i64::from_le_bytes(truncated_hash); + // connection_id = Hash(Contact(time_bound_pepper,authentication_string)) (64-bit) ConnectionId(connection_id) } From 842487c0de6b5fb0690672cf312cea27669455dd Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 17:17:00 +0100 Subject: [PATCH 36/90] docs: fix typo --- src/udp/connection_id.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 55a6e98a9..d486569c2 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -20,13 +20,13 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd // (32-bytes), cached, expires every two minutes. let time_bound_pepper = TimeBoundPepper::new(&server_secret, current_timestamp); - // Contact(time_bound_pepper, authentication_string) (64 bytes) + // Concat(time_bound_pepper, authentication_string) (64 bytes) let input: Vec = [ time_bound_pepper.get_pepper().as_generic_byte_array(), authentication_string.as_generic_byte_array(), ].concat(); - // Hash(Contact(...) (32 bytes) + // Hash(Concat(...) (32 bytes) let hash = blake3::hash(&input); // Truncate(Hash(...)) (8 bytes, 64-bits) @@ -36,7 +36,7 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd let connection_id = i64::from_le_bytes(truncated_hash); - // connection_id = Hash(Contact(time_bound_pepper,authentication_string)) (64-bit) + // connection_id = Hash(Concat(time_bound_pepper,authentication_string)) (64-bit) ConnectionId(connection_id) } From e6afa46c1bb289a9f2a9b3a6c51229dfecc15df6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 17 Aug 2022 17:24:54 +0100 Subject: [PATCH 37/90] test: ByteArray32 converter from socker port (u16) --- src/udp/connection_id.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index d486569c2..d74a20739 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -102,6 +102,12 @@ mod tests { fn ip_address_should_be_converted_to_a_32_bytes_array() { let ip_address = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); assert_eq!(ByteArray32::from(ip_address), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1])); + } + + #[test] + fn socket_port_should_be_converted_to_a_32_bytes_array() { + let port = 0x1F_90u16; // 8080 + assert_eq!(ByteArray32::from(port), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1F, 0x90])); } #[test] From a2d184951da2d2f915e49d04a5615afcf300f1ed Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 18 Aug 2022 10:05:21 +0100 Subject: [PATCH 38/90] docs: add description for connection_id mod --- src/udp/byte_array_32.rs | 7 ++++ src/udp/connection_id.rs | 80 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/udp/byte_array_32.rs b/src/udp/byte_array_32.rs index af7b0f6e4..ebe5b39f8 100644 --- a/src/udp/byte_array_32.rs +++ b/src/udp/byte_array_32.rs @@ -1,3 +1,7 @@ +//! A struct wrapper for type `[u8; 32]`. +//! +//! It adds some convenient methods to work with arrays of bytes. +//! Specially constructors from other types. use std::ops::BitOr; use arraytools::ArrayTools; use std::convert::From; @@ -7,10 +11,12 @@ pub struct ByteArray32([u8; 32]); impl ByteArray32 { + /// Constructs a new `ByteArray32` from a `[u8, 32]` array. pub fn new(bytes: [u8; 32]) -> Self { ByteArray32(bytes) } + /// Returns the underlying `[u8; 32]` array. pub fn as_generic_byte_array(self) -> [u8; 32] { self.0 } @@ -26,6 +32,7 @@ impl BitOr for ByteArray32 { } impl From for ByteArray32 { + /// Convert a u64 to a ByteArray32. fn from(item: u64) -> Self { let vec: Vec = [ [0u8; 24].as_slice(), diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index d74a20739..153c96316 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -1,3 +1,83 @@ +//! Connection ID is a value generate by the tracker and sent to the client +//! to avoid the client spoofing it's source IP address. +//! +//! Detailed info in [BEP 15](https://www.bittorrent.org/beps/bep_0015.html) +//! +//! In order for the client to connect to the tracker, it must send a connection ID +//! previously generated by the server. +//! +//! The client has to send a "connect" request: +//! +//! | Offset | Size | Name | Value | +//! |--------|----------------|----------------|---------------------------------| +//! | 0 | 64-bit integer | protocol_id | 0x41727101980 // magic constant | +//! | 8 | 32-bit integer | action | 0 // connect | +//! | 12 | 32-bit integer | transaction_id | | +//! | 16 | | | | +//! +//! And it receives a Connection ID in the response: +//! +//! | Offset | Size | Name | Value | +//! |--------|----------------|----------------|--------------| +//! | 0 | 32-bit integer | action | 0 // connect | +//! | 4 | 32-bit integer | transaction_id | | +//! | 8 | 64-bit integer | connection_id | | +//! | 16 | | | | +//! +//! The client has to send the Connection ID in all subsequent requests. +//! The tracker verifies the connection_id and ignores the request if it doesn't match. +//! +//! A Connection ID: +//! +//! - Should not be guessable by the client. +//! - Can be used for multiple requests. +//! - Can be used by a client until one minute after it has received it. +//! - Can be accepted by the tracker until two minutes after it has been send. +//! +//! # Why do we need a connection ID? +//! +//! With the Connection ID we check for two things: +//! +//! - The announcing client owns the ip and port it is announcing with. +//! - The announcing client is an online BitTorrent peer. +//! +//! It's a kind of "proof of IP ownership" and "proof of online BitTorrent peer". +//! This makes sure that the client is not a fake client. And it makes harder for attackers +//! to fill the tracker peer list with fake clients. +//! +//! The only way to send an "announce" request is actually being an active and accessible BitTorrent client. +//! +//! It also avoid clients to send requests on behave of other clients. +//! If there is a legitimate client on the network, attackers could impersonate that client, +//! since they know the IP and port of the legitimate client. +//! An attacker could send an "announce" request for a torrent that the legitimate client does not have. +//! That's a kind of DOS attack because it would make harder to find a torrent. +//! The information about what torrents have each client could be easily manipulated. +//! +//! # Implementation +//! +//! Some tracker implementations use a time bound connection ID to avoid storing the connection ID +//! in memory or in the DB. +//! +//! ```ignore +//! static uint64_t _genCiD (uint32_t ip, uint16_t port) +//! { +//! uint64_t x; +//! x = (time(NULL) / 3600) * port; // x will probably overload. +//! x = (ip ^ port); +//! x <<= 16; +//! x |= (~port); +//! return x; +//! } +//! ``` +//! +//! From [here](https://github.com/troydm/udpt/blob/master/src/db/driver_sqlite.cpp#L410-L418). +//! +//! We use the same approach but using a hash with a server secret in order to keep the connection ID +//! not guessable. We also use the client IP and port to make it unique for each client. +//! +//! The secret used for hashing changes every time the server starts. +//! use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; From c7637a86feeee46ced1f132e1c4f66530fc96053 Mon Sep 17 00:00:00 2001 From: Cameron Garnham Date: Wed, 31 Aug 2022 14:19:19 +0100 Subject: [PATCH 39/90] docs: add properties for connection id --- src/udp/connection_id.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 153c96316..a369bbed1 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -27,12 +27,20 @@ //! The client has to send the Connection ID in all subsequent requests. //! The tracker verifies the connection_id and ignores the request if it doesn't match. //! -//! A Connection ID: +//! From the BEP 15 specification a Connection ID: //! //! - Should not be guessable by the client. //! - Can be used for multiple requests. //! - Can be used by a client until one minute after it has received it. //! - Can be accepted by the tracker until two minutes after it has been send. +//! +//! Additionally we define the Connection ID as a value that: +//! +//! - That is unpredictable. The user should not be able to construct their own Connection ID. +//! - That is unique to the the particular connection. Locked to a IP and Port. +//! - That is time bound. It expires after certain time. +//! - That is memoryless. The server doesn't remember what ID's it gave out. +//! - That is stateless. The issuer and the verifier can work interdependently without a dynamic common state. //! //! # Why do we need a connection ID? //! From 2b2ec2bc203e8d347f4323bd1db5637ff72c5546 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 31 Aug 2022 18:46:37 +0100 Subject: [PATCH 40/90] refactor: WIP. Encrypted connection id This refactor implements a new poposal by @da2ce7 to use encryption to generate the connection ID in the UDP tracker. https://github.com/torrust/torrust-tracker/pull/60#issuecomment-1221020599 TODO: - Encrypt the connection ID when it's generated. - Decrypt the connection ID when it's verified. --- src/udp/connection_id.rs | 166 +++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 84 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index a369bbed1..9cbf49d52 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -92,49 +92,63 @@ use aquatic_udp_protocol::ConnectionId; use std::convert::From; use super::byte_array_32::ByteArray32; -use super::time_bound_pepper::{TimeBoundPepper, Timestamp}; +use super::time_bound_pepper::Timestamp; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. -/// time_bound_pepper = Hash(Server_Secret || Unix_Time_Minutes / 2) (32 bytes, 256 bits) -/// hash_input = Concat(time_bound_pepper, authentication_string) (64 bytes, 512 bits) -/// connection_id = Truncate(Hash(hash_input)) ( 8 bytes, 64-bits) -pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { - - // authentication_string = IP_Address || Port - // (32-bytes), unique for each client. - let authentication_string = ByteArray32::from(remote_address.ip()) | ByteArray32::from(remote_address.port()); - - // time_bound_pepper = Hash(Static_Secret || Unix_Time_Minutes / 2) - // (32-bytes), cached, expires every two minutes. - let time_bound_pepper = TimeBoundPepper::new(&server_secret, current_timestamp); - - // Concat(time_bound_pepper, authentication_string) (64 bytes) - let input: Vec = [ - time_bound_pepper.get_pepper().as_generic_byte_array(), - authentication_string.as_generic_byte_array(), +pub fn get_connection_id(_server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { + + // remote_id : [u8;4] = blake3(concat(remote_addr, remote_port)); + let remote_address_byte_array = ByteArray32::from(remote_address.ip()); + let remote_port_byte_array = ByteArray32::from(remote_address.port()); + let input2: Vec = [ + remote_address_byte_array.as_generic_byte_array(), + remote_port_byte_array.as_generic_byte_array(), + ].concat(); + let remote_id_full_hash = blake3::hash(&input2); + let mut truncated_remote_ip: [u8; 4] = [0u8; 4]; // 4 bytes = 32 bits + truncated_remote_ip.copy_from_slice(&remote_id_full_hash.as_bytes()[..4]); + + // connection_id : [u8;8] = concat(remote_id, timestamp) + let _new_connection_id: Vec = [ + truncated_remote_ip.as_slice(), + ¤t_timestamp.to_le_bytes()[..4] ].concat(); - // Hash(Concat(...) (32 bytes) - let hash = blake3::hash(&input); - - // Truncate(Hash(...)) (8 bytes, 64-bits) - let mut truncated_hash: [u8; 8] = [0u8; 8]; // 8 bytes = 64 bits - - truncated_hash.copy_from_slice(&hash.as_bytes()[..8]); + let connection_as_array: [u8; 8] = _new_connection_id.try_into().expect("slice with incorrect length"); - let connection_id = i64::from_le_bytes(truncated_hash); + /* + println!("generate: bytes {:?}", _new_connection_id); + println!("generate: timestamp {:?}", current_timestamp); + println!("generate: timestamp bytes {:?}", ¤t_timestamp.to_le_bytes()[..4]); + println!("generate: i64 {:?}", i64::from_le_bytes(connection_as_array)); + */ - // connection_id = Hash(Concat(time_bound_pepper,authentication_string)) (64-bit) - ConnectionId(connection_id) + ConnectionId(i64::from_le_bytes(connection_as_array)) } /// Verifies whether a connection id is valid at this time for a given remote address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { - match connection_id { - cid if cid == get_connection_id(server_secret, remote_address, current_timestamp) => Ok(()), - cid if cid == get_connection_id(server_secret, remote_address, current_timestamp - 120) => Ok(()), - _ => Err(()) +pub fn verify_connection_id(connection_id: ConnectionId, _server_secret: &ByteArray32, _remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { + + let id_as_byte_array: [u8; 8] = connection_id.0.to_le_bytes(); + + let timestamp_bytes = &id_as_byte_array[4..]; + let timestamp_array = [timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]; // Little Endian + let created_at_timestamp = u64::from_le_bytes(timestamp_array); + + /* + println!("verify: i64 {:?}", &connection_id.0); + println!("verify: bytes {:?}", &id_as_byte_array); + println!("verify: timestamp bytes {:?}", &id_as_byte_array[4..]); + println!("verify: timestamp {:?}", created_at_timestamp); + */ + + let expire_timestamp = created_at_timestamp + 120; + + if expire_timestamp < current_timestamp { + return Err(()) } + + Ok(()) } impl From for ByteArray32 { @@ -198,35 +212,58 @@ mod tests { assert_eq!(ByteArray32::from(port), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1F, 0x90])); } - #[test] - fn test_pre_calculate_value() { + /*#[test] + fn it_should_be_the_same_for_one_client_during_two_minutes() { let server_secret = generate_server_secret_for_testing(); let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let now_as_timestamp = 946684800u64; // GMT/UTC date and time is: 01-01-2000 00:00:00 + let now = 946684800u64; - let connection_id = get_connection_id(&server_secret, &client_addr, now_as_timestamp); + let connection_id = get_connection_id(&server_secret, &client_addr, now); - assert_eq!(connection_id, ConnectionId(6587457301375199145)); - } + let in_two_minutes = now + 120 - 1; + + let connection_id_after_two_minutes = get_connection_id(&server_secret, &client_addr, in_two_minutes); + + assert_eq!(connection_id, connection_id_after_two_minutes); + }*/ #[test] - fn it_should_be_the_same_for_one_client_during_two_minutes() { + fn it_should_be_valid_for_two_minutes_after_the_generation() { let server_secret = generate_server_secret_for_testing(); - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let now = 946684800u64; // 01-01-2000 00:00:00 + + let connection_id = get_connection_id(&server_secret, &client_addr, now); + + let ret = verify_connection_id(connection_id, &server_secret, &client_addr, now); + + println!("ret: {:?}", ret); + assert_eq!(ret, Ok(())); + + let after_two_minutes = now + (2*60) - 1; + + assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, after_two_minutes), Ok(())); + } + + #[test] + fn it_should_expire_after_two_minutes_from_the_generation() { + let server_secret = generate_server_secret_for_testing(); + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; let connection_id = get_connection_id(&server_secret, &client_addr, now); - let in_two_minutes = now + 120 - 1; + let ret = verify_connection_id(connection_id, &server_secret, &client_addr, now); - let connection_id_after_two_minutes = get_connection_id(&server_secret, &client_addr, in_two_minutes); + println!("ret: {:?}", ret); - assert_eq!(connection_id, connection_id_after_two_minutes); - } + let after_more_than_two_minutes = now + (2*60) + 1; + + assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, after_more_than_two_minutes), Err(())); + } #[test] fn it_should_change_for_the_same_client_ip_and_port_after_two_minutes() { @@ -274,43 +311,4 @@ mod tests { assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } - - #[test] - fn it_should_be_valid_for_the_current_two_minute_window_since_unix_epoch_and_the_previous_window() { - - // The implementation generates a different connection id for each client and port every two minutes. - // Connection should expire 2 minutes after the generation but we do not store the exact time - // when it was generated. In order to implement a stateless connection ID generation, - // we change it automatically and we approximate it to the 2-minute window. - // - // | Date | Timestamp | Unix Epoch in minutes | Connection IDs | - // |----------------------------------------------------------------------------| - // | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | - // | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | - // | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | - // | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | - // | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | - // | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | - // - // Because of the implementation, the have to verify the current connection id and the previous one. - // If the ID was generated at the end of a 2-minute slot I won't be valid just after some seconds. - // For the worse scenario if the ID was generated at the beginning of a 2-minute slot, - // It will be valid for almost 4 minutes. - - let server_secret = generate_server_secret_for_testing(); - - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - - let unix_epoch = 0u64; - - let connection_id = get_connection_id(&server_secret, &client_addr, unix_epoch); - - assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, unix_epoch), Ok(())); - - // X = Y - assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, unix_epoch + 120), Ok(())); - - // X != Z - assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, unix_epoch + 240 + 1), Err(())); - } } \ No newline at end of file From 13cf81ebe8a1243d01eb3e2c50b78a6fc58b8594 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 1 Sep 2022 10:03:55 +0100 Subject: [PATCH 41/90] refactor: add rust-crypto dependency for blowfish cypher --- Cargo.lock | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 1 + 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5a7bf319..1fd9c244e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -747,6 +747,12 @@ dependencies = [ "syn", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "funty" version = "1.2.0" @@ -842,6 +848,12 @@ dependencies = [ "slab", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.14.5" @@ -1292,7 +1304,7 @@ dependencies = [ "mime", "mime_guess", "quick-error", - "rand", + "rand 0.8.5", "safemem", "tempfile", "twoway", @@ -1346,7 +1358,7 @@ dependencies = [ "lexical", "num-bigint 0.4.3", "num-traits 0.2.15", - "rand", + "rand 0.8.5", "regex", "rust_decimal", "saturating", @@ -1750,6 +1762,29 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -1758,7 +1793,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.3", ] [[package]] @@ -1768,9 +1803,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", ] +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.3" @@ -1804,6 +1854,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1874,6 +1933,19 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +dependencies = [ + "gcc", + "libc", + "rand 0.3.23", + "rustc-serialize", + "time 0.1.44", +] + [[package]] name = "rust-ini" version = "0.13.0" @@ -1897,6 +1969,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2624,7 +2702,8 @@ dependencies = [ "r2d2", "r2d2_mysql", "r2d2_sqlite", - "rand", + "rand 0.8.5", + "rust-crypto", "serde 1.0.137", "serde_bencode", "serde_json", @@ -2693,7 +2772,7 @@ dependencies = [ "http", "httparse", "log", - "rand", + "rand 0.8.5", "sha-1 0.9.8", "thiserror", "url", @@ -2716,7 +2795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "rand", + "rand 0.8.5", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index f9544bc68..66c67cad9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ hex = "0.4.3" percent-encoding = "2.1.0" binascii = "0.1" blake3 = "1.3.1" +rust-crypto = "^0.2" openssl = { version = "0.10.41", features = ["vendored"] } From e6a205b3274eef96b29ce7a6b44aa03c95d3ffd4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 1 Sep 2022 12:28:53 +0100 Subject: [PATCH 42/90] refactor: add encryption to connection id --- src/main.rs | 2 ++ src/udp/connection_id.rs | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 963419f03..6f1d1f078 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ use torrust_tracker::logging; use torrust_tracker::setup; use torrust_tracker::tracker::tracker::TorrentTracker; +extern crate crypto; + #[tokio::main] async fn main() { const CONFIG_PATH: &str = "config.toml"; diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 9cbf49d52..f21b1afaa 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -89,13 +89,15 @@ use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; +use crypto::blowfish::Blowfish; +use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; use std::convert::From; use super::byte_array_32::ByteArray32; use super::time_bound_pepper::Timestamp; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. -pub fn get_connection_id(_server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { +pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { // remote_id : [u8;4] = blake3(concat(remote_addr, remote_port)); let remote_address_byte_array = ByteArray32::from(remote_address.ip()); @@ -116,6 +118,12 @@ pub fn get_connection_id(_server_secret: &ByteArray32, remote_address: &SocketAd let connection_as_array: [u8; 8] = _new_connection_id.try_into().expect("slice with incorrect length"); + let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); + + let mut encrypted_connection_as_array = [0u8; 8]; + + blowfish.encrypt_block(&connection_as_array, &mut encrypted_connection_as_array); + /* println!("generate: bytes {:?}", _new_connection_id); println!("generate: timestamp {:?}", current_timestamp); @@ -123,13 +131,19 @@ pub fn get_connection_id(_server_secret: &ByteArray32, remote_address: &SocketAd println!("generate: i64 {:?}", i64::from_le_bytes(connection_as_array)); */ - ConnectionId(i64::from_le_bytes(connection_as_array)) + ConnectionId(i64::from_le_bytes(encrypted_connection_as_array)) } /// Verifies whether a connection id is valid at this time for a given remote address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, _server_secret: &ByteArray32, _remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { +pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, _remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { - let id_as_byte_array: [u8; 8] = connection_id.0.to_le_bytes(); + let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); + + let encrypted_id_as_byte_array: [u8; 8] = connection_id.0.to_le_bytes(); + + let mut id_as_byte_array = [0u8; 8]; + + blowfish.decrypt_block(&encrypted_id_as_byte_array, &mut id_as_byte_array); let timestamp_bytes = &id_as_byte_array[4..]; let timestamp_array = [timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]; // Little Endian From a33db6dc924d19ea2c7af0abb1a1745066352967 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 1 Sep 2022 14:54:24 +0100 Subject: [PATCH 43/90] refactor: extract functions --- src/udp/connection_id.rs | 128 +++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 32 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index f21b1afaa..e539d80e2 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -89,6 +89,7 @@ use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; +use blake3::OUT_LEN; use crypto::blowfish::Blowfish; use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; use std::convert::From; @@ -99,42 +100,16 @@ use super::time_bound_pepper::Timestamp; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { - // remote_id : [u8;4] = blake3(concat(remote_addr, remote_port)); - let remote_address_byte_array = ByteArray32::from(remote_address.ip()); - let remote_port_byte_array = ByteArray32::from(remote_address.port()); - let input2: Vec = [ - remote_address_byte_array.as_generic_byte_array(), - remote_port_byte_array.as_generic_byte_array(), - ].concat(); - let remote_id_full_hash = blake3::hash(&input2); - let mut truncated_remote_ip: [u8; 4] = [0u8; 4]; // 4 bytes = 32 bits - truncated_remote_ip.copy_from_slice(&remote_id_full_hash.as_bytes()[..4]); - - // connection_id : [u8;8] = concat(remote_id, timestamp) - let _new_connection_id: Vec = [ - truncated_remote_ip.as_slice(), - ¤t_timestamp.to_le_bytes()[..4] - ].concat(); - - let connection_as_array: [u8; 8] = _new_connection_id.try_into().expect("slice with incorrect length"); - - let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); - - let mut encrypted_connection_as_array = [0u8; 8]; + let remote_id = generate_id_for_socket_address(remote_address); - blowfish.encrypt_block(&connection_as_array, &mut encrypted_connection_as_array); + let connection_id = concat(remote_id, timestamp_to_le_bytes(current_timestamp)); - /* - println!("generate: bytes {:?}", _new_connection_id); - println!("generate: timestamp {:?}", current_timestamp); - println!("generate: timestamp bytes {:?}", ¤t_timestamp.to_le_bytes()[..4]); - println!("generate: i64 {:?}", i64::from_le_bytes(connection_as_array)); - */ + let encrypted_connection_id = encrypt(&connection_id, server_secret); - ConnectionId(i64::from_le_bytes(encrypted_connection_as_array)) + ConnectionId(byte_array_to_i64(encrypted_connection_id)) } -/// Verifies whether a connection id is valid at this time for a given remote address (ip + port) +/// Verifies whether a connection id is valid at this time for a given remote socket address (ip + port) pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, _remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); @@ -165,6 +140,81 @@ pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArr Ok(()) } +/// It generates an unique ID for a socket address (IP + port) +fn generate_id_for_socket_address(remote_address: &SocketAddr) -> [u8; 4] { + let socket_addr_as_bytes: Vec = convert_socket_address_into_bytes(remote_address); + + let hashed_socket_addr = hash(&socket_addr_as_bytes); + + let remote_id = get_first_four_bytes_from(&hashed_socket_addr); + + remote_id +} + +fn convert_socket_address_into_bytes(socket_addr: &SocketAddr) -> Vec { + let bytes: Vec = [ + convert_ip_into_bytes(socket_addr.ip()).as_slice(), + convert_port_into_bytes(socket_addr.port()).as_slice(), + ].concat(); + bytes +} + +fn convert_ip_into_bytes(ip_addr: IpAddr) -> Vec { + match ip_addr { + IpAddr::V4(ip) => ip.octets().to_vec(), + IpAddr::V6(ip) => ip.octets().to_vec(), + } +} + +fn convert_port_into_bytes(port: u16) -> [u8; 2] { + port.to_be_bytes() +} + +fn hash(bytes: &[u8]) -> [u8; OUT_LEN]{ + let hash = blake3::hash(bytes); + let bytes = hash.as_bytes().clone(); + bytes +} + +fn get_first_four_bytes_from(bytes: &[u8; OUT_LEN]) -> [u8; 4] { + let mut first_four_bytes: [u8; 4] = [0u8; 4]; // 4 bytes = 32 bits + first_four_bytes.copy_from_slice(&bytes[..4]); + first_four_bytes +} + +fn timestamp_to_le_bytes(current_timestamp: Timestamp) -> [u8; 4] { + let mut bytes: [u8; 4] = [0u8; 4]; + bytes.copy_from_slice(¤t_timestamp.to_le_bytes()[..4]); + bytes +} + +/// Contact two 4-byte arrays +fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { + let connection_id: Vec = [ + remote_id.as_slice(), + timestamp.as_slice(), + ].concat(); + + let connection_as_array: [u8; 8] = connection_id.try_into().unwrap(); + + connection_as_array +} + +fn encrypt(connection_id: &[u8; 8], server_secret: &ByteArray32) -> [u8; 8] { + // TODO: pass as an argument. It's expensive. + let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); + + let mut encrypted_connection_id = [0u8; 8]; + + blowfish.encrypt_block(connection_id, &mut encrypted_connection_id); + + encrypted_connection_id +} + +fn byte_array_to_i64(connection_id: [u8;8]) -> i64 { + i64::from_le_bytes(connection_id) +} + impl From for ByteArray32 { //// Converts an IpAddr to a ByteArray32 fn from(ip: IpAddr) -> Self { @@ -208,7 +258,7 @@ impl From for ByteArray32 { #[cfg(test)] mod tests { use super::*; - use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; + use std::{net::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr}}; fn generate_server_secret_for_testing() -> ByteArray32 { ByteArray32::new([0u8;32]) @@ -220,6 +270,20 @@ mod tests { assert_eq!(ByteArray32::from(ip_address), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1])); } + #[test] + fn ipv4_address_should_be_converted_to_a_byte_vector() { + let ip_address = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let bytes = convert_ip_into_bytes(ip_address); + assert_eq!(bytes, vec![127, 0, 0, 1]); // 4 bytes + } + + #[test] + fn ipv6_address_should_be_converted_to_a_byte_vector() { + let ip_address = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + let bytes = convert_ip_into_bytes(ip_address); + assert_eq!(bytes, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // 16 bytes + } + #[test] fn socket_port_should_be_converted_to_a_32_bytes_array() { let port = 0x1F_90u16; // 8080 From 88ca2f4156511061b72f041ccd237e1b9f6a3814 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 08:46:28 +0100 Subject: [PATCH 44/90] refactor: rename varaiblbe --- src/udp/connection_id.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index e539d80e2..1234e9ff6 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -100,9 +100,9 @@ use super::time_bound_pepper::Timestamp; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { - let remote_id = generate_id_for_socket_address(remote_address); + let client_id = generate_id_for_socket_address(remote_address); - let connection_id = concat(remote_id, timestamp_to_le_bytes(current_timestamp)); + let connection_id = concat(client_id, timestamp_to_le_bytes(current_timestamp)); let encrypted_connection_id = encrypt(&connection_id, server_secret); From 1b651d5d0ebda3ee6bd6dd57da80ca4e232f3df4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 10:48:38 +0100 Subject: [PATCH 45/90] refactor: extract function --- src/udp/connection_id.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 1234e9ff6..cee32d3a5 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -112,25 +112,12 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd /// Verifies whether a connection id is valid at this time for a given remote socket address (ip + port) pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, _remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { - let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); - - let encrypted_id_as_byte_array: [u8; 8] = connection_id.0.to_le_bytes(); - - let mut id_as_byte_array = [0u8; 8]; - - blowfish.decrypt_block(&encrypted_id_as_byte_array, &mut id_as_byte_array); + let id_as_byte_array = decrypt(&connection_id.0.to_le_bytes(), server_secret); let timestamp_bytes = &id_as_byte_array[4..]; let timestamp_array = [timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]; // Little Endian let created_at_timestamp = u64::from_le_bytes(timestamp_array); - /* - println!("verify: i64 {:?}", &connection_id.0); - println!("verify: bytes {:?}", &id_as_byte_array); - println!("verify: timestamp bytes {:?}", &id_as_byte_array[4..]); - println!("verify: timestamp {:?}", created_at_timestamp); - */ - let expire_timestamp = created_at_timestamp + 120; if expire_timestamp < current_timestamp { @@ -211,6 +198,17 @@ fn encrypt(connection_id: &[u8; 8], server_secret: &ByteArray32) -> [u8; 8] { encrypted_connection_id } +fn decrypt(encrypted_connection_id: &[u8; 8], server_secret: &ByteArray32) -> [u8; 8] { + // TODO: pass as an argument. It's expensive. + let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); + + let mut connection_id = [0u8; 8]; + + blowfish.decrypt_block(encrypted_connection_id, &mut connection_id); + + connection_id +} + fn byte_array_to_i64(connection_id: [u8;8]) -> i64 { i64::from_le_bytes(connection_id) } From 430c5dd7ce22f3a592512441fbc004d36a260747 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 13:32:24 +0100 Subject: [PATCH 46/90] refactor:extract functions --- src/udp/connection_id.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index cee32d3a5..9c8ca05fc 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -112,11 +112,13 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd /// Verifies whether a connection id is valid at this time for a given remote socket address (ip + port) pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, _remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { - let id_as_byte_array = decrypt(&connection_id.0.to_le_bytes(), server_secret); + let encrypted_connection_id = connection_id.0.to_le_bytes(); + + let decrypted_connection_id = decrypt(&encrypted_connection_id, server_secret); + + let timestamp_bytes = extract_timestamp(&decrypted_connection_id); - let timestamp_bytes = &id_as_byte_array[4..]; - let timestamp_array = [timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]; // Little Endian - let created_at_timestamp = u64::from_le_bytes(timestamp_array); + let created_at_timestamp = timestamp_from_le_bytes(timestamp_bytes); let expire_timestamp = created_at_timestamp + 120; @@ -170,11 +172,18 @@ fn get_first_four_bytes_from(bytes: &[u8; OUT_LEN]) -> [u8; 4] { } fn timestamp_to_le_bytes(current_timestamp: Timestamp) -> [u8; 4] { + // Little Endian let mut bytes: [u8; 4] = [0u8; 4]; bytes.copy_from_slice(¤t_timestamp.to_le_bytes()[..4]); bytes } +fn timestamp_from_le_bytes(timestamp_bytes: &[u8]) -> Timestamp { + // Little Endian + let timestamp = u64::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]); + timestamp +} + /// Contact two 4-byte arrays fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { let connection_id: Vec = [ @@ -187,6 +196,11 @@ fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { connection_as_array } +fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> &[u8] { + let timestamp_bytes = &decrypted_connection_id[4..]; + timestamp_bytes +} + fn encrypt(connection_id: &[u8; 8], server_secret: &ByteArray32) -> [u8; 8] { // TODO: pass as an argument. It's expensive. let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); From e065f46710226d7afdf386ce17cf9448b32bee19 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 13:33:07 +0100 Subject: [PATCH 47/90] refactor: removed unused test --- src/udp/connection_id.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 9c8ca05fc..7b0dd8476 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -302,23 +302,6 @@ mod tests { assert_eq!(ByteArray32::from(port), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1F, 0x90])); } - /*#[test] - fn it_should_be_the_same_for_one_client_during_two_minutes() { - let server_secret = generate_server_secret_for_testing(); - - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - - let now = 946684800u64; - - let connection_id = get_connection_id(&server_secret, &client_addr, now); - - let in_two_minutes = now + 120 - 1; - - let connection_id_after_two_minutes = get_connection_id(&server_secret, &client_addr, in_two_minutes); - - assert_eq!(connection_id, connection_id_after_two_minutes); - }*/ - #[test] fn it_should_be_valid_for_two_minutes_after_the_generation() { let server_secret = generate_server_secret_for_testing(); From d6e428fe9f2034b1bc6f51ee6b6484903f7d0b8b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 13:37:01 +0100 Subject: [PATCH 48/90] refactor: extract mod for Timestamp --- src/udp/connection_id.rs | 2 +- src/udp/mod.rs | 1 + src/udp/time_bound_pepper.rs | 4 +--- src/udp/timestamp.rs | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 src/udp/timestamp.rs diff --git a/src/udp/connection_id.rs b/src/udp/connection_id.rs index 7b0dd8476..1e101386b 100644 --- a/src/udp/connection_id.rs +++ b/src/udp/connection_id.rs @@ -95,7 +95,7 @@ use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; use std::convert::From; use super::byte_array_32::ByteArray32; -use super::time_bound_pepper::Timestamp; +use super::timestamp::Timestamp; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { diff --git a/src/udp/mod.rs b/src/udp/mod.rs index aa425ac12..f5a12247d 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -16,6 +16,7 @@ pub mod request; pub mod server; pub mod handlers; pub mod time_bound_pepper; +pub mod timestamp; pub type Bytes = u64; pub type Port = u16; diff --git a/src/udp/time_bound_pepper.rs b/src/udp/time_bound_pepper.rs index 4d087cc27..524d964c4 100644 --- a/src/udp/time_bound_pepper.rs +++ b/src/udp/time_bound_pepper.rs @@ -10,9 +10,7 @@ //! | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | //! | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | -use super::byte_array_32::ByteArray32; - -pub type Timestamp = u64; +use super::{byte_array_32::ByteArray32, timestamp::Timestamp}; #[derive(PartialEq, Debug)] pub struct TimeBoundPepper { diff --git a/src/udp/timestamp.rs b/src/udp/timestamp.rs new file mode 100644 index 000000000..9acc36112 --- /dev/null +++ b/src/udp/timestamp.rs @@ -0,0 +1 @@ +pub type Timestamp = u64; From 2f929197850176c180a54f904fc209ee5287447b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 13:38:09 +0100 Subject: [PATCH 49/90] refactor: remove unused mod time_bound_pepper --- src/udp/mod.rs | 1 - src/udp/time_bound_pepper.rs | 80 ------------------------------------ 2 files changed, 81 deletions(-) delete mode 100644 src/udp/time_bound_pepper.rs diff --git a/src/udp/mod.rs b/src/udp/mod.rs index f5a12247d..77014a912 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -15,7 +15,6 @@ pub mod errors; pub mod request; pub mod server; pub mod handlers; -pub mod time_bound_pepper; pub mod timestamp; pub type Bytes = u64; diff --git a/src/udp/time_bound_pepper.rs b/src/udp/time_bound_pepper.rs deleted file mode 100644 index 524d964c4..000000000 --- a/src/udp/time_bound_pepper.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! A secret hash value bound to time. -//! It changes every two minutes starting at Unix Epoch. -//! -//! | Date | Timestamp | Unix Epoch in minutes | TimeBoundPepper | -//! |-----------------------|-----------|-----------------------|-----------------| -//! | 1/1/1970, 12:00:00 AM | 0 | minute 0 | X | -//! | 1/1/1970, 12:01:00 AM | 60 | minute 1 | X | -//! | 1/1/1970, 12:02:00 AM | 120 | minute 2 | Y = X | -//! | 1/1/1970, 12:03:00 AM | 180 | minute 3 | Y = X | -//! | 1/1/1970, 12:04:00 AM | 240 | minute 4 | Z != X | -//! | 1/1/1970, 12:05:00 AM | 300 | minute 5 | Z != X | - -use super::{byte_array_32::ByteArray32, timestamp::Timestamp}; - -#[derive(PartialEq, Debug)] -pub struct TimeBoundPepper { - pepper: ByteArray32, -} - -impl TimeBoundPepper { - pub fn new(server_secret: &ByteArray32, current_timestamp: Timestamp) -> Self { - Self { - pepper: Self::generate_pepper(server_secret, current_timestamp) - } - } - - /// Time_Bound_Pepper = Hash(Server_Secret || Unix_Time_Minutes / 2) - fn generate_pepper(server_secret: &ByteArray32, current_timestamp: Timestamp) -> ByteArray32 { - - let unix_time_minutes: u64 = current_timestamp / 60; - - // Server Secret | Unix_Time_Minutes / 2 - let server_secret_or_two_minute_counter = *server_secret | ByteArray32::from(unix_time_minutes / 2); - - // Hash(Server Secret | Unix_Time_Minutes / 2) - let time_bound_pepper = blake3::hash(&server_secret_or_two_minute_counter.as_generic_byte_array()); - - ByteArray32::new(*time_bound_pepper.as_bytes()) - } - - pub fn get_pepper(&self) -> &ByteArray32 { - &self.pepper - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn generate_server_secret_for_testing() -> ByteArray32 { - ByteArray32::new([0u8;32]) - } - - #[test] - fn it_should_be_the_same_during_each_two_minute_window_since_unix_epoch() { - - let server_secret = generate_server_secret_for_testing(); - - let initial_timestamp = 0u64; - - let time_bound_pepper = TimeBoundPepper::new(&server_secret, initial_timestamp); - let time_bound_pepper_after_two_minutes = TimeBoundPepper::new(&server_secret, initial_timestamp + 120 - 1); - - assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); - } - - #[test] - fn it_should_change_after_a_two_minute_window_starting_time_windows_at_unix_epoch() { - - let server_secret = generate_server_secret_for_testing(); - - let initial_timestamp = 0u64; - - let time_bound_pepper = TimeBoundPepper::new(&server_secret, initial_timestamp); - let time_bound_pepper_after_two_minutes = TimeBoundPepper::new(&server_secret, initial_timestamp + 120 - 1); - - assert_eq!(time_bound_pepper_after_two_minutes, time_bound_pepper); - } - -} \ No newline at end of file From c4115b1f0c3f2063ba75bcf427a8c313fa602f7a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 13:47:05 +0100 Subject: [PATCH 50/90] refactor: create mod for connection id --- src/udp/{ => connection}/byte_array_32.rs | 0 src/udp/{ => connection}/connection_id.rs | 0 src/udp/connection/mod.rs | 4 ++++ src/udp/{ => connection}/timestamp.rs | 0 src/udp/handlers.rs | 5 ++--- src/udp/mod.rs | 4 +--- 6 files changed, 7 insertions(+), 6 deletions(-) rename src/udp/{ => connection}/byte_array_32.rs (100%) rename src/udp/{ => connection}/connection_id.rs (100%) create mode 100644 src/udp/connection/mod.rs rename src/udp/{ => connection}/timestamp.rs (100%) diff --git a/src/udp/byte_array_32.rs b/src/udp/connection/byte_array_32.rs similarity index 100% rename from src/udp/byte_array_32.rs rename to src/udp/connection/byte_array_32.rs diff --git a/src/udp/connection_id.rs b/src/udp/connection/connection_id.rs similarity index 100% rename from src/udp/connection_id.rs rename to src/udp/connection/connection_id.rs diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs new file mode 100644 index 000000000..eb16e02b1 --- /dev/null +++ b/src/udp/connection/mod.rs @@ -0,0 +1,4 @@ + +pub mod byte_array_32; +pub mod connection_id; +pub mod timestamp; diff --git a/src/udp/timestamp.rs b/src/udp/connection/timestamp.rs similarity index 100% rename from src/udp/timestamp.rs rename to src/udp/connection/timestamp.rs diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index d5b34a75d..0842889be 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -4,6 +4,8 @@ use std::sync::Arc; use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId}; use log::debug; +use crate::udp::connection::byte_array_32::ByteArray32; +use crate::udp::connection::connection_id::get_connection_id; use crate::{InfoHash, MAX_SCRAPE_TORRENTS}; use crate::peer::TorrentPeer; use crate::tracker::torrent::{TorrentError}; @@ -13,9 +15,6 @@ use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; use crate::protocol::clock::current_timestamp; -use super::byte_array_32::ByteArray32; -use super::connection_id::get_connection_id; - pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { match tracker.authenticate_request(info_hash, &None).await { Ok(_) => Ok(()), diff --git a/src/udp/mod.rs b/src/udp/mod.rs index 77014a912..67672aca2 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -9,13 +9,11 @@ pub use self::handlers::*; pub use self::request::*; pub use self::server::*; -pub mod byte_array_32; -pub mod connection_id; +pub mod connection; pub mod errors; pub mod request; pub mod server; pub mod handlers; -pub mod timestamp; pub type Bytes = u64; pub type Port = u16; From 5f613a65869fdf669882530a4de8aeb6b27c02df Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 13:51:07 +0100 Subject: [PATCH 51/90] refactor: store expiration time in connection id instead of creation time. This allows us to change it per client in the future, if we want to. As proposed by Cameron (@da2ce7). Co-authored-by: Cameron Garnham --- src/udp/connection/connection_id.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 1e101386b..2103f02bb 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -102,7 +102,9 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd let client_id = generate_id_for_socket_address(remote_address); - let connection_id = concat(client_id, timestamp_to_le_bytes(current_timestamp)); + let expiration_timestamp = current_timestamp + 120; + + let connection_id = concat(client_id, timestamp_to_le_bytes(expiration_timestamp)); let encrypted_connection_id = encrypt(&connection_id, server_secret); @@ -116,13 +118,11 @@ pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArr let decrypted_connection_id = decrypt(&encrypted_connection_id, server_secret); - let timestamp_bytes = extract_timestamp(&decrypted_connection_id); - - let created_at_timestamp = timestamp_from_le_bytes(timestamp_bytes); + let expiration_timestamp_bytes = extract_timestamp(&decrypted_connection_id); - let expire_timestamp = created_at_timestamp + 120; + let expiration_timestamp = timestamp_from_le_bytes(expiration_timestamp_bytes); - if expire_timestamp < current_timestamp { + if expiration_timestamp < current_timestamp { return Err(()) } From ec0915d902e7be0f458e302fb6de335e3a66bcf7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 13:55:47 +0100 Subject: [PATCH 52/90] docs: remove false test from test execution output --- src/udp/connection/connection_id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 2103f02bb..ce070db6d 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -67,7 +67,7 @@ //! Some tracker implementations use a time bound connection ID to avoid storing the connection ID //! in memory or in the DB. //! -//! ```ignore +//! ```text //! static uint64_t _genCiD (uint32_t ip, uint16_t port) //! { //! uint64_t x; From 9511174a71c5f50970831e642c79f2eff559d7f2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 2 Sep 2022 14:50:03 +0100 Subject: [PATCH 53/90] refactor: extract struct ClientId --- src/udp/connection/client_id.rs | 116 ++++++++++++++++++++++++++++ src/udp/connection/connection_id.rs | 62 +-------------- src/udp/connection/mod.rs | 1 + 3 files changed, 120 insertions(+), 59 deletions(-) create mode 100644 src/udp/connection/client_id.rs diff --git a/src/udp/connection/client_id.rs b/src/udp/connection/client_id.rs new file mode 100644 index 000000000..e57512bfb --- /dev/null +++ b/src/udp/connection/client_id.rs @@ -0,0 +1,116 @@ +//! ClientId is a unique ID for the UDP tracker client. +//! Currently implemented with a hash of the IP and port. +use std::net::{SocketAddr, IpAddr}; + +use blake3::OUT_LEN; + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct ClientId { + value: [u8; 4], +} + +impl ClientId { + /// It generates the ID from the socket address (IP + port) + pub fn from_socket_address(remote_address: &SocketAddr) -> Self { + let unique_socket_id = generate_id_for_socket_address(remote_address); + ClientId { + value: unique_socket_id + } + } + + pub fn to_bytes(&self) -> [u8; 4] { + self.value + } +} + +/// It generates an unique ID for a socket address (IP + port) +fn generate_id_for_socket_address(remote_address: &SocketAddr) -> [u8; 4] { + let socket_addr_as_bytes: Vec = convert_socket_address_into_bytes(remote_address); + + let hashed_socket_addr = hash(&socket_addr_as_bytes); + + let remote_id = get_first_four_bytes_from(&hashed_socket_addr); + + remote_id +} + +fn convert_socket_address_into_bytes(socket_addr: &SocketAddr) -> Vec { + let bytes: Vec = [ + convert_ip_into_bytes(socket_addr.ip()).as_slice(), + convert_port_into_bytes(socket_addr.port()).as_slice(), + ].concat(); + bytes +} + +fn convert_ip_into_bytes(ip_addr: IpAddr) -> Vec { + match ip_addr { + IpAddr::V4(ip) => ip.octets().to_vec(), + IpAddr::V6(ip) => ip.octets().to_vec(), + } +} + +fn convert_port_into_bytes(port: u16) -> [u8; 2] { + port.to_be_bytes() +} + +fn hash(bytes: &[u8]) -> [u8; OUT_LEN]{ + let hash = blake3::hash(bytes); + let bytes = hash.as_bytes().clone(); + bytes +} + +fn get_first_four_bytes_from(bytes: &[u8; OUT_LEN]) -> [u8; 4] { + let mut first_four_bytes: [u8; 4] = [0u8; 4]; // 4 bytes = 32 bits + first_four_bytes.copy_from_slice(&bytes[..4]); + first_four_bytes +} + + +#[cfg(test)] +mod tests { + use std::net::{IpAddr, Ipv4Addr, SocketAddr, Ipv6Addr}; + + use crate::udp::connection::client_id::convert_ip_into_bytes; + + use super::ClientId; + + #[test] + fn client_id_should_generate_a_unique_four_byte_id_from_a_socket_address() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let client_id = ClientId::from_socket_address(&client_addr); + + assert_eq!(client_id.value, [58, 221, 231, 39]); + } + + #[test] + fn client_id_should_be_unique_for_clients_with_different_ip() { + let client_1_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let client_2_with_different_socket_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080); + + assert_ne!(ClientId::from_socket_address(&client_1_socket_addr), ClientId::from_socket_address(&client_2_with_different_socket_ip)); + } + + #[test] + fn client_id_should_be_unique_for_clients_with_different_port() { + let client_1_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let client_2_with_different_socket_port = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081); + + assert_ne!(ClientId::from_socket_address(&client_1_socket_addr), ClientId::from_socket_address(&client_2_with_different_socket_port)); + } + + #[test] + fn ipv4_address_should_be_converted_to_a_byte_vector() { + let ip_address = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let bytes = convert_ip_into_bytes(ip_address); + + assert_eq!(bytes, vec![127, 0, 0, 1]); // 4 bytes + } + + #[test] + fn ipv6_address_should_be_converted_to_a_byte_vector() { + let ip_address = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + let bytes = convert_ip_into_bytes(ip_address); + + assert_eq!(bytes, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // 16 bytes + } +} \ No newline at end of file diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index ce070db6d..f6cc6f400 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -89,18 +89,18 @@ use std::{net::SocketAddr}; use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; -use blake3::OUT_LEN; use crypto::blowfish::Blowfish; use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; use std::convert::From; use super::byte_array_32::ByteArray32; +use super::client_id::ClientId; use super::timestamp::Timestamp; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { - let client_id = generate_id_for_socket_address(remote_address); + let client_id = ClientId::from_socket_address(remote_address).to_bytes(); let expiration_timestamp = current_timestamp + 120; @@ -129,48 +129,6 @@ pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArr Ok(()) } -/// It generates an unique ID for a socket address (IP + port) -fn generate_id_for_socket_address(remote_address: &SocketAddr) -> [u8; 4] { - let socket_addr_as_bytes: Vec = convert_socket_address_into_bytes(remote_address); - - let hashed_socket_addr = hash(&socket_addr_as_bytes); - - let remote_id = get_first_four_bytes_from(&hashed_socket_addr); - - remote_id -} - -fn convert_socket_address_into_bytes(socket_addr: &SocketAddr) -> Vec { - let bytes: Vec = [ - convert_ip_into_bytes(socket_addr.ip()).as_slice(), - convert_port_into_bytes(socket_addr.port()).as_slice(), - ].concat(); - bytes -} - -fn convert_ip_into_bytes(ip_addr: IpAddr) -> Vec { - match ip_addr { - IpAddr::V4(ip) => ip.octets().to_vec(), - IpAddr::V6(ip) => ip.octets().to_vec(), - } -} - -fn convert_port_into_bytes(port: u16) -> [u8; 2] { - port.to_be_bytes() -} - -fn hash(bytes: &[u8]) -> [u8; OUT_LEN]{ - let hash = blake3::hash(bytes); - let bytes = hash.as_bytes().clone(); - bytes -} - -fn get_first_four_bytes_from(bytes: &[u8; OUT_LEN]) -> [u8; 4] { - let mut first_four_bytes: [u8; 4] = [0u8; 4]; // 4 bytes = 32 bits - first_four_bytes.copy_from_slice(&bytes[..4]); - first_four_bytes -} - fn timestamp_to_le_bytes(current_timestamp: Timestamp) -> [u8; 4] { // Little Endian let mut bytes: [u8; 4] = [0u8; 4]; @@ -270,7 +228,7 @@ impl From for ByteArray32 { #[cfg(test)] mod tests { use super::*; - use std::{net::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr}}; + use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; fn generate_server_secret_for_testing() -> ByteArray32 { ByteArray32::new([0u8;32]) @@ -282,20 +240,6 @@ mod tests { assert_eq!(ByteArray32::from(ip_address), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1])); } - #[test] - fn ipv4_address_should_be_converted_to_a_byte_vector() { - let ip_address = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - let bytes = convert_ip_into_bytes(ip_address); - assert_eq!(bytes, vec![127, 0, 0, 1]); // 4 bytes - } - - #[test] - fn ipv6_address_should_be_converted_to_a_byte_vector() { - let ip_address = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); - let bytes = convert_ip_into_bytes(ip_address); - assert_eq!(bytes, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // 16 bytes - } - #[test] fn socket_port_should_be_converted_to_a_32_bytes_array() { let port = 0x1F_90u16; // 8080 diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index eb16e02b1..da793185a 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -1,4 +1,5 @@ pub mod byte_array_32; +pub mod client_id; pub mod connection_id; pub mod timestamp; From f9a136d64b4daa5ae55a4b42d6d165ca0f6df209 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 3 Sep 2022 10:08:59 +0100 Subject: [PATCH 54/90] test: add test for encryption --- src/udp/connection/connection_id.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index f6cc6f400..bb0592550 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -328,4 +328,17 @@ mod tests { assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } + + #[test] + fn it_should_encrypt_and_decrypt_a_byte_array() { + let server_secret = generate_server_secret_for_testing(); + + let text = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8]; + + let encrypted_text = encrypt(&text, &server_secret); + + let decrypted_text = decrypt(&encrypted_text, &server_secret); + + assert_eq!(decrypted_text, text); + } } \ No newline at end of file From 94c2bd4a332e5d7246b52ba97a60511495dbfe1c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 3 Sep 2022 10:10:45 +0100 Subject: [PATCH 55/90] fix: check client id in connection id verification --- src/udp/connection/client_id.rs | 9 +++++++++ src/udp/connection/connection_id.rs | 29 +++++++++++++++-------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/udp/connection/client_id.rs b/src/udp/connection/client_id.rs index e57512bfb..00b6ef43e 100644 --- a/src/udp/connection/client_id.rs +++ b/src/udp/connection/client_id.rs @@ -18,6 +18,15 @@ impl ClientId { } } + /// It generates the ID with a previously generated value + pub fn from_slice(slice: &[u8]) -> Self { + let mut client_id = ClientId { + value: [0u8; 4] + }; + client_id.value.copy_from_slice(slice); + client_id + } + pub fn to_bytes(&self) -> [u8; 4] { self.value } diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index bb0592550..87f509f69 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -112,18 +112,23 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd } /// Verifies whether a connection id is valid at this time for a given remote socket address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, _remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), ()> { +pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), &'static str> { let encrypted_connection_id = connection_id.0.to_le_bytes(); - let decrypted_connection_id = decrypt(&encrypted_connection_id, server_secret); - let expiration_timestamp_bytes = extract_timestamp(&decrypted_connection_id); + let client_id = extract_client_id(&decrypted_connection_id); + let expected_client_id = ClientId::from_socket_address(remote_address); + + if client_id != expected_client_id { + return Err("Invalid client id") + } + let expiration_timestamp_bytes = extract_timestamp(&decrypted_connection_id); let expiration_timestamp = timestamp_from_le_bytes(expiration_timestamp_bytes); if expiration_timestamp < current_timestamp { - return Err(()) + return Err("Expired connection id") } Ok(()) @@ -159,6 +164,10 @@ fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> &[u8] { timestamp_bytes } +fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { + ClientId::from_slice(&decrypted_connection_id[..4]) +} + fn encrypt(connection_id: &[u8; 8], server_secret: &ByteArray32) -> [u8; 8] { // TODO: pass as an argument. It's expensive. let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); @@ -254,11 +263,7 @@ mod tests { let connection_id = get_connection_id(&server_secret, &client_addr, now); - let ret = verify_connection_id(connection_id, &server_secret, &client_addr, now); - - println!("ret: {:?}", ret); - - assert_eq!(ret, Ok(())); + assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, now), Ok(())); let after_two_minutes = now + (2*60) - 1; @@ -273,13 +278,9 @@ mod tests { let connection_id = get_connection_id(&server_secret, &client_addr, now); - let ret = verify_connection_id(connection_id, &server_secret, &client_addr, now); - - println!("ret: {:?}", ret); - let after_more_than_two_minutes = now + (2*60) + 1; - assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, after_more_than_two_minutes), Err(())); + assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, after_more_than_two_minutes), Err("Expired connection id")); } #[test] From 169228f9d51328a54b8cac5951f196b3f32af495 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 3 Sep 2022 10:17:25 +0100 Subject: [PATCH 56/90] refactor: remove unused code --- src/udp/connection/connection_id.rs | 54 ----------------------------- 1 file changed, 54 deletions(-) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 87f509f69..686cb1871 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -87,11 +87,9 @@ //! The secret used for hashing changes every time the server starts. //! use std::{net::SocketAddr}; -use std::net::IpAddr; use aquatic_udp_protocol::ConnectionId; use crypto::blowfish::Blowfish; use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; -use std::convert::From; use super::byte_array_32::ByteArray32; use super::client_id::ClientId; @@ -194,46 +192,6 @@ fn byte_array_to_i64(connection_id: [u8;8]) -> i64 { i64::from_le_bytes(connection_id) } -impl From for ByteArray32 { - //// Converts an IpAddr to a ByteArray32 - fn from(ip: IpAddr) -> Self { - let peer_ip_as_bytes = match ip { - IpAddr::V4(ip) => [ - [0u8; 28].as_slice(), // 28 bytes - ip.octets().as_slice(), // 4 bytes - ].concat(), - IpAddr::V6(ip) => [ - [0u8; 16].as_slice(), // 16 bytes - ip.octets().as_slice(), // 16 bytes - ].concat(), - }; - - let peer_ip_address_32_bytes: [u8; 32] = match peer_ip_as_bytes.try_into() { - Ok(bytes) => bytes, - Err(_) => panic!("Expected a Vec of length 32"), - }; - - ByteArray32::new(peer_ip_address_32_bytes) - } -} - -impl From for ByteArray32 { - /// Converts a u16 to a ByteArray32 - fn from(port: u16) -> Self { - let port = [ - [0u8; 30].as_slice(), // 30 bytes - port.to_be_bytes().as_slice(), // 2 bytes - ].concat(); - - let port_32_bytes: [u8; 32] = match port.try_into() { - Ok(bytes) => bytes, - Err(_) => panic!("Expected a Vec of length 32"), - }; - - ByteArray32::new(port_32_bytes) - } -} - #[cfg(test)] mod tests { use super::*; @@ -243,18 +201,6 @@ mod tests { ByteArray32::new([0u8;32]) } - #[test] - fn ip_address_should_be_converted_to_a_32_bytes_array() { - let ip_address = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - assert_eq!(ByteArray32::from(ip_address), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1])); - } - - #[test] - fn socket_port_should_be_converted_to_a_32_bytes_array() { - let port = 0x1F_90u16; // 8080 - assert_eq!(ByteArray32::from(port), ByteArray32::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1F, 0x90])); - } - #[test] fn it_should_be_valid_for_two_minutes_after_the_generation() { let server_secret = generate_server_secret_for_testing(); From 90a033d4abed7a0b52e48740a1561f623e5ec010 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 3 Sep 2022 10:36:35 +0100 Subject: [PATCH 57/90] refactor: move functions --- src/udp/connection/connection_id.rs | 23 +++++------------------ src/udp/connection/timestamp.rs | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 686cb1871..7402f992c 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -93,7 +93,7 @@ use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; use super::byte_array_32::ByteArray32; use super::client_id::ClientId; -use super::timestamp::Timestamp; +use super::timestamp::{Timestamp, timestamp_from_le_bytes, timestamp_to_le_bytes}; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { @@ -122,8 +122,7 @@ pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArr return Err("Invalid client id") } - let expiration_timestamp_bytes = extract_timestamp(&decrypted_connection_id); - let expiration_timestamp = timestamp_from_le_bytes(expiration_timestamp_bytes); + let expiration_timestamp = extract_timestamp(&decrypted_connection_id); if expiration_timestamp < current_timestamp { return Err("Expired connection id") @@ -132,19 +131,6 @@ pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArr Ok(()) } -fn timestamp_to_le_bytes(current_timestamp: Timestamp) -> [u8; 4] { - // Little Endian - let mut bytes: [u8; 4] = [0u8; 4]; - bytes.copy_from_slice(¤t_timestamp.to_le_bytes()[..4]); - bytes -} - -fn timestamp_from_le_bytes(timestamp_bytes: &[u8]) -> Timestamp { - // Little Endian - let timestamp = u64::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]); - timestamp -} - /// Contact two 4-byte arrays fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { let connection_id: Vec = [ @@ -157,9 +143,10 @@ fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { connection_as_array } -fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> &[u8] { +fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp { let timestamp_bytes = &decrypted_connection_id[4..]; - timestamp_bytes + let expiration_timestamp = timestamp_from_le_bytes(timestamp_bytes); + expiration_timestamp } fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { diff --git a/src/udp/connection/timestamp.rs b/src/udp/connection/timestamp.rs index 9acc36112..ce224f558 100644 --- a/src/udp/connection/timestamp.rs +++ b/src/udp/connection/timestamp.rs @@ -1 +1,16 @@ +/// Connection id contains a timestamp in 4 bytes due to its 64 bits limit. pub type Timestamp = u64; + +pub fn timestamp_to_le_bytes(current_timestamp: Timestamp) -> [u8; 4] { + // Little Endian + let mut bytes: [u8; 4] = [0u8; 4]; + bytes.copy_from_slice(¤t_timestamp.to_le_bytes()[..4]); + bytes +} + +pub fn timestamp_from_le_bytes(timestamp_bytes: &[u8]) -> Timestamp { + // Little Endian + let timestamp = u64::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]); + timestamp +} + From cdf5cc60ce7a0c93074522e7252baf8db7a4c1e5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 3 Sep 2022 13:12:12 +0100 Subject: [PATCH 58/90] refactor: extract struct Timestamp32 --- src/udp/connection/connection_id.rs | 14 ++-- src/udp/connection/mod.rs | 3 +- src/udp/connection/timestamp_32.rs | 80 +++++++++++++++++++ .../{timestamp.rs => timestamp_64.rs} | 11 +-- 4 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 src/udp/connection/timestamp_32.rs rename src/udp/connection/{timestamp.rs => timestamp_64.rs} (56%) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 7402f992c..5d7233681 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -93,16 +93,17 @@ use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; use super::byte_array_32::ByteArray32; use super::client_id::ClientId; -use super::timestamp::{Timestamp, timestamp_from_le_bytes, timestamp_to_le_bytes}; +use super::timestamp_32::Timestamp32; +use super::timestamp_64::{Timestamp64, timestamp_from_le_bytes}; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. -pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> ConnectionId { +pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { let client_id = ClientId::from_socket_address(remote_address).to_bytes(); - let expiration_timestamp = current_timestamp + 120; + let expiration_timestamp = Timestamp32::from_timestamp_64(current_timestamp + 120).unwrap(); - let connection_id = concat(client_id, timestamp_to_le_bytes(expiration_timestamp)); + let connection_id = concat(client_id, expiration_timestamp.to_le_bytes()); let encrypted_connection_id = encrypt(&connection_id, server_secret); @@ -110,7 +111,7 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd } /// Verifies whether a connection id is valid at this time for a given remote socket address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp) -> Result<(), &'static str> { +pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), &'static str> { let encrypted_connection_id = connection_id.0.to_le_bytes(); let decrypted_connection_id = decrypt(&encrypted_connection_id, server_secret); @@ -122,6 +123,7 @@ pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArr return Err("Invalid client id") } + // TODO: refactor in progress. Return a Timestamp32. let expiration_timestamp = extract_timestamp(&decrypted_connection_id); if expiration_timestamp < current_timestamp { @@ -143,7 +145,7 @@ fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { connection_as_array } -fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp { +fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp64 { let timestamp_bytes = &decrypted_connection_id[4..]; let expiration_timestamp = timestamp_from_le_bytes(timestamp_bytes); expiration_timestamp diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index da793185a..53a844afb 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -2,4 +2,5 @@ pub mod byte_array_32; pub mod client_id; pub mod connection_id; -pub mod timestamp; +pub mod timestamp_32; +pub mod timestamp_64; diff --git a/src/udp/connection/timestamp_32.rs b/src/udp/connection/timestamp_32.rs new file mode 100644 index 000000000..035ae2eb0 --- /dev/null +++ b/src/udp/connection/timestamp_32.rs @@ -0,0 +1,80 @@ +//! A UNIX 32-bit timestamp. + +use std::num::TryFromIntError; + +use super::timestamp_64::Timestamp64; + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct Timestamp32 { + value: u32 +} + +impl Timestamp32 { + pub fn from_timestamp_64(timestamp64: Timestamp64) -> Result { + let timestamp32: u32 = u32::try_from(timestamp64)?; + + Ok(Self { + value: timestamp32 + }) + } + + fn from_le_bytes(timestamp_bytes: &[u8]) -> Self { + // Little Endian + let timestamp = u32::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3]]); + Self { + value: timestamp + } + } + + pub fn to_le_bytes(self: Self) -> [u8; 4] { + // Little Endian + let mut bytes: [u8; 4] = [0u8; 4]; + bytes.copy_from_slice(&self.value.to_le_bytes()[..4]); + bytes + } +} + +#[cfg(test)] +mod tests { + use crate::udp::connection::timestamp_32::Timestamp32; + + #[test] + fn it_should_be_instantiated_from_a_four_byte_array_in_little_indian() { + + let min_timestamp = Timestamp32::from_le_bytes(&[0u8, 0u8, 0u8, 0u8]); + + assert_eq!(min_timestamp, Timestamp32 { value: u32::MIN }); + + let max_timestamp = Timestamp32::from_le_bytes(&[255u8, 255u8, 255u8, 255u8]); + + assert_eq!(max_timestamp, Timestamp32 { value: u32::MAX }); + } + + #[test] + fn it_should_be_converted_to_a_four_byte_array_in_little_indian() { + + let min_timestamp = Timestamp32 { value: u32::MIN }; + + assert_eq!(min_timestamp.to_le_bytes(), [0u8, 0u8, 0u8, 0u8]); + + let max_timestamp = Timestamp32 { value: u32::MAX }; + + assert_eq!(max_timestamp.to_le_bytes(), [255u8, 255u8, 255u8, 255u8]); + } + + #[test] + fn it_should_be_instantiated_from_a_64_bit_unix_timestamp() { + + let timestamp = Timestamp32::from_timestamp_64(0u64); + + assert_eq!(timestamp.unwrap(), Timestamp32 { value: u32::MIN }); + } + + #[test] + fn it_should_fail_trying_to_instantiate_from_a_64_bit_unix_timestamp_which_overflows_u32_range() { + + let timestamp = Timestamp32::from_timestamp_64((u32::MAX as u64) + 1u64); + + assert_eq!(timestamp.is_err(), true); + } +} \ No newline at end of file diff --git a/src/udp/connection/timestamp.rs b/src/udp/connection/timestamp_64.rs similarity index 56% rename from src/udp/connection/timestamp.rs rename to src/udp/connection/timestamp_64.rs index ce224f558..ab0092aaa 100644 --- a/src/udp/connection/timestamp.rs +++ b/src/udp/connection/timestamp_64.rs @@ -1,14 +1,7 @@ /// Connection id contains a timestamp in 4 bytes due to its 64 bits limit. -pub type Timestamp = u64; +pub type Timestamp64 = u64; -pub fn timestamp_to_le_bytes(current_timestamp: Timestamp) -> [u8; 4] { - // Little Endian - let mut bytes: [u8; 4] = [0u8; 4]; - bytes.copy_from_slice(¤t_timestamp.to_le_bytes()[..4]); - bytes -} - -pub fn timestamp_from_le_bytes(timestamp_bytes: &[u8]) -> Timestamp { +pub fn timestamp_from_le_bytes(timestamp_bytes: &[u8]) -> Timestamp64 { // Little Endian let timestamp = u64::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]); timestamp From 68b65b5e83eedd1b2b0726a6c3cce383cb7fb0f1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 09:52:48 +0100 Subject: [PATCH 59/90] refactor: remove comment I think we can keep the Timestamp64 there. --- src/udp/connection/connection_id.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 5d7233681..294378304 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -123,7 +123,6 @@ pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArr return Err("Invalid client id") } - // TODO: refactor in progress. Return a Timestamp32. let expiration_timestamp = extract_timestamp(&decrypted_connection_id); if expiration_timestamp < current_timestamp { From 09fa7ccf852adbb766047db2e87fe4c80983eb63 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 10:01:20 +0100 Subject: [PATCH 60/90] refactor: rename struct This struct it's now only use to store a secret for encryption. --- src/udp/connection/byte_array_32.rs | 88 ----------------------------- src/udp/connection/connection_id.rs | 18 +++--- src/udp/connection/mod.rs | 2 +- src/udp/connection/secret.rs | 28 +++++++++ src/udp/handlers.rs | 4 +- 5 files changed, 40 insertions(+), 100 deletions(-) delete mode 100644 src/udp/connection/byte_array_32.rs create mode 100644 src/udp/connection/secret.rs diff --git a/src/udp/connection/byte_array_32.rs b/src/udp/connection/byte_array_32.rs deleted file mode 100644 index ebe5b39f8..000000000 --- a/src/udp/connection/byte_array_32.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! A struct wrapper for type `[u8; 32]`. -//! -//! It adds some convenient methods to work with arrays of bytes. -//! Specially constructors from other types. -use std::ops::BitOr; -use arraytools::ArrayTools; -use std::convert::From; - -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct ByteArray32([u8; 32]); - -impl ByteArray32 { - - /// Constructs a new `ByteArray32` from a `[u8, 32]` array. - pub fn new(bytes: [u8; 32]) -> Self { - ByteArray32(bytes) - } - - /// Returns the underlying `[u8; 32]` array. - pub fn as_generic_byte_array(self) -> [u8; 32] { - self.0 - } -} - -impl BitOr for ByteArray32 { - type Output = Self; - - // rhs is the "right-hand side" of the expression `a | b` - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0.zip_with(rhs.0, BitOr::bitor)) - } -} - -impl From for ByteArray32 { - /// Convert a u64 to a ByteArray32. - fn from(item: u64) -> Self { - let vec: Vec = [ - [0u8; 24].as_slice(), - item.to_be_bytes().as_slice(), // 8 bytes - ].concat(); - - let bytes: [u8; 32] = match vec.try_into() { - Ok(bytes) => bytes, - Err(_) => panic!("Expected a Vec of length 32"), - }; - - Self(bytes) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_should_be_instantiated_from_an_u64() { - - // Pad numbers with zeros on the left - - assert_eq!(ByteArray32::from(0x00_00_00_00_00_00_00_00_u64), ByteArray32::new([ - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].as_slice(), // 24 bytes - [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].as_slice(), // + 8 bytes (64 bits, u64) - ].concat().try_into().unwrap())); // 32 bytes - - assert_eq!(ByteArray32::from(0xFF_FF_FF_FF_FF_FF_FF_00_u64), ByteArray32::new([ - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].as_slice(), // 24 bytes - [ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00].as_slice(), // + 8 bytes (64 bits, u64) - ].concat().try_into().unwrap())); // 32 bytes // 32 bytes - } - - #[test] - fn it_should_be_converted_into_a_generic_byte_array() { - - let byte_array_32 = ByteArray32::new([0; 32]); - - assert_eq!(byte_array_32.as_generic_byte_array(), [0u8; 32]); - } - - #[test] - fn it_should_support_bitwise_or_operator() { - assert_eq!(ByteArray32::new([ 0; 32]) | ByteArray32::new([ 0; 32]), ByteArray32::new([ 0; 32])); // 0 | 0 = 0 - assert_eq!(ByteArray32::new([ 0; 32]) | ByteArray32::new([0xFF; 32]), ByteArray32::new([0xFF; 32])); // 0 | 1 = 1 - assert_eq!(ByteArray32::new([0xFF; 32]) | ByteArray32::new([ 0; 32]), ByteArray32::new([0xFF; 32])); // 1 | 0 = 1 - assert_eq!(ByteArray32::new([0xFF; 32]) | ByteArray32::new([0xFF; 32]), ByteArray32::new([0xFF; 32])); // 1 | 1 = 1 - } -} \ No newline at end of file diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 294378304..0681c075e 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -91,13 +91,13 @@ use aquatic_udp_protocol::ConnectionId; use crypto::blowfish::Blowfish; use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; -use super::byte_array_32::ByteArray32; +use super::secret::Secret; use super::client_id::ClientId; use super::timestamp_32::Timestamp32; use super::timestamp_64::{Timestamp64, timestamp_from_le_bytes}; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. -pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { +pub fn get_connection_id(server_secret: &Secret, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { let client_id = ClientId::from_socket_address(remote_address).to_bytes(); @@ -111,7 +111,7 @@ pub fn get_connection_id(server_secret: &ByteArray32, remote_address: &SocketAdd } /// Verifies whether a connection id is valid at this time for a given remote socket address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &ByteArray32, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), &'static str> { +pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &Secret, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), &'static str> { let encrypted_connection_id = connection_id.0.to_le_bytes(); let decrypted_connection_id = decrypt(&encrypted_connection_id, server_secret); @@ -154,9 +154,9 @@ fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { ClientId::from_slice(&decrypted_connection_id[..4]) } -fn encrypt(connection_id: &[u8; 8], server_secret: &ByteArray32) -> [u8; 8] { +fn encrypt(connection_id: &[u8; 8], server_secret: &Secret) -> [u8; 8] { // TODO: pass as an argument. It's expensive. - let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); + let blowfish = Blowfish::new(&server_secret.to_bytes()); let mut encrypted_connection_id = [0u8; 8]; @@ -165,9 +165,9 @@ fn encrypt(connection_id: &[u8; 8], server_secret: &ByteArray32) -> [u8; 8] { encrypted_connection_id } -fn decrypt(encrypted_connection_id: &[u8; 8], server_secret: &ByteArray32) -> [u8; 8] { +fn decrypt(encrypted_connection_id: &[u8; 8], server_secret: &Secret) -> [u8; 8] { // TODO: pass as an argument. It's expensive. - let blowfish = Blowfish::new(&server_secret.as_generic_byte_array()); + let blowfish = Blowfish::new(&server_secret.to_bytes()); let mut connection_id = [0u8; 8]; @@ -185,8 +185,8 @@ mod tests { use super::*; use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; - fn generate_server_secret_for_testing() -> ByteArray32 { - ByteArray32::new([0u8;32]) + fn generate_server_secret_for_testing() -> Secret { + Secret::new([0u8;32]) } #[test] diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index 53a844afb..8ca69184e 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -1,5 +1,5 @@ -pub mod byte_array_32; +pub mod secret; pub mod client_id; pub mod connection_id; pub mod timestamp_32; diff --git a/src/udp/connection/secret.rs b/src/udp/connection/secret.rs new file mode 100644 index 000000000..4b7d0482f --- /dev/null +++ b/src/udp/connection/secret.rs @@ -0,0 +1,28 @@ +//! A secret for encryption. + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct Secret([u8; 32]); + +impl Secret { + + pub fn new(bytes: [u8; 32]) -> Self { + Secret(bytes) + } + + pub fn to_bytes(self) -> [u8; 32] { + self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_should_be_converted_into_a_generic_byte_array() { + + let byte_array_32 = Secret::new([0; 32]); + + assert_eq!(byte_array_32.to_bytes(), [0u8; 32]); + } +} \ No newline at end of file diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 0842889be..4b56dab87 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId}; use log::debug; -use crate::udp::connection::byte_array_32::ByteArray32; +use crate::udp::connection::secret::Secret; use crate::udp::connection::connection_id::get_connection_id; use crate::{InfoHash, MAX_SCRAPE_TORRENTS}; use crate::peer::TorrentPeer; @@ -73,7 +73,7 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: } pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let server_secret = ByteArray32::new([0;32]); // todo: server_secret should be randomly generated on startup + let server_secret = Secret::new([0;32]); // todo: server_secret should be randomly generated on startup let current_timestamp = current_timestamp(); let connection_id = get_connection_id(&server_secret, &remote_addr, current_timestamp); From 0efd85851ee5bdf2bf59da3c5b017eabd45c7b9f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 10:38:58 +0100 Subject: [PATCH 61/90] refactor: Timestamp32. Use trait for convertion --- src/udp/connection/connection_id.rs | 8 ++-- src/udp/connection/timestamp_32.rs | 61 +++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 0681c075e..735b57ff8 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -94,14 +94,14 @@ use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; use super::secret::Secret; use super::client_id::ClientId; use super::timestamp_32::Timestamp32; -use super::timestamp_64::{Timestamp64, timestamp_from_le_bytes}; +use super::timestamp_64::Timestamp64; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. pub fn get_connection_id(server_secret: &Secret, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { let client_id = ClientId::from_socket_address(remote_address).to_bytes(); - let expiration_timestamp = Timestamp32::from_timestamp_64(current_timestamp + 120).unwrap(); + let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); let connection_id = concat(client_id, expiration_timestamp.to_le_bytes()); @@ -146,8 +146,8 @@ fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp64 { let timestamp_bytes = &decrypted_connection_id[4..]; - let expiration_timestamp = timestamp_from_le_bytes(timestamp_bytes); - expiration_timestamp + let timestamp = Timestamp32::from_le_bytes(timestamp_bytes); + timestamp.into() } fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { diff --git a/src/udp/connection/timestamp_32.rs b/src/udp/connection/timestamp_32.rs index 035ae2eb0..21d596f6e 100644 --- a/src/udp/connection/timestamp_32.rs +++ b/src/udp/connection/timestamp_32.rs @@ -10,15 +10,7 @@ pub struct Timestamp32 { } impl Timestamp32 { - pub fn from_timestamp_64(timestamp64: Timestamp64) -> Result { - let timestamp32: u32 = u32::try_from(timestamp64)?; - - Ok(Self { - value: timestamp32 - }) - } - - fn from_le_bytes(timestamp_bytes: &[u8]) -> Self { + pub fn from_le_bytes(timestamp_bytes: &[u8]) -> Self { // Little Endian let timestamp = u32::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3]]); Self { @@ -34,9 +26,27 @@ impl Timestamp32 { } } +impl TryFrom for Timestamp32 { + type Error = TryFromIntError; + + fn try_from(value: Timestamp64) -> Result { + let timestamp32: u32 = u32::try_from(value)?; + + Ok(Self { + value: timestamp32 + }) + } +} + +impl Into for Timestamp32 { + fn into(self) -> Timestamp64 { + u64::from(self.value) + } +} + #[cfg(test)] mod tests { - use crate::udp::connection::timestamp_32::Timestamp32; + use crate::udp::connection::{timestamp_32::Timestamp32, timestamp_64::Timestamp64}; #[test] fn it_should_be_instantiated_from_a_four_byte_array_in_little_indian() { @@ -63,18 +73,37 @@ mod tests { } #[test] - fn it_should_be_instantiated_from_a_64_bit_unix_timestamp() { + fn it_should_be_converted_from_a_64_bit_unix_timestamp() { - let timestamp = Timestamp32::from_timestamp_64(0u64); + let timestamp32: Timestamp32 = 0u64.try_into().unwrap(); - assert_eq!(timestamp.unwrap(), Timestamp32 { value: u32::MIN }); + assert_eq!(timestamp32, Timestamp32 { value: u32::MIN }); } #[test] - fn it_should_fail_trying_to_instantiate_from_a_64_bit_unix_timestamp_which_overflows_u32_range() { + fn it_should_fail_trying_to_convert_it_from_a_64_bit_unix_timestamp_which_overflows_u32_range() { + + let out_of_range_value = (u32::MAX as u64) + 1; + + let timestamp32: Result = out_of_range_value.try_into(); + + assert_eq!(timestamp32.is_err(), true); + } + + #[test] + fn it_should_be_converted_to_a_timestamp_64() { + + let min_timestamp_32 = Timestamp32 { value: u32::MIN }; + + let min_timestamp_64: Timestamp64 = min_timestamp_32.into(); + + assert_eq!(min_timestamp_64, u32::MIN as u64); + + + let max_timestamp_32 = Timestamp32 { value: u32::MAX }; - let timestamp = Timestamp32::from_timestamp_64((u32::MAX as u64) + 1u64); + let max_timestamp_64: Timestamp64 = max_timestamp_32.into(); - assert_eq!(timestamp.is_err(), true); + assert_eq!(max_timestamp_64, u32::MAX as u64); } } \ No newline at end of file From 0fee0b5e744196f5fb931835eefcfc45a2c2cd3f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 11:22:26 +0100 Subject: [PATCH 62/90] feat: new CYpher trait and Blowfish impl --- src/udp/connection/cypher.rs | 61 ++++++++++++++++++++++++++++++++++++ src/udp/connection/mod.rs | 1 + 2 files changed, 62 insertions(+) create mode 100644 src/udp/connection/cypher.rs diff --git a/src/udp/connection/cypher.rs b/src/udp/connection/cypher.rs new file mode 100644 index 000000000..a7d289033 --- /dev/null +++ b/src/udp/connection/cypher.rs @@ -0,0 +1,61 @@ +use crypto::{blowfish::Blowfish, symmetriccipher::{BlockEncryptor, BlockDecryptor}}; + +use super::secret::Secret; + +trait Cypher { + fn encrypt(&self, decrypted_bytes: &[u8; 8]) -> [u8; 8]; + + fn decrypt(&self, encrypted_bytes: &[u8; 8]) -> [u8; 8]; +} + +struct BlowfishCypher { + blowfish: Blowfish +} + +impl BlowfishCypher { + pub fn new(secret: Secret) -> Self { + let blowfish = Blowfish::new(&secret.to_bytes()); + BlowfishCypher { + blowfish + } + } +} + +impl Cypher for BlowfishCypher { + fn encrypt(&self, decrypted_bytes: &[u8; 8]) -> [u8; 8] { + let mut encrypted_bytes = [0u8; 8]; + + self.blowfish.encrypt_block(decrypted_bytes, &mut encrypted_bytes); + + encrypted_bytes + } + + fn decrypt(&self, encrypted_bytes: &[u8; 8]) -> [u8; 8] { + let mut decrypted_bytes = [0u8; 8]; + + self.blowfish.decrypt_block(encrypted_bytes, &mut decrypted_bytes); + + decrypted_bytes + } +} + +#[cfg(test)] +mod tests { + use crate::udp::connection::{secret::Secret, cypher::{BlowfishCypher, Cypher}}; + + + #[test] + fn it_should_encrypt_and_decrypt_a_byte_array() { + let secret = Secret::new([0u8;32]); + + let cypher = BlowfishCypher::new(secret); + + let text = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8]; + + let encrypted_text = cypher.encrypt(&text); + + let decrypted_text = cypher.decrypt(&encrypted_text); + + assert_eq!(decrypted_text, text); + } +} \ No newline at end of file diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index 8ca69184e..769864fcb 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -4,3 +4,4 @@ pub mod client_id; pub mod connection_id; pub mod timestamp_32; pub mod timestamp_64; +mod cypher; From 14e6e5e8f2f8f052c9fc7c9a936cfea04112a22e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 11:46:02 +0100 Subject: [PATCH 63/90] refactor: extract struct Cypher --- src/udp/connection/connection_id.rs | 93 ++++++++++------------------- src/udp/connection/cypher.rs | 4 +- src/udp/connection/mod.rs | 2 +- src/udp/handlers.rs | 25 ++++++-- 4 files changed, 55 insertions(+), 69 deletions(-) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index 735b57ff8..caab62eac 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -88,16 +88,15 @@ //! use std::{net::SocketAddr}; use aquatic_udp_protocol::ConnectionId; -use crypto::blowfish::Blowfish; -use crypto::symmetriccipher::{BlockEncryptor, BlockDecryptor}; -use super::secret::Secret; +use super::cypher::Cypher; + use super::client_id::ClientId; use super::timestamp_32::Timestamp32; use super::timestamp_64::Timestamp64; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. -pub fn get_connection_id(server_secret: &Secret, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { +pub fn get_connection_id(cypher: &dyn Cypher, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { let client_id = ClientId::from_socket_address(remote_address).to_bytes(); @@ -105,20 +104,21 @@ pub fn get_connection_id(server_secret: &Secret, remote_address: &SocketAddr, cu let connection_id = concat(client_id, expiration_timestamp.to_le_bytes()); - let encrypted_connection_id = encrypt(&connection_id, server_secret); + let encrypted_connection_id = cypher.encrypt(&connection_id); ConnectionId(byte_array_to_i64(encrypted_connection_id)) } /// Verifies whether a connection id is valid at this time for a given remote socket address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, server_secret: &Secret, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), &'static str> { +pub fn verify_connection_id(connection_id: ConnectionId, cypher: &dyn Cypher, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), &'static str> { let encrypted_connection_id = connection_id.0.to_le_bytes(); - let decrypted_connection_id = decrypt(&encrypted_connection_id, server_secret); + + let decrypted_connection_id = cypher.decrypt(&encrypted_connection_id); let client_id = extract_client_id(&decrypted_connection_id); - let expected_client_id = ClientId::from_socket_address(remote_address); + let expected_client_id = ClientId::from_socket_address(remote_address); if client_id != expected_client_id { return Err("Invalid client id") } @@ -154,126 +154,99 @@ fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { ClientId::from_slice(&decrypted_connection_id[..4]) } -fn encrypt(connection_id: &[u8; 8], server_secret: &Secret) -> [u8; 8] { - // TODO: pass as an argument. It's expensive. - let blowfish = Blowfish::new(&server_secret.to_bytes()); - - let mut encrypted_connection_id = [0u8; 8]; - - blowfish.encrypt_block(connection_id, &mut encrypted_connection_id); - - encrypted_connection_id -} - -fn decrypt(encrypted_connection_id: &[u8; 8], server_secret: &Secret) -> [u8; 8] { - // TODO: pass as an argument. It's expensive. - let blowfish = Blowfish::new(&server_secret.to_bytes()); - - let mut connection_id = [0u8; 8]; - - blowfish.decrypt_block(encrypted_connection_id, &mut connection_id); - - connection_id -} - fn byte_array_to_i64(connection_id: [u8;8]) -> i64 { i64::from_le_bytes(connection_id) } #[cfg(test)] mod tests { + use crate::udp::connection::{cypher::BlowfishCypher, secret::Secret}; + use super::*; use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; - fn generate_server_secret_for_testing() -> Secret { + fn cypher_secret_for_testing() -> Secret { Secret::new([0u8;32]) } + fn new_cypher() -> BlowfishCypher { + let secret = cypher_secret_for_testing(); + let blowfish = BlowfishCypher::new(secret); + blowfish + } + #[test] fn it_should_be_valid_for_two_minutes_after_the_generation() { - let server_secret = generate_server_secret_for_testing(); + let cypher = new_cypher(); let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; // 01-01-2000 00:00:00 - let connection_id = get_connection_id(&server_secret, &client_addr, now); + let connection_id = get_connection_id(&cypher, &client_addr, now); - assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, now), Ok(())); + assert_eq!(verify_connection_id(connection_id, &cypher, &client_addr, now), Ok(())); let after_two_minutes = now + (2*60) - 1; - assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, after_two_minutes), Ok(())); + assert_eq!(verify_connection_id(connection_id, &cypher, &client_addr, after_two_minutes), Ok(())); } #[test] fn it_should_expire_after_two_minutes_from_the_generation() { - let server_secret = generate_server_secret_for_testing(); + let cypher = new_cypher(); let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; - let connection_id = get_connection_id(&server_secret, &client_addr, now); + let connection_id = get_connection_id(&cypher, &client_addr, now); let after_more_than_two_minutes = now + (2*60) + 1; - assert_eq!(verify_connection_id(connection_id, &server_secret, &client_addr, after_more_than_two_minutes), Err("Expired connection id")); + assert_eq!(verify_connection_id(connection_id, &cypher, &client_addr, after_more_than_two_minutes), Err("Expired connection id")); } #[test] fn it_should_change_for_the_same_client_ip_and_port_after_two_minutes() { - let server_secret = generate_server_secret_for_testing(); + let cypher = new_cypher(); let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; - let connection_id = get_connection_id(&server_secret, &client_addr, now); + let connection_id = get_connection_id(&cypher, &client_addr, now); let after_two_minutes = now + 120; - let connection_id_after_two_minutes = get_connection_id(&server_secret, &client_addr, after_two_minutes); + let connection_id_after_two_minutes = get_connection_id(&cypher, &client_addr, after_two_minutes); assert_ne!(connection_id, connection_id_after_two_minutes); } #[test] fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { - let server_secret = generate_server_secret_for_testing(); + let cypher = new_cypher(); let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let now = 946684800u64; - let connection_id_for_client_1 = get_connection_id(&server_secret, &client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&server_secret, &client_2_addr, now); + let connection_id_for_client_1 = get_connection_id(&cypher, &client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&cypher, &client_2_addr, now); assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } #[test] fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { - let server_secret = generate_server_secret_for_testing(); + let cypher = new_cypher(); let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); let now = 946684800u64; - let connection_id_for_client_1 = get_connection_id(&server_secret, &client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&server_secret, &client_2_addr, now); + let connection_id_for_client_1 = get_connection_id(&cypher, &client_1_addr, now); + let connection_id_for_client_2 = get_connection_id(&cypher, &client_2_addr, now); assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } - - #[test] - fn it_should_encrypt_and_decrypt_a_byte_array() { - let server_secret = generate_server_secret_for_testing(); - - let text = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8]; - - let encrypted_text = encrypt(&text, &server_secret); - - let decrypted_text = decrypt(&encrypted_text, &server_secret); - - assert_eq!(decrypted_text, text); - } } \ No newline at end of file diff --git a/src/udp/connection/cypher.rs b/src/udp/connection/cypher.rs index a7d289033..17d65c14d 100644 --- a/src/udp/connection/cypher.rs +++ b/src/udp/connection/cypher.rs @@ -2,13 +2,13 @@ use crypto::{blowfish::Blowfish, symmetriccipher::{BlockEncryptor, BlockDecrypto use super::secret::Secret; -trait Cypher { +pub trait Cypher { fn encrypt(&self, decrypted_bytes: &[u8; 8]) -> [u8; 8]; fn decrypt(&self, encrypted_bytes: &[u8; 8]) -> [u8; 8]; } -struct BlowfishCypher { +pub struct BlowfishCypher { blowfish: Blowfish } diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index 769864fcb..e98ad7ef3 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -4,4 +4,4 @@ pub mod client_id; pub mod connection_id; pub mod timestamp_32; pub mod timestamp_64; -mod cypher; +pub mod cypher; diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 4b56dab87..32d62aa78 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -1,9 +1,10 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; -use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId}; +use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId, ConnectionId}; use log::debug; +use crate::udp::connection::cypher::BlowfishCypher; use crate::udp::connection::secret::Secret; use crate::udp::connection::connection_id::get_connection_id; use crate::{InfoHash, MAX_SCRAPE_TORRENTS}; @@ -73,11 +74,7 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: } pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let server_secret = Secret::new([0;32]); // todo: server_secret should be randomly generated on startup - let current_timestamp = current_timestamp(); - let connection_id = get_connection_id(&server_secret, &remote_addr, current_timestamp); - - debug!("connection_id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); + let connection_id = generate_new_connection_id(&remote_addr); let response = Response::from(ConnectResponse { transaction_id: request.transaction_id, @@ -93,6 +90,22 @@ pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, t Ok(response) } +pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { + // todo: server_secret should be randomly generated on startup + let server_secret = Secret::new([0;32]); + + // todo: this is expensive. It should be generated on startup + let cypher = BlowfishCypher::new(server_secret); + + let current_timestamp = current_timestamp(); + + let connection_id = get_connection_id(&cypher, remote_addr, current_timestamp); + + debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); + + connection_id +} + pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { let wrapped_announce_request = AnnounceRequestWrapper::new(announce_request.clone()); From f46ce2f6124516dc0cde1cdd2a023383d63cb4b7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 11:55:39 +0100 Subject: [PATCH 64/90] refactor: rename function I think "new" makes more explicit you are generating new one. --- src/udp/connection/connection_id.rs | 18 +++++++++--------- src/udp/handlers.rs | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs index caab62eac..215d7a16b 100644 --- a/src/udp/connection/connection_id.rs +++ b/src/udp/connection/connection_id.rs @@ -96,7 +96,7 @@ use super::timestamp_32::Timestamp32; use super::timestamp_64::Timestamp64; /// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. -pub fn get_connection_id(cypher: &dyn Cypher, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { +pub fn new_connection_id(cypher: &dyn Cypher, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { let client_id = ClientId::from_socket_address(remote_address).to_bytes(); @@ -181,7 +181,7 @@ mod tests { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; // 01-01-2000 00:00:00 - let connection_id = get_connection_id(&cypher, &client_addr, now); + let connection_id = new_connection_id(&cypher, &client_addr, now); assert_eq!(verify_connection_id(connection_id, &cypher, &client_addr, now), Ok(())); @@ -196,7 +196,7 @@ mod tests { let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let now = 946684800u64; - let connection_id = get_connection_id(&cypher, &client_addr, now); + let connection_id = new_connection_id(&cypher, &client_addr, now); let after_more_than_two_minutes = now + (2*60) + 1; @@ -211,11 +211,11 @@ mod tests { let now = 946684800u64; - let connection_id = get_connection_id(&cypher, &client_addr, now); + let connection_id = new_connection_id(&cypher, &client_addr, now); let after_two_minutes = now + 120; - let connection_id_after_two_minutes = get_connection_id(&cypher, &client_addr, after_two_minutes); + let connection_id_after_two_minutes = new_connection_id(&cypher, &client_addr, after_two_minutes); assert_ne!(connection_id, connection_id_after_two_minutes); } @@ -229,8 +229,8 @@ mod tests { let now = 946684800u64; - let connection_id_for_client_1 = get_connection_id(&cypher, &client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&cypher, &client_2_addr, now); + let connection_id_for_client_1 = new_connection_id(&cypher, &client_1_addr, now); + let connection_id_for_client_2 = new_connection_id(&cypher, &client_2_addr, now); assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } @@ -244,8 +244,8 @@ mod tests { let now = 946684800u64; - let connection_id_for_client_1 = get_connection_id(&cypher, &client_1_addr, now); - let connection_id_for_client_2 = get_connection_id(&cypher, &client_2_addr, now); + let connection_id_for_client_1 = new_connection_id(&cypher, &client_1_addr, now); + let connection_id_for_client_2 = new_connection_id(&cypher, &client_2_addr, now); assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 32d62aa78..c19965e8d 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -6,7 +6,7 @@ use log::debug; use crate::udp::connection::cypher::BlowfishCypher; use crate::udp::connection::secret::Secret; -use crate::udp::connection::connection_id::get_connection_id; +use crate::udp::connection::connection_id::new_connection_id; use crate::{InfoHash, MAX_SCRAPE_TORRENTS}; use crate::peer::TorrentPeer; use crate::tracker::torrent::{TorrentError}; @@ -99,7 +99,7 @@ pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { let current_timestamp = current_timestamp(); - let connection_id = get_connection_id(&cypher, remote_addr, current_timestamp); + let connection_id = new_connection_id(&cypher, remote_addr, current_timestamp); debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); From 02a1460f98a432b0e96e4716b7587dc93ead7a17 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 12:37:44 +0100 Subject: [PATCH 65/90] refactor: extract connection ID issuer The ConnectionIdIssuer is responsible for generating and verifing connection ids. --- src/udp/connection/connection_id.rs | 252 --------------------- src/udp/connection/connection_id_issuer.rs | 184 +++++++++++++++ src/udp/connection/mod.rs | 84 ++++++- src/udp/handlers.rs | 11 +- 4 files changed, 273 insertions(+), 258 deletions(-) delete mode 100644 src/udp/connection/connection_id.rs create mode 100644 src/udp/connection/connection_id_issuer.rs diff --git a/src/udp/connection/connection_id.rs b/src/udp/connection/connection_id.rs deleted file mode 100644 index 215d7a16b..000000000 --- a/src/udp/connection/connection_id.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! Connection ID is a value generate by the tracker and sent to the client -//! to avoid the client spoofing it's source IP address. -//! -//! Detailed info in [BEP 15](https://www.bittorrent.org/beps/bep_0015.html) -//! -//! In order for the client to connect to the tracker, it must send a connection ID -//! previously generated by the server. -//! -//! The client has to send a "connect" request: -//! -//! | Offset | Size | Name | Value | -//! |--------|----------------|----------------|---------------------------------| -//! | 0 | 64-bit integer | protocol_id | 0x41727101980 // magic constant | -//! | 8 | 32-bit integer | action | 0 // connect | -//! | 12 | 32-bit integer | transaction_id | | -//! | 16 | | | | -//! -//! And it receives a Connection ID in the response: -//! -//! | Offset | Size | Name | Value | -//! |--------|----------------|----------------|--------------| -//! | 0 | 32-bit integer | action | 0 // connect | -//! | 4 | 32-bit integer | transaction_id | | -//! | 8 | 64-bit integer | connection_id | | -//! | 16 | | | | -//! -//! The client has to send the Connection ID in all subsequent requests. -//! The tracker verifies the connection_id and ignores the request if it doesn't match. -//! -//! From the BEP 15 specification a Connection ID: -//! -//! - Should not be guessable by the client. -//! - Can be used for multiple requests. -//! - Can be used by a client until one minute after it has received it. -//! - Can be accepted by the tracker until two minutes after it has been send. -//! -//! Additionally we define the Connection ID as a value that: -//! -//! - That is unpredictable. The user should not be able to construct their own Connection ID. -//! - That is unique to the the particular connection. Locked to a IP and Port. -//! - That is time bound. It expires after certain time. -//! - That is memoryless. The server doesn't remember what ID's it gave out. -//! - That is stateless. The issuer and the verifier can work interdependently without a dynamic common state. -//! -//! # Why do we need a connection ID? -//! -//! With the Connection ID we check for two things: -//! -//! - The announcing client owns the ip and port it is announcing with. -//! - The announcing client is an online BitTorrent peer. -//! -//! It's a kind of "proof of IP ownership" and "proof of online BitTorrent peer". -//! This makes sure that the client is not a fake client. And it makes harder for attackers -//! to fill the tracker peer list with fake clients. -//! -//! The only way to send an "announce" request is actually being an active and accessible BitTorrent client. -//! -//! It also avoid clients to send requests on behave of other clients. -//! If there is a legitimate client on the network, attackers could impersonate that client, -//! since they know the IP and port of the legitimate client. -//! An attacker could send an "announce" request for a torrent that the legitimate client does not have. -//! That's a kind of DOS attack because it would make harder to find a torrent. -//! The information about what torrents have each client could be easily manipulated. -//! -//! # Implementation -//! -//! Some tracker implementations use a time bound connection ID to avoid storing the connection ID -//! in memory or in the DB. -//! -//! ```text -//! static uint64_t _genCiD (uint32_t ip, uint16_t port) -//! { -//! uint64_t x; -//! x = (time(NULL) / 3600) * port; // x will probably overload. -//! x = (ip ^ port); -//! x <<= 16; -//! x |= (~port); -//! return x; -//! } -//! ``` -//! -//! From [here](https://github.com/troydm/udpt/blob/master/src/db/driver_sqlite.cpp#L410-L418). -//! -//! We use the same approach but using a hash with a server secret in order to keep the connection ID -//! not guessable. We also use the client IP and port to make it unique for each client. -//! -//! The secret used for hashing changes every time the server starts. -//! -use std::{net::SocketAddr}; -use aquatic_udp_protocol::ConnectionId; - -use super::cypher::Cypher; - -use super::client_id::ClientId; -use super::timestamp_32::Timestamp32; -use super::timestamp_64::Timestamp64; - -/// It generates a connection id needed for the BitTorrent UDP Tracker Protocol. -pub fn new_connection_id(cypher: &dyn Cypher, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { - - let client_id = ClientId::from_socket_address(remote_address).to_bytes(); - - let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); - - let connection_id = concat(client_id, expiration_timestamp.to_le_bytes()); - - let encrypted_connection_id = cypher.encrypt(&connection_id); - - ConnectionId(byte_array_to_i64(encrypted_connection_id)) -} - -/// Verifies whether a connection id is valid at this time for a given remote socket address (ip + port) -pub fn verify_connection_id(connection_id: ConnectionId, cypher: &dyn Cypher, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), &'static str> { - - let encrypted_connection_id = connection_id.0.to_le_bytes(); - - let decrypted_connection_id = cypher.decrypt(&encrypted_connection_id); - - let client_id = extract_client_id(&decrypted_connection_id); - - let expected_client_id = ClientId::from_socket_address(remote_address); - if client_id != expected_client_id { - return Err("Invalid client id") - } - - let expiration_timestamp = extract_timestamp(&decrypted_connection_id); - - if expiration_timestamp < current_timestamp { - return Err("Expired connection id") - } - - Ok(()) -} - -/// Contact two 4-byte arrays -fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { - let connection_id: Vec = [ - remote_id.as_slice(), - timestamp.as_slice(), - ].concat(); - - let connection_as_array: [u8; 8] = connection_id.try_into().unwrap(); - - connection_as_array -} - -fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp64 { - let timestamp_bytes = &decrypted_connection_id[4..]; - let timestamp = Timestamp32::from_le_bytes(timestamp_bytes); - timestamp.into() -} - -fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { - ClientId::from_slice(&decrypted_connection_id[..4]) -} - -fn byte_array_to_i64(connection_id: [u8;8]) -> i64 { - i64::from_le_bytes(connection_id) -} - -#[cfg(test)] -mod tests { - use crate::udp::connection::{cypher::BlowfishCypher, secret::Secret}; - - use super::*; - use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; - - fn cypher_secret_for_testing() -> Secret { - Secret::new([0u8;32]) - } - - fn new_cypher() -> BlowfishCypher { - let secret = cypher_secret_for_testing(); - let blowfish = BlowfishCypher::new(secret); - blowfish - } - - #[test] - fn it_should_be_valid_for_two_minutes_after_the_generation() { - let cypher = new_cypher(); - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let now = 946684800u64; // 01-01-2000 00:00:00 - - let connection_id = new_connection_id(&cypher, &client_addr, now); - - assert_eq!(verify_connection_id(connection_id, &cypher, &client_addr, now), Ok(())); - - let after_two_minutes = now + (2*60) - 1; - - assert_eq!(verify_connection_id(connection_id, &cypher, &client_addr, after_two_minutes), Ok(())); - } - - #[test] - fn it_should_expire_after_two_minutes_from_the_generation() { - let cypher = new_cypher(); - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let now = 946684800u64; - - let connection_id = new_connection_id(&cypher, &client_addr, now); - - let after_more_than_two_minutes = now + (2*60) + 1; - - assert_eq!(verify_connection_id(connection_id, &cypher, &client_addr, after_more_than_two_minutes), Err("Expired connection id")); - } - - #[test] - fn it_should_change_for_the_same_client_ip_and_port_after_two_minutes() { - let cypher = new_cypher(); - - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - - let now = 946684800u64; - - let connection_id = new_connection_id(&cypher, &client_addr, now); - - let after_two_minutes = now + 120; - - let connection_id_after_two_minutes = new_connection_id(&cypher, &client_addr, after_two_minutes); - - assert_ne!(connection_id, connection_id_after_two_minutes); - } - - #[test] - fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { - let cypher = new_cypher(); - - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - - let now = 946684800u64; - - let connection_id_for_client_1 = new_connection_id(&cypher, &client_1_addr, now); - let connection_id_for_client_2 = new_connection_id(&cypher, &client_2_addr, now); - - assert_ne!(connection_id_for_client_1, connection_id_for_client_2); - } - - #[test] - fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { - let cypher = new_cypher(); - - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); - - let now = 946684800u64; - - let connection_id_for_client_1 = new_connection_id(&cypher, &client_1_addr, now); - let connection_id_for_client_2 = new_connection_id(&cypher, &client_2_addr, now); - - assert_ne!(connection_id_for_client_1, connection_id_for_client_2); - } -} \ No newline at end of file diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs new file mode 100644 index 000000000..aefd0d2f9 --- /dev/null +++ b/src/udp/connection/connection_id_issuer.rs @@ -0,0 +1,184 @@ +use std::net::SocketAddr; + +use aquatic_udp_protocol::ConnectionId; + +use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::ClientId, timestamp_32::Timestamp32}; + +pub trait ConnectionIdIssuer { + type Error; + + fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId; + + fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error>; +} + +/// An implementation of a ConnectionIdIssuer by encrypting the connection id +pub struct EncryptedConnectionIdIssuer { + cypher: BlowfishCypher +} + +impl EncryptedConnectionIdIssuer { + + pub fn new(secret: Secret) -> Self { + let cypher = BlowfishCypher::new(secret); + Self { + cypher + } + } +} + +impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { + type Error = &'static str; + + fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { + let client_id = ClientId::from_socket_address(remote_address).to_bytes(); + + let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); + + let connection_id = concat(client_id, expiration_timestamp.to_le_bytes()); + + let encrypted_connection_id = self.cypher.encrypt(&connection_id); + + ConnectionId(byte_array_to_i64(encrypted_connection_id)) + } + + fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { + let encrypted_connection_id = connection_id.0.to_le_bytes(); + + let decrypted_connection_id = self.cypher.decrypt(&encrypted_connection_id); + + let client_id = extract_client_id(&decrypted_connection_id); + + let expected_client_id = ClientId::from_socket_address(remote_address); + if client_id != expected_client_id { + return Err("Invalid client id") + } + + let expiration_timestamp = extract_timestamp(&decrypted_connection_id); + + if expiration_timestamp < current_timestamp { + return Err("Expired connection id") + } + + Ok(()) + } +} + +/// Contact two 4-byte arrays +fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { + let connection_id: Vec = [ + remote_id.as_slice(), + timestamp.as_slice(), + ].concat(); + + let connection_as_array: [u8; 8] = connection_id.try_into().unwrap(); + + connection_as_array +} + +fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp64 { + let timestamp_bytes = &decrypted_connection_id[4..]; + let timestamp = Timestamp32::from_le_bytes(timestamp_bytes); + timestamp.into() +} + +fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { + ClientId::from_slice(&decrypted_connection_id[..4]) +} + +fn byte_array_to_i64(connection_id: [u8;8]) -> i64 { + i64::from_le_bytes(connection_id) +} + +#[cfg(test)] +mod tests { + use crate::udp::connection::{secret::Secret, connection_id_issuer::{EncryptedConnectionIdIssuer, ConnectionIdIssuer}}; + + use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; + + fn cypher_secret_for_testing() -> Secret { + Secret::new([0u8;32]) + } + + fn new_issuer() -> EncryptedConnectionIdIssuer { + let issuer = EncryptedConnectionIdIssuer::new(cypher_secret_for_testing()); + issuer + } + + #[test] + fn it_should_be_valid_for_two_minutes_after_the_generation() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let now = 946684800u64; // 01-01-2000 00:00:00 + + let issuer = new_issuer(); + + let connection_id = issuer.new_connection_id(&client_addr, now); + + assert_eq!(issuer.verify_connection_id(connection_id, &client_addr, now), Ok(())); + + let after_two_minutes = now + (2*60) - 1; + + assert_eq!(issuer.verify_connection_id(connection_id, &client_addr, after_two_minutes), Ok(())); + } + + #[test] + fn it_should_expire_after_two_minutes_from_the_generation() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let now = 946684800u64; + + let issuer = new_issuer(); + + let connection_id = issuer.new_connection_id(&client_addr, now); + + let after_more_than_two_minutes = now + (2*60) + 1; + + assert_eq!(issuer.verify_connection_id(connection_id, &client_addr, after_more_than_two_minutes), Err("Expired connection id")); + } + + #[test] + fn it_should_change_for_the_same_client_ip_and_port_after_two_minutes() { + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + + let now = 946684800u64; + + let issuer = new_issuer(); + + let connection_id = issuer.new_connection_id( &client_addr, now); + + let after_two_minutes = now + 120; + + let connection_id_after_two_minutes = issuer.new_connection_id(&client_addr, after_two_minutes); + + assert_ne!(connection_id, connection_id_after_two_minutes); + } + + #[test] + fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + + let now = 946684800u64; + + let issuer = new_issuer(); + + let connection_id_for_client_1 = issuer.new_connection_id(&client_1_addr, now); + let connection_id_for_client_2 = issuer.new_connection_id(&client_2_addr, now); + + assert_ne!(connection_id_for_client_1, connection_id_for_client_2); + } + + #[test] + fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); + + let now = 946684800u64; + + let issuer = new_issuer(); + + let connection_id_for_client_1 = issuer.new_connection_id(&client_1_addr, now); + let connection_id_for_client_2 = issuer.new_connection_id(&client_2_addr, now); + + assert_ne!(connection_id_for_client_1, connection_id_for_client_2); + } +} \ No newline at end of file diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index e98ad7ef3..1e1ec62ee 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -1,7 +1,89 @@ +//! Connection ID is a value generated by the tracker and sent to the client +//! to avoid the client spoofing it's source IP address. +//! +//! Detailed info in [BEP 15](https://www.bittorrent.org/beps/bep_0015.html) +//! +//! In order for the client to connect to the tracker, it must send a connection ID +//! previously generated by the server. +//! +//! The client has to send a "connect" request: +//! +//! | Offset | Size | Name | Value | +//! |--------|----------------|----------------|---------------------------------| +//! | 0 | 64-bit integer | protocol_id | 0x41727101980 // magic constant | +//! | 8 | 32-bit integer | action | 0 // connect | +//! | 12 | 32-bit integer | transaction_id | | +//! | 16 | | | | +//! +//! And it receives a Connection ID in the response: +//! +//! | Offset | Size | Name | Value | +//! |--------|----------------|----------------|--------------| +//! | 0 | 32-bit integer | action | 0 // connect | +//! | 4 | 32-bit integer | transaction_id | | +//! | 8 | 64-bit integer | connection_id | | +//! | 16 | | | | +//! +//! The client has to send the Connection ID in all subsequent requests. +//! The tracker verifies the connection_id and ignores the request if it doesn't match. +//! +//! From the BEP 15 specification a Connection ID: +//! +//! - Should not be guessable by the client. +//! - Can be used for multiple requests. +//! - Can be used by a client until one minute after it has received it. +//! - Can be accepted by the tracker until two minutes after it has been send. +//! +//! Additionally we define the Connection ID as a value that: +//! +//! - That is unpredictable. The user should not be able to construct their own Connection ID. +//! - That is unique to the the particular connection. Locked to a IP and Port. +//! - That is time bound. It expires after certain time. +//! - That is memoryless. The server doesn't remember what ID's it gave out. +//! - That is stateless. The issuer and the verifier can work interdependently without a dynamic common state. +//! +//! # Why do we need a connection ID? +//! +//! With the Connection ID we check for two things: +//! +//! - The announcing client owns the ip and port it is announcing with. +//! - The announcing client is an online BitTorrent peer. +//! +//! It's a kind of "proof of IP ownership" and "proof of online BitTorrent peer". +//! This makes sure that the client is not a fake client. And it makes harder for attackers +//! to fill the tracker peer list with fake clients. +//! +//! The only way to send an "announce" request is actually being an active and accessible BitTorrent client. +//! +//! It also avoid clients to send requests on behave of other clients. +//! If there is a legitimate client on the network, attackers could impersonate that client, +//! since they know the IP and port of the legitimate client. +//! An attacker could send an "announce" request for a torrent that the legitimate client does not have. +//! That's a kind of DOS attack because it would make harder to find a torrent. +//! The information about what torrents have each client could be easily manipulated. +//! +//! # Example Implementation +//! +//! Some tracker implementations use a time bound connection ID to avoid storing the connection ID +//! in memory or in the DB. +//! +//! ```text +//! static uint64_t _genCiD (uint32_t ip, uint16_t port) +//! { +//! uint64_t x; +//! x = (time(NULL) / 3600) * port; // x will probably overload. +//! x = (ip ^ port); +//! x <<= 16; +//! x |= (~port); +//! return x; +//! } +//! ``` +//! +//! From [here](https://github.com/troydm/udpt/blob/master/src/db/driver_sqlite.cpp#L410-L418). pub mod secret; pub mod client_id; -pub mod connection_id; pub mod timestamp_32; pub mod timestamp_64; pub mod cypher; +pub mod connection_id_issuer; diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index c19965e8d..4b522a3c2 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -4,9 +4,9 @@ use std::sync::Arc; use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId, ConnectionId}; use log::debug; -use crate::udp::connection::cypher::BlowfishCypher; +use crate::udp::connection::connection_id_issuer::{EncryptedConnectionIdIssuer, ConnectionIdIssuer}; + use crate::udp::connection::secret::Secret; -use crate::udp::connection::connection_id::new_connection_id; use crate::{InfoHash, MAX_SCRAPE_TORRENTS}; use crate::peer::TorrentPeer; use crate::tracker::torrent::{TorrentError}; @@ -94,12 +94,13 @@ pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { // todo: server_secret should be randomly generated on startup let server_secret = Secret::new([0;32]); - // todo: this is expensive. It should be generated on startup - let cypher = BlowfishCypher::new(server_secret); + // todo: this is expensive because of the blowfish cypher instantiation. + // It should be generated on startup. + let issuer = EncryptedConnectionIdIssuer::new(server_secret); let current_timestamp = current_timestamp(); - let connection_id = new_connection_id(&cypher, remote_addr, current_timestamp); + let connection_id = issuer.new_connection_id(remote_addr, current_timestamp); debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); From 35819b4c6d8e88e7cab33ad6b98f3271c2a7055a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 13:39:02 +0100 Subject: [PATCH 66/90] refactor: extract struct ConnectionIdData --- src/udp/connection/connection_id_data.rs | 91 ++++++++++++++++++++++ src/udp/connection/connection_id_issuer.rs | 46 +++-------- src/udp/connection/mod.rs | 1 + src/udp/connection/timestamp_32.rs | 18 ++++- 4 files changed, 120 insertions(+), 36 deletions(-) create mode 100644 src/udp/connection/connection_id_data.rs diff --git a/src/udp/connection/connection_id_data.rs b/src/udp/connection/connection_id_data.rs new file mode 100644 index 000000000..17178fd22 --- /dev/null +++ b/src/udp/connection/connection_id_data.rs @@ -0,0 +1,91 @@ +use super::{client_id::ClientId, timestamp_32::Timestamp32}; + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct ConnectionIdData { + pub client_id: ClientId, + pub expiration_timestamp: Timestamp32 +} + +impl ConnectionIdData { + pub fn from_bytes(bytes: &[u8; 8]) -> Self { + let client_id = Self::extract_client_id(bytes); + let expiration_timestamp = Self::extract_timestamp(bytes); + Self { + client_id, + expiration_timestamp + } + } + + pub fn to_bytes(&self) -> [u8; 8] { + let connection_id: Vec = [ + self.client_id.to_bytes().as_slice(), + self.expiration_timestamp.to_le_bytes().as_slice(), + ].concat(); + + let connection_as_array: [u8; 8] = connection_id.try_into().unwrap(); + + connection_as_array + } + + fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp32 { + let timestamp_bytes = &decrypted_connection_id[4..]; + let timestamp = Timestamp32::from_le_bytes(timestamp_bytes); + timestamp + } + + fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { + ClientId::from_slice(&decrypted_connection_id[..4]) + } +} + +#[cfg(test)] +mod tests { + use crate::udp::connection::{connection_id_data::ConnectionIdData, client_id::ClientId}; + + + #[test] + fn it_contains_a_client_id() { + + let connection_id = ConnectionIdData { + client_id: ClientId::from_slice(&[0u8; 4]), + expiration_timestamp: 0u32.into(), + }; + + assert_eq!(connection_id.client_id, ClientId::from_slice(&[0u8; 4])); + } + + #[test] + fn it_contains_an_expiration_timestamp() { + + let connection_id = ConnectionIdData { + client_id: ClientId::from_slice(&[0u8; 4]), + expiration_timestamp: 0u32.into(), + }; + + assert_eq!(connection_id.expiration_timestamp, 0u32.into()); + } + + #[test] + fn it_should_be_converted_to_a_byte_array() { + + let connection_id = ConnectionIdData { + client_id: ClientId::from_slice(&[0u8; 4]), + expiration_timestamp: (u32::MAX).into(), + }; + + assert_eq!(connection_id.to_bytes(), [0, 0, 0, 0, 255, 255, 255, 255]); + } + + #[test] + fn it_should_be_instantiated_from_a_byte_array() { + + let connection_id = ConnectionIdData::from_bytes(&[0, 0, 0, 0, 255, 255, 255, 255]); + + let expected_connection_id = ConnectionIdData { + client_id: ClientId::from_slice(&[0, 0, 0, 0]), + expiration_timestamp: (u32::MAX).into(), + }; + + assert_eq!(connection_id, expected_connection_id); + } +} diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index aefd0d2f9..21553641e 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use aquatic_udp_protocol::ConnectionId; -use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::ClientId, timestamp_32::Timestamp32}; +use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::ClientId, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData}; pub trait ConnectionIdIssuer { type Error; @@ -31,32 +31,36 @@ impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { type Error = &'static str; fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { - let client_id = ClientId::from_socket_address(remote_address).to_bytes(); + let client_id = ClientId::from_socket_address(remote_address); let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); - let connection_id = concat(client_id, expiration_timestamp.to_le_bytes()); + let connection_id = ConnectionIdData { + client_id, + expiration_timestamp + }.to_bytes(); let encrypted_connection_id = self.cypher.encrypt(&connection_id); - ConnectionId(byte_array_to_i64(encrypted_connection_id)) + ConnectionId(i64::from_le_bytes(encrypted_connection_id)) } fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { let encrypted_connection_id = connection_id.0.to_le_bytes(); let decrypted_connection_id = self.cypher.decrypt(&encrypted_connection_id); + + let connection_id_data = ConnectionIdData::from_bytes(&decrypted_connection_id); - let client_id = extract_client_id(&decrypted_connection_id); + let client_id = connection_id_data.client_id; let expected_client_id = ClientId::from_socket_address(remote_address); if client_id != expected_client_id { return Err("Invalid client id") } - let expiration_timestamp = extract_timestamp(&decrypted_connection_id); - - if expiration_timestamp < current_timestamp { + let expiration_timestamp = Timestamp64::try_from(connection_id_data.expiration_timestamp).unwrap(); + if expiration_timestamp < current_timestamp { return Err("Expired connection id") } @@ -64,32 +68,6 @@ impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { } } -/// Contact two 4-byte arrays -fn concat(remote_id: [u8; 4], timestamp: [u8; 4]) -> [u8; 8] { - let connection_id: Vec = [ - remote_id.as_slice(), - timestamp.as_slice(), - ].concat(); - - let connection_as_array: [u8; 8] = connection_id.try_into().unwrap(); - - connection_as_array -} - -fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp64 { - let timestamp_bytes = &decrypted_connection_id[4..]; - let timestamp = Timestamp32::from_le_bytes(timestamp_bytes); - timestamp.into() -} - -fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { - ClientId::from_slice(&decrypted_connection_id[..4]) -} - -fn byte_array_to_i64(connection_id: [u8;8]) -> i64 { - i64::from_le_bytes(connection_id) -} - #[cfg(test)] mod tests { use crate::udp::connection::{secret::Secret, connection_id_issuer::{EncryptedConnectionIdIssuer, ConnectionIdIssuer}}; diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index 1e1ec62ee..c84083c65 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -87,3 +87,4 @@ pub mod timestamp_32; pub mod timestamp_64; pub mod cypher; pub mod connection_id_issuer; +pub mod connection_id_data; diff --git a/src/udp/connection/timestamp_32.rs b/src/udp/connection/timestamp_32.rs index 21d596f6e..939330ff1 100644 --- a/src/udp/connection/timestamp_32.rs +++ b/src/udp/connection/timestamp_32.rs @@ -6,7 +6,7 @@ use super::timestamp_64::Timestamp64; #[derive(PartialEq, Debug, Copy, Clone)] pub struct Timestamp32 { - value: u32 + pub value: u32 } impl Timestamp32 { @@ -26,6 +26,12 @@ impl Timestamp32 { } } +impl From for Timestamp32 { + fn from(value: u32) -> Self { + Self { value } + } +} + impl TryFrom for Timestamp32 { type Error = TryFromIntError; @@ -90,6 +96,14 @@ mod tests { assert_eq!(timestamp32.is_err(), true); } + #[test] + fn it_should_be_converted_from_a_u32() { + + let timestamp32: Timestamp32 = u32::MIN.into(); + + assert_eq!(timestamp32, Timestamp32 { value: u32::MIN }); + } + #[test] fn it_should_be_converted_to_a_timestamp_64() { @@ -105,5 +119,5 @@ mod tests { let max_timestamp_64: Timestamp64 = max_timestamp_32.into(); assert_eq!(max_timestamp_64, u32::MAX as u64); - } + } } \ No newline at end of file From 8eaefb16cdc76fdc501e49f2b2d436d3d7df6f37 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 14:24:36 +0100 Subject: [PATCH 67/90] refactor: extract struct EncryptedConnectionIdData --- src/udp/connection/connection_id_issuer.rs | 26 ++++++---- .../encrypted_connection_id_data.rs | 52 +++++++++++++++++++ src/udp/connection/mod.rs | 1 + 3 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 src/udp/connection/encrypted_connection_id_data.rs diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index 21553641e..4c32f125e 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use aquatic_udp_protocol::ConnectionId; -use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::ClientId, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData}; +use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::ClientId, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData, encrypted_connection_id_data::EncryptedConnectionIdData}; pub trait ConnectionIdIssuer { type Error; @@ -35,30 +35,34 @@ impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); - let connection_id = ConnectionIdData { + let connection_id_data = ConnectionIdData { client_id, expiration_timestamp - }.to_bytes(); + }; - let encrypted_connection_id = self.cypher.encrypt(&connection_id); + let decrypted_raw_data = connection_id_data.to_bytes(); + + let encrypted_raw_data = self.cypher.encrypt(&decrypted_raw_data); + + let encrypted_connection_id_data = EncryptedConnectionIdData::from_encrypted_bytes(&encrypted_raw_data); - ConnectionId(i64::from_le_bytes(encrypted_connection_id)) + ConnectionId(encrypted_connection_id_data.into()) } fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { - let encrypted_connection_id = connection_id.0.to_le_bytes(); + let encrypted_raw_data: EncryptedConnectionIdData = connection_id.0.into(); - let decrypted_connection_id = self.cypher.decrypt(&encrypted_connection_id); + let decrypted_raw_data = self.cypher.decrypt(&encrypted_raw_data.bytes); - let connection_id_data = ConnectionIdData::from_bytes(&decrypted_connection_id); - - let client_id = connection_id_data.client_id; + let connection_id_data = ConnectionIdData::from_bytes(&decrypted_raw_data); + // guard that current client matches connection id client let expected_client_id = ClientId::from_socket_address(remote_address); - if client_id != expected_client_id { + if connection_id_data.client_id != expected_client_id { return Err("Invalid client id") } + // guard that connection id has not expired let expiration_timestamp = Timestamp64::try_from(connection_id_data.expiration_timestamp).unwrap(); if expiration_timestamp < current_timestamp { return Err("Expired connection id") diff --git a/src/udp/connection/encrypted_connection_id_data.rs b/src/udp/connection/encrypted_connection_id_data.rs new file mode 100644 index 000000000..f32702883 --- /dev/null +++ b/src/udp/connection/encrypted_connection_id_data.rs @@ -0,0 +1,52 @@ +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct EncryptedConnectionIdData { + pub bytes: [u8; 8] +} + +impl EncryptedConnectionIdData { + pub fn from_encrypted_bytes(encrypted_bytes: &[u8; 8]) -> Self { + Self { bytes: encrypted_bytes.clone() } + } +} + +impl Into for EncryptedConnectionIdData { + fn into(self) -> i64 { + i64::from_le_bytes(self.bytes) + } +} + +impl From for EncryptedConnectionIdData { + fn from(value: i64) -> Self { + Self { bytes: value.to_le_bytes() } + } +} + +#[cfg(test)] +mod tests { + use crate::udp::connection::encrypted_connection_id_data::EncryptedConnectionIdData; + + + #[test] + fn it_should_be_generated_from_the_encrypted_connection_id_data() { + + let encrypted_data = EncryptedConnectionIdData::from_encrypted_bytes(&[0u8; 8]); + + assert_eq!(encrypted_data, EncryptedConnectionIdData { bytes: [0u8; 8]}); + } + + #[test] + fn it_should_be_converted_into_a_i64() { + + let encrypted_data: i64 = EncryptedConnectionIdData::from_encrypted_bytes(&[0u8; 8]).into(); + + assert_eq!(encrypted_data, 0i64); + } + + #[test] + fn it_should_be_converted_from_a_i64() { + + let encrypted_data: EncryptedConnectionIdData = 0i64.into(); + + assert_eq!(encrypted_data, EncryptedConnectionIdData { bytes: [0u8; 8]}); + } +} \ No newline at end of file diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index c84083c65..d27a8268a 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -88,3 +88,4 @@ pub mod timestamp_64; pub mod cypher; pub mod connection_id_issuer; pub mod connection_id_data; +pub mod encrypted_connection_id_data; From 65c1b3b90939337de64243227430b62ce193d76e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 14:30:55 +0100 Subject: [PATCH 68/90] fix: remove unneeded unwrap --- src/udp/connection/connection_id_issuer.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index 4c32f125e..637fb0df8 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -63,8 +63,7 @@ impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { } // guard that connection id has not expired - let expiration_timestamp = Timestamp64::try_from(connection_id_data.expiration_timestamp).unwrap(); - if expiration_timestamp < current_timestamp { + if current_timestamp > connection_id_data.expiration_timestamp.into() { return Err("Expired connection id") } From dcf6d8239be9b6bc1ada723a9c3bbdc58f4316d7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 16:04:57 +0100 Subject: [PATCH 69/90] refactor: extract guard clauses for connection id verification --- src/udp/connection/connection_id_issuer.rs | 59 ++++++++++++++++------ 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index 637fb0df8..d885f0b18 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -18,13 +18,37 @@ pub struct EncryptedConnectionIdIssuer { } impl EncryptedConnectionIdIssuer { - pub fn new(secret: Secret) -> Self { let cypher = BlowfishCypher::new(secret); Self { cypher } } + + fn unpack_connection_id_data(&self, connection_id: &ConnectionId) -> ConnectionIdData { + let encrypted_raw_data: EncryptedConnectionIdData = connection_id.0.into(); + + let decrypted_raw_data = self.cypher.decrypt(&encrypted_raw_data.bytes); + + let connection_id_data = ConnectionIdData::from_bytes(&decrypted_raw_data); + + connection_id_data + } + + fn guard_that_current_client_id_matches_client_id_in_connection_id(&self, connection_id_data: &ConnectionIdData, remote_address: &SocketAddr) -> Result<(), &'static str> { + let current_client_id = ClientId::from_socket_address(remote_address); + if connection_id_data.client_id != current_client_id { + return Err("Invalid client id: current client id does not match client in connection id"); + } + Ok(()) + } + + fn guard_that_connection_id_has_not_expired(&self, connection_id_data: &ConnectionIdData, current_timestamp:Timestamp64) -> Result<(), &'static str> { + if current_timestamp > connection_id_data.expiration_timestamp.into() { + return Err("Expired connection id") + } + Ok(()) + } } impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { @@ -50,22 +74,11 @@ impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { } fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { - let encrypted_raw_data: EncryptedConnectionIdData = connection_id.0.into(); - - let decrypted_raw_data = self.cypher.decrypt(&encrypted_raw_data.bytes); - - let connection_id_data = ConnectionIdData::from_bytes(&decrypted_raw_data); + let connection_id_data = self.unpack_connection_id_data(&connection_id); - // guard that current client matches connection id client - let expected_client_id = ClientId::from_socket_address(remote_address); - if connection_id_data.client_id != expected_client_id { - return Err("Invalid client id") - } + self.guard_that_current_client_id_matches_client_id_in_connection_id(&connection_id_data, &remote_address)?; - // guard that connection id has not expired - if current_timestamp > connection_id_data.expiration_timestamp.into() { - return Err("Expired connection id") - } + self.guard_that_connection_id_has_not_expired(&connection_id_data, current_timestamp)?; Ok(()) } @@ -162,4 +175,20 @@ mod tests { assert_ne!(connection_id_for_client_1, connection_id_for_client_2); } + + #[test] + fn it_should_fails_verifying_a_connection_id_when_the_client_id_in_the_connection_id_data_does_not_the_current_client_id() { + let issuer = new_issuer(); + + // Generate connection id for a given client + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let now = 946684800u64; + let connection_id = issuer.new_connection_id(&client_addr, now); + + // Verify the connection id with a different client address + let different_client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0002); + let result = issuer.verify_connection_id(connection_id, &different_client_addr, now); + + assert!(result.is_err()); + } } \ No newline at end of file From cd32b862e83bfb59760af1f8c825a9a7e597d0d5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 5 Sep 2022 16:28:20 +0100 Subject: [PATCH 70/90] refactor: extract methods for EncryptedConnectionIdIssuer --- src/udp/connection/connection_id_data.rs | 1 + src/udp/connection/connection_id_issuer.rs | 75 ++++++++++++++-------- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/udp/connection/connection_id_data.rs b/src/udp/connection/connection_id_data.rs index 17178fd22..7164e0cad 100644 --- a/src/udp/connection/connection_id_data.rs +++ b/src/udp/connection/connection_id_data.rs @@ -1,5 +1,6 @@ use super::{client_id::ClientId, timestamp_32::Timestamp32}; +/// The data stored inside the connection id #[derive(PartialEq, Debug, Copy, Clone)] pub struct ConnectionIdData { pub client_id: ClientId, diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index d885f0b18..d1cf8501a 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -12,11 +12,37 @@ pub trait ConnectionIdIssuer { fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error>; } -/// An implementation of a ConnectionIdIssuer by encrypting the connection id +/// An implementation of a ConnectionIdIssuer which encrypts the connection id pub struct EncryptedConnectionIdIssuer { cypher: BlowfishCypher } +impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { + type Error = &'static str; + + fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { + + let connection_id_data = self.generate_connection_id_data(&remote_address, current_timestamp); + + let encrypted_connection_id_data = self.encrypt_connection_id_data(&connection_id_data); + + ConnectionId(encrypted_connection_id_data.into()) + } + + fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { + + let encrypted_connection_id_data: EncryptedConnectionIdData = self.unpack_encrypted_connection_id_data(connection_id); + + let connection_id_data = self.decrypt_connection_id_data(&encrypted_connection_id_data); + + self.guard_that_current_client_id_matches_client_id_in_connection_id(&connection_id_data, &remote_address)?; + + self.guard_that_connection_id_has_not_expired(&connection_id_data, current_timestamp)?; + + Ok(()) + } +} + impl EncryptedConnectionIdIssuer { pub fn new(secret: Secret) -> Self { let cypher = BlowfishCypher::new(secret); @@ -25,10 +51,26 @@ impl EncryptedConnectionIdIssuer { } } - fn unpack_connection_id_data(&self, connection_id: &ConnectionId) -> ConnectionIdData { + fn generate_connection_id_data(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionIdData { + let client_id = ClientId::from_socket_address(remote_address); + + let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); + + let connection_id_data = ConnectionIdData { + client_id, + expiration_timestamp + }; + + connection_id_data + } + + fn unpack_encrypted_connection_id_data(&self, connection_id: ConnectionId) -> EncryptedConnectionIdData { let encrypted_raw_data: EncryptedConnectionIdData = connection_id.0.into(); + encrypted_raw_data + } - let decrypted_raw_data = self.cypher.decrypt(&encrypted_raw_data.bytes); + fn decrypt_connection_id_data(&self, encrypted_connection_id_data: &EncryptedConnectionIdData) -> ConnectionIdData { + let decrypted_raw_data = self.cypher.decrypt(&encrypted_connection_id_data.bytes); let connection_id_data = ConnectionIdData::from_bytes(&decrypted_raw_data); @@ -49,38 +91,15 @@ impl EncryptedConnectionIdIssuer { } Ok(()) } -} -impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { - type Error = &'static str; - - fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { - let client_id = ClientId::from_socket_address(remote_address); - - let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); - - let connection_id_data = ConnectionIdData { - client_id, - expiration_timestamp - }; - + fn encrypt_connection_id_data(&self, connection_id_data: &ConnectionIdData) -> EncryptedConnectionIdData { let decrypted_raw_data = connection_id_data.to_bytes(); let encrypted_raw_data = self.cypher.encrypt(&decrypted_raw_data); let encrypted_connection_id_data = EncryptedConnectionIdData::from_encrypted_bytes(&encrypted_raw_data); - - ConnectionId(encrypted_connection_id_data.into()) - } - fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { - let connection_id_data = self.unpack_connection_id_data(&connection_id); - - self.guard_that_current_client_id_matches_client_id_in_connection_id(&connection_id_data, &remote_address)?; - - self.guard_that_connection_id_has_not_expired(&connection_id_data, current_timestamp)?; - - Ok(()) + encrypted_connection_id_data } } From 4f0188115351d633ce04dccdcf080d747f33563b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Sep 2022 15:52:31 +0100 Subject: [PATCH 71/90] refactor: rename methods --- src/udp/connection/connection_id_issuer.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index d1cf8501a..795f41e37 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -26,12 +26,12 @@ impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { let encrypted_connection_id_data = self.encrypt_connection_id_data(&connection_id_data); - ConnectionId(encrypted_connection_id_data.into()) + self.pack_connection_id(encrypted_connection_id_data) } fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { - let encrypted_connection_id_data: EncryptedConnectionIdData = self.unpack_encrypted_connection_id_data(connection_id); + let encrypted_connection_id_data: EncryptedConnectionIdData = self.unpack_connection_id(connection_id); let connection_id_data = self.decrypt_connection_id_data(&encrypted_connection_id_data); @@ -64,10 +64,14 @@ impl EncryptedConnectionIdIssuer { connection_id_data } - fn unpack_encrypted_connection_id_data(&self, connection_id: ConnectionId) -> EncryptedConnectionIdData { + fn pack_connection_id(&self, encrypted_connection_id_data: EncryptedConnectionIdData) -> ConnectionId { + ConnectionId(encrypted_connection_id_data.into()) + } + + fn unpack_connection_id(&self, connection_id: ConnectionId) -> EncryptedConnectionIdData { let encrypted_raw_data: EncryptedConnectionIdData = connection_id.0.into(); encrypted_raw_data - } + } fn decrypt_connection_id_data(&self, encrypted_connection_id_data: &EncryptedConnectionIdData) -> ConnectionIdData { let decrypted_raw_data = self.cypher.decrypt(&encrypted_connection_id_data.bytes); From b659d017a80c1f685271b91afc3f561e33b7d845 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Sep 2022 16:55:20 +0100 Subject: [PATCH 72/90] refactor: ClientId. Extract trait for hasher This allow us to change the hasher. Proposed by Cameron here: https://github.com/torrust/torrust-tracker/pull/60#issuecomment-1237523274 Co-authored-by: Cameron Garnham --- src/udp/connection/client_id.rs | 153 +++++++++------------ src/udp/connection/connection_id_data.rs | 14 +- src/udp/connection/connection_id_issuer.rs | 6 +- 3 files changed, 78 insertions(+), 95 deletions(-) diff --git a/src/udp/connection/client_id.rs b/src/udp/connection/client_id.rs index 00b6ef43e..fda0cab74 100644 --- a/src/udp/connection/client_id.rs +++ b/src/udp/connection/client_id.rs @@ -1,125 +1,108 @@ //! ClientId is a unique ID for the UDP tracker client. -//! Currently implemented with a hash of the IP and port. -use std::net::{SocketAddr, IpAddr}; +//! Currently implemented with a hash of the socket, i.e the IP and port. +use std::collections::hash_map::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; +use std::net::{SocketAddr}; -use blake3::OUT_LEN; - -#[derive(PartialEq, Debug, Copy, Clone)] +#[derive(PartialEq, Debug, Clone)] pub struct ClientId { value: [u8; 4], } -impl ClientId { - /// It generates the ID from the socket address (IP + port) - pub fn from_socket_address(remote_address: &SocketAddr) -> Self { - let unique_socket_id = generate_id_for_socket_address(remote_address); +pub struct Default; +pub struct Blake; + +pub trait Make { + fn new(remote_socket_address: &SocketAddr) -> Self; +} + +impl Make for ClientId { + fn new(socket: &SocketAddr) -> Self { + let mut hasher = DefaultHasher::new(); + socket.hash(&mut hasher); + + let mut truncated_hash: [u8; 4] = [0u8; 4]; + truncated_hash.copy_from_slice(&hasher.finish().to_le_bytes()[..4]); + ClientId { - value: unique_socket_id + value: truncated_hash, } } +} + +impl Make for ClientId { + fn new(_remote_socket_address: &SocketAddr) -> Self { + unimplemented!("Todo using Blake Hasher"); + } +} +impl ClientId { /// It generates the ID with a previously generated value - pub fn from_slice(slice: &[u8]) -> Self { + pub fn from_bytes(bytes: &[u8]) -> Self { let mut client_id = ClientId { value: [0u8; 4] }; - client_id.value.copy_from_slice(slice); + client_id.value.copy_from_slice(bytes); client_id } pub fn to_bytes(&self) -> [u8; 4] { - self.value + let bytes: [u8; 4] = self.value.clone().try_into().unwrap(); + bytes } } -/// It generates an unique ID for a socket address (IP + port) -fn generate_id_for_socket_address(remote_address: &SocketAddr) -> [u8; 4] { - let socket_addr_as_bytes: Vec = convert_socket_address_into_bytes(remote_address); - - let hashed_socket_addr = hash(&socket_addr_as_bytes); - - let remote_id = get_first_four_bytes_from(&hashed_socket_addr); - - remote_id -} - -fn convert_socket_address_into_bytes(socket_addr: &SocketAddr) -> Vec { - let bytes: Vec = [ - convert_ip_into_bytes(socket_addr.ip()).as_slice(), - convert_port_into_bytes(socket_addr.port()).as_slice(), - ].concat(); - bytes -} - -fn convert_ip_into_bytes(ip_addr: IpAddr) -> Vec { - match ip_addr { - IpAddr::V4(ip) => ip.octets().to_vec(), - IpAddr::V6(ip) => ip.octets().to_vec(), - } -} - -fn convert_port_into_bytes(port: u16) -> [u8; 2] { - port.to_be_bytes() -} - -fn hash(bytes: &[u8]) -> [u8; OUT_LEN]{ - let hash = blake3::hash(bytes); - let bytes = hash.as_bytes().clone(); - bytes -} - -fn get_first_four_bytes_from(bytes: &[u8; OUT_LEN]) -> [u8; 4] { - let mut first_four_bytes: [u8; 4] = [0u8; 4]; // 4 bytes = 32 bits - first_four_bytes.copy_from_slice(&bytes[..4]); - first_four_bytes -} - - #[cfg(test)] mod tests { - use std::net::{IpAddr, Ipv4Addr, SocketAddr, Ipv6Addr}; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use crate::udp::connection::client_id::convert_ip_into_bytes; - - use super::ClientId; + use super::{ClientId, Default, Make}; #[test] - fn client_id_should_generate_a_unique_four_byte_id_from_a_socket_address() { - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let client_id = ClientId::from_socket_address(&client_addr); + fn it_should_be_a_hash_of_the_socket() { + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let id: ClientId = Make::::new(&socket); - assert_eq!(client_id.value, [58, 221, 231, 39]); + assert_eq!(id.value, [213, 195, 130, 185]); } #[test] - fn client_id_should_be_unique_for_clients_with_different_ip() { - let client_1_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let client_2_with_different_socket_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080); - - assert_ne!(ClientId::from_socket_address(&client_1_socket_addr), ClientId::from_socket_address(&client_2_with_different_socket_ip)); - } + fn id_should_be_converted_to_a_byte_array() { + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let id: ClientId = Make::::new(&socket); - #[test] - fn client_id_should_be_unique_for_clients_with_different_port() { - let client_1_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let client_2_with_different_socket_port = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081); - - assert_ne!(ClientId::from_socket_address(&client_1_socket_addr), ClientId::from_socket_address(&client_2_with_different_socket_port)); + assert_eq!(id.to_bytes(), [213, 195, 130, 185]); } #[test] - fn ipv4_address_should_be_converted_to_a_byte_vector() { - let ip_address = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - let bytes = convert_ip_into_bytes(ip_address); + fn id_should_be_instantiate_from_a_previously_generated_value() { + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let id: ClientId = Make::::new(&socket); + let bytes = id.to_bytes(); - assert_eq!(bytes, vec![127, 0, 0, 1]); // 4 bytes + assert_eq!(ClientId::from_bytes(&bytes), id); } #[test] - fn ipv6_address_should_be_converted_to_a_byte_vector() { - let ip_address = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); - let bytes = convert_ip_into_bytes(ip_address); + fn it_should_be_unique_with_different_socket_ips() { + let socket_1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let socket_2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080); + + assert_ne!( + >::new(&socket_1), + >::new(&socket_2) + ); + } - assert_eq!(bytes, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // 16 bytes - } + #[test] + fn it_should_be_unique_with_different_socket_ports() { + let socket_1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let socket_2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081); + + assert_ne!( + >::new(&socket_1), + >::new(&socket_2) + ); + } } \ No newline at end of file diff --git a/src/udp/connection/connection_id_data.rs b/src/udp/connection/connection_id_data.rs index 7164e0cad..3acb80e3c 100644 --- a/src/udp/connection/connection_id_data.rs +++ b/src/udp/connection/connection_id_data.rs @@ -1,7 +1,7 @@ use super::{client_id::ClientId, timestamp_32::Timestamp32}; /// The data stored inside the connection id -#[derive(PartialEq, Debug, Copy, Clone)] +#[derive(PartialEq, Debug, Clone)] pub struct ConnectionIdData { pub client_id: ClientId, pub expiration_timestamp: Timestamp32 @@ -35,7 +35,7 @@ impl ConnectionIdData { } fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { - ClientId::from_slice(&decrypted_connection_id[..4]) + ClientId::from_bytes(&decrypted_connection_id[..4]) } } @@ -48,18 +48,18 @@ mod tests { fn it_contains_a_client_id() { let connection_id = ConnectionIdData { - client_id: ClientId::from_slice(&[0u8; 4]), + client_id: ClientId::from_bytes(&[0u8; 4]), expiration_timestamp: 0u32.into(), }; - assert_eq!(connection_id.client_id, ClientId::from_slice(&[0u8; 4])); + assert_eq!(connection_id.client_id, ClientId::from_bytes(&[0u8; 4])); } #[test] fn it_contains_an_expiration_timestamp() { let connection_id = ConnectionIdData { - client_id: ClientId::from_slice(&[0u8; 4]), + client_id: ClientId::from_bytes(&[0u8; 4]), expiration_timestamp: 0u32.into(), }; @@ -70,7 +70,7 @@ mod tests { fn it_should_be_converted_to_a_byte_array() { let connection_id = ConnectionIdData { - client_id: ClientId::from_slice(&[0u8; 4]), + client_id: ClientId::from_bytes(&[0u8; 4]), expiration_timestamp: (u32::MAX).into(), }; @@ -83,7 +83,7 @@ mod tests { let connection_id = ConnectionIdData::from_bytes(&[0, 0, 0, 0, 255, 255, 255, 255]); let expected_connection_id = ConnectionIdData { - client_id: ClientId::from_slice(&[0, 0, 0, 0]), + client_id: ClientId::from_bytes(&[0, 0, 0, 0]), expiration_timestamp: (u32::MAX).into(), }; diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index 795f41e37..e91d91585 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use aquatic_udp_protocol::ConnectionId; -use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::ClientId, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData, encrypted_connection_id_data::EncryptedConnectionIdData}; +use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::{Make, Default}, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData, encrypted_connection_id_data::EncryptedConnectionIdData}; pub trait ConnectionIdIssuer { type Error; @@ -52,7 +52,7 @@ impl EncryptedConnectionIdIssuer { } fn generate_connection_id_data(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionIdData { - let client_id = ClientId::from_socket_address(remote_address); + let client_id = Make::::new(remote_address); let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); @@ -82,7 +82,7 @@ impl EncryptedConnectionIdIssuer { } fn guard_that_current_client_id_matches_client_id_in_connection_id(&self, connection_id_data: &ConnectionIdData, remote_address: &SocketAddr) -> Result<(), &'static str> { - let current_client_id = ClientId::from_socket_address(remote_address); + let current_client_id = Make::::new(remote_address); if connection_id_data.client_id != current_client_id { return Err("Invalid client id: current client id does not match client in connection id"); } From 1b98b155bcd4d7794e1a31fcb63ea21fd8421c5e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Sep 2022 17:49:48 +0100 Subject: [PATCH 73/90] refactor: remove unused hasher implemention for client id We can add it later if we use it. --- src/udp/connection/client_id.rs | 46 ++++++++++------------ src/udp/connection/connection_id_issuer.rs | 8 ++-- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/udp/connection/client_id.rs b/src/udp/connection/client_id.rs index fda0cab74..7320aa1e6 100644 --- a/src/udp/connection/client_id.rs +++ b/src/udp/connection/client_id.rs @@ -1,29 +1,31 @@ //! ClientId is a unique ID for the UDP tracker client. //! Currently implemented with a hash of the socket, i.e the IP and port. -use std::collections::hash_map::DefaultHasher; use std::hash::Hash; use std::hash::Hasher; -use std::net::{SocketAddr}; +use std::net::SocketAddr; #[derive(PartialEq, Debug, Clone)] pub struct ClientId { value: [u8; 4], } -pub struct Default; -pub struct Blake; +pub trait Make { + fn new(socket: &SocketAddr) -> Self; -pub trait Make { - fn new(remote_socket_address: &SocketAddr) -> Self; + fn hash(socket: &SocketAddr) -> [u8;8] { + let mut hasher = T::default(); + socket.hash(&mut hasher); + + hasher.finish().to_le_bytes() + } } -impl Make for ClientId { +impl Make for ClientId { fn new(socket: &SocketAddr) -> Self { - let mut hasher = DefaultHasher::new(); - socket.hash(&mut hasher); + let hash = >::hash(socket); let mut truncated_hash: [u8; 4] = [0u8; 4]; - truncated_hash.copy_from_slice(&hasher.finish().to_le_bytes()[..4]); + truncated_hash.copy_from_slice(&hash[..4]); ClientId { value: truncated_hash, @@ -31,12 +33,6 @@ impl Make for ClientId { } } -impl Make for ClientId { - fn new(_remote_socket_address: &SocketAddr) -> Self { - unimplemented!("Todo using Blake Hasher"); - } -} - impl ClientId { /// It generates the ID with a previously generated value pub fn from_bytes(bytes: &[u8]) -> Self { @@ -55,14 +51,14 @@ impl ClientId { #[cfg(test)] mod tests { - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::{net::{IpAddr, Ipv4Addr, SocketAddr}, collections::hash_map::DefaultHasher}; - use super::{ClientId, Default, Make}; + use super::{ClientId, Make}; #[test] fn it_should_be_a_hash_of_the_socket() { let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let id: ClientId = Make::::new(&socket); + let id: ClientId = Make::::new(&socket); assert_eq!(id.value, [213, 195, 130, 185]); } @@ -70,7 +66,7 @@ mod tests { #[test] fn id_should_be_converted_to_a_byte_array() { let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let id: ClientId = Make::::new(&socket); + let id: ClientId = Make::::new(&socket); assert_eq!(id.to_bytes(), [213, 195, 130, 185]); } @@ -78,7 +74,7 @@ mod tests { #[test] fn id_should_be_instantiate_from_a_previously_generated_value() { let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - let id: ClientId = Make::::new(&socket); + let id: ClientId = Make::::new(&socket); let bytes = id.to_bytes(); assert_eq!(ClientId::from_bytes(&bytes), id); @@ -90,8 +86,8 @@ mod tests { let socket_2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080); assert_ne!( - >::new(&socket_1), - >::new(&socket_2) + >::new(&socket_1), + >::new(&socket_2) ); } @@ -101,8 +97,8 @@ mod tests { let socket_2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081); assert_ne!( - >::new(&socket_1), - >::new(&socket_2) + >::new(&socket_1), + >::new(&socket_2) ); } } \ No newline at end of file diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index e91d91585..0f4f4cba4 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -1,8 +1,8 @@ -use std::net::SocketAddr; +use std::{net::SocketAddr, collections::hash_map::DefaultHasher}; use aquatic_udp_protocol::ConnectionId; -use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::{Make, Default}, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData, encrypted_connection_id_data::EncryptedConnectionIdData}; +use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::Make, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData, encrypted_connection_id_data::EncryptedConnectionIdData}; pub trait ConnectionIdIssuer { type Error; @@ -52,7 +52,7 @@ impl EncryptedConnectionIdIssuer { } fn generate_connection_id_data(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionIdData { - let client_id = Make::::new(remote_address); + let client_id = Make::::new(remote_address); let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); @@ -82,7 +82,7 @@ impl EncryptedConnectionIdIssuer { } fn guard_that_current_client_id_matches_client_id_in_connection_id(&self, connection_id_data: &ConnectionIdData, remote_address: &SocketAddr) -> Result<(), &'static str> { - let current_client_id = Make::::new(remote_address); + let current_client_id = Make::::new(remote_address); if connection_id_data.client_id != current_client_id { return Err("Invalid client id: current client id does not match client in connection id"); } From 460d62ac8a9f19ee46dffcf33fcacbf868d57df7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Sep 2022 18:15:25 +0100 Subject: [PATCH 74/90] feat: verify connection id in announce and scrape requests --- src/udp/connection/connection_id_issuer.rs | 14 ++--- src/udp/errors.rs | 3 ++ src/udp/handlers.rs | 63 ++++++++++++++++------ 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index 0f4f4cba4..10a31adbc 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -9,7 +9,7 @@ pub trait ConnectionIdIssuer { fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId; - fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error>; + fn verify_connection_id(&self, connection_id: &ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error>; } /// An implementation of a ConnectionIdIssuer which encrypts the connection id @@ -29,7 +29,7 @@ impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { self.pack_connection_id(encrypted_connection_id_data) } - fn verify_connection_id(&self, connection_id: ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { + fn verify_connection_id(&self, connection_id: &ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { let encrypted_connection_id_data: EncryptedConnectionIdData = self.unpack_connection_id(connection_id); @@ -68,7 +68,7 @@ impl EncryptedConnectionIdIssuer { ConnectionId(encrypted_connection_id_data.into()) } - fn unpack_connection_id(&self, connection_id: ConnectionId) -> EncryptedConnectionIdData { + fn unpack_connection_id(&self, connection_id: &ConnectionId) -> EncryptedConnectionIdData { let encrypted_raw_data: EncryptedConnectionIdData = connection_id.0.into(); encrypted_raw_data } @@ -131,11 +131,11 @@ mod tests { let connection_id = issuer.new_connection_id(&client_addr, now); - assert_eq!(issuer.verify_connection_id(connection_id, &client_addr, now), Ok(())); + assert_eq!(issuer.verify_connection_id(&connection_id, &client_addr, now), Ok(())); let after_two_minutes = now + (2*60) - 1; - assert_eq!(issuer.verify_connection_id(connection_id, &client_addr, after_two_minutes), Ok(())); + assert_eq!(issuer.verify_connection_id(&connection_id, &client_addr, after_two_minutes), Ok(())); } #[test] @@ -149,7 +149,7 @@ mod tests { let after_more_than_two_minutes = now + (2*60) + 1; - assert_eq!(issuer.verify_connection_id(connection_id, &client_addr, after_more_than_two_minutes), Err("Expired connection id")); + assert_eq!(issuer.verify_connection_id(&connection_id, &client_addr, after_more_than_two_minutes), Err("Expired connection id")); } #[test] @@ -210,7 +210,7 @@ mod tests { // Verify the connection id with a different client address let different_client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0002); - let result = issuer.verify_connection_id(connection_id, &different_client_addr, now); + let result = issuer.verify_connection_id(&connection_id, &different_client_addr, now); assert!(result.is_err()); } diff --git a/src/udp/errors.rs b/src/udp/errors.rs index fb29e969e..057026f57 100644 --- a/src/udp/errors.rs +++ b/src/udp/errors.rs @@ -28,4 +28,7 @@ pub enum ServerError { #[error("bad request")] BadRequest, + + #[error("invalid connection id")] + InvalidConnectionId, } diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 4b522a3c2..8b5431525 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -90,24 +90,13 @@ pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, t Ok(response) } -pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { - // todo: server_secret should be randomly generated on startup - let server_secret = Secret::new([0;32]); - - // todo: this is expensive because of the blowfish cypher instantiation. - // It should be generated on startup. - let issuer = EncryptedConnectionIdIssuer::new(server_secret); - - let current_timestamp = current_timestamp(); - - let connection_id = issuer.new_connection_id(remote_addr, current_timestamp); - - debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); - - connection_id -} - pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { + // Verify connection id + let result = verify_connection_id(&announce_request.connection_id, &remote_addr); + if result.is_err() { + return Err(ServerError::InvalidConnectionId); + } + let wrapped_announce_request = AnnounceRequestWrapper::new(announce_request.clone()); authenticate(&wrapped_announce_request.info_hash, tracker.clone()).await?; @@ -168,6 +157,12 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc // todo: refactor this, db lock can be a lot shorter pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { + // Verify connection id + let result = verify_connection_id(&request.connection_id, &remote_addr); + if result.is_err() { + return Err(ServerError::InvalidConnectionId); + } + let db = tracker.get_torrents().await; let mut torrent_stats: Vec = Vec::new(); @@ -223,3 +218,37 @@ fn handle_error(e: ServerError, transaction_id: TransactionId) -> Response { let message = e.to_string(); Response::from(ErrorResponse { transaction_id, message: message.into() }) } + +pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { + // todo: server_secret should be randomly generated on startup + let server_secret = Secret::new([0;32]); + + // todo: this is expensive because of the blowfish cypher instantiation. + // It should be generated on startup. + let issuer = EncryptedConnectionIdIssuer::new(server_secret); + + let current_timestamp = current_timestamp(); + + let connection_id = issuer.new_connection_id(remote_addr, current_timestamp); + + debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); + + connection_id +} + +pub fn verify_connection_id(connection_id: &ConnectionId, remote_addr: &SocketAddr) -> Result<(), &'static str> { + // todo: server_secret should be randomly generated on startup + let server_secret = Secret::new([0;32]); + + // todo: this is expensive because of the blowfish cypher instantiation. + // It should be generated on startup. + let issuer = EncryptedConnectionIdIssuer::new(server_secret); + + let current_timestamp = current_timestamp(); + + let result = issuer.verify_connection_id(connection_id, remote_addr, current_timestamp); + + debug!("verify connection id: {:?}, current timestamp: {:?}, result: {:?}", connection_id, current_timestamp, result); + + result +} From 55c040dc4236e283540cf28466aec054dcd20709 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Sep 2022 13:16:16 +0100 Subject: [PATCH 75/90] refactor: simplify trait ConnectionIdIssuer Since we are not handling the error and other implementations could have different errors I prefer to keep it simply. The original idea was allowing the implementaions to raise concrete errors messages. If we need to handle concrete error in the future for a given implementaion we could add a new method "validate_connection_id". --- src/udp/connection/connection_id_issuer.rs | 39 ++++++++-------------- src/udp/handlers.rs | 18 +++++----- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index 10a31adbc..1e3014c97 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -9,7 +9,7 @@ pub trait ConnectionIdIssuer { fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId; - fn verify_connection_id(&self, connection_id: &ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error>; + fn is_connection_id_valid(&self, connection_id: &ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> bool; } /// An implementation of a ConnectionIdIssuer which encrypts the connection id @@ -29,17 +29,22 @@ impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { self.pack_connection_id(encrypted_connection_id_data) } - fn verify_connection_id(&self, connection_id: &ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> Result<(), Self::Error> { + fn is_connection_id_valid(&self, connection_id: &ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> bool { let encrypted_connection_id_data: EncryptedConnectionIdData = self.unpack_connection_id(connection_id); let connection_id_data = self.decrypt_connection_id_data(&encrypted_connection_id_data); - self.guard_that_current_client_id_matches_client_id_in_connection_id(&connection_id_data, &remote_address)?; + let current_client_id = Make::::new(remote_address); + if connection_id_data.client_id != current_client_id { + return false; + } - self.guard_that_connection_id_has_not_expired(&connection_id_data, current_timestamp)?; + if current_timestamp > connection_id_data.expiration_timestamp.into() { + return false; + } - Ok(()) + true } } @@ -81,21 +86,6 @@ impl EncryptedConnectionIdIssuer { connection_id_data } - fn guard_that_current_client_id_matches_client_id_in_connection_id(&self, connection_id_data: &ConnectionIdData, remote_address: &SocketAddr) -> Result<(), &'static str> { - let current_client_id = Make::::new(remote_address); - if connection_id_data.client_id != current_client_id { - return Err("Invalid client id: current client id does not match client in connection id"); - } - Ok(()) - } - - fn guard_that_connection_id_has_not_expired(&self, connection_id_data: &ConnectionIdData, current_timestamp:Timestamp64) -> Result<(), &'static str> { - if current_timestamp > connection_id_data.expiration_timestamp.into() { - return Err("Expired connection id") - } - Ok(()) - } - fn encrypt_connection_id_data(&self, connection_id_data: &ConnectionIdData) -> EncryptedConnectionIdData { let decrypted_raw_data = connection_id_data.to_bytes(); @@ -131,11 +121,11 @@ mod tests { let connection_id = issuer.new_connection_id(&client_addr, now); - assert_eq!(issuer.verify_connection_id(&connection_id, &client_addr, now), Ok(())); + assert!(issuer.is_connection_id_valid(&connection_id, &client_addr, now)); let after_two_minutes = now + (2*60) - 1; - assert_eq!(issuer.verify_connection_id(&connection_id, &client_addr, after_two_minutes), Ok(())); + assert!(issuer.is_connection_id_valid(&connection_id, &client_addr, after_two_minutes)); } #[test] @@ -149,7 +139,7 @@ mod tests { let after_more_than_two_minutes = now + (2*60) + 1; - assert_eq!(issuer.verify_connection_id(&connection_id, &client_addr, after_more_than_two_minutes), Err("Expired connection id")); + assert_eq!(issuer.is_connection_id_valid(&connection_id, &client_addr, after_more_than_two_minutes), false); } #[test] @@ -210,8 +200,7 @@ mod tests { // Verify the connection id with a different client address let different_client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0002); - let result = issuer.verify_connection_id(&connection_id, &different_client_addr, now); - assert!(result.is_err()); + assert_eq!(issuer.is_connection_id_valid(&connection_id, &different_client_addr, now), false); } } \ No newline at end of file diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 8b5431525..7c4eedbc1 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -91,9 +91,8 @@ pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, t } pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { - // Verify connection id - let result = verify_connection_id(&announce_request.connection_id, &remote_addr); - if result.is_err() { + let valid = is_connection_id_valid(&announce_request.connection_id, &remote_addr); + if !valid { return Err(ServerError::InvalidConnectionId); } @@ -157,9 +156,8 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc // todo: refactor this, db lock can be a lot shorter pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { - // Verify connection id - let result = verify_connection_id(&request.connection_id, &remote_addr); - if result.is_err() { + let valid = is_connection_id_valid(&request.connection_id, &remote_addr); + if !valid { return Err(ServerError::InvalidConnectionId); } @@ -236,7 +234,7 @@ pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { connection_id } -pub fn verify_connection_id(connection_id: &ConnectionId, remote_addr: &SocketAddr) -> Result<(), &'static str> { +pub fn is_connection_id_valid(connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { // todo: server_secret should be randomly generated on startup let server_secret = Secret::new([0;32]); @@ -246,9 +244,9 @@ pub fn verify_connection_id(connection_id: &ConnectionId, remote_addr: &SocketAd let current_timestamp = current_timestamp(); - let result = issuer.verify_connection_id(connection_id, remote_addr, current_timestamp); + let valid = issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); - debug!("verify connection id: {:?}, current timestamp: {:?}, result: {:?}", connection_id, current_timestamp, result); + debug!("verify connection id: {:?}, current timestamp: {:?}, valid: {:?}", connection_id, current_timestamp, valid); - result + valid } From c891e83b1bbbc401881cfc1c9ae058fe29861f96 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Sep 2022 13:30:48 +0100 Subject: [PATCH 76/90] feat: do not send response when connection id is not valid BREAKING CHANGE: Following the BEP 15 (https://www.bittorrent.org/beps/bep_0015.html) we do not send amny response when the connection id is not valid in the UDP tracker. In the previous behavior we were sending an error response with a description. We want to avoid involuntary smoke testing by malicious actors. https://github.com/torrust/torrust-tracker/pull/60#issuecomment-1239191142 --- src/udp/handlers.rs | 9 +++++---- src/udp/server.rs | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/udp/handlers.rs b/src/udp/handlers.rs index 7c4eedbc1..09bdd9ca4 100644 --- a/src/udp/handlers.rs +++ b/src/udp/handlers.rs @@ -34,7 +34,7 @@ pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> } } -pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Response { +pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Option { match Request::from_bytes(&payload[..payload.len()], MAX_SCRAPE_TORRENTS).map_err(|_| ServerError::InternalServerError) { Ok(request) => { let transaction_id = match &request { @@ -50,12 +50,13 @@ pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: A }; match handle_request(request, remote_addr, tracker).await { - Ok(response) => response, - Err(e) => handle_error(e, transaction_id) + Ok(response) => Some(response), + Err(ServerError::InvalidConnectionId) => None, + Err(e) => Some(handle_error(e, transaction_id)) } } // bad request - Err(_) => handle_error(ServerError::BadRequest, TransactionId(0)) + Err(_) => Some(handle_error(ServerError::BadRequest, TransactionId(0))) } } diff --git a/src/udp/server.rs b/src/udp/server.rs index bcacc2642..9afc9e891 100644 --- a/src/udp/server.rs +++ b/src/udp/server.rs @@ -41,8 +41,9 @@ impl UdpServer { debug!("Received {} bytes from {}", payload.len(), remote_addr); debug!("{:?}", payload); - let response = handle_packet(remote_addr, payload, tracker).await; - UdpServer::send_response(socket, remote_addr, response).await; + if let Some(response) = handle_packet(remote_addr, payload, tracker).await { + UdpServer::send_response(socket, remote_addr, response).await; + } } } } From 75a2e5a9c528087d087130b92c5c6448bcde7989 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Sep 2022 16:21:28 +0100 Subject: [PATCH 77/90] refactor: rename mod handlers to packet_handler Reactor in progress. I want to extract the request handlers into a new mod. --- src/udp/mod.rs | 4 ++-- src/udp/{handlers.rs => packet_handler.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/udp/{handlers.rs => packet_handler.rs} (100%) diff --git a/src/udp/mod.rs b/src/udp/mod.rs index 67672aca2..38d143dc0 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -5,7 +5,7 @@ //! [BEP 15](https://www.bittorrent.org/beps/bep_0015.html) pub use self::errors::*; -pub use self::handlers::*; +pub use self::packet_handler::*; pub use self::request::*; pub use self::server::*; @@ -13,7 +13,7 @@ pub mod connection; pub mod errors; pub mod request; pub mod server; -pub mod handlers; +pub mod packet_handler; pub type Bytes = u64; pub type Port = u16; diff --git a/src/udp/handlers.rs b/src/udp/packet_handler.rs similarity index 100% rename from src/udp/handlers.rs rename to src/udp/packet_handler.rs From a2b38d478ef8211248e7c3cfa0744ca82188e4fe Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Sep 2022 16:33:52 +0100 Subject: [PATCH 78/90] refactor: extract mod request_handler --- src/udp/mod.rs | 1 + src/udp/packet_handler.rs | 223 +------------------------------------ src/udp/request_handler.rs | 219 ++++++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+), 219 deletions(-) create mode 100644 src/udp/request_handler.rs diff --git a/src/udp/mod.rs b/src/udp/mod.rs index 38d143dc0..155afddc0 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -14,6 +14,7 @@ pub mod errors; pub mod request; pub mod server; pub mod packet_handler; +pub mod request_handler; pub type Bytes = u64; pub type Port = u16; diff --git a/src/udp/packet_handler.rs b/src/udp/packet_handler.rs index 09bdd9ca4..a9df4fb62 100644 --- a/src/udp/packet_handler.rs +++ b/src/udp/packet_handler.rs @@ -1,38 +1,10 @@ -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::net::SocketAddr; use std::sync::Arc; - -use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId, ConnectionId}; -use log::debug; - -use crate::udp::connection::connection_id_issuer::{EncryptedConnectionIdIssuer, ConnectionIdIssuer}; - -use crate::udp::connection::secret::Secret; -use crate::{InfoHash, MAX_SCRAPE_TORRENTS}; -use crate::peer::TorrentPeer; -use crate::tracker::torrent::{TorrentError}; +use aquatic_udp_protocol::{ErrorResponse, Request, Response, TransactionId}; +use crate::MAX_SCRAPE_TORRENTS; use crate::udp::errors::ServerError; -use crate::udp::request::AnnounceRequestWrapper; -use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; -use crate::protocol::clock::current_timestamp; - -pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { - match tracker.authenticate_request(info_hash, &None).await { - Ok(_) => Ok(()), - Err(e) => { - let err = match e { - TorrentError::TorrentNotWhitelisted => ServerError::TorrentNotWhitelisted, - TorrentError::PeerNotAuthenticated => ServerError::PeerNotAuthenticated, - TorrentError::PeerKeyNotValid => ServerError::PeerKeyNotValid, - TorrentError::NoPeersFound => ServerError::NoPeersFound, - TorrentError::CouldNotSendResponse => ServerError::InternalServerError, - TorrentError::InvalidInfoHash => ServerError::InvalidInfoHash, - }; - - Err(err) - } - } -} +use super::request_handler::handle_request; pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Option { match Request::from_bytes(&payload[..payload.len()], MAX_SCRAPE_TORRENTS).map_err(|_| ServerError::InternalServerError) { @@ -60,194 +32,7 @@ pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: A } } -pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: Arc) -> Result { - 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::Scrape(scrape_request) => { - handle_scrape(remote_addr, &scrape_request, tracker).await - } - } -} - -pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let connection_id = generate_new_connection_id(&remote_addr); - - let response = Response::from(ConnectResponse { - transaction_id: request.transaction_id, - connection_id, - }); - - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Connect).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Connect).await; } - } - - Ok(response) -} - -pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { - let valid = is_connection_id_valid(&announce_request.connection_id, &remote_addr); - if !valid { - return Err(ServerError::InvalidConnectionId); - } - - let wrapped_announce_request = AnnounceRequestWrapper::new(announce_request.clone()); - - authenticate(&wrapped_announce_request.info_hash, tracker.clone()).await?; - - let peer = TorrentPeer::from_udp_announce_request(&wrapped_announce_request.announce_request, remote_addr.ip(), tracker.config.get_ext_ip()); - - //let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; - - let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; - - // get all peers excluding the client_addr - let peers = tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await; - - let announce_response = if remote_addr.is_ipv4() { - Response::from(AnnounceResponse { - transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), - leechers: NumberOfPeers(torrent_stats.leechers as i32), - seeders: NumberOfPeers(torrent_stats.seeders as i32), - peers: peers.iter() - .filter_map(|peer| if let IpAddr::V4(ip) = peer.peer_addr.ip() { - Some(ResponsePeer:: { - ip_address: ip, - port: Port(peer.peer_addr.port()), - }) - } else { - None - } - ).collect(), - }) - } else { - Response::from(AnnounceResponse { - transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), - leechers: NumberOfPeers(torrent_stats.leechers as i32), - seeders: NumberOfPeers(torrent_stats.seeders as i32), - peers: peers.iter() - .filter_map(|peer| if let IpAddr::V6(ip) = peer.peer_addr.ip() { - Some(ResponsePeer:: { - ip_address: ip, - port: Port(peer.peer_addr.port()), - }) - } else { - None - } - ).collect(), - }) - }; - - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Announce).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Announce).await; } - } - - Ok(announce_response) -} - -// todo: refactor this, db lock can be a lot shorter -pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { - let valid = is_connection_id_valid(&request.connection_id, &remote_addr); - if !valid { - return Err(ServerError::InvalidConnectionId); - } - - let db = tracker.get_torrents().await; - - let mut torrent_stats: Vec = Vec::new(); - - for info_hash in request.info_hashes.iter() { - let info_hash = InfoHash(info_hash.0); - - let scrape_entry = match db.get(&info_hash) { - Some(torrent_info) => { - if authenticate(&info_hash, tracker.clone()).await.is_ok() { - let (seeders, completed, leechers) = torrent_info.get_stats(); - - TorrentScrapeStatistics { - seeders: NumberOfPeers(seeders as i32), - completed: NumberOfDownloads(completed as i32), - leechers: NumberOfPeers(leechers as i32), - } - } else { - TorrentScrapeStatistics { - seeders: NumberOfPeers(0), - completed: NumberOfDownloads(0), - leechers: NumberOfPeers(0), - } - } - } - None => { - TorrentScrapeStatistics { - seeders: NumberOfPeers(0), - completed: NumberOfDownloads(0), - leechers: NumberOfPeers(0), - } - } - }; - - torrent_stats.push(scrape_entry); - } - - drop(db); - - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Scrape).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Scrape).await; } - } - - Ok(Response::from(ScrapeResponse { - transaction_id: request.transaction_id, - torrent_stats, - })) -} - fn handle_error(e: ServerError, transaction_id: TransactionId) -> Response { let message = e.to_string(); Response::from(ErrorResponse { transaction_id, message: message.into() }) } - -pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { - // todo: server_secret should be randomly generated on startup - let server_secret = Secret::new([0;32]); - - // todo: this is expensive because of the blowfish cypher instantiation. - // It should be generated on startup. - let issuer = EncryptedConnectionIdIssuer::new(server_secret); - - let current_timestamp = current_timestamp(); - - let connection_id = issuer.new_connection_id(remote_addr, current_timestamp); - - debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); - - connection_id -} - -pub fn is_connection_id_valid(connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { - // todo: server_secret should be randomly generated on startup - let server_secret = Secret::new([0;32]); - - // todo: this is expensive because of the blowfish cypher instantiation. - // It should be generated on startup. - let issuer = EncryptedConnectionIdIssuer::new(server_secret); - - let current_timestamp = current_timestamp(); - - let valid = issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); - - debug!("verify connection id: {:?}, current timestamp: {:?}, valid: {:?}", connection_id, current_timestamp, valid); - - valid -} diff --git a/src/udp/request_handler.rs b/src/udp/request_handler.rs new file mode 100644 index 000000000..fdd20bbff --- /dev/null +++ b/src/udp/request_handler.rs @@ -0,0 +1,219 @@ +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::sync::Arc; +use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, ConnectionId}; +use log::debug; +use crate::torrent::TorrentError; +use crate::udp::connection::connection_id_issuer::{EncryptedConnectionIdIssuer, ConnectionIdIssuer}; +use crate::udp::connection::secret::Secret; +use crate::{InfoHash}; +use crate::peer::TorrentPeer; +use crate::udp::errors::ServerError; +use crate::udp::request::AnnounceRequestWrapper; +use crate::tracker::statistics::TrackerStatisticsEvent; +use crate::tracker::tracker::TorrentTracker; +use crate::protocol::clock::current_timestamp; + +pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: Arc) -> Result { + 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::Scrape(scrape_request) => { + handle_scrape(remote_addr, &scrape_request, tracker).await + } + } +} + +pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { + let connection_id = generate_new_connection_id(&remote_addr); + + let response = Response::from(ConnectResponse { + transaction_id: request.transaction_id, + connection_id, + }); + + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Connect).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Connect).await; } + } + + Ok(response) +} + +pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { + let valid = is_connection_id_valid(&announce_request.connection_id, &remote_addr); + if !valid { + return Err(ServerError::InvalidConnectionId); + } + + let wrapped_announce_request = AnnounceRequestWrapper::new(announce_request.clone()); + + authenticate(&wrapped_announce_request.info_hash, tracker.clone()).await?; + + let peer = TorrentPeer::from_udp_announce_request(&wrapped_announce_request.announce_request, remote_addr.ip(), tracker.config.get_ext_ip()); + + //let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + + let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + + // get all peers excluding the client_addr + let peers = tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await; + + let announce_response = if remote_addr.is_ipv4() { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V4(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()), + }) + } else { + None + } + ).collect(), + }) + } else { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V6(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()), + }) + } else { + None + } + ).collect(), + }) + }; + + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Announce).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Announce).await; } + } + + Ok(announce_response) +} + +// todo: refactor this, db lock can be a lot shorter +pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { + let valid = is_connection_id_valid(&request.connection_id, &remote_addr); + if !valid { + return Err(ServerError::InvalidConnectionId); + } + + let db = tracker.get_torrents().await; + + let mut torrent_stats: Vec = Vec::new(); + + for info_hash in request.info_hashes.iter() { + let info_hash = InfoHash(info_hash.0); + + let scrape_entry = match db.get(&info_hash) { + Some(torrent_info) => { + if authenticate(&info_hash, tracker.clone()).await.is_ok() { + let (seeders, completed, leechers) = torrent_info.get_stats(); + + TorrentScrapeStatistics { + seeders: NumberOfPeers(seeders as i32), + completed: NumberOfDownloads(completed as i32), + leechers: NumberOfPeers(leechers as i32), + } + } else { + TorrentScrapeStatistics { + seeders: NumberOfPeers(0), + completed: NumberOfDownloads(0), + leechers: NumberOfPeers(0), + } + } + } + None => { + TorrentScrapeStatistics { + seeders: NumberOfPeers(0), + completed: NumberOfDownloads(0), + leechers: NumberOfPeers(0), + } + } + }; + + torrent_stats.push(scrape_entry); + } + + drop(db); + + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Scrape).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Scrape).await; } + } + + Ok(Response::from(ScrapeResponse { + transaction_id: request.transaction_id, + torrent_stats, + })) +} + +pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { + match tracker.authenticate_request(info_hash, &None).await { + Ok(_) => Ok(()), + Err(e) => { + let err = match e { + TorrentError::TorrentNotWhitelisted => ServerError::TorrentNotWhitelisted, + TorrentError::PeerNotAuthenticated => ServerError::PeerNotAuthenticated, + TorrentError::PeerKeyNotValid => ServerError::PeerKeyNotValid, + TorrentError::NoPeersFound => ServerError::NoPeersFound, + TorrentError::CouldNotSendResponse => ServerError::InternalServerError, + TorrentError::InvalidInfoHash => ServerError::InvalidInfoHash, + }; + + Err(err) + } + } +} + +pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { + // todo: server_secret should be randomly generated on startup + let server_secret = Secret::new([0;32]); + + // todo: this is expensive because of the blowfish cypher instantiation. + // It should be generated on startup. + let issuer = EncryptedConnectionIdIssuer::new(server_secret); + + let current_timestamp = current_timestamp(); + + let connection_id = issuer.new_connection_id(remote_addr, current_timestamp); + + debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); + + connection_id +} + +pub fn is_connection_id_valid(connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { + // todo: server_secret should be randomly generated on startup + let server_secret = Secret::new([0;32]); + + // todo: this is expensive because of the blowfish cypher instantiation. + // It should be generated on startup. + let issuer = EncryptedConnectionIdIssuer::new(server_secret); + + let current_timestamp = current_timestamp(); + + let valid = issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); + + debug!("verify connection id: {:?}, current timestamp: {:?}, valid: {:?}", connection_id, current_timestamp, valid); + + valid +} From 5801e5fb9eabe578faa89379e5930224efb12efb Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 7 Sep 2022 16:58:41 +0100 Subject: [PATCH 79/90] refactor: extract UDP RequestHandler --- src/udp/packet_handler.rs | 9 +- src/udp/request_handler.rs | 322 ++++++++++++++++++------------------- 2 files changed, 167 insertions(+), 164 deletions(-) diff --git a/src/udp/packet_handler.rs b/src/udp/packet_handler.rs index a9df4fb62..0e967aacd 100644 --- a/src/udp/packet_handler.rs +++ b/src/udp/packet_handler.rs @@ -4,7 +4,8 @@ use aquatic_udp_protocol::{ErrorResponse, Request, Response, TransactionId}; use crate::MAX_SCRAPE_TORRENTS; use crate::udp::errors::ServerError; use crate::tracker::tracker::TorrentTracker; -use super::request_handler::handle_request; +use super::connection::secret::Secret; +use super::request_handler::RequestHandler; pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Option { match Request::from_bytes(&payload[..payload.len()], MAX_SCRAPE_TORRENTS).map_err(|_| ServerError::InternalServerError) { @@ -21,7 +22,11 @@ pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: A } }; - match handle_request(request, remote_addr, tracker).await { + // todo: server_secret should be randomly generated on startup + let server_secret = Secret::new([0;32]); + let request_handler = RequestHandler::new(server_secret); + + match request_handler.handle(request, remote_addr, tracker).await { Ok(response) => Some(response), Err(ServerError::InvalidConnectionId) => None, Err(e) => Some(handle_error(e, transaction_id)) diff --git a/src/udp/request_handler.rs b/src/udp/request_handler.rs index fdd20bbff..aa001d7c7 100644 --- a/src/udp/request_handler.rs +++ b/src/udp/request_handler.rs @@ -13,207 +13,205 @@ use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; use crate::protocol::clock::current_timestamp; -pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: Arc) -> Result { - 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::Scrape(scrape_request) => { - handle_scrape(remote_addr, &scrape_request, tracker).await - } - } +pub struct RequestHandler { + encrypted_connection_id_issuer: EncryptedConnectionIdIssuer, + // todo: inject also a crate::protocol::Clock in order to make it easier to test it. } -pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let connection_id = generate_new_connection_id(&remote_addr); - - let response = Response::from(ConnectResponse { - transaction_id: request.transaction_id, - connection_id, - }); +impl RequestHandler { + pub fn new(secret: Secret) -> Self { + let encrypted_connection_id_issuer = EncryptedConnectionIdIssuer::new(secret); + Self { encrypted_connection_id_issuer } + } - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Connect).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Connect).await; } + pub async fn handle(&self, request: Request, remote_addr: SocketAddr, tracker: Arc) -> Result { + match request { + Request::Connect(connect_request) => { + self.handle_connect(remote_addr, &connect_request, tracker).await + } + Request::Announce(announce_request) => { + self.handle_announce(remote_addr, &announce_request, tracker).await + } + Request::Scrape(scrape_request) => { + self.handle_scrape(remote_addr, &scrape_request, tracker).await + } + } } - Ok(response) -} + pub async fn handle_connect(&self, remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { + let connection_id = self.generate_new_connection_id(&remote_addr); -pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { - let valid = is_connection_id_valid(&announce_request.connection_id, &remote_addr); - if !valid { - return Err(ServerError::InvalidConnectionId); - } + let response = Response::from(ConnectResponse { + transaction_id: request.transaction_id, + connection_id, + }); - let wrapped_announce_request = AnnounceRequestWrapper::new(announce_request.clone()); + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Connect).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Connect).await; } + } - authenticate(&wrapped_announce_request.info_hash, tracker.clone()).await?; + Ok(response) + } - let peer = TorrentPeer::from_udp_announce_request(&wrapped_announce_request.announce_request, remote_addr.ip(), tracker.config.get_ext_ip()); + pub async fn handle_announce(&self, remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { + let valid = self.is_connection_id_valid(&announce_request.connection_id, &remote_addr); + if !valid { + return Err(ServerError::InvalidConnectionId); + } - //let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + let wrapped_announce_request = AnnounceRequestWrapper::new(announce_request.clone()); - let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + self.authenticate(&wrapped_announce_request.info_hash, tracker.clone()).await?; - // get all peers excluding the client_addr - let peers = tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await; + let peer = TorrentPeer::from_udp_announce_request(&wrapped_announce_request.announce_request, remote_addr.ip(), tracker.config.get_ext_ip()); - let announce_response = if remote_addr.is_ipv4() { - Response::from(AnnounceResponse { - transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), - leechers: NumberOfPeers(torrent_stats.leechers as i32), - seeders: NumberOfPeers(torrent_stats.seeders as i32), - peers: peers.iter() - .filter_map(|peer| if let IpAddr::V4(ip) = peer.peer_addr.ip() { - Some(ResponsePeer:: { - ip_address: ip, - port: Port(peer.peer_addr.port()), - }) - } else { - None - } - ).collect(), - }) - } else { - Response::from(AnnounceResponse { - transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), - leechers: NumberOfPeers(torrent_stats.leechers as i32), - seeders: NumberOfPeers(torrent_stats.seeders as i32), - peers: peers.iter() - .filter_map(|peer| if let IpAddr::V6(ip) = peer.peer_addr.ip() { - Some(ResponsePeer:: { - ip_address: ip, - port: Port(peer.peer_addr.port()), - }) - } else { - None - } - ).collect(), - }) - }; - - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Announce).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Announce).await; } - } + //let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; - Ok(announce_response) -} + let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; -// todo: refactor this, db lock can be a lot shorter -pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { - let valid = is_connection_id_valid(&request.connection_id, &remote_addr); - if !valid { - return Err(ServerError::InvalidConnectionId); - } + // get all peers excluding the client_addr + let peers = tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await; - let db = tracker.get_torrents().await; + let announce_response = if remote_addr.is_ipv4() { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V4(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()), + }) + } else { + None + } + ).collect(), + }) + } else { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V6(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()), + }) + } else { + None + } + ).collect(), + }) + }; - let mut torrent_stats: Vec = Vec::new(); + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Announce).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Announce).await; } + } - for info_hash in request.info_hashes.iter() { - let info_hash = InfoHash(info_hash.0); + Ok(announce_response) + } - let scrape_entry = match db.get(&info_hash) { - Some(torrent_info) => { - if authenticate(&info_hash, tracker.clone()).await.is_ok() { - let (seeders, completed, leechers) = torrent_info.get_stats(); + // todo: refactor this, db lock can be a lot shorter + pub async fn handle_scrape(&self, remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { + let valid = self.is_connection_id_valid(&request.connection_id, &remote_addr); + if !valid { + return Err(ServerError::InvalidConnectionId); + } - TorrentScrapeStatistics { - seeders: NumberOfPeers(seeders as i32), - completed: NumberOfDownloads(completed as i32), - leechers: NumberOfPeers(leechers as i32), + let db = tracker.get_torrents().await; + + let mut torrent_stats: Vec = Vec::new(); + + for info_hash in request.info_hashes.iter() { + let info_hash = InfoHash(info_hash.0); + + let scrape_entry = match db.get(&info_hash) { + Some(torrent_info) => { + if self.authenticate(&info_hash, tracker.clone()).await.is_ok() { + let (seeders, completed, leechers) = torrent_info.get_stats(); + + TorrentScrapeStatistics { + seeders: NumberOfPeers(seeders as i32), + completed: NumberOfDownloads(completed as i32), + leechers: NumberOfPeers(leechers as i32), + } + } else { + TorrentScrapeStatistics { + seeders: NumberOfPeers(0), + completed: NumberOfDownloads(0), + leechers: NumberOfPeers(0), + } } - } else { + } + None => { TorrentScrapeStatistics { seeders: NumberOfPeers(0), completed: NumberOfDownloads(0), leechers: NumberOfPeers(0), } } - } - None => { - TorrentScrapeStatistics { - seeders: NumberOfPeers(0), - completed: NumberOfDownloads(0), - leechers: NumberOfPeers(0), - } - } - }; - - torrent_stats.push(scrape_entry); - } - - drop(db); - - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Scrape).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Scrape).await; } - } - - Ok(Response::from(ScrapeResponse { - transaction_id: request.transaction_id, - torrent_stats, - })) -} - -pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { - match tracker.authenticate_request(info_hash, &None).await { - Ok(_) => Ok(()), - Err(e) => { - let err = match e { - TorrentError::TorrentNotWhitelisted => ServerError::TorrentNotWhitelisted, - TorrentError::PeerNotAuthenticated => ServerError::PeerNotAuthenticated, - TorrentError::PeerKeyNotValid => ServerError::PeerKeyNotValid, - TorrentError::NoPeersFound => ServerError::NoPeersFound, - TorrentError::CouldNotSendResponse => ServerError::InternalServerError, - TorrentError::InvalidInfoHash => ServerError::InvalidInfoHash, }; - Err(err) + torrent_stats.push(scrape_entry); } - } -} -pub fn generate_new_connection_id(remote_addr: &SocketAddr) -> ConnectionId { - // todo: server_secret should be randomly generated on startup - let server_secret = Secret::new([0;32]); + drop(db); - // todo: this is expensive because of the blowfish cypher instantiation. - // It should be generated on startup. - let issuer = EncryptedConnectionIdIssuer::new(server_secret); + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Scrape).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Scrape).await; } + } - let current_timestamp = current_timestamp(); + Ok(Response::from(ScrapeResponse { + transaction_id: request.transaction_id, + torrent_stats, + })) + } - let connection_id = issuer.new_connection_id(remote_addr, current_timestamp); + pub async fn authenticate(&self, info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { + match tracker.authenticate_request(info_hash, &None).await { + Ok(_) => Ok(()), + Err(e) => { + let err = match e { + TorrentError::TorrentNotWhitelisted => ServerError::TorrentNotWhitelisted, + TorrentError::PeerNotAuthenticated => ServerError::PeerNotAuthenticated, + TorrentError::PeerKeyNotValid => ServerError::PeerKeyNotValid, + TorrentError::NoPeersFound => ServerError::NoPeersFound, + TorrentError::CouldNotSendResponse => ServerError::InternalServerError, + TorrentError::InvalidInfoHash => ServerError::InvalidInfoHash, + }; + + Err(err) + } + } + } - debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); + pub fn generate_new_connection_id(&self, remote_addr: &SocketAddr) -> ConnectionId { + let current_timestamp = current_timestamp(); - connection_id -} + let connection_id = self.encrypted_connection_id_issuer.new_connection_id(remote_addr, current_timestamp); -pub fn is_connection_id_valid(connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { - // todo: server_secret should be randomly generated on startup - let server_secret = Secret::new([0;32]); + debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); - // todo: this is expensive because of the blowfish cypher instantiation. - // It should be generated on startup. - let issuer = EncryptedConnectionIdIssuer::new(server_secret); + connection_id + } - let current_timestamp = current_timestamp(); + pub fn is_connection_id_valid(&self, connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { + let current_timestamp = current_timestamp(); - let valid = issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); + let valid = self.encrypted_connection_id_issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); - debug!("verify connection id: {:?}, current timestamp: {:?}, valid: {:?}", connection_id, current_timestamp, valid); + debug!("verify connection id: {:?}, current timestamp: {:?}, valid: {:?}", connection_id, current_timestamp, valid); - valid -} + valid + } +} \ No newline at end of file From c3c31e54423cd909fffbb6853db628b0caac01bc Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 8 Sep 2022 12:34:10 +0200 Subject: [PATCH 80/90] refactor: moved RequestHandler creation to UdpServer::start() --- src/udp/mod.rs | 1 - src/udp/packet_handler.rs | 264 +++++++++++++++++++++++++++++++++---- src/udp/request_handler.rs | 217 ------------------------------ src/udp/server.rs | 11 +- 4 files changed, 244 insertions(+), 249 deletions(-) delete mode 100644 src/udp/request_handler.rs diff --git a/src/udp/mod.rs b/src/udp/mod.rs index 155afddc0..38d143dc0 100644 --- a/src/udp/mod.rs +++ b/src/udp/mod.rs @@ -14,7 +14,6 @@ pub mod errors; pub mod request; pub mod server; pub mod packet_handler; -pub mod request_handler; pub type Bytes = u64; pub type Port = u16; diff --git a/src/udp/packet_handler.rs b/src/udp/packet_handler.rs index 0e967aacd..79150ccc4 100644 --- a/src/udp/packet_handler.rs +++ b/src/udp/packet_handler.rs @@ -1,43 +1,249 @@ -use std::net::SocketAddr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; -use aquatic_udp_protocol::{ErrorResponse, Request, Response, TransactionId}; -use crate::MAX_SCRAPE_TORRENTS; +use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, ConnectionId, ErrorResponse, TransactionId}; +use log::debug; +use crate::torrent::TorrentError; +use crate::udp::connection::connection_id_issuer::{EncryptedConnectionIdIssuer, ConnectionIdIssuer}; +use crate::udp::connection::secret::Secret; +use crate::{InfoHash, MAX_SCRAPE_TORRENTS}; +use crate::peer::TorrentPeer; use crate::udp::errors::ServerError; +use crate::udp::request::AnnounceRequestWrapper; +use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; -use super::connection::secret::Secret; -use super::request_handler::RequestHandler; - -pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Option { - match Request::from_bytes(&payload[..payload.len()], MAX_SCRAPE_TORRENTS).map_err(|_| ServerError::InternalServerError) { - Ok(request) => { - let transaction_id = match &request { - Request::Connect(connect_request) => { - connect_request.transaction_id +use crate::protocol::clock::current_timestamp; + +pub struct RequestHandler { + encrypted_connection_id_issuer: EncryptedConnectionIdIssuer, + // todo: inject also a crate::protocol::Clock in order to make it easier to test it. +} + +impl RequestHandler { + pub fn new(secret: Secret) -> Self { + let encrypted_connection_id_issuer = EncryptedConnectionIdIssuer::new(secret); + Self { encrypted_connection_id_issuer } + } + + pub async fn handle_packet(&self, remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Option { + match Request::from_bytes(&payload[..payload.len()], MAX_SCRAPE_TORRENTS).map_err(|_| ServerError::InternalServerError) { + Ok(request) => { + let transaction_id = match &request { + Request::Connect(connect_request) => { + connect_request.transaction_id + } + Request::Announce(announce_request) => { + announce_request.transaction_id + } + Request::Scrape(scrape_request) => { + scrape_request.transaction_id + } + }; + + match self.handle_request(request, remote_addr, tracker).await { + Ok(response) => Some(response), + Err(ServerError::InvalidConnectionId) => None, + Err(e) => Some(Self::handle_error(e, transaction_id)) } - Request::Announce(announce_request) => { - announce_request.transaction_id + } + // don't respond to bad requests + Err(_) => None + } + } + + fn handle_error(e: ServerError, transaction_id: TransactionId) -> Response { + let message = e.to_string(); + Response::from(ErrorResponse { transaction_id, message: message.into() }) + } + + + async fn handle_request(&self, request: Request, remote_addr: SocketAddr, tracker: Arc) -> Result { + match request { + Request::Connect(connect_request) => { + self.handle_connect(remote_addr, &connect_request, tracker).await + } + Request::Announce(announce_request) => { + self.handle_announce(remote_addr, &announce_request, tracker).await + } + Request::Scrape(scrape_request) => { + self.handle_scrape(remote_addr, &scrape_request, tracker).await + } + } + } + + async fn handle_connect(&self, remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { + let connection_id = self.generate_new_connection_id(&remote_addr); + + let response = Response::from(ConnectResponse { + transaction_id: request.transaction_id, + connection_id, + }); + + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Connect).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Connect).await; } + } + + Ok(response) + } + + async fn handle_announce(&self, remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { + let valid = self.is_connection_id_valid(&announce_request.connection_id, &remote_addr); + if !valid { + return Err(ServerError::InvalidConnectionId); + } + + let wrapped_announce_request = AnnounceRequestWrapper::new(announce_request.clone()); + + self.authenticate(&wrapped_announce_request.info_hash, tracker.clone()).await?; + + let peer = TorrentPeer::from_udp_announce_request(&wrapped_announce_request.announce_request, remote_addr.ip(), tracker.config.get_ext_ip()); + + //let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + + let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + + // get all peers excluding the client_addr + let peers = tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await; + + let announce_response = if remote_addr.is_ipv4() { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V4(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()), + }) + } else { + None + } + ).collect(), + }) + } else { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V6(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()), + }) + } else { + None + } + ).collect(), + }) + }; + + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Announce).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Announce).await; } + } + + Ok(announce_response) + } + + // todo: refactor this, db lock can be a lot shorter + async fn handle_scrape(&self, remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { + let valid = self.is_connection_id_valid(&request.connection_id, &remote_addr); + if !valid { + return Err(ServerError::InvalidConnectionId); + } + + let db = tracker.get_torrents().await; + + let mut torrent_stats: Vec = Vec::new(); + + for info_hash in request.info_hashes.iter() { + let info_hash = InfoHash(info_hash.0); + + let scrape_entry = match db.get(&info_hash) { + Some(torrent_info) => { + if self.authenticate(&info_hash, tracker.clone()).await.is_ok() { + let (seeders, completed, leechers) = torrent_info.get_stats(); + + TorrentScrapeStatistics { + seeders: NumberOfPeers(seeders as i32), + completed: NumberOfDownloads(completed as i32), + leechers: NumberOfPeers(leechers as i32), + } + } else { + TorrentScrapeStatistics { + seeders: NumberOfPeers(0), + completed: NumberOfDownloads(0), + leechers: NumberOfPeers(0), + } + } } - Request::Scrape(scrape_request) => { - scrape_request.transaction_id + None => { + TorrentScrapeStatistics { + seeders: NumberOfPeers(0), + completed: NumberOfDownloads(0), + leechers: NumberOfPeers(0), + } } }; - // todo: server_secret should be randomly generated on startup - let server_secret = Secret::new([0;32]); - let request_handler = RequestHandler::new(server_secret); + torrent_stats.push(scrape_entry); + } + + drop(db); - match request_handler.handle(request, remote_addr, tracker).await { - Ok(response) => Some(response), - Err(ServerError::InvalidConnectionId) => None, - Err(e) => Some(handle_error(e, transaction_id)) + // send stats event + match remote_addr { + SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Scrape).await; } + SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Scrape).await; } + } + + Ok(Response::from(ScrapeResponse { + transaction_id: request.transaction_id, + torrent_stats, + })) + } + + async fn authenticate(&self, info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { + match tracker.authenticate_request(info_hash, &None).await { + Ok(_) => Ok(()), + Err(e) => { + let err = match e { + TorrentError::TorrentNotWhitelisted => ServerError::TorrentNotWhitelisted, + TorrentError::PeerNotAuthenticated => ServerError::PeerNotAuthenticated, + TorrentError::PeerKeyNotValid => ServerError::PeerKeyNotValid, + TorrentError::NoPeersFound => ServerError::NoPeersFound, + TorrentError::CouldNotSendResponse => ServerError::InternalServerError, + TorrentError::InvalidInfoHash => ServerError::InvalidInfoHash, + }; + + Err(err) } } - // bad request - Err(_) => Some(handle_error(ServerError::BadRequest, TransactionId(0))) } -} -fn handle_error(e: ServerError, transaction_id: TransactionId) -> Response { - let message = e.to_string(); - Response::from(ErrorResponse { transaction_id, message: message.into() }) + fn generate_new_connection_id(&self, remote_addr: &SocketAddr) -> ConnectionId { + let current_timestamp = current_timestamp(); + + let connection_id = self.encrypted_connection_id_issuer.new_connection_id(remote_addr, current_timestamp); + + debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); + + connection_id + } + + fn is_connection_id_valid(&self, connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { + let current_timestamp = current_timestamp(); + + let valid = self.encrypted_connection_id_issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); + + debug!("verify connection id: {:?}, current timestamp: {:?}, valid: {:?}", connection_id, current_timestamp, valid); + + valid + } } diff --git a/src/udp/request_handler.rs b/src/udp/request_handler.rs deleted file mode 100644 index aa001d7c7..000000000 --- a/src/udp/request_handler.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use std::sync::Arc; -use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, ConnectionId}; -use log::debug; -use crate::torrent::TorrentError; -use crate::udp::connection::connection_id_issuer::{EncryptedConnectionIdIssuer, ConnectionIdIssuer}; -use crate::udp::connection::secret::Secret; -use crate::{InfoHash}; -use crate::peer::TorrentPeer; -use crate::udp::errors::ServerError; -use crate::udp::request::AnnounceRequestWrapper; -use crate::tracker::statistics::TrackerStatisticsEvent; -use crate::tracker::tracker::TorrentTracker; -use crate::protocol::clock::current_timestamp; - -pub struct RequestHandler { - encrypted_connection_id_issuer: EncryptedConnectionIdIssuer, - // todo: inject also a crate::protocol::Clock in order to make it easier to test it. -} - -impl RequestHandler { - pub fn new(secret: Secret) -> Self { - let encrypted_connection_id_issuer = EncryptedConnectionIdIssuer::new(secret); - Self { encrypted_connection_id_issuer } - } - - pub async fn handle(&self, request: Request, remote_addr: SocketAddr, tracker: Arc) -> Result { - match request { - Request::Connect(connect_request) => { - self.handle_connect(remote_addr, &connect_request, tracker).await - } - Request::Announce(announce_request) => { - self.handle_announce(remote_addr, &announce_request, tracker).await - } - Request::Scrape(scrape_request) => { - self.handle_scrape(remote_addr, &scrape_request, tracker).await - } - } - } - - pub async fn handle_connect(&self, remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { - let connection_id = self.generate_new_connection_id(&remote_addr); - - let response = Response::from(ConnectResponse { - transaction_id: request.transaction_id, - connection_id, - }); - - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Connect).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Connect).await; } - } - - Ok(response) - } - - pub async fn handle_announce(&self, remote_addr: SocketAddr, announce_request: &AnnounceRequest, tracker: Arc) -> Result { - let valid = self.is_connection_id_valid(&announce_request.connection_id, &remote_addr); - if !valid { - return Err(ServerError::InvalidConnectionId); - } - - let wrapped_announce_request = AnnounceRequestWrapper::new(announce_request.clone()); - - self.authenticate(&wrapped_announce_request.info_hash, tracker.clone()).await?; - - let peer = TorrentPeer::from_udp_announce_request(&wrapped_announce_request.announce_request, remote_addr.ip(), tracker.config.get_ext_ip()); - - //let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; - - let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; - - // get all peers excluding the client_addr - let peers = tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await; - - let announce_response = if remote_addr.is_ipv4() { - Response::from(AnnounceResponse { - transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), - leechers: NumberOfPeers(torrent_stats.leechers as i32), - seeders: NumberOfPeers(torrent_stats.seeders as i32), - peers: peers.iter() - .filter_map(|peer| if let IpAddr::V4(ip) = peer.peer_addr.ip() { - Some(ResponsePeer:: { - ip_address: ip, - port: Port(peer.peer_addr.port()), - }) - } else { - None - } - ).collect(), - }) - } else { - Response::from(AnnounceResponse { - transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), - leechers: NumberOfPeers(torrent_stats.leechers as i32), - seeders: NumberOfPeers(torrent_stats.seeders as i32), - peers: peers.iter() - .filter_map(|peer| if let IpAddr::V6(ip) = peer.peer_addr.ip() { - Some(ResponsePeer:: { - ip_address: ip, - port: Port(peer.peer_addr.port()), - }) - } else { - None - } - ).collect(), - }) - }; - - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Announce).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Announce).await; } - } - - Ok(announce_response) - } - - // todo: refactor this, db lock can be a lot shorter - pub async fn handle_scrape(&self, remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { - let valid = self.is_connection_id_valid(&request.connection_id, &remote_addr); - if !valid { - return Err(ServerError::InvalidConnectionId); - } - - let db = tracker.get_torrents().await; - - let mut torrent_stats: Vec = Vec::new(); - - for info_hash in request.info_hashes.iter() { - let info_hash = InfoHash(info_hash.0); - - let scrape_entry = match db.get(&info_hash) { - Some(torrent_info) => { - if self.authenticate(&info_hash, tracker.clone()).await.is_ok() { - let (seeders, completed, leechers) = torrent_info.get_stats(); - - TorrentScrapeStatistics { - seeders: NumberOfPeers(seeders as i32), - completed: NumberOfDownloads(completed as i32), - leechers: NumberOfPeers(leechers as i32), - } - } else { - TorrentScrapeStatistics { - seeders: NumberOfPeers(0), - completed: NumberOfDownloads(0), - leechers: NumberOfPeers(0), - } - } - } - None => { - TorrentScrapeStatistics { - seeders: NumberOfPeers(0), - completed: NumberOfDownloads(0), - leechers: NumberOfPeers(0), - } - } - }; - - torrent_stats.push(scrape_entry); - } - - drop(db); - - // send stats event - match remote_addr { - SocketAddr::V4(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp4Scrape).await; } - SocketAddr::V6(_) => { tracker.send_stats_event(TrackerStatisticsEvent::Udp6Scrape).await; } - } - - Ok(Response::from(ScrapeResponse { - transaction_id: request.transaction_id, - torrent_stats, - })) - } - - pub async fn authenticate(&self, info_hash: &InfoHash, tracker: Arc) -> Result<(), ServerError> { - match tracker.authenticate_request(info_hash, &None).await { - Ok(_) => Ok(()), - Err(e) => { - let err = match e { - TorrentError::TorrentNotWhitelisted => ServerError::TorrentNotWhitelisted, - TorrentError::PeerNotAuthenticated => ServerError::PeerNotAuthenticated, - TorrentError::PeerKeyNotValid => ServerError::PeerKeyNotValid, - TorrentError::NoPeersFound => ServerError::NoPeersFound, - TorrentError::CouldNotSendResponse => ServerError::InternalServerError, - TorrentError::InvalidInfoHash => ServerError::InvalidInfoHash, - }; - - Err(err) - } - } - } - - pub fn generate_new_connection_id(&self, remote_addr: &SocketAddr) -> ConnectionId { - let current_timestamp = current_timestamp(); - - let connection_id = self.encrypted_connection_id_issuer.new_connection_id(remote_addr, current_timestamp); - - debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); - - connection_id - } - - pub fn is_connection_id_valid(&self, connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { - let current_timestamp = current_timestamp(); - - let valid = self.encrypted_connection_id_issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); - - debug!("verify connection id: {:?}, current timestamp: {:?}, valid: {:?}", connection_id, current_timestamp, valid); - - valid - } -} \ No newline at end of file diff --git a/src/udp/server.rs b/src/udp/server.rs index 9afc9e891..3324068bd 100644 --- a/src/udp/server.rs +++ b/src/udp/server.rs @@ -7,7 +7,9 @@ use log::{debug, info}; use tokio::net::UdpSocket; use crate::tracker::tracker::TorrentTracker; -use crate::udp::{handle_packet, MAX_PACKET_SIZE}; +use crate::udp::{MAX_PACKET_SIZE}; +use crate::udp::connection::secret::Secret; +use crate::udp::packet_handler::RequestHandler; pub struct UdpServer { socket: Arc, @@ -25,11 +27,16 @@ impl UdpServer { } pub async fn start(&self) { + let server_secret = Secret::new([0;32]); + let request_handler = Arc::new(RequestHandler::new(server_secret)); + loop { let mut data = [0; MAX_PACKET_SIZE]; let socket = self.socket.clone(); let tracker = self.tracker.clone(); + let request_handler = request_handler.clone(); + // needed for graceful shutdown tokio::select! { _ = tokio::signal::ctrl_c() => { info!("Stopping UDP server: {}..", socket.local_addr().unwrap()); @@ -41,7 +48,7 @@ impl UdpServer { debug!("Received {} bytes from {}", payload.len(), remote_addr); debug!("{:?}", payload); - if let Some(response) = handle_packet(remote_addr, payload, tracker).await { + if let Some(response) = request_handler.handle_packet(remote_addr, payload, tracker).await { UdpServer::send_response(socket, remote_addr, response).await; } } From f1257814301877eab051accebbe775a92a830ba3 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 8 Sep 2022 12:36:01 +0200 Subject: [PATCH 81/90] refactor: renamed RequestHandler to PacketHandler --- src/udp/packet_handler.rs | 4 ++-- src/udp/server.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/udp/packet_handler.rs b/src/udp/packet_handler.rs index 79150ccc4..09a616805 100644 --- a/src/udp/packet_handler.rs +++ b/src/udp/packet_handler.rs @@ -13,12 +13,12 @@ use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; use crate::protocol::clock::current_timestamp; -pub struct RequestHandler { +pub struct PacketHandler { encrypted_connection_id_issuer: EncryptedConnectionIdIssuer, // todo: inject also a crate::protocol::Clock in order to make it easier to test it. } -impl RequestHandler { +impl PacketHandler { pub fn new(secret: Secret) -> Self { let encrypted_connection_id_issuer = EncryptedConnectionIdIssuer::new(secret); Self { encrypted_connection_id_issuer } diff --git a/src/udp/server.rs b/src/udp/server.rs index 3324068bd..f104a2b2e 100644 --- a/src/udp/server.rs +++ b/src/udp/server.rs @@ -9,7 +9,7 @@ use tokio::net::UdpSocket; use crate::tracker::tracker::TorrentTracker; use crate::udp::{MAX_PACKET_SIZE}; use crate::udp::connection::secret::Secret; -use crate::udp::packet_handler::RequestHandler; +use crate::udp::packet_handler::PacketHandler; pub struct UdpServer { socket: Arc, @@ -28,13 +28,13 @@ impl UdpServer { pub async fn start(&self) { let server_secret = Secret::new([0;32]); - let request_handler = Arc::new(RequestHandler::new(server_secret)); + let request_handler = Arc::new(PacketHandler::new(server_secret)); loop { let mut data = [0; MAX_PACKET_SIZE]; let socket = self.socket.clone(); let tracker = self.tracker.clone(); - let request_handler = request_handler.clone(); + let packet_handler = request_handler.clone(); // needed for graceful shutdown tokio::select! { @@ -48,7 +48,7 @@ impl UdpServer { debug!("Received {} bytes from {}", payload.len(), remote_addr); debug!("{:?}", payload); - if let Some(response) = request_handler.handle_packet(remote_addr, payload, tracker).await { + if let Some(response) = packet_handler.handle_packet(remote_addr, payload, tracker).await { UdpServer::send_response(socket, remote_addr, response).await; } } From f70c11c93eab5775c6e1327fbc1e679b0ca1cc21 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Sep 2022 14:28:16 +0100 Subject: [PATCH 82/90] feat: generate a random encryption key The UDP tracker server needs an encryption key to encrypt the conenction id. It generates a new one on server start up. --- src/udp/server.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/udp/server.rs b/src/udp/server.rs index f104a2b2e..50a8ef9f9 100644 --- a/src/udp/server.rs +++ b/src/udp/server.rs @@ -27,8 +27,9 @@ impl UdpServer { } pub async fn start(&self) { - let server_secret = Secret::new([0;32]); - let request_handler = Arc::new(PacketHandler::new(server_secret)); + let encryption_key = Secret::new(rand::Rng::gen(&mut rand::rngs::ThreadRng::default())); + + let request_handler = Arc::new(PacketHandler::new(encryption_key)); loop { let mut data = [0; MAX_PACKET_SIZE]; From 6c6c33d53d32ff00fbe62be16fd57007b40afe91 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Sep 2022 16:01:58 +0100 Subject: [PATCH 83/90] refactor: use a tuple struct for Timestamp32 Proposed by @WarmBeer in this pull request: https://github.com/torrust/torrust-tracker/pull/76 Co-authored-by: Warm Beer --- src/udp/connection/timestamp_32.rs | 35 ++++++++++++------------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/udp/connection/timestamp_32.rs b/src/udp/connection/timestamp_32.rs index 939330ff1..bf30f474e 100644 --- a/src/udp/connection/timestamp_32.rs +++ b/src/udp/connection/timestamp_32.rs @@ -5,30 +5,26 @@ use std::num::TryFromIntError; use super::timestamp_64::Timestamp64; #[derive(PartialEq, Debug, Copy, Clone)] -pub struct Timestamp32 { - pub value: u32 -} +pub struct Timestamp32(pub u32); impl Timestamp32 { pub fn from_le_bytes(timestamp_bytes: &[u8]) -> Self { // Little Endian let timestamp = u32::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3]]); - Self { - value: timestamp - } + Self(timestamp) } pub fn to_le_bytes(self: Self) -> [u8; 4] { // Little Endian let mut bytes: [u8; 4] = [0u8; 4]; - bytes.copy_from_slice(&self.value.to_le_bytes()[..4]); + bytes.copy_from_slice(&self.0.to_le_bytes()[..4]); bytes } } impl From for Timestamp32 { fn from(value: u32) -> Self { - Self { value } + Self(value) } } @@ -37,16 +33,13 @@ impl TryFrom for Timestamp32 { fn try_from(value: Timestamp64) -> Result { let timestamp32: u32 = u32::try_from(value)?; - - Ok(Self { - value: timestamp32 - }) + Ok(Self(timestamp32)) } } impl Into for Timestamp32 { fn into(self) -> Timestamp64 { - u64::from(self.value) + u64::from(self.0) } } @@ -59,21 +52,21 @@ mod tests { let min_timestamp = Timestamp32::from_le_bytes(&[0u8, 0u8, 0u8, 0u8]); - assert_eq!(min_timestamp, Timestamp32 { value: u32::MIN }); + assert_eq!(min_timestamp, Timestamp32(u32::MIN)); let max_timestamp = Timestamp32::from_le_bytes(&[255u8, 255u8, 255u8, 255u8]); - assert_eq!(max_timestamp, Timestamp32 { value: u32::MAX }); + assert_eq!(max_timestamp, Timestamp32(u32::MAX)); } #[test] fn it_should_be_converted_to_a_four_byte_array_in_little_indian() { - let min_timestamp = Timestamp32 { value: u32::MIN }; + let min_timestamp = Timestamp32(u32::MIN); assert_eq!(min_timestamp.to_le_bytes(), [0u8, 0u8, 0u8, 0u8]); - let max_timestamp = Timestamp32 { value: u32::MAX }; + let max_timestamp = Timestamp32(u32::MAX); assert_eq!(max_timestamp.to_le_bytes(), [255u8, 255u8, 255u8, 255u8]); } @@ -83,7 +76,7 @@ mod tests { let timestamp32: Timestamp32 = 0u64.try_into().unwrap(); - assert_eq!(timestamp32, Timestamp32 { value: u32::MIN }); + assert_eq!(timestamp32, Timestamp32(u32::MIN)); } #[test] @@ -101,20 +94,20 @@ mod tests { let timestamp32: Timestamp32 = u32::MIN.into(); - assert_eq!(timestamp32, Timestamp32 { value: u32::MIN }); + assert_eq!(timestamp32, Timestamp32(u32::MIN)); } #[test] fn it_should_be_converted_to_a_timestamp_64() { - let min_timestamp_32 = Timestamp32 { value: u32::MIN }; + let min_timestamp_32 = Timestamp32(u32::MIN); let min_timestamp_64: Timestamp64 = min_timestamp_32.into(); assert_eq!(min_timestamp_64, u32::MIN as u64); - let max_timestamp_32 = Timestamp32 { value: u32::MAX }; + let max_timestamp_32 = Timestamp32(u32::MAX); let max_timestamp_64: Timestamp64 = max_timestamp_32.into(); From e793731a33c548455373d9f395c63be35a0b1dcc Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Sep 2022 17:11:37 +0100 Subject: [PATCH 84/90] refactor: move method --- src/udp/connection/connection_id_issuer.rs | 2 +- src/udp/connection/cypher.rs | 4 +-- src/udp/connection/secret.rs | 38 ++++++++++++++++++---- src/udp/server.rs | 4 +-- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index 1e3014c97..a52173fed 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -104,7 +104,7 @@ mod tests { use std::{net::{SocketAddr, IpAddr, Ipv4Addr}}; fn cypher_secret_for_testing() -> Secret { - Secret::new([0u8;32]) + Secret::from_bytes([0u8;32]) } fn new_issuer() -> EncryptedConnectionIdIssuer { diff --git a/src/udp/connection/cypher.rs b/src/udp/connection/cypher.rs index 17d65c14d..43ad277e0 100644 --- a/src/udp/connection/cypher.rs +++ b/src/udp/connection/cypher.rs @@ -14,7 +14,7 @@ pub struct BlowfishCypher { impl BlowfishCypher { pub fn new(secret: Secret) -> Self { - let blowfish = Blowfish::new(&secret.to_bytes()); + let blowfish = Blowfish::new(&secret.into_bytes()); BlowfishCypher { blowfish } @@ -46,7 +46,7 @@ mod tests { #[test] fn it_should_encrypt_and_decrypt_a_byte_array() { - let secret = Secret::new([0u8;32]); + let secret = Secret::from_bytes([0u8;32]); let cypher = BlowfishCypher::new(secret); diff --git a/src/udp/connection/secret.rs b/src/udp/connection/secret.rs index 4b7d0482f..658694f5f 100644 --- a/src/udp/connection/secret.rs +++ b/src/udp/connection/secret.rs @@ -5,11 +5,27 @@ pub struct Secret([u8; 32]); impl Secret { - pub fn new(bytes: [u8; 32]) -> Self { + pub fn new() -> Self { + let key = Self::generate_random_key(); + Self::from_bytes(key) + } + + pub fn generate_random_key() -> [u8; 32] { + let key: [u8; 32] = rand::Rng::gen(&mut rand::rngs::ThreadRng::default()); + key + } + + pub fn from_bytes(bytes: [u8; 32]) -> Self { Secret(bytes) } - pub fn to_bytes(self) -> [u8; 32] { + pub fn into_bytes(self) -> [u8; 32] { + self.0 + } +} + +impl Into<[u8; 32]> for Secret { + fn into(self) -> [u8; 32] { self.0 } } @@ -19,10 +35,20 @@ mod tests { use super::*; #[test] - fn it_should_be_converted_into_a_generic_byte_array() { + fn it_should_be_created_from_a_preexisting_byte_array_key() { + let secret = Secret::from_bytes([0; 32]); + assert_eq!(secret, Secret([0u8; 32])); + } - let byte_array_32 = Secret::new([0; 32]); + #[test] + fn it_should_be_converted_into_a_byte_array_using_the_standard_trait() { + let byte_array_32: [u8; 32] = Secret::from_bytes([0; 32]).into(); + assert_eq!(byte_array_32, [0u8; 32]); + } - assert_eq!(byte_array_32.to_bytes(), [0u8; 32]); + #[test] + fn it_should_be_converted_into_a_byte_array() { + let byte_array_32_1 = Secret::from_bytes([0; 32]); + assert_eq!(byte_array_32_1.into_bytes(), [0u8; 32]); } -} \ No newline at end of file +} diff --git a/src/udp/server.rs b/src/udp/server.rs index 50a8ef9f9..7c1044731 100644 --- a/src/udp/server.rs +++ b/src/udp/server.rs @@ -7,7 +7,7 @@ use log::{debug, info}; use tokio::net::UdpSocket; use crate::tracker::tracker::TorrentTracker; -use crate::udp::{MAX_PACKET_SIZE}; +use crate::udp::MAX_PACKET_SIZE; use crate::udp::connection::secret::Secret; use crate::udp::packet_handler::PacketHandler; @@ -27,7 +27,7 @@ impl UdpServer { } pub async fn start(&self) { - let encryption_key = Secret::new(rand::Rng::gen(&mut rand::rngs::ThreadRng::default())); + let encryption_key = Secret::new(); let request_handler = Arc::new(PacketHandler::new(encryption_key)); From 90af290a05d64f4a0430f494ba3f7cfd52461471 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Sep 2022 18:24:52 +0100 Subject: [PATCH 85/90] refactor: extract EncodedConnectionIdData --- src/udp/connection/connection_id_data.rs | 54 ++----------------- src/udp/connection/connection_id_issuer.rs | 20 ++++--- .../connection/encoded_connection_id_data.rs | 46 ++++++++++++++++ src/udp/connection/mod.rs | 1 + 4 files changed, 60 insertions(+), 61 deletions(-) create mode 100644 src/udp/connection/encoded_connection_id_data.rs diff --git a/src/udp/connection/connection_id_data.rs b/src/udp/connection/connection_id_data.rs index 3acb80e3c..86425bec2 100644 --- a/src/udp/connection/connection_id_data.rs +++ b/src/udp/connection/connection_id_data.rs @@ -8,35 +8,13 @@ pub struct ConnectionIdData { } impl ConnectionIdData { - pub fn from_bytes(bytes: &[u8; 8]) -> Self { - let client_id = Self::extract_client_id(bytes); - let expiration_timestamp = Self::extract_timestamp(bytes); - Self { - client_id, - expiration_timestamp - } + pub fn client_id(&self) -> &ClientId { + &self.client_id } - pub fn to_bytes(&self) -> [u8; 8] { - let connection_id: Vec = [ - self.client_id.to_bytes().as_slice(), - self.expiration_timestamp.to_le_bytes().as_slice(), - ].concat(); - - let connection_as_array: [u8; 8] = connection_id.try_into().unwrap(); - - connection_as_array + pub fn expiration_timestamp(&self) -> &Timestamp32 { + &self.expiration_timestamp } - - fn extract_timestamp(decrypted_connection_id: &[u8; 8]) -> Timestamp32 { - let timestamp_bytes = &decrypted_connection_id[4..]; - let timestamp = Timestamp32::from_le_bytes(timestamp_bytes); - timestamp - } - - fn extract_client_id(decrypted_connection_id: &[u8; 8]) -> ClientId { - ClientId::from_bytes(&decrypted_connection_id[..4]) - } } #[cfg(test)] @@ -65,28 +43,4 @@ mod tests { assert_eq!(connection_id.expiration_timestamp, 0u32.into()); } - - #[test] - fn it_should_be_converted_to_a_byte_array() { - - let connection_id = ConnectionIdData { - client_id: ClientId::from_bytes(&[0u8; 4]), - expiration_timestamp: (u32::MAX).into(), - }; - - assert_eq!(connection_id.to_bytes(), [0, 0, 0, 0, 255, 255, 255, 255]); - } - - #[test] - fn it_should_be_instantiated_from_a_byte_array() { - - let connection_id = ConnectionIdData::from_bytes(&[0, 0, 0, 0, 255, 255, 255, 255]); - - let expected_connection_id = ConnectionIdData { - client_id: ClientId::from_bytes(&[0, 0, 0, 0]), - expiration_timestamp: (u32::MAX).into(), - }; - - assert_eq!(connection_id, expected_connection_id); - } } diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index a52173fed..5b6f4c4cb 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -2,11 +2,9 @@ use std::{net::SocketAddr, collections::hash_map::DefaultHasher}; use aquatic_udp_protocol::ConnectionId; -use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::Make, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData, encrypted_connection_id_data::EncryptedConnectionIdData}; +use super::{cypher::{BlowfishCypher, Cypher}, secret::Secret, timestamp_64::Timestamp64, client_id::Make, timestamp_32::Timestamp32, connection_id_data::{ConnectionIdData}, encrypted_connection_id_data::EncryptedConnectionIdData, encoded_connection_id_data::EncodedConnectionIdData}; pub trait ConnectionIdIssuer { - type Error; - fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId; fn is_connection_id_valid(&self, connection_id: &ConnectionId, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> bool; @@ -18,13 +16,13 @@ pub struct EncryptedConnectionIdIssuer { } impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { - type Error = &'static str; - fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { let connection_id_data = self.generate_connection_id_data(&remote_address, current_timestamp); - let encrypted_connection_id_data = self.encrypt_connection_id_data(&connection_id_data); + let encoded_connection_id_data: EncodedConnectionIdData = connection_id_data.into(); + + let encrypted_connection_id_data = self.encrypt_connection_id_data(&encoded_connection_id_data); self.pack_connection_id(encrypted_connection_id_data) } @@ -81,15 +79,15 @@ impl EncryptedConnectionIdIssuer { fn decrypt_connection_id_data(&self, encrypted_connection_id_data: &EncryptedConnectionIdData) -> ConnectionIdData { let decrypted_raw_data = self.cypher.decrypt(&encrypted_connection_id_data.bytes); - let connection_id_data = ConnectionIdData::from_bytes(&decrypted_raw_data); + let encoded_connection_id_data = EncodedConnectionIdData::from_bytes(&decrypted_raw_data); + + let connection_id_data: ConnectionIdData = encoded_connection_id_data.into(); connection_id_data } - fn encrypt_connection_id_data(&self, connection_id_data: &ConnectionIdData) -> EncryptedConnectionIdData { - let decrypted_raw_data = connection_id_data.to_bytes(); - - let encrypted_raw_data = self.cypher.encrypt(&decrypted_raw_data); + fn encrypt_connection_id_data(&self, encoded_connection_id_data: &EncodedConnectionIdData) -> EncryptedConnectionIdData { + let encrypted_raw_data = self.cypher.encrypt(&encoded_connection_id_data.as_bytes()); let encrypted_connection_id_data = EncryptedConnectionIdData::from_encrypted_bytes(&encrypted_raw_data); diff --git a/src/udp/connection/encoded_connection_id_data.rs b/src/udp/connection/encoded_connection_id_data.rs new file mode 100644 index 000000000..185de13dc --- /dev/null +++ b/src/udp/connection/encoded_connection_id_data.rs @@ -0,0 +1,46 @@ +use super::{client_id::ClientId, timestamp_32::Timestamp32, connection_id_data::ConnectionIdData}; + +/// The encoded version of ConnectionIdData to be use in the UPD tracker package field "connection_id" +pub struct EncodedConnectionIdData([u8; 8]); + +impl EncodedConnectionIdData { + pub fn from_bytes(bytes: &[u8; 8]) -> Self { + let mut sized_bytes_arr = [0u8; 8]; + sized_bytes_arr.copy_from_slice(&bytes[..8]); + Self(sized_bytes_arr) + } + + pub fn as_bytes(&self) -> &[u8; 8] { + &self.0 + } + + fn extract_client_id(&self) -> ClientId { + ClientId::from_bytes(&self.0[..4]) + } + + fn extract_expiration_timestamp(&self) -> Timestamp32 { + let timestamp_bytes = &self.0[4..]; + let timestamp = Timestamp32::from_le_bytes(timestamp_bytes); + timestamp + } +} + +impl From for ConnectionIdData { + fn from(encoded_connection_id_data: EncodedConnectionIdData) -> Self { + Self { + client_id: encoded_connection_id_data.extract_client_id(), + expiration_timestamp: encoded_connection_id_data.extract_expiration_timestamp() + } + } +} + +impl From for EncodedConnectionIdData { + fn from(connection_id_data: ConnectionIdData) -> Self { + let byte_vec: Vec = [ + connection_id_data.client_id.to_bytes().as_slice(), + connection_id_data.expiration_timestamp.to_le_bytes().as_slice(), + ].concat(); + let bytes: [u8; 8] = byte_vec.try_into().unwrap(); + EncodedConnectionIdData::from_bytes(&bytes) + } +} diff --git a/src/udp/connection/mod.rs b/src/udp/connection/mod.rs index d27a8268a..294db3a81 100644 --- a/src/udp/connection/mod.rs +++ b/src/udp/connection/mod.rs @@ -89,3 +89,4 @@ pub mod cypher; pub mod connection_id_issuer; pub mod connection_id_data; pub mod encrypted_connection_id_data; +pub mod encoded_connection_id_data; From 84ae58cba3e2947a8b19f021d0b240071407d806 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Sep 2022 21:34:41 +0100 Subject: [PATCH 86/90] refactor: rename methods As porposed by @WarmBeer here: https://github.com/torrust/torrust-tracker/pull/60#issuecomment-1241136421 Co-authored-by: Warm Beer --- src/udp/connection/encoded_connection_id_data.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/udp/connection/encoded_connection_id_data.rs b/src/udp/connection/encoded_connection_id_data.rs index 185de13dc..5a6a9f6c7 100644 --- a/src/udp/connection/encoded_connection_id_data.rs +++ b/src/udp/connection/encoded_connection_id_data.rs @@ -14,22 +14,21 @@ impl EncodedConnectionIdData { &self.0 } - fn extract_client_id(&self) -> ClientId { + fn to_client_id(&self) -> ClientId { ClientId::from_bytes(&self.0[..4]) } - fn extract_expiration_timestamp(&self) -> Timestamp32 { + fn to_expiration_timestamp(&self) -> Timestamp32 { let timestamp_bytes = &self.0[4..]; - let timestamp = Timestamp32::from_le_bytes(timestamp_bytes); - timestamp + Timestamp32::from_le_bytes(timestamp_bytes) } } impl From for ConnectionIdData { fn from(encoded_connection_id_data: EncodedConnectionIdData) -> Self { Self { - client_id: encoded_connection_id_data.extract_client_id(), - expiration_timestamp: encoded_connection_id_data.extract_expiration_timestamp() + client_id: encoded_connection_id_data.to_client_id(), + expiration_timestamp: encoded_connection_id_data.to_expiration_timestamp() } } } From ce45c5737088d3e9d67884e45c9465f228c9ca5a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Sep 2022 10:52:56 +0100 Subject: [PATCH 87/90] refactor: fix clippy warnings in upd::connection --- src/udp/connection/client_id.rs | 2 +- src/udp/connection/connection_id_issuer.rs | 34 ++++++++----------- .../encrypted_connection_id_data.rs | 8 ++--- src/udp/connection/secret.rs | 12 +++++-- src/udp/connection/timestamp_32.rs | 10 +++--- src/udp/connection/timestamp_64.rs | 3 +- 6 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/udp/connection/client_id.rs b/src/udp/connection/client_id.rs index 7320aa1e6..d652235bf 100644 --- a/src/udp/connection/client_id.rs +++ b/src/udp/connection/client_id.rs @@ -44,7 +44,7 @@ impl ClientId { } pub fn to_bytes(&self) -> [u8; 4] { - let bytes: [u8; 4] = self.value.clone().try_into().unwrap(); + let bytes: [u8; 4] = self.value; bytes } } diff --git a/src/udp/connection/connection_id_issuer.rs b/src/udp/connection/connection_id_issuer.rs index 5b6f4c4cb..c662abb42 100644 --- a/src/udp/connection/connection_id_issuer.rs +++ b/src/udp/connection/connection_id_issuer.rs @@ -18,7 +18,7 @@ pub struct EncryptedConnectionIdIssuer { impl ConnectionIdIssuer for EncryptedConnectionIdIssuer { fn new_connection_id(&self, remote_address: &SocketAddr, current_timestamp: Timestamp64) -> ConnectionId { - let connection_id_data = self.generate_connection_id_data(&remote_address, current_timestamp); + let connection_id_data = self.generate_connection_id_data(remote_address, current_timestamp); let encoded_connection_id_data: EncodedConnectionIdData = connection_id_data.into(); @@ -59,12 +59,10 @@ impl EncryptedConnectionIdIssuer { let expiration_timestamp: Timestamp32 = (current_timestamp + 120).try_into().unwrap(); - let connection_id_data = ConnectionIdData { + ConnectionIdData { client_id, expiration_timestamp - }; - - connection_id_data + } } fn pack_connection_id(&self, encrypted_connection_id_data: EncryptedConnectionIdData) -> ConnectionId { @@ -87,11 +85,8 @@ impl EncryptedConnectionIdIssuer { } fn encrypt_connection_id_data(&self, encoded_connection_id_data: &EncodedConnectionIdData) -> EncryptedConnectionIdData { - let encrypted_raw_data = self.cypher.encrypt(&encoded_connection_id_data.as_bytes()); - - let encrypted_connection_id_data = EncryptedConnectionIdData::from_encrypted_bytes(&encrypted_raw_data); - - encrypted_connection_id_data + let encrypted_raw_data = self.cypher.encrypt(encoded_connection_id_data.as_bytes()); + EncryptedConnectionIdData::from_encrypted_bytes(&encrypted_raw_data) } } @@ -106,8 +101,7 @@ mod tests { } fn new_issuer() -> EncryptedConnectionIdIssuer { - let issuer = EncryptedConnectionIdIssuer::new(cypher_secret_for_testing()); - issuer + EncryptedConnectionIdIssuer::new(cypher_secret_for_testing()) } #[test] @@ -137,7 +131,7 @@ mod tests { let after_more_than_two_minutes = now + (2*60) + 1; - assert_eq!(issuer.is_connection_id_valid(&connection_id, &client_addr, after_more_than_two_minutes), false); + assert!(!issuer.is_connection_id_valid(&connection_id, &client_addr, after_more_than_two_minutes)); } #[test] @@ -159,8 +153,8 @@ mod tests { #[test] fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_ip() { - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0001); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 1); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1); let now = 946684800u64; @@ -174,8 +168,8 @@ mod tests { #[test] fn it_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() { - let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); - let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002); + let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1); + let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 2); let now = 946684800u64; @@ -192,13 +186,13 @@ mod tests { let issuer = new_issuer(); // Generate connection id for a given client - let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001); + let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1); let now = 946684800u64; let connection_id = issuer.new_connection_id(&client_addr, now); // Verify the connection id with a different client address - let different_client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 0002); + let different_client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 2); - assert_eq!(issuer.is_connection_id_valid(&connection_id, &different_client_addr, now), false); + assert!(!issuer.is_connection_id_valid(&connection_id, &different_client_addr, now)); } } \ No newline at end of file diff --git a/src/udp/connection/encrypted_connection_id_data.rs b/src/udp/connection/encrypted_connection_id_data.rs index f32702883..4149813e2 100644 --- a/src/udp/connection/encrypted_connection_id_data.rs +++ b/src/udp/connection/encrypted_connection_id_data.rs @@ -5,13 +5,13 @@ pub struct EncryptedConnectionIdData { impl EncryptedConnectionIdData { pub fn from_encrypted_bytes(encrypted_bytes: &[u8; 8]) -> Self { - Self { bytes: encrypted_bytes.clone() } + Self { bytes: *encrypted_bytes } } } -impl Into for EncryptedConnectionIdData { - fn into(self) -> i64 { - i64::from_le_bytes(self.bytes) +impl From for i64 { + fn from(value: EncryptedConnectionIdData) -> Self { + i64::from_le_bytes(value.bytes) } } diff --git a/src/udp/connection/secret.rs b/src/udp/connection/secret.rs index 658694f5f..601549859 100644 --- a/src/udp/connection/secret.rs +++ b/src/udp/connection/secret.rs @@ -24,9 +24,15 @@ impl Secret { } } -impl Into<[u8; 32]> for Secret { - fn into(self) -> [u8; 32] { - self.0 +impl Default for Secret { + fn default() -> Self { + Self::new() + } +} + +impl From for [u8; 32] { + fn from(secret: Secret) -> Self { + secret.0 } } diff --git a/src/udp/connection/timestamp_32.rs b/src/udp/connection/timestamp_32.rs index bf30f474e..f1e09612c 100644 --- a/src/udp/connection/timestamp_32.rs +++ b/src/udp/connection/timestamp_32.rs @@ -14,7 +14,7 @@ impl Timestamp32 { Self(timestamp) } - pub fn to_le_bytes(self: Self) -> [u8; 4] { + pub fn to_le_bytes(self) -> [u8; 4] { // Little Endian let mut bytes: [u8; 4] = [0u8; 4]; bytes.copy_from_slice(&self.0.to_le_bytes()[..4]); @@ -37,9 +37,9 @@ impl TryFrom for Timestamp32 { } } -impl Into for Timestamp32 { - fn into(self) -> Timestamp64 { - u64::from(self.0) +impl From for Timestamp64 { + fn from(timestamp32: Timestamp32) -> Self { + u64::from(timestamp32.0) } } @@ -86,7 +86,7 @@ mod tests { let timestamp32: Result = out_of_range_value.try_into(); - assert_eq!(timestamp32.is_err(), true); + assert!(timestamp32.is_err()); } #[test] diff --git a/src/udp/connection/timestamp_64.rs b/src/udp/connection/timestamp_64.rs index ab0092aaa..16bb8aa5f 100644 --- a/src/udp/connection/timestamp_64.rs +++ b/src/udp/connection/timestamp_64.rs @@ -3,7 +3,6 @@ pub type Timestamp64 = u64; pub fn timestamp_from_le_bytes(timestamp_bytes: &[u8]) -> Timestamp64 { // Little Endian - let timestamp = u64::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]); - timestamp + u64::from_le_bytes([timestamp_bytes[0], timestamp_bytes[1], timestamp_bytes[2], timestamp_bytes[3], 0, 0, 0, 0]) } From 9ad1ed798ad1afa2d0dad5b5d6d9078f4fa4a733 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Sep 2022 17:13:04 +0100 Subject: [PATCH 88/90] refactor: inject clock into PackerHandler --- src/protocol/clock.rs | 22 ++++++++++++---------- src/udp/packet_handler.rs | 14 ++++++++------ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/protocol/clock.rs b/src/protocol/clock.rs index dfef5d45b..501466404 100644 --- a/src/protocol/clock.rs +++ b/src/protocol/clock.rs @@ -1,20 +1,22 @@ -use std::time::{SystemTime}; +use std::time::SystemTime; -pub trait Clock { - fn now_as_timestamp(&self) -> u64; +pub type UnixTime = u64; + +/// A Clock which uses the UNIX time. +pub trait UnixClock { + fn now(&self) -> UnixTime; } -/// A [`Clock`] which uses the operating system to determine the time. -struct SystemClock; +/// A Clock which uses the operating system to determine the time. +pub struct SystemUnixClock; -impl Clock for SystemClock { - fn now_as_timestamp(&self) -> u64 { +impl UnixClock for SystemUnixClock { + fn now(&self) -> UnixTime { SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() } } /// It returns the current timestamp using the system clock. -pub fn current_timestamp() -> u64 { - let system_clock = SystemClock; - system_clock.now_as_timestamp() +pub fn current_timestamp() -> UnixTime { + SystemUnixClock.now() } diff --git a/src/udp/packet_handler.rs b/src/udp/packet_handler.rs index 09a616805..47ede0c97 100644 --- a/src/udp/packet_handler.rs +++ b/src/udp/packet_handler.rs @@ -11,17 +11,19 @@ use crate::udp::errors::ServerError; use crate::udp::request::AnnounceRequestWrapper; use crate::tracker::statistics::TrackerStatisticsEvent; use crate::tracker::tracker::TorrentTracker; -use crate::protocol::clock::current_timestamp; +use crate::protocol::clock::{SystemUnixClock, UnixClock}; pub struct PacketHandler { encrypted_connection_id_issuer: EncryptedConnectionIdIssuer, - // todo: inject also a crate::protocol::Clock in order to make it easier to test it. + clock: SystemUnixClock } impl PacketHandler { pub fn new(secret: Secret) -> Self { - let encrypted_connection_id_issuer = EncryptedConnectionIdIssuer::new(secret); - Self { encrypted_connection_id_issuer } + Self { + encrypted_connection_id_issuer: EncryptedConnectionIdIssuer::new(secret), + clock: SystemUnixClock, + } } pub async fn handle_packet(&self, remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Option { @@ -228,7 +230,7 @@ impl PacketHandler { } fn generate_new_connection_id(&self, remote_addr: &SocketAddr) -> ConnectionId { - let current_timestamp = current_timestamp(); + let current_timestamp = self.clock.now(); let connection_id = self.encrypted_connection_id_issuer.new_connection_id(remote_addr, current_timestamp); @@ -238,7 +240,7 @@ impl PacketHandler { } fn is_connection_id_valid(&self, connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { - let current_timestamp = current_timestamp(); + let current_timestamp = self.clock.now(); let valid = self.encrypted_connection_id_issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); From 0d45e207add755e13000697c670e877c89472b54 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Sep 2022 17:15:30 +0100 Subject: [PATCH 89/90] refactor: rename PacketHandler attribtues --- src/udp/packet_handler.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/udp/packet_handler.rs b/src/udp/packet_handler.rs index 47ede0c97..5ad51a02d 100644 --- a/src/udp/packet_handler.rs +++ b/src/udp/packet_handler.rs @@ -14,14 +14,14 @@ use crate::tracker::tracker::TorrentTracker; use crate::protocol::clock::{SystemUnixClock, UnixClock}; pub struct PacketHandler { - encrypted_connection_id_issuer: EncryptedConnectionIdIssuer, + connection_id_issuer: EncryptedConnectionIdIssuer, clock: SystemUnixClock } impl PacketHandler { pub fn new(secret: Secret) -> Self { Self { - encrypted_connection_id_issuer: EncryptedConnectionIdIssuer::new(secret), + connection_id_issuer: EncryptedConnectionIdIssuer::new(secret), clock: SystemUnixClock, } } @@ -232,7 +232,7 @@ impl PacketHandler { fn generate_new_connection_id(&self, remote_addr: &SocketAddr) -> ConnectionId { let current_timestamp = self.clock.now(); - let connection_id = self.encrypted_connection_id_issuer.new_connection_id(remote_addr, current_timestamp); + let connection_id = self.connection_id_issuer.new_connection_id(remote_addr, current_timestamp); debug!("new connection id: {:?}, current timestamp: {:?}", connection_id, current_timestamp); @@ -242,7 +242,7 @@ impl PacketHandler { fn is_connection_id_valid(&self, connection_id: &ConnectionId, remote_addr: &SocketAddr) -> bool { let current_timestamp = self.clock.now(); - let valid = self.encrypted_connection_id_issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); + let valid = self.connection_id_issuer.is_connection_id_valid(connection_id, remote_addr, current_timestamp); debug!("verify connection id: {:?}, current timestamp: {:?}, valid: {:?}", connection_id, current_timestamp, valid); From ab6a99fb3a7c5a54de5e07e82c123eb75b7492bf Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Sep 2022 18:12:16 +0100 Subject: [PATCH 90/90] refactor: replace deprecated crate for Blowfish cypher --- Cargo.lock | 125 +++++++++++------------------------ Cargo.toml | 4 +- src/main.rs | 2 - src/udp/connection/cypher.rs | 31 +++++---- 4 files changed, 59 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fd9c244e..10899d4ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,6 +202,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "bstr" version = "0.2.17" @@ -289,6 +299,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.3.1" @@ -747,12 +767,6 @@ dependencies = [ "syn", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "funty" version = "1.2.0" @@ -848,12 +862,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generic-array" version = "0.14.5" @@ -1065,6 +1073,15 @@ dependencies = [ "serde 1.0.137", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1304,7 +1321,7 @@ dependencies = [ "mime", "mime_guess", "quick-error", - "rand 0.8.5", + "rand", "safemem", "tempfile", "twoway", @@ -1358,7 +1375,7 @@ dependencies = [ "lexical", "num-bigint 0.4.3", "num-traits 0.2.15", - "rand 0.8.5", + "rand", "regex", "rust_decimal", "saturating", @@ -1762,29 +1779,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -1793,7 +1787,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", + "rand_core", ] [[package]] @@ -1803,24 +1797,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.3" @@ -1854,15 +1833,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.2.13" @@ -1933,19 +1903,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "rust-crypto" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" -dependencies = [ - "gcc", - "libc", - "rand 0.3.23", - "rustc-serialize", - "time 0.1.44", -] - [[package]] name = "rust-ini" version = "0.13.0" @@ -1969,12 +1926,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-serialize" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" - [[package]] name = "rustc_version" version = "0.2.3" @@ -2689,7 +2640,10 @@ dependencies = [ "async-trait", "binascii", "blake3", + "blowfish", + "byteorder", "chrono", + "cipher", "config", "criterion", "derive_more", @@ -2702,8 +2656,7 @@ dependencies = [ "r2d2", "r2d2_mysql", "r2d2_sqlite", - "rand 0.8.5", - "rust-crypto", + "rand", "serde 1.0.137", "serde_bencode", "serde_json", @@ -2772,7 +2725,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "sha-1 0.9.8", "thiserror", "url", @@ -2795,7 +2748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "rand 0.8.5", + "rand", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index 66c67cad9..a2ef536fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,9 @@ hex = "0.4.3" percent-encoding = "2.1.0" binascii = "0.1" blake3 = "1.3.1" -rust-crypto = "^0.2" +cipher = "0.4.3" +blowfish = "0.9.1" +byteorder = "1.4.3" openssl = { version = "0.10.41", features = ["vendored"] } diff --git a/src/main.rs b/src/main.rs index 6f1d1f078..963419f03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,6 @@ use torrust_tracker::logging; use torrust_tracker::setup; use torrust_tracker::tracker::tracker::TorrentTracker; -extern crate crypto; - #[tokio::main] async fn main() { const CONFIG_PATH: &str = "config.toml"; diff --git a/src/udp/connection/cypher.rs b/src/udp/connection/cypher.rs index 43ad277e0..a6e0fb94d 100644 --- a/src/udp/connection/cypher.rs +++ b/src/udp/connection/cypher.rs @@ -1,6 +1,9 @@ -use crypto::{blowfish::Blowfish, symmetriccipher::{BlockEncryptor, BlockDecryptor}}; - use super::secret::Secret; +use std::convert::TryInto; +use blowfish::{BlowfishLE, cipher::{KeyInit, BlockEncrypt, BlockDecrypt}, Blowfish}; +use byteorder::LittleEndian; +use cipher::generic_array::GenericArray; +use cipher::BlockSizeUser; pub trait Cypher { fn encrypt(&self, decrypted_bytes: &[u8; 8]) -> [u8; 8]; @@ -9,33 +12,34 @@ pub trait Cypher { } pub struct BlowfishCypher { - blowfish: Blowfish + blowfish: BlowfishLE } impl BlowfishCypher { pub fn new(secret: Secret) -> Self { - let blowfish = Blowfish::new(&secret.into_bytes()); - BlowfishCypher { - blowfish + Self { + blowfish: BlowfishLE::new_from_slice(&secret.into_bytes()).unwrap() } } } +type BlowfishArray = GenericArray as BlockSizeUser>::BlockSize>; + impl Cypher for BlowfishCypher { fn encrypt(&self, decrypted_bytes: &[u8; 8]) -> [u8; 8] { - let mut encrypted_bytes = [0u8; 8]; + let mut encrypted_bytes: BlowfishArray = BlowfishArray::from(*decrypted_bytes); - self.blowfish.encrypt_block(decrypted_bytes, &mut encrypted_bytes); + self.blowfish.encrypt_block(&mut encrypted_bytes); - encrypted_bytes + encrypted_bytes.try_into().unwrap() } fn decrypt(&self, encrypted_bytes: &[u8; 8]) -> [u8; 8] { - let mut decrypted_bytes = [0u8; 8]; + let mut decrypted_bytes: BlowfishArray = BlowfishArray::from(*encrypted_bytes); - self.blowfish.decrypt_block(encrypted_bytes, &mut decrypted_bytes); + self.blowfish.decrypt_block(&mut decrypted_bytes); - decrypted_bytes + decrypted_bytes.try_into().unwrap() } } @@ -43,7 +47,6 @@ impl Cypher for BlowfishCypher { mod tests { use crate::udp::connection::{secret::Secret, cypher::{BlowfishCypher, Cypher}}; - #[test] fn it_should_encrypt_and_decrypt_a_byte_array() { let secret = Secret::from_bytes([0u8;32]); @@ -56,6 +59,6 @@ mod tests { let decrypted_text = cypher.decrypt(&encrypted_text); - assert_eq!(decrypted_text, text); + assert_eq!(decrypted_text, [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8]); } } \ No newline at end of file