diff --git a/Cargo.lock b/Cargo.lock index 2eb4626..207cf33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "base-x" version = "0.2.8" @@ -50,6 +56,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426" +[[package]] +name = "bstr" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.5.0" @@ -68,6 +86,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +[[package]] +name = "cast" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +dependencies = [ + "rustc_version", +] + [[package]] name = "cc" version = "1.0.66" @@ -107,6 +134,110 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" +[[package]] +name = "criterion" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools 0.10.0", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" +dependencies = [ + "cast", + "itertools 0.9.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" +dependencies = [ + "cfg-if 1.0.0", + "const_fn", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "csv" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "daemonize" version = "0.4.1" @@ -129,6 +260,12 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "fnv" version = "1.0.7" @@ -156,6 +293,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "half" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" + [[package]] name = "heck" version = "0.3.2" @@ -209,6 +352,24 @@ dependencies = [ "xmltree", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.7" @@ -257,6 +418,21 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "memoffset" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg", +] + [[package]] name = "nix" version = "0.14.1" @@ -282,18 +458,71 @@ dependencies = [ "libc", ] +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "plotters" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" + +[[package]] +name = "plotters-svg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" +dependencies = [ + "plotters-backend", +] + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -398,6 +627,31 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.4" @@ -407,6 +661,30 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] + +[[package]] +name = "regex-syntax" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -446,6 +724,21 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[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 = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "semver" version = "0.9.0" @@ -470,6 +763,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_cbor" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.123" @@ -712,6 +1015,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tinytemplate" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.1.1" @@ -804,6 +1117,7 @@ name = "vpncloud" version = "2.0.1" dependencies = [ "byteorder", + "criterion", "daemonize", "fnv", "igd", @@ -823,6 +1137,17 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "wasi" version = "0.10.1+wasi-snapshot-preview1" @@ -915,6 +1240,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index dab142f..5c2c5bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,12 +32,17 @@ smallvec = "1.6" [dev-dependencies] tempfile = "3" +criterion = "0.3" [features] default = ["nat"] bench = [] nat = ["igd"] +[[bench]] +name = "bench" +harness = false + [profile.release] lto = true diff --git a/benches/bench.rs b/benches/bench.rs new file mode 100644 index 0000000..5c2b676 --- /dev/null +++ b/benches/bench.rs @@ -0,0 +1,149 @@ +#![allow(dead_code, unused_macros, unused_imports)] +#[macro_use] extern crate serde; +#[macro_use] extern crate log; + +use criterion::{criterion_group, criterion_main, Criterion, Throughput}; + +use smallvec::smallvec; +use ring::aead; + +use std::str::FromStr; +use std::net::{SocketAddr, Ipv4Addr, SocketAddrV4, UdpSocket}; + +mod util { + include!("../src/util.rs"); +} +mod error { + include!("../src/error.rs"); +} +mod payload { + include!("../src/payload.rs"); +} +mod types { + include!("../src/types.rs"); +} +mod table { + include!("../src/table.rs"); +} +mod crypto_core { + include!("../src/crypto/core.rs"); +} + +pub use error::Error; +use util::{MockTimeSource, MsgBuffer}; +use types::{Address, Range}; +use table::{ClaimTable}; +use payload::{Packet, Frame, Protocol}; +use crypto_core::{create_dummy_pair, EXTRA_LEN}; + +fn udp_send(c: &mut Criterion) { + let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); + let data = [0; 1400]; + let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1); + let mut g = c.benchmark_group("udp_send"); + g.throughput(Throughput::Bytes(1400)); + g.bench_function("udp_send", |b| { + b.iter(|| sock.send_to(&data, &addr).unwrap()); + }); + g.finish(); +} + +fn decode_ipv4(c: &mut Criterion) { + let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2]; + let mut g = c.benchmark_group("payload"); + g.throughput(Throughput::Bytes(1400)); + g.bench_function("decode_ipv4", |b| { + b.iter(|| Packet::parse(&data).unwrap()); + }); + g.finish(); +} + +fn decode_ipv6(c: &mut Criterion) { + let data = [ + 0x60, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5, + 4, 3, 2, 1 + ]; + let mut g = c.benchmark_group("payload"); + g.throughput(Throughput::Bytes(1400)); + g.bench_function("decode_ipv6", |b| { + b.iter(|| Packet::parse(&data).unwrap()); + }); + g.finish(); +} + +fn decode_ethernet(c: &mut Criterion) { + let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]; + let mut g = c.benchmark_group("payload"); + g.throughput(Throughput::Bytes(1400)); + g.bench_function("decode_ethernet", |b| { + b.iter(|| Frame::parse(&data).unwrap()); + }); + g.finish(); +} + +fn decode_ethernet_with_vlan(c: &mut Criterion) { + let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0, 4, 210, 1, 2, 3, 4, 5, 6, 7, 8]; + let mut g = c.benchmark_group("payload"); + g.throughput(Throughput::Bytes(1400)); + g.bench_function("decode_ethernet_with_vlan", |b| { + b.iter(|| Frame::parse(&data).unwrap()); + }); + g.finish(); +} + +fn lookup_warm(c: &mut Criterion) { + let mut table = ClaimTable::::new(60, 60); + let addr = Address::from_str("1.2.3.4").unwrap(); + table.cache(addr, SocketAddr::from_str("1.2.3.4:3210").unwrap()); + let mut g = c.benchmark_group("table"); + g.throughput(Throughput::Bytes(1400)); + g.bench_function("lookup_warm", |b| { + b.iter(|| table.lookup(addr)); + }); + g.finish(); +} + +fn lookup_cold(c: &mut Criterion) { + let mut table = ClaimTable::::new(60, 60); + let addr = Address::from_str("1.2.3.4").unwrap(); + table.set_claims(SocketAddr::from_str("1.2.3.4:3210").unwrap(), smallvec![Range::from_str("1.2.3.4/32").unwrap()]); + let mut g = c.benchmark_group("table"); + g.throughput(Throughput::Bytes(1400)); + g.bench_function("lookup_cold", |b| { + b.iter(|| { + table.clear_cache(); + table.lookup(addr) + }); + }); + g.finish(); +} + +fn crypto_bench(c: &mut Criterion, algo: &'static aead::Algorithm) { + let mut buffer = MsgBuffer::new(EXTRA_LEN); + buffer.set_length(1400); + let (mut sender, mut receiver) = create_dummy_pair(algo); + let mut g = c.benchmark_group("crypto"); + g.throughput(Throughput::Bytes(2*1400)); + g.bench_function(format!("{:?}", algo), |b| { + b.iter(|| { + sender.encrypt(&mut buffer); + receiver.decrypt(&mut buffer).unwrap(); + }); + }); + g.finish() +} + +fn crypto_chacha20(c: &mut Criterion) { + crypto_bench(c, &aead::CHACHA20_POLY1305) +} + +fn crypto_aes128(c: &mut Criterion) { + crypto_bench(c, &aead::AES_128_GCM) +} + +fn crypto_aes256(c: &mut Criterion) { + crypto_bench(c, &aead::AES_256_GCM) +} + +criterion_group!(benches, udp_send, decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan, lookup_cold, lookup_warm, crypto_chacha20, crypto_aes128, crypto_aes256); +criterion_main!(benches); \ No newline at end of file diff --git a/src/crypto/core.rs b/src/crypto/core.rs index 9afad84..da0ad01 100644 --- a/src/crypto/core.rs +++ b/src/crypto/core.rs @@ -1,41 +1,41 @@ -//! This module implements a crypto core for encrypting and decrypting message streams -//! -//! The crypto core only encrypts and decrypts messages, using given keys. Negotiating and rotating the keys is out of -//! scope of the crypto core. The crypto core assumes that the remote node will always have the necessary key to decrypt -//! the message. -//! -//! The crypto core encrypts messages in place, writes some extra data (key id and nonce) into a given space and -//! includes the given header data in the authentication tag. When decrypting messages, the crypto core reads the extra -//! data, uses the key id to find the right key to decrypting the message and then decrypts the message, using the given -//! nonce and including the given header data in the verification of the authentication tag. -//! -//! While the core only uses a single key at a time for encrypting messages, it is ready to decrypt messages based on -//! one of 4 stored keys (the encryption key being one of them). An external key rotation is responsible for adding the -//! key to the remote peer before switching to the key on the local peer for encryption. -//! -//! As mentioned, the encryption and decryption works in place. Therefore the parameter payload_and_tag contains (when -//! decrypting) or provides space for (when encrypting) the payload and the authentication tag. When encrypting, that -//! means, that the last TAG_LEN bytes of payload_and_tag must be reserved for the tag and must not contain payload -//! bytes. -//! -//! The nonce is a value of 12 bytes (192 bits). Since both nodes can use the same key for encryption, the most -//! significant byte (msb) of the nonce is initialized differently on both peers: one peer uses the value 0x00 and the -//! other one 0x80. That means that the nonce space is essentially divided in two halves, one for each node. -//! -//! To save space and keep the encrypted data aligned to 64 bits, not all bytes of the nonce are transferred. Instead, -//! only 7 bytes are included in messages (another byte is used for the key id, hence 64 bit alignment). The rest of the -//! nonce is deduced by the nodes: All other bytes are assumed to be 0x00, except for the most significant byte, which -//! is assumed to be the opposite ones own msb. This has two nice effects: -//! 1) Long before the nonce could theoretically repeat, the messages can no longer be decrypted by the peer as the -//! higher bytes are no longer zero as assumed. -//! 2) By deducing the msb to be the opposite of ones own msb, it is no longer possible for an attacker to redirect a -//! message back to the sender because then the assumed nonce will be wrong and the message fails to decrypt. Otherwise, -//! this could lead to problems as nodes would be able to accidentally decrypt their own messages. -//! -//! In order to be resistent against replay attacks but allow for reordering of messages, the crypto core uses nonce -//! pinning. For every active key, the biggest nonce seen so far is being tracked. Every second, the biggest nonce seen -//! one second ago plus 1 becomes the minimum nonce that is accepted for that key. That means, that reordering can -//! happen within one second but after a second, old messages will not be accepted anymore. +// This module implements a crypto core for encrypting and decrypting message streams +// +// The crypto core only encrypts and decrypts messages, using given keys. Negotiating and rotating the keys is out of +// scope of the crypto core. The crypto core assumes that the remote node will always have the necessary key to decrypt +// the message. +// +// The crypto core encrypts messages in place, writes some extra data (key id and nonce) into a given space and +// includes the given header data in the authentication tag. When decrypting messages, the crypto core reads the extra +// data, uses the key id to find the right key to decrypting the message and then decrypts the message, using the given +// nonce and including the given header data in the verification of the authentication tag. +// +// While the core only uses a single key at a time for encrypting messages, it is ready to decrypt messages based on +// one of 4 stored keys (the encryption key being one of them). An external key rotation is responsible for adding the +// key to the remote peer before switching to the key on the local peer for encryption. +// +// As mentioned, the encryption and decryption works in place. Therefore the parameter payload_and_tag contains (when +// decrypting) or provides space for (when encrypting) the payload and the authentication tag. When encrypting, that +// means, that the last TAG_LEN bytes of payload_and_tag must be reserved for the tag and must not contain payload +// bytes. +// +// The nonce is a value of 12 bytes (192 bits). Since both nodes can use the same key for encryption, the most +// significant byte (msb) of the nonce is initialized differently on both peers: one peer uses the value 0x00 and the +// other one 0x80. That means that the nonce space is essentially divided in two halves, one for each node. +// +// To save space and keep the encrypted data aligned to 64 bits, not all bytes of the nonce are transferred. Instead, +// only 7 bytes are included in messages (another byte is used for the key id, hence 64 bit alignment). The rest of the +// nonce is deduced by the nodes: All other bytes are assumed to be 0x00, except for the most significant byte, which +// is assumed to be the opposite ones own msb. This has two nice effects: +// 1) Long before the nonce could theoretically repeat, the messages can no longer be decrypted by the peer as the +// higher bytes are no longer zero as assumed. +// 2) By deducing the msb to be the opposite of ones own msb, it is no longer possible for an attacker to redirect a +// message back to the sender because then the assumed nonce will be wrong and the message fails to decrypt. Otherwise, +// this could lead to problems as nodes would be able to accidentally decrypt their own messages. +// +// In order to be resistent against replay attacks but allow for reordering of messages, the crypto core uses nonce +// pinning. For every active key, the biggest nonce seen so far is being tracked. Every second, the biggest nonce seen +// one second ago plus 1 becomes the minimum nonce that is accepted for that key. That means, that reordering can +// happen within one second but after a second, old messages will not be accepted anymore. use byteorder::{ReadBytesExt, WriteBytesExt}; use ring::{ @@ -454,37 +454,3 @@ mod tests { assert!(speed > 10.0); } } - -#[cfg(feature = "bench")] -mod benches { - - use super::*; - use test::Bencher; - - fn crypto_bench(b: &mut Bencher, algo: &'static aead::Algorithm) { - let mut buffer = MsgBuffer::new(EXTRA_LEN); - buffer.set_length(1400); - let (mut sender, mut receiver) = create_dummy_pair(algo); - b.iter(|| { - sender.encrypt(&mut buffer); - receiver.decrypt(&mut buffer).unwrap(); - }); - b.bytes = 1400; - } - - - #[bench] - fn crypto_chacha20(b: &mut Bencher) { - crypto_bench(b, &aead::CHACHA20_POLY1305) - } - - #[bench] - fn crypto_aes128(b: &mut Bencher) { - crypto_bench(b, &aead::AES_128_GCM) - } - - #[bench] - fn crypto_aes256(b: &mut Bencher) { - crypto_bench(b, &aead::AES_256_GCM) - } -} diff --git a/src/crypto/init.rs b/src/crypto/init.rs index 2895294..77688a5 100644 --- a/src/crypto/init.rs +++ b/src/crypto/init.rs @@ -1,54 +1,54 @@ -//! This module implements a 3-way handshake to initialize an authenticated and encrypted connection. -//! -//! The handshake assumes that each node has a asymmetric Curve 25519 key pair as well as a list of trusted public keys -//! and a set of supported crypto algorithms as well as the expected speed when using them. If successful, the handshake -//! will negotiate a crypto algorithm to use and a common ephemeral symmetric key and exchange a given payload between -//! the nodes. -//! -//! The handshake consists of 3 stages, "ping", "pong" and "peng". In the following description, the node that initiates -//! the connection is named "A" and the other node is named "B". Since a lot of things are going on in parallel in the -//! handshake, those aspects are described separately in the following paragraphs. -//! -//! Every message contains the node id of the sender. If a node receives a message with its own node id, it just ignores -//! it and closes the connection. This is the way nodes avoid to connect to themselves as it is not trivial for a node -//! to know its own addresses (especially in the case of NAT). -//! -//! All initialization messages are signed by the asymmetric key of the sender. Also the messages indicate the public -//! key being used, so the receiver can use the correct public key to verify the signature. The public key itself is not -//! attached to the message for privacy reasons (the public key is stable over multiple restarts while the node id is -//! only valid for a single run). Instead, a 2 byte salt value as well as the last 2 bytes of the salted sha 2 hash of -//! the public key are used to identify the public key. This way, a receiver that trusts this public key can identify -//! it but a random observer can't. If the public key is unknown or the signature can't be verified, the message is -//! ignored. -//! -//! Every message contains a byte that specifies the stage (ping = 1, pong = 2, peng = 3). If a message with an -//! unexpected stage is received, it is ignored and the last message that has been sent is repeated. There is only one -//! exception to this rule: if a "pong" message is expected, but a "ping" message is received instead AND the node id of -//! the sender is greater than the node id of the receiver, the receiving node will reset its state and assume the role -//! of a receiver of the initialization (i.e. "B"). This is used to "negotiate" the roles A and B when both nodes -//! initiate the connection in parallel and think they are A. -//! -//! Upon connection creation, both nodes create a random ephemeral ECDH key pair and exchange the public keys in the -//! ping and pong messages. A sends the ping message to B containing A's public key and B replies with a pong message -//! containing B's public key. That means, that after receiving the ping message B can calculate the shared key material -//! and after receiving the pong message A can calculate the shared key material. -//! -//! The ping message and the pong message contain a set of supported crypto algorithms together with the estimated -//! speeds of the algorithms. When B receives a ping message, or A receives a pong message, it can combine this -//! information with its own algorithm list and select the algorithm with the best expected speed for the crypto core. -//! -//! The pong and peng message contain the payload that the nodes want to exchange in the initialization phase apart from -//! the cryptographic initialization. This payload is encoded according to the application and encrypted using the key -//! material and the crypto algorithm that have been negotiated via the ping and pong messages. The pong message, -//! therefore contains information to set up symmetric encryption as well as a part that is already encrypted. -//! -//! The handshake ends for A after sending the peng message and for B after receiving this message. At this time both -//! nodes initialize the connection using the payload and enter normal operation. The negotiated crypto core is used for -//! future communication and the key rotation is started. Since the peng message can be lost, A needs to keep the -//! initialization state in order to repeat a lost peng message. After one second, A removes that state. -//! -//! Once every second, both nodes check whether they have already finished the initialization. If not, they repeat their -//! last message. After 5 seconds, the initialization is aborted as failed. +// This module implements a 3-way handshake to initialize an authenticated and encrypted connection. +// +// The handshake assumes that each node has a asymmetric Curve 25519 key pair as well as a list of trusted public keys +// and a set of supported crypto algorithms as well as the expected speed when using them. If successful, the handshake +// will negotiate a crypto algorithm to use and a common ephemeral symmetric key and exchange a given payload between +// the nodes. +// +// The handshake consists of 3 stages, "ping", "pong" and "peng". In the following description, the node that initiates +// the connection is named "A" and the other node is named "B". Since a lot of things are going on in parallel in the +// handshake, those aspects are described separately in the following paragraphs. +// +// Every message contains the node id of the sender. If a node receives a message with its own node id, it just ignores +// it and closes the connection. This is the way nodes avoid to connect to themselves as it is not trivial for a node +// to know its own addresses (especially in the case of NAT). +// +// All initialization messages are signed by the asymmetric key of the sender. Also the messages indicate the public +// key being used, so the receiver can use the correct public key to verify the signature. The public key itself is not +// attached to the message for privacy reasons (the public key is stable over multiple restarts while the node id is +// only valid for a single run). Instead, a 2 byte salt value as well as the last 2 bytes of the salted sha 2 hash of +// the public key are used to identify the public key. This way, a receiver that trusts this public key can identify +// it but a random observer can't. If the public key is unknown or the signature can't be verified, the message is +// ignored. +// +// Every message contains a byte that specifies the stage (ping = 1, pong = 2, peng = 3). If a message with an +// unexpected stage is received, it is ignored and the last message that has been sent is repeated. There is only one +// exception to this rule: if a "pong" message is expected, but a "ping" message is received instead AND the node id of +// the sender is greater than the node id of the receiver, the receiving node will reset its state and assume the role +// of a receiver of the initialization (i.e. "B"). This is used to "negotiate" the roles A and B when both nodes +// initiate the connection in parallel and think they are A. +// +// Upon connection creation, both nodes create a random ephemeral ECDH key pair and exchange the public keys in the +// ping and pong messages. A sends the ping message to B containing A's public key and B replies with a pong message +// containing B's public key. That means, that after receiving the ping message B can calculate the shared key material +// and after receiving the pong message A can calculate the shared key material. +// +// The ping message and the pong message contain a set of supported crypto algorithms together with the estimated +// speeds of the algorithms. When B receives a ping message, or A receives a pong message, it can combine this +// information with its own algorithm list and select the algorithm with the best expected speed for the crypto core. +// +// The pong and peng message contain the payload that the nodes want to exchange in the initialization phase apart from +// the cryptographic initialization. This payload is encoded according to the application and encrypted using the key +// material and the crypto algorithm that have been negotiated via the ping and pong messages. The pong message, +// therefore contains information to set up symmetric encryption as well as a part that is already encrypted. +// +// The handshake ends for A after sending the peng message and for B after receiving this message. At this time both +// nodes initialize the connection using the payload and enter normal operation. The negotiated crypto core is used for +// future communication and the key rotation is started. Since the peng message can be lost, A needs to keep the +// initialization state in order to repeat a lost peng message. After one second, A removes that state. +// +// Once every second, both nodes check whether they have already finished the initialization. If not, they repeat their +// last message. After 5 seconds, the initialization is aborted as failed. use super::{ diff --git a/src/crypto/rotate.rs b/src/crypto/rotate.rs index e11f0ff..e9dc138 100644 --- a/src/crypto/rotate.rs +++ b/src/crypto/rotate.rs @@ -1,29 +1,29 @@ -//! This module implements a turn based key rotation. -//! -//! The main idea is that both peers periodically create ecdh key pairs and exchange their public keys to create -//! common key material. There are always two separate ecdh handshakes going on: one initiated by each peer. -//! However, one handshake is always one step ahead of the other. That means that every message being sent contains a -//! public key from step 1 of the handshake "proposed key" and a public key from step 2 of the handshake "confirmed -//! key" (all messages except first message). -//! -//! When receiving a message from the peer, the node will create a new ecdh key pair and perform the key -//! calculation for the proposed key. The peer will store the public key for the confirmation as pending to be -//! confirmed in the next cycle. Also, if the message contains a confirmation (all but the very first message do), -//! the node will use the stored private key to perform the ecdh key calculation and emit that key to be used in -//! the crypto stream. -//! -//! Upon each cycle, a node first checks if it still has a proposed key that has not been confirmed by the remote -//! peer. If so, a message must have been lost and the whole last message including the proposed key as well as the -//! last confirmed key is being resent. If no proposed key is stored, the node will create a new ecdh key pair, and -//! store the private key as proposed key. It then sends out a message containing the public key as proposal, as -//! well as confirming the pending key. This key is also emitted to be added to the crypto stream but not to be -//! used for encrypting. -//! -//! Monotonically increasing message ids guard the communication from message duplication and also serve as -//! identifiers for the keys to be used in the crypto stream. Since the keys are rotating, the last 2 bits of the -//! id are enough to identify the key. -//! -//! The whole communication is sent via the crypto stream and is therefore encrypted and protected against tampering. +// This module implements a turn based key rotation. +// +// The main idea is that both peers periodically create ecdh key pairs and exchange their public keys to create +// common key material. There are always two separate ecdh handshakes going on: one initiated by each peer. +// However, one handshake is always one step ahead of the other. That means that every message being sent contains a +// public key from step 1 of the handshake "proposed key" and a public key from step 2 of the handshake "confirmed +// key" (all messages except first message). +// +// When receiving a message from the peer, the node will create a new ecdh key pair and perform the key +// calculation for the proposed key. The peer will store the public key for the confirmation as pending to be +// confirmed in the next cycle. Also, if the message contains a confirmation (all but the very first message do), +// the node will use the stored private key to perform the ecdh key calculation and emit that key to be used in +// the crypto stream. +// +// Upon each cycle, a node first checks if it still has a proposed key that has not been confirmed by the remote +// peer. If so, a message must have been lost and the whole last message including the proposed key as well as the +// last confirmed key is being resent. If no proposed key is stored, the node will create a new ecdh key pair, and +// store the private key as proposed key. It then sends out a message containing the public key as proposal, as +// well as confirming the pending key. This key is also emitted to be added to the crypto stream but not to be +// used for encrypting. +// +// Monotonically increasing message ids guard the communication from message duplication and also serve as +// identifiers for the keys to be used in the crypto stream. Since the keys are rotating, the last 2 bits of the +// id are enough to identify the key. +// +// The whole communication is sent via the crypto stream and is therefore encrypted and protected against tampering. use super::{Error, Key, MsgBuffer}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; diff --git a/src/main.rs b/src/main.rs index a37baf9..97f39e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,13 +2,10 @@ // Copyright (C) 2015-2020 Dennis Schwerdel // This software is licensed under GPL-3 or newer (see LICENSE.md) -#![cfg_attr(feature = "bench", feature(test))] - #[macro_use] extern crate log; #[macro_use] extern crate serde; #[cfg(test)] extern crate tempfile; -#[cfg(feature = "bench")] extern crate test; #[macro_use] pub mod util; diff --git a/src/net.rs b/src/net.rs index 296c6e9..87e89e7 100644 --- a/src/net.rs +++ b/src/net.rs @@ -132,19 +132,4 @@ impl Socket for MockSocket { fn address(&self) -> Result { Ok(self.address) } -} - -#[cfg(feature = "bench")] -mod bench { - use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket}; - use test::Bencher; - - #[bench] - fn udp_send(b: &mut Bencher) { - let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); - let data = [0; 1400]; - let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1); - b.iter(|| sock.send_to(&data, &addr).unwrap()); - b.bytes = 1400; - } -} +} \ No newline at end of file diff --git a/src/payload.rs b/src/payload.rs index 25d7e05..84fdbd3 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -77,26 +77,6 @@ fn decode_invalid_frame() { assert!(Frame::parse(&[6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0x00]).is_err()); } -#[cfg(feature = "bench")] -mod bench_ethernet { - use super::*; - use test::Bencher; - - #[bench] - fn decode_ethernet(b: &mut Bencher) { - let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]; - b.iter(|| Frame::parse(&data).unwrap()); - b.bytes = 1400; - } - - #[bench] - fn decode_ethernet_with_vlan(b: &mut Bencher) { - let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0, 4, 210, 1, 2, 3, 4, 5, 6, 7, 8]; - b.iter(|| Frame::parse(&data).unwrap()); - b.bytes = 1400; - } -} - /// An IP packet dissector /// /// This dissector is able to extract the source and destination ip addresses of ipv4 packets and @@ -176,28 +156,4 @@ fn decode_invalid_packet() { 4, 3, 2 ]) .is_err()); -} - - -#[cfg(feature = "bench")] -mod bench_ip { - use super::*; - use test::Bencher; - - #[bench] - fn decode_ipv4(b: &mut Bencher) { - let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2]; - b.iter(|| Packet::parse(&data).unwrap()); - b.bytes = 1400; - } - - #[bench] - fn decode_ipv6(b: &mut Bencher) { - let data = [ - 0x60, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, - 5, 4, 3, 2, 1 - ]; - b.iter(|| Packet::parse(&data).unwrap()); - b.bytes = 1400; - } -} +} \ No newline at end of file diff --git a/src/table.rs b/src/table.rs index cefa194..dd0c8bd 100644 --- a/src/table.rs +++ b/src/table.rs @@ -44,6 +44,10 @@ impl ClaimTable { self.cache.insert(addr, CacheValue { peer, timeout: TS::now() + self.cache_timeout as Time }); } + pub fn clear_cache(&mut self) { + self.cache.clear() + } + pub fn set_claims(&mut self, peer: SocketAddr, mut claims: RangeList) { for entry in &mut self.claims { if entry.peer == peer { @@ -148,37 +152,4 @@ impl ClaimTable { } } -// TODO: test - -#[cfg(feature = "bench")] -mod bench { - use super::*; - use crate::util::MockTimeSource; - - use smallvec::smallvec; - use std::str::FromStr; - use test::Bencher; - - #[bench] - fn lookup_warm(b: &mut Bencher) { - let mut table = ClaimTable::::new(60, 60); - let addr = Address::from_str("1.2.3.4").unwrap(); - table.cache(addr, SocketAddr::from_str("1.2.3.4:3210").unwrap()); - b.iter(|| table.lookup(addr)); - b.bytes = 1400; - } - - #[bench] - fn lookup_cold(b: &mut Bencher) { - let mut table = ClaimTable::::new(60, 60); - let addr = Address::from_str("1.2.3.4").unwrap(); - table.set_claims(SocketAddr::from_str("1.2.3.4:3210").unwrap(), smallvec![ - Range::from_str("1.2.3.4/32").unwrap() - ]); - b.iter(|| { - table.cache.clear(); - table.lookup(addr) - }); - b.bytes = 1400; - } -} +// TODO: test \ No newline at end of file