diff --git a/Cargo.lock b/Cargo.lock index c52f6767b..94188a64f 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" @@ -176,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" @@ -210,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" @@ -302,6 +340,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" @@ -342,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" @@ -352,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" @@ -428,6 +575,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common", + "subtle", ] [[package]] @@ -734,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" @@ -791,7 +945,7 @@ checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.1", ] [[package]] @@ -838,7 +992,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 1.0.1", "pin-project-lite", "socket2", "tokio", @@ -896,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" @@ -1311,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" @@ -1471,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" @@ -1574,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" @@ -1594,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" @@ -1706,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" @@ -1843,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" @@ -1860,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", ] @@ -1872,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", ] @@ -2086,6 +2338,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" @@ -2187,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", @@ -2216,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" @@ -2341,8 +2609,10 @@ dependencies = [ "aquatic_udp_protocol", "async-trait", "binascii", + "blake3", "chrono", "config", + "criterion", "derive_more", "fern", "futures", @@ -2547,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 554ba940d..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"] } @@ -28,6 +35,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/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); diff --git a/src/protocol/utils.rs b/src/protocol/utils.rs index feb88c908..63c47bc4d 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,9 +64,11 @@ 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_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,45 +94,45 @@ 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_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] - 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,25 +143,18 @@ 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; - use serde::Serialize; - - use crate::protocol::utils::get_connection_id; - #[warn(unused_imports)] use super::ser_instant; @@ -139,7 +169,7 @@ mod tests { use std::{thread, time}; - let t1 = time::Instant::now(); + let t1 = Instant::now(); let s = S { time: t1 }; @@ -152,4 +182,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 +}