partly working implementation

This commit is contained in:
Nils Andreas Svee committed 2022-09-17 14:39:27 +02:00
1 parent 27aa972fe6
commit b147006a69
13 files changed
+1406 -542

No files matched your search

Generated
+314 -107
View File
@@ -4,13 +4,39 @@ version = 3
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@@ -26,6 +52,29 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bindgen"
version = "0.58.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -44,13 +93,28 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bincode", "bincode",
"byteorder", "byteorder",
"etherparse",
"libc", "libc",
"neli", "neli",
"nix", "nix",
"pnet", "rand",
"pnet_transport", "rust-uci",
"serde", "serde",
"serde_bytes", ]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
] ]
[[package]] [[package]]
@@ -59,12 +123,80 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "cmake"
version = "0.1.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.7.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "etherparse"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcb08c4aab4e2985045305551e67126b43f1b6b136bc4e1cd87fb0327877a611"
dependencies = [
"arrayvec",
]
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.0" version = "0.3.0"
@@ -72,20 +204,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "ipnetwork" name = "hermit-abi"
version = "0.19.0" version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f84f1612606f3753f205a4e9a2efd6fe5b4c573a6269b2cc6c3003d44a0d127" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [ dependencies = [
"serde", "libc",
] ]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.132" version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "libloading"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libuci-sys"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "544c17966d9131e80b3616783974a31b526197ca5b56948bb130eeb6c04fbaf1"
dependencies = [
"bindgen",
"cmake",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.17" version = "0.4.17"
@@ -148,10 +318,20 @@ dependencies = [
] ]
[[package]] [[package]]
name = "no-std-net" name = "nom"
version = "0.6.0" version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@@ -160,93 +340,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pnet" name = "ppv-lite86"
version = "0.31.0" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0caaf5b11fd907ff15cf14a4477bfabca4b37ab9e447a4f8dead969a59cdafad" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
dependencies = [
"pnet_base",
"pnet_datalink",
"pnet_packet",
"pnet_transport",
]
[[package]]
name = "pnet_base"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d3a993d49e5fd5d4d854d6999d4addca1f72d86c65adf224a36757161c02b6"
dependencies = [
"no-std-net",
]
[[package]]
name = "pnet_datalink"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e466faf03a98ad27f6e15cd27a2b7cc89e73e640a43527742977bc503c37f8aa"
dependencies = [
"ipnetwork",
"libc",
"pnet_base",
"pnet_sys",
"winapi",
]
[[package]]
name = "pnet_macros"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48dd52a5211fac27e7acb14cfc9f30ae16ae0e956b7b779c8214c74559cef4c3"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "pnet_macros_support"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89de095dc7739349559913aed1ef6a11e73ceade4897dadc77c5e09de6740750"
dependencies = [
"pnet_base",
]
[[package]]
name = "pnet_packet"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc3b5111e697c39c8b9795b9fdccbc301ab696699e88b9ea5a4e4628978f495f"
dependencies = [
"glob",
"pnet_base",
"pnet_macros",
"pnet_macros_support",
]
[[package]]
name = "pnet_sys"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "328e231f0add6d247d82421bf3790b4b33b39c8930637f428eef24c4c6a90805"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "pnet_transport"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff597185e6f1f5671b3122e4dba892a1c73e17c17e723d7669bd9299cbe7f124"
dependencies = [
"libc",
"pnet_base",
"pnet_packet",
"pnet_sys",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
@@ -266,6 +363,36 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.6.0" version = "1.6.0"
@@ -283,6 +410,23 @@ version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rust-uci"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c664edb3ec081c1f2746960b04861354bbeaafdbb962fb8789d6ded5153ac3bb"
dependencies = [
"libc",
"libuci-sys",
"log",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.144" version = "1.0.144"
@@ -292,15 +436,6 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde_bytes"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.144" version = "1.0.144"
@@ -312,6 +447,18 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.99" version = "1.0.99"
@@ -323,12 +470,63 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.3" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@@ -345,6 +543,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 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]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
+6 -3
View File
@@ -15,13 +15,16 @@ strip = "symbols"
[dependencies] [dependencies]
bincode = "1.3.3" bincode = "1.3.3"
byteorder = "1.4.3" byteorder = "1.4.3"
etherparse = "0.12.0"
libc = "0.2.132" libc = "0.2.132"
neli = "0.6.2" neli = "0.6.2"
nix = { version = "0.25.0", features = ["net", "socket", "time"] } nix = { version = "0.25.0", features = ["net", "socket", "time"] }
pnet = "0.31.0" rand = "0.8.5"
pnet_transport = "0.31.0" rust-uci = { version = "0.1.5", optional = true }
serde = { version = "1.0.144", features = ["derive"] } serde = { version = "1.0.144", features = ["derive"] }
serde_bytes = "0.11"
[features]
uci = ["dep:rust-uci"]
[patch.crates-io] [patch.crates-io]
nix = { path = "../nix" } nix = { path = "../nix" }
+113
View File
@@ -0,0 +1,113 @@
use crate::pinger::PingReply;
use crate::{Config, Utils};
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{Arc, Mutex};
#[derive(Copy, Clone)]
pub struct ReflectorStats {
pub(crate) down_ewma: f64,
pub(crate) up_ewma: f64,
pub(crate) last_receive_time_s: f64,
}
pub struct Baseliner {
pub(crate) config: Config,
pub(crate) owd_baseline: Arc<Mutex<HashMap<IpAddr, ReflectorStats>>>,
pub(crate) owd_recent: Arc<Mutex<HashMap<IpAddr, ReflectorStats>>>,
pub(crate) reselect_trigger: Sender<bool>,
pub(crate) stats_receiver: Receiver<PingReply>,
}
impl Baseliner {
pub fn run(&self) {
/*
* 135 seconds to decay to 50% for the slow factor and
* 0.4 seconds to decay to 50% for the fast factor.
* The fast one can be adjusted to tune, try anything from 0.01 to 3.0 to get more or less sensitivity
* with more sensitivity we respond faster to bloat, but are at risk from triggering due to lag spikes that
* aren't bloat related, with less sensitivity (bigger numbers) we smooth through quick spikes
* but take longer to respond to real bufferbloat
*/
let slow_factor = Utils::ewma_factor(self.config.tick_duration, 135.0);
let fast_factor = Utils::ewma_factor(self.config.tick_duration, 0.4);
loop {
let time_data = self.stats_receiver.recv().unwrap();
let mut owd_baseline_map = self.owd_baseline.lock().unwrap();
let mut owd_recent_map = self.owd_recent.lock().unwrap();
let mut owd_baseline_new = ReflectorStats {
down_ewma: time_data.down_time,
up_ewma: time_data.down_time,
last_receive_time_s: time_data.last_receive_time_s,
};
let mut owd_recent_new = ReflectorStats {
down_ewma: time_data.down_time,
up_ewma: time_data.down_time,
last_receive_time_s: time_data.last_receive_time_s,
};
let mut owd_baseline = owd_baseline_map
.entry(time_data.reflector)
.or_insert(owd_baseline_new);
let mut owd_recent = owd_recent_map
.entry(time_data.reflector)
.or_insert(owd_recent_new);
if time_data.last_receive_time_s - owd_baseline.last_receive_time_s > 30.0
|| time_data.last_receive_time_s - owd_recent.last_receive_time_s > 30.0
{
owd_baseline.down_ewma = time_data.down_time;
owd_baseline.up_ewma = time_data.up_time;
owd_baseline.last_receive_time_s = time_data.last_receive_time_s;
owd_recent.down_ewma = time_data.down_time;
owd_recent.up_ewma = time_data.up_time;
owd_recent.last_receive_time_s = time_data.last_receive_time_s;
}
owd_baseline.last_receive_time_s = time_data.last_receive_time_s;
owd_recent.last_receive_time_s = time_data.last_receive_time_s;
// if this reflection is more than 5 seconds higher than baseline... mark it no good and trigger a reselection
if time_data.up_time > owd_baseline.up_ewma + 5000.0
|| time_data.down_time > owd_baseline.down_ewma + 5000.0
{
owd_baseline.last_receive_time_s = time_data.last_receive_time_s - 60.0;
owd_recent.last_receive_time_s = time_data.last_receive_time_s - 60.0;
let _ = self.reselect_trigger.send(true);
} else {
owd_baseline.down_ewma = owd_baseline.down_ewma * slow_factor
+ (1.0 - slow_factor) * time_data.down_time;
owd_baseline.up_ewma =
owd_baseline.up_ewma * slow_factor + (1.0 - slow_factor) * time_data.up_time;
owd_recent.down_ewma =
owd_recent.down_ewma * fast_factor + (1.0 - fast_factor) * time_data.down_time;
owd_recent.up_ewma =
owd_recent.up_ewma * fast_factor + (1.0 - fast_factor) * time_data.up_time;
if owd_baseline.down_ewma > owd_recent.down_ewma {
owd_baseline.down_ewma = owd_recent.down_ewma;
}
if owd_baseline.up_ewma > owd_recent.up_ewma {
owd_baseline.up_ewma = owd_recent.up_ewma;
}
}
println!(
"Reflector {} up baseline = {} down baseline = {}",
time_data.reflector, owd_baseline.up_ewma, owd_baseline.down_ewma
);
println!(
"Reflector {} up recent = {} down recent = {}",
time_data.reflector, owd_recent.up_ewma, owd_recent.down_ewma
);
}
}
}
+76 -3
View File
@@ -1,17 +1,90 @@
use std::env; #[cfg(feature = "uci")]
use rust_uci::Uci;
use std::error::Error;
use std::fs::File;
use std::io::BufRead;
use std::net::IpAddr;
use std::path::Path;
use std::str::FromStr;
use std::{env, io};
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone)]
pub(crate) struct Config { pub(crate) struct Config {
pub(crate) download_interface: String, pub(crate) download_interface: String,
pub(crate) upload_interface: String, pub(crate) upload_interface: String,
pub(crate) reflector_list_file: String,
pub(crate) min_change_interval: f64,
pub(crate) num_reflectors: u8,
pub(crate) tick_duration: f64,
} }
impl Config { impl Config {
pub fn new() -> Self { pub fn new() -> Self {
Self { // First try to load from UCI (if enabled), if nothing is returned, use the environment instead.
match Self::load_from_uci() {
Some(config) => config,
None => Self {
download_interface: env::var("DOWNLOAD_INTERFACE") download_interface: env::var("DOWNLOAD_INTERFACE")
.expect("A download interface is required"), .expect("A download interface is required"),
upload_interface: env::var("UPLOAD_INTERFACE").expect("A upload interface is required"), upload_interface: env::var("UPLOAD_INTERFACE")
.expect("A upload interface is required"),
reflector_list_file: env::var("REFLECTOR_LIST_FILE")
.expect("A reflector list is required"),
min_change_interval: 0.5,
num_reflectors: 5,
tick_duration: 0.5,
},
} }
} }
#[cfg(feature = "uci")]
fn load_from_uci() -> Option<Self> {
let uci: Uci = Uci::new().expect("Couldn't create UCI instance");
Some(Self {
download_interface: "wan".to_string(),
upload_interface: "wan".to_string(),
reflector_list_file: "".to_string(),
min_change_interval: 0.5,
num_reflectors: 5,
tick_duration: 0.5,
})
}
#[cfg(not(feature = "uci"))]
fn load_from_uci() -> Option<Self> {
None
}
pub fn load_reflectors(&self) -> Result<Vec<IpAddr>, Box<dyn Error>> {
let lines = read_lines(self.reflector_list_file.clone())?;
let mut reflectors: Vec<IpAddr> = Vec::with_capacity(50);
let mut first = true;
for line in lines {
if first {
first = false;
continue;
}
let line = line?;
let columns: Vec<&str> = line.split(",").collect();
reflectors.push(IpAddr::from_str(columns[0])?);
}
Ok(reflectors)
}
} }
+37
View File
@@ -0,0 +1,37 @@
use std::error::Error;
use std::fmt;
use std::fmt::Display;
#[derive(Default, Debug)]
pub struct NoQdiscFoundError;
impl Error for NoQdiscFoundError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
// The compiler transparently casts `&sqlx::Error` into a `&dyn Error`
None
}
}
// Implement std::fmt::Display for AppError
impl Display for NoQdiscFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Couldn't find a CAKE qdisc for interface") // user-facing output
}
}
#[derive(Default, Debug)]
pub struct PingParseError;
impl Error for PingParseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
// The compiler transparently casts `&sqlx::Error` into a `&dyn Error`
None
}
}
// Implement std::fmt::Display for AppError
impl Display for PingParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Couldn't parse received ping packet") // user-facing output
}
}
+140 -64
View File
@@ -1,97 +1,173 @@
extern crate core;
mod baseliner;
mod cake; mod cake;
mod config; mod config;
mod error;
mod netlink; mod netlink;
mod pinger; mod pinger;
mod pinger_icmp; mod pinger_icmp;
mod pinger_icmp_ts; mod pinger_icmp_ts;
mod ratecontroller;
mod reflector_selector;
mod utils;
use crate::baseliner::{Baseliner, ReflectorStats};
use std::collections::HashMap;
use std::net::IpAddr; use std::net::IpAddr;
use std::process::ExitCode;
use std::str::FromStr; use std::str::FromStr;
use std::{process, thread, time}; use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::Duration;
use std::{process, thread};
use crate::config::Config; use crate::config::Config;
use crate::netlink::{find_interface, find_qdisc, get_interface_stats, set_qdisc_rate}; use crate::netlink::Netlink;
use crate::pinger::{PingListener, PingSender, Pinger}; use crate::pinger::{PingListener, PingSender, SocketType};
use crate::pinger_icmp::{PingerICMPEcho, PingerICMPEchoListener, PingerICMPEchoSender}; use crate::pinger_icmp::{PingerICMPEchoListener, PingerICMPEchoSender};
use crate::pinger_icmp_ts::PingerICMPTimestamps; use crate::pinger_icmp_ts::{PingerICMPTimestampListener, PingerICMPTimestampSender};
use crate::ratecontroller::{Ratecontroller, StatsDirection};
use crate::reflector_selector::ReflectorSelector;
use crate::utils::Utils;
#[macro_export] fn main() -> ExitCode {
macro_rules! error {
($a:expr,$b:expr) => {
match $a {
//Some(e) => return e,
Ok(e) => e,
Err(e) => {
println!("{}: {}", $b, $a);
}
}
};
}
fn main() {
println!("starting up"); println!("starting up");
let config = Config::new(); let config = Config::new();
test(&config); let mut reflectors = match config.load_reflectors() {
Ok(refl) => refl,
Err(e) => {
println!("Couldn't load reflectors: {}", e.to_string());
return ExitCode::FAILURE;
}
};
let mut reflectors: Vec<IpAddr> = Vec::new(); // The identifier field in ICMP is only 4 bytes
// so take the last 4 bytes of the PID as the ID
let id = (process::id() & 0xFFFF) as u16;
reflectors.push(IpAddr::from_str("9.9.9.9").unwrap()); // Create data structures shared by different threads
reflectors.push(IpAddr::from_str("9.9.9.10").unwrap()); let mut owd_baseline = Arc::new(Mutex::new(HashMap::<IpAddr, ReflectorStats>::new()));
let mut owd_recent = Arc::new(Mutex::new(HashMap::<IpAddr, ReflectorStats>::new()));
let mut reflector_peers_lock = Arc::new(Mutex::new(Vec::<IpAddr>::new()));
let mut reflector_pool = Vec::<IpAddr>::new();
let reflector_pool_size = reflectors.len();
get_interface_stats(config.upload_interface.as_str()).unwrap(); let default_reflectors = [
IpAddr::from_str("9.9.9.9").unwrap(),
IpAddr::from_str("8.238.120.14").unwrap(),
IpAddr::from_str("74.82.42.42").unwrap(),
IpAddr::from_str("194.242.2.2").unwrap(),
IpAddr::from_str("208.67.222.222").unwrap(),
IpAddr::from_str("94.140.14.14").unwrap(),
];
/*let mut pinger_receiver = match reflector_pool_size > 5 {
PingerICMPTimestamps::new((process::id() & 0xFFFF) as u16, reflectors.clone()); true => {
let mut pinger_sender = let mut peers = reflector_peers_lock.lock().unwrap();
PingerICMPTimestamps::new((process::id() & 0xFFFF) as u16, reflectors.clone());*/ peers.append(default_reflectors.to_vec().as_mut());
reflector_pool.append(reflectors.as_mut());
}
false => {
let mut peers = reflector_peers_lock.lock().unwrap();
peers.append(default_reflectors.to_vec().as_mut());
}
}
let mut pinger_receiver = let (baseliner_stats_sender, baseliner_stats_receiver) = channel();
PingerICMPEchoListener::new((process::id() & 0xFFF) as u16, reflectors.clone()); let (reselect_sender, reselect_receiver) = channel();
let mut pinger_sender = //let mut pinger_receiver = PingerICMPTimestampListener::new(id);
PingerICMPEchoSender::new((process::id() & 0xFFF) as u16, reflectors.clone()); //let mut pinger_sender = PingerICMPTimestampSender::new(id);
let mut pinger_receiver = PingerICMPEchoListener::new(id);
let mut pinger_sender = PingerICMPEchoSender::new(id);
let mut baseliner = Baseliner {
config: config.clone(),
owd_baseline: owd_baseline.clone(),
owd_recent: owd_recent.clone(),
reselect_trigger: reselect_sender.clone(),
stats_receiver: baseliner_stats_receiver,
};
let dl_intf = config.clone().download_interface;
let ul_intf = config.clone().upload_interface;
let down_ifindex = Netlink::find_interface(dl_intf.as_str()).unwrap();
let up_ifindex = Netlink::find_interface(ul_intf.as_str()).unwrap();
let down_qdisc = Netlink::find_qdisc(down_ifindex).unwrap();
let up_qdisc = Netlink::find_qdisc(up_ifindex).unwrap();
/* Set initial TC values to minimum
* so there should be no initial bufferbloat to
* fool the baseliner
*/
Netlink::set_qdisc_rate(down_qdisc, 5000).expect("Couldn't set ingress bandwidth");
Netlink::set_qdisc_rate(up_qdisc, 1000).expect("Couldn't set egress bandwidth");
sleep(Duration::new(0, 5e8 as u32));
let reflector_peers_lock_clone = reflector_peers_lock.clone();
let receiver_handle = thread::Builder::new() let receiver_handle = thread::Builder::new()
.name("receiver".to_string()) .name("receiver".to_string())
.spawn(move || pinger_receiver.listen()) .spawn(move || {
.unwrap(); pinger_receiver.listen(
SocketType::ICMP,
reflector_peers_lock_clone,
baseliner_stats_sender,
)
})
.expect("Couldn't spawn ping receiver thread");
let baseliner_handle = thread::Builder::new()
.name("baseliner".to_string())
.spawn(move || baseliner.run())
.expect("Couldn't spawn baseliner thread");
let reflector_peers_lock_clone = reflector_peers_lock.clone();
let sender_handle = thread::Builder::new() let sender_handle = thread::Builder::new()
.name("sender".to_string()) .name("sender".to_string())
.spawn(move || pinger_sender.send()) .spawn(move || pinger_sender.send(SocketType::ICMP, reflector_peers_lock_clone))
.unwrap(); .expect("Couldn't spawn ping sender thread");
let threads = vec![receiver_handle, sender_handle]; let mut threads = vec![receiver_handle, sender_handle, baseliner_handle];
if reflector_pool_size > 5 {
let reflector_selector = ReflectorSelector {
config: config.clone(),
owd_baseline: owd_baseline.clone(),
owd_recent: owd_recent.clone(),
reflector_peers_lock: reflector_peers_lock.clone(),
reflector_pool,
trigger_channel: reselect_receiver,
};
let reselection_handle = thread::Builder::new()
.name("reselection".to_string())
.spawn(move || reflector_selector.run())
.expect("Couldn't spawn reflector selector thread");
threads.push(reselection_handle);
}
// Sleep 10 seconds before we start adjusting speeds
sleep(Duration::new(10, 0));
let ratecontroller = Ratecontroller {
config: config.clone(),
owd_baseline: owd_baseline.clone(),
owd_recent: owd_recent.clone(),
reflectors_lock: reflector_peers_lock.clone(),
reselect_trigger: reselect_sender.clone(),
};
let ratecontroller_handle = thread::Builder::new()
.name("ratecontroller".to_string())
.spawn(move || ratecontroller.run(StatsDirection::TX, StatsDirection::RX))
.expect("Couldn't spawn ratecontroller thread");
threads.push(ratecontroller_handle);
for thread in threads { for thread in threads {
println!("thread: {}", thread.thread().name().unwrap());
thread.join().unwrap(); thread.join().unwrap();
} }
}
fn test(config: &Config) { ExitCode::SUCCESS
if true {
return;
}
let start = time::Instant::now();
let ifindex =
find_interface(config.upload_interface.as_str()).expect("Couldn't find interface");
let find_if_time = time::Instant::now().duration_since(start);
println!("time get intf: {}", find_if_time.as_micros());
let start = time::Instant::now();
let qdisc = find_qdisc(ifindex).expect("Couldn't find qdisc");
let find_qdisc_time = time::Instant::now().duration_since(start);
println!("time get qdisc: {}", find_qdisc_time.as_micros());
let start = time::Instant::now();
set_qdisc_rate(qdisc, 1000 * 1000 / 8).unwrap();
let set_qdisc_time = time::Instant::now().duration_since(start);
println!("time set qdisc: {}", set_qdisc_time.as_micros());
println!(
"time total: {}",
find_if_time.as_micros() + find_qdisc_time.as_micros() + set_qdisc_time.as_micros()
);
} }
+18 -30
View File
@@ -1,4 +1,5 @@
use crate::cake::TcaCake; use crate::cake::TcaCake;
use crate::error::NoQdiscFoundError;
use neli::consts::nl::{NlmF, NlmFFlags}; use neli::consts::nl::{NlmF, NlmFFlags};
use neli::consts::rtnl::{Arphrd, IffFlags, Ifla, RtAddrFamily, Rtm, Tca}; use neli::consts::rtnl::{Arphrd, IffFlags, Ifla, RtAddrFamily, Rtm, Tca};
@@ -10,27 +11,8 @@ use neli::socket::NlSocketHandle;
use neli::types::{Buffer, RtBuffer}; use neli::types::{Buffer, RtBuffer};
use serde::Deserialize; use serde::Deserialize;
use std::error::Error; use std::error::Error;
use std::fmt::Display;
use bincode::deserialize; use bincode::deserialize;
use std::fmt;
#[derive(Default, Debug)]
struct NoQdiscFoundError;
impl Error for NoQdiscFoundError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
// The compiler transparently casts `&sqlx::Error` into a `&dyn Error`
None
}
}
// Implement std::fmt::Display for AppError
impl Display for NoQdiscFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Couldn't find a CAKE qdisc for interface") // user-facing output
}
}
#[derive(Deserialize, Copy, Clone, Default, Debug)] #[derive(Deserialize, Copy, Clone, Default, Debug)]
pub struct RtnlLinkStats64 { pub struct RtnlLinkStats64 {
@@ -61,6 +43,7 @@ pub struct RtnlLinkStats64 {
} }
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone, Copy)]
pub struct Qdisc { pub struct Qdisc {
ifindex: i32, ifindex: i32,
handle: u32, handle: u32,
@@ -69,7 +52,10 @@ pub struct Qdisc {
parent: u32, parent: u32,
} }
fn nl_interface_get(socket: &mut NlSocketHandle, ifname: &str) -> Result<(), Box<dyn Error>> { pub struct Netlink {}
impl Netlink {
fn nl_interface_get(socket: &mut NlSocketHandle, ifname: &str) -> Result<(), Box<dyn Error>> {
let mut attrs = RtBuffer::new(); let mut attrs = RtBuffer::new();
const RTEXT_FILTER_VF: i32 = 1 << 0; const RTEXT_FILTER_VF: i32 = 1 << 0;
@@ -100,12 +86,12 @@ fn nl_interface_get(socket: &mut NlSocketHandle, ifname: &str) -> Result<(), Box
socket.send(nlhdr)?; socket.send(nlhdr)?;
Ok(()) Ok(())
} }
pub fn find_interface(ifname: &str) -> Result<i32, Box<dyn Error>> { pub fn find_interface(ifname: &str) -> Result<i32, Box<dyn Error>> {
let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap();
nl_interface_get(&mut socket, ifname).expect("Wrong when sending intf"); Self::nl_interface_get(&mut socket, ifname).expect("Wrong when sending intf");
for response in socket.iter(false) { for response in socket.iter(false) {
let header: Nlmsghdr<Rtm, Ifinfomsg> = response?; let header: Nlmsghdr<Rtm, Ifinfomsg> = response?;
@@ -121,12 +107,12 @@ pub fn find_interface(ifname: &str) -> Result<i32, Box<dyn Error>> {
// we shouldn't reach here // we shouldn't reach here
Ok(-1) Ok(-1)
} }
pub fn get_interface_stats(ifname: &str) -> Result<RtnlLinkStats64, Box<dyn Error>> { pub fn get_interface_stats(ifname: &str) -> Result<RtnlLinkStats64, Box<dyn Error>> {
let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap();
nl_interface_get(&mut socket, ifname).expect("Wrong when sending intf"); Self::nl_interface_get(&mut socket, ifname)?;
for response in socket.iter(false) { for response in socket.iter(false) {
let header: Nlmsghdr<Rtm, Ifinfomsg> = response?; let header: Nlmsghdr<Rtm, Ifinfomsg> = response?;
@@ -149,9 +135,9 @@ pub fn get_interface_stats(ifname: &str) -> Result<RtnlLinkStats64, Box<dyn Erro
} }
Err(Box::new(NlError::msg("Error getting stats"))) Err(Box::new(NlError::msg("Error getting stats")))
} }
pub fn find_qdisc(ifindex: i32) -> Result<Qdisc, Box<dyn Error>> { pub fn find_qdisc(ifindex: i32) -> Result<Qdisc, Box<dyn Error>> {
let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap();
let tc_msg = Tcmsg::new( let tc_msg = Tcmsg::new(
u8::from(RtAddrFamily::Unspecified), u8::from(RtAddrFamily::Unspecified),
@@ -211,10 +197,11 @@ pub fn find_qdisc(ifindex: i32) -> Result<Qdisc, Box<dyn Error>> {
} }
return Err(Box::new(NoQdiscFoundError)); return Err(Box::new(NoQdiscFoundError));
} }
pub fn set_qdisc_rate(qdisc: Qdisc, bandwidth: u64) -> Result<(), Box<dyn Error>> { pub fn set_qdisc_rate(qdisc: Qdisc, bandwidth: u64) -> Result<(), Box<dyn Error>> {
let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap();
let bandwidth = bandwidth * 1000 / 8;
let mut attrs = RtBuffer::new(); let mut attrs = RtBuffer::new();
@@ -249,4 +236,5 @@ pub fn set_qdisc_rate(qdisc: Qdisc, bandwidth: u64) -> Result<(), Box<dyn Error>
socket.send(nlhdr)?; socket.send(nlhdr)?;
Ok(()) Ok(())
}
} }
+73 -30
View File
@@ -4,68 +4,118 @@ use nix::sys::socket::{
}; };
use std::error::Error; use std::error::Error;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
use std::os::unix::io::RawFd;
use std::str::FromStr; use std::str::FromStr;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
pub trait PingListener { pub enum SocketType {
fn new(id: u16, reflectors: Vec<IpAddr>) -> Self; ICMP,
UDP,
}
fn get_id(&self) -> u16; pub struct PingReply {
fn get_reflectors(&self) -> Vec<IpAddr>; pub(crate) reflector: IpAddr,
pub(crate) seq: u16,
pub(crate) rtt: i64,
pub(crate) current_time: i64,
pub(crate) down_time: f64,
pub(crate) up_time: f64,
pub(crate) originate_timestamp: i64,
pub(crate) receive_timestamp: i64,
pub(crate) transmit_timestamp: i64,
pub(crate) last_receive_time_s: f64,
}
fn listen(&mut self) { fn open_socket(type_: SocketType) -> RawFd {
let socket = socket( match type_ {
ICMP => {
socket(
AddressFamily::Inet, AddressFamily::Inet,
SockType::Raw, SockType::Raw,
SockFlag::empty(), /* value */ SockFlag::empty(), /* value */
SockProtocol::ICMP, SockProtocol::ICMP,
) )
.expect("Couldn't open socket"); }
UDP => {
socket(
AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(), /* value */
SockProtocol::Udp,
)
}
}
.expect("Couldn't open socket")
}
pub trait PingListener {
fn new(id: u16) -> Self;
fn get_id(&self) -> u16;
fn listen(
&mut self,
type_: SocketType,
reflectors_lock: Arc<Mutex<Vec<IpAddr>>>,
stats_sender: Sender<PingReply>,
) {
let socket = open_socket(type_);
loop { loop {
let mut v: Vec<u8> = vec![0; 128]; let mut v: Vec<u8> = vec![0; 40];
let buf = v.as_mut_slice(); let mut buf = v.as_mut_slice();
let (size, sender) = match recvfrom::<SockaddrIn>(socket, buf) { let (size, sender) = match recvfrom::<SockaddrIn>(socket, buf) {
Ok(val) => val, Ok(val) => val,
Err(_) => continue, Err(_) => continue,
}; };
let (rtt, down_time, up_time) = match Self::parse_packet(buf, size) { // etherparse doesn't like when the size in the header doesn't match the buffer
// so resize the buffer when actual packet size is known
buf = buf[..size].as_mut();
let addr_bytes = sender.expect("Should be an address here").ip();
let addr = IpAddr::from(addr_bytes.to_be_bytes());
let reflectors = reflectors_lock.lock().unwrap();
if !reflectors.contains(&addr) {
continue;
}
let reply = match Self::parse_packet(addr, buf, size) {
Ok(val) => val, Ok(val) => val,
Err(e) => { Err(e) => {
println!("Shit went to hell: {}", e.to_string()); println!("Shit went to hell: {}", e.to_string());
continue; continue;
} }
}; };
println!("Type: {:4} | Reflector IP: {:>15} | Seq: {:5} | Current time: {:8} | Originate: {:8} | Received time: {:8} | Transmit time : {:8} | RTT: {:8} | UL time: {:5} | DL time: {:5}", "ICMP", addr.to_string(), reply.seq, reply.current_time, reply.originate_timestamp, reply.receive_timestamp, reply.transmit_timestamp, reply.rtt, reply.up_time, reply.down_time);
stats_sender.send(reply).unwrap();
} }
} }
fn parse_packet(buf: &[u8], len: usize) -> Result<(i64, i64, i64), Box<dyn Error>>; fn parse_packet(reflector: IpAddr, buf: &[u8], len: usize)
-> Result<PingReply, Box<dyn Error>>;
} }
pub trait PingSender { pub trait PingSender {
fn new(id: u16, reflectors: Vec<IpAddr>) -> Self; fn new(id: u16) -> Self;
fn get_id(&self) -> u16; fn get_id(&self) -> u16;
fn get_reflectors(&self) -> Vec<IpAddr>;
fn send(&mut self) { fn send(&mut self, type_: SocketType, reflectors_lock: Arc<Mutex<Vec<IpAddr>>>) {
let socket = socket( let socket = open_socket(type_);
AddressFamily::Inet,
SockType::Raw,
SockFlag::empty(), /* value */
SockProtocol::ICMP,
)
.expect("Couldn't open socket");
let mut seq: u16 = 0; let mut seq: u16 = 0;
let tick_duration_ms: u16 = 500; let tick_duration_ms: u16 = 500;
let sleep_duration = Duration::from_millis(tick_duration_ms as u64); let sleep_duration = Duration::from_millis(tick_duration_ms as u64);
loop { loop {
let reflectors = Self::get_reflectors(self); let reflectors_unlocked = reflectors_lock.lock().unwrap();
let reflectors = reflectors_unlocked.clone();
drop(reflectors_unlocked);
for reflector in reflectors.iter() { for reflector in reflectors.iter() {
let addr: Box<dyn SockaddrLike>; let addr: Box<dyn SockaddrLike>;
@@ -101,10 +151,3 @@ pub trait PingSender {
fn craft_packet(&self, seq: u16) -> Vec<u8>; fn craft_packet(&self, seq: u16) -> Vec<u8>;
} }
pub trait Pinger {
fn new(id: u16, reflectors: Vec<IpAddr>) -> Self;
fn receive_loop(&mut self);
fn sender_loop(&mut self);
fn send_ping(&mut self, reflector: &IpAddr, id: u16, seq: u16) -> Result<(), Box<dyn Error>>;
}
+78 -185
View File
@@ -1,228 +1,121 @@
use bincode::{deserialize, serialize};
use std::error::Error; use std::error::Error;
use std::net::IpAddr; use std::net::IpAddr;
use std::{thread, time};
use crate::pinger::{PingListener, PingSender}; use crate::pinger::{PingListener, PingReply, PingSender};
use crate::Pinger;
use byteorder::*; use byteorder::*;
use neli::err::NlError; use etherparse::TransportSlice::{Icmpv4, Icmpv6};
use etherparse::{IcmpEchoHeader, Icmpv4Header, Icmpv4Type, SlicedPacket};
use nix::sys::time::TimeValLike; use nix::sys::time::TimeValLike;
use nix::time::{clock_gettime, ClockId}; use nix::time::{clock_gettime, ClockId};
use pnet::packet::icmp::IcmpTypes::EchoReply;
use pnet::packet::ip::IpNextHeaderProtocols;
use pnet::packet::*;
use pnet_transport::TransportChannelType::Layer4;
use pnet_transport::TransportProtocol::Ipv4;
use pnet_transport::*;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
//#[repr(C)]
pub struct ICMPEcho {
type_: u8,
code: u8,
checksum: u16,
identifier: u16,
sequence: u16,
#[serde(with = "serde_bytes")]
payload: Vec<u8>,
}
pub struct PingerICMPEcho {
id: u16,
reflectors: Vec<IpAddr>,
rx: TransportReceiver,
tx: TransportSender,
}
pub struct PingerICMPEchoListener { pub struct PingerICMPEchoListener {
id: u16, id: u16,
reflectors: Vec<IpAddr>,
} }
pub struct PingerICMPEchoSender { pub struct PingerICMPEchoSender {
id: u16, id: u16,
reflectors: Vec<IpAddr>,
}
fn calculate_checksum(buffer: &mut [u8]) -> u16 {
let mut sum = 0u32;
for word in buffer.chunks(2) {
let mut part = u16::from(word[0]) << 8;
if word.len() > 1 {
part += u16::from(word[1]);
}
sum = sum.wrapping_add(u32::from(part));
}
while (sum >> 16) > 0 {
sum = (sum & 0xffff) + (sum >> 16);
}
!sum as u16
}
impl Pinger for PingerICMPEcho {
fn new(id: u16, reflectors: Vec<IpAddr>) -> Self {
let protocol = Layer4(Ipv4(IpNextHeaderProtocols::Icmp));
// Create a new transport channel, dealing with layer 4 packets on a test protocol
// It has a receive buffer of 4096 bytes.
let (tx, rx) = match transport_channel(4096, protocol) {
Ok((tx, rx)) => (tx, rx),
Err(e) => panic!(
"An error occurred when creating the transport channel: {}",
e
),
};
PingerICMPEcho {
id,
reflectors,
rx,
tx,
}
}
fn receive_loop(&mut self) {
let mut iter = icmp_packet_iter(&mut self.rx);
loop {
let (packet, sender) = match iter.next() {
Ok(res) => res,
Err(_) => continue,
};
let curr_time: u64 = clock_gettime(ClockId::CLOCK_MONOTONIC)
.unwrap()
.num_milliseconds() as u64;
if packet.get_icmp_type() != EchoReply {
continue;
}
let icmp_response = icmp::echo_reply::EchoReplyPacket::new(packet.packet()).unwrap();
if icmp_response.get_identifier() != self.id {
continue;
}
let sent_time: u64 = icmp_response.payload().read_u64::<NativeEndian>().unwrap();
let rtt = curr_time - sent_time;
println!("Type: {:4} | Reflector IP: {:>15} | Seq: {:5} | Current time: {:8} | Sent time: {:8} | RTT: {:8}", "ICMP", sender.to_string(), icmp_response.get_sequence_number(), curr_time, sent_time, rtt);
}
}
fn sender_loop(&mut self) {
println!("My ID is: {}", self.id);
let mut seq: u16 = 0;
let tick_duration_ms: u16 = 500;
let sleep_duration = time::Duration::from_millis(tick_duration_ms as u64);
loop {
let reflectors = self.reflectors.clone();
for reflector in reflectors.iter() {
self.send_ping(reflector, self.id, seq)
.expect("TODO: panic message");
}
thread::sleep(sleep_duration);
seq += 1;
if seq > u16::MAX {
seq = 0;
}
}
}
fn send_ping(&mut self, reflector: &IpAddr, id: u16, seq: u16) -> Result<(), Box<dyn Error>> {
let time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
let mut buf = [0u8; 8 + 56];
let mut packet = icmp::echo_request::MutableEchoRequestPacket::new(&mut buf).unwrap();
packet.populate(&icmp::echo_request::EchoRequest {
icmp_type: icmp::IcmpTypes::EchoRequest,
icmp_code: icmp::IcmpCode::new(0),
checksum: 0,
identifier: id,
sequence_number: seq,
payload: Vec::from(time.num_milliseconds().to_ne_bytes()),
});
packet.set_checksum(icmp::checksum(
&icmp::IcmpPacket::new(&packet.packet()).unwrap(),
));
self.tx.send_to(packet, *reflector).expect("Error sending");
Ok(())
}
} }
impl PingListener for PingerICMPEchoListener { impl PingListener for PingerICMPEchoListener {
fn new(id: u16, reflectors: Vec<IpAddr>) -> Self { fn new(id: u16) -> Self {
PingerICMPEchoListener { id, reflectors } PingerICMPEchoListener { id }
} }
fn get_id(&self) -> u16 { fn get_id(&self) -> u16 {
self.id self.id
} }
fn get_reflectors(&self) -> Vec<IpAddr> { // Result: RTT, down time, up time
self.reflectors.clone() fn parse_packet(
reflector: IpAddr,
buf: &[u8],
len: usize,
) -> Result<PingReply, Box<dyn Error>> {
match SlicedPacket::from_ip(buf) {
Err(value) => println!("Err {:?}", value),
Ok(value) => match value.transport {
Some(Icmpv4(icmp)) => match icmp.icmp_type() {
Icmpv4Type::EchoReply(echo) => {
let time_sent = icmp
.payload()
.read_u64::<NativeEndian>()
.expect("Couldn't parse payload to time")
as i64;
let time_now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
let time_ms = time_now.num_milliseconds();
let rtt = time_ms - time_sent;
return Ok(PingReply {
reflector,
seq: echo.seq,
rtt,
current_time: time_ms,
down_time: (rtt / 2) as f64,
up_time: (rtt / 2) as f64,
originate_timestamp: 0,
receive_timestamp: 0,
transmit_timestamp: 0,
last_receive_time_s: time_now.tv_sec() as f64
+ (time_now.tv_nsec() as f64 / 1e9),
});
}
_ => {}
},
Some(Icmpv6(icmp)) => {}
Some(_) => {}
None => {}
},
} }
fn parse_packet(buf: &[u8], len: usize) -> Result<(i64, i64, i64), Box<dyn Error>> { Ok(PingReply {
if len != 44 { reflector: IpAddr::from([0, 0, 0, 0]),
println!("len: {}", len); seq: 0,
return Err(Box::new(NlError::msg("Wrong length"))); rtt: 0,
} current_time: 0,
down_time: 0.0,
let hdr: ICMPEcho = deserialize(buf).expect("Failed to parse ICMP packet"); up_time: 0.0,
originate_timestamp: 0,
todo!() receive_timestamp: 0,
transmit_timestamp: 0,
last_receive_time_s: 0.0,
})
} }
} }
impl PingSender for PingerICMPEchoSender { impl PingSender for PingerICMPEchoSender {
fn new(id: u16, reflectors: Vec<IpAddr>) -> Self { fn new(id: u16) -> Self {
PingerICMPEchoSender { id, reflectors } PingerICMPEchoSender { id }
} }
fn get_id(&self) -> u16 { fn get_id(&self) -> u16 {
self.id self.id
} }
fn get_reflectors(&self) -> Vec<IpAddr> {
self.reflectors.clone()
}
fn craft_packet(&self, seq: u16) -> Vec<u8> { fn craft_packet(&self, seq: u16) -> Vec<u8> {
let time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap(); let time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
let time_u64: u64 = time.num_milliseconds() as u64; let time_u64: u64 = time.num_milliseconds() as u64;
let payload = time_u64.to_ne_bytes();
let mut hdr = ICMPEcho { // Construct a header with checksum based on the payload
type_: 8, let hdr = Icmpv4Header::with_checksum(
code: 0, Icmpv4Type::EchoRequest(IcmpEchoHeader {
checksum: 0, id: self.get_id(),
identifier: self.get_id(), seq,
sequence: seq, }),
payload: time_u64.to_ne_bytes().to_vec(), &payload,
}; );
println!("id: {}", hdr.identifier); // Create a buffer to hold the result of header + payload
println!("seq: {}", hdr.sequence); let mut result = Vec::<u8>::with_capacity(hdr.header_len() + payload.len());
let mut tmp_buf_v = serialize(&hdr).unwrap(); // Write the header to the buffer
let mut tmp_buf = tmp_buf_v.as_mut_slice(); hdr.write(&mut result).expect("Error writing packet");
hdr.checksum = calculate_checksum(tmp_buf).to_be();
serialize(&hdr).unwrap() // Write the payload to the buffer
result.append(&mut payload.to_vec());
result
} }
} }
+82 -120
View File
@@ -1,151 +1,113 @@
use bincode::{deserialize, serialize};
use std::error::Error; use std::error::Error;
use std::net::IpAddr; use std::net::IpAddr;
use std::{thread, time};
use crate::Pinger; use crate::error::PingParseError;
use crate::pinger::{PingListener, PingReply, PingSender};
use crate::Utils;
use etherparse::icmpv4::TimestampMessage;
use etherparse::TransportSlice::Icmpv4;
use etherparse::{Icmpv4Header, Icmpv4Type, SlicedPacket};
use nix::time::{clock_gettime, ClockId}; use nix::time::{clock_gettime, ClockId};
use pnet::packet::icmp::IcmpTypes::{Timestamp, TimestampReply};
use pnet::packet::ip::IpNextHeaderProtocols;
use pnet::packet::*;
use pnet_transport::TransportChannelType::Layer4;
use pnet_transport::TransportProtocol::Ipv4;
use pnet_transport::*;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)] pub struct PingerICMPTimestampListener {
pub struct ICMPTimestamp {
type_: u8,
code: u8,
checksum: u16,
identifier: u16,
sequence: u16,
originate_time: u32,
receive_time: u32,
transmit_time: u32,
}
pub struct PingerICMPTimestamps {
id: u16, id: u16,
reflectors: Vec<IpAddr>,
rx: TransportReceiver,
tx: TransportSender,
} }
impl Pinger for PingerICMPTimestamps { pub struct PingerICMPTimestampSender {
fn new(id: u16, reflectors: Vec<IpAddr>) -> Self { id: u16,
let protocol = Layer4(Ipv4(IpNextHeaderProtocols::Icmp)); }
// Create a new transport channel, dealing with layer 4 packets on a test protocol impl PingListener for PingerICMPTimestampListener {
// It has a receive buffer of 4096 bytes. fn new(id: u16) -> Self {
let (tx, rx) = match transport_channel(4096, protocol) { PingerICMPTimestampListener { id }
Ok((tx, rx)) => (tx, rx),
Err(e) => panic!(
"An error occurred when creating the transport channel: {}",
e
),
};
PingerICMPTimestamps {
id,
reflectors,
rx,
tx,
}
} }
fn receive_loop(&mut self) { fn get_id(&self) -> u16 {
let mut iter = icmp_packet_iter(&mut self.rx); self.id
loop {
let (packet, sender) = match iter.next() {
Ok(res) => res,
Err(_) => continue,
};
let time = clock_gettime(ClockId::CLOCK_REALTIME).unwrap();
let time_since_midnight: i64 =
(time.tv_sec() % 86400 * 1000) + (time.tv_nsec() / 1000000);
if packet.get_icmp_type() != TimestampReply {
continue;
} }
let hdr: ICMPTimestamp = deserialize(packet.packet()).unwrap(); // Result: RTT, down time, up time
fn parse_packet(
reflector: IpAddr,
buf: &[u8],
len: usize,
) -> Result<PingReply, Box<dyn Error>> {
match SlicedPacket::from_ip(buf) {
Err(value) => println!("Err {:?}", value),
Ok(value) => match value.transport {
Some(Icmpv4(icmp)) => match icmp.icmp_type() {
Icmpv4Type::TimestampReply(reply) => {
let time_now = clock_gettime(ClockId::CLOCK_REALTIME).unwrap();
let time_since_midnight: i64 = (time_now.tv_sec() as i64 % 86400 * 1000)
+ (time_now.tv_nsec() as i64 / 1000000);
if hdr.identifier != self.id { let originate_timestamp = Utils::to_ne(reply.originate_timestamp);
println!("wrong id: {} != {}", hdr.identifier, self.id); let receive_timestamp = Utils::to_ne(reply.receive_timestamp);
continue; let transmit_timestamp = Utils::to_ne(reply.transmit_timestamp);
let rtt: i64 = time_since_midnight - originate_timestamp as i64;
let dl_time: i64 = time_since_midnight - transmit_timestamp as i64;
let ul_time: i64 = receive_timestamp as i64 - originate_timestamp as i64;
return Ok(PingReply {
reflector,
seq: reply.seq,
rtt,
current_time: time_since_midnight,
down_time: dl_time as f64,
up_time: ul_time as f64,
originate_timestamp: originate_timestamp as i64,
receive_timestamp: receive_timestamp as i64,
transmit_timestamp: transmit_timestamp as i64,
last_receive_time_s: time_now.tv_sec() as f64
+ (time_now.tv_nsec() as f64 / 1e9),
});
}
_ => {}
},
Some(_) => {}
None => {}
},
} }
let rtt: i64 = time_since_midnight - hdr.originate_time.to_be() as i64; Err(Box::new(PingParseError {}))
let dl_time: i64 = time_since_midnight - hdr.transmit_time.to_be() as i64;
let ul_time: i64 = hdr.receive_time.to_be() as i64 - hdr.originate_time.to_be() as i64;
println!("Type: {:4} | Reflector IP: {:>15} | Seq: {:5} | Current time: {:8} | Originate: {:8} | Received time: {:8} | Transmit time : {:8} | RTT: {:8} | UL time: {:5} | DL time: {:5}", "ICMP", sender.to_string(), hdr.sequence, time_since_midnight, hdr.originate_time.to_be(), hdr.receive_time.to_be(), hdr.transmit_time.to_be(), rtt, ul_time, dl_time);
} }
}
impl PingSender for PingerICMPTimestampSender {
fn new(id: u16) -> Self {
PingerICMPTimestampSender { id }
} }
fn sender_loop(&mut self) { fn get_id(&self) -> u16 {
println!("My ID is: {}", self.id); self.id
let mut seq: u16 = 0;
let tick_duration_ms: u16 = 500;
let sleep_duration = time::Duration::from_millis(tick_duration_ms as u64);
loop {
let reflectors = self.reflectors.clone();
for reflector in reflectors.iter() {
self.send_ping(reflector, self.id, seq)
.expect("TODO: panic message");
} }
thread::sleep(sleep_duration); fn craft_packet(&self, seq: u16) -> Vec<u8> {
seq += 1;
if seq > u16::MAX {
seq = 0;
}
}
}
fn send_ping(&mut self, reflector: &IpAddr, id: u16, seq: u16) -> Result<(), Box<dyn Error>> {
let mut buf = [0u8; 8 + 56];
let mut packet = pnet::packet::icmp::MutableIcmpPacket::new(&mut buf).unwrap();
let time = clock_gettime(ClockId::CLOCK_REALTIME).unwrap(); let time = clock_gettime(ClockId::CLOCK_REALTIME).unwrap();
let time_since_midnight: u32 = let time_since_midnight: u32 =
((time.tv_sec() % 86400 * 1000) + (time.tv_nsec() / 1000000)) as u32; ((time.tv_sec() % 86400 * 1000) + (time.tv_nsec() / 1000000)) as u32;
let icmp = ICMPTimestamp { let payload: [u8; 0] = [];
type_: 0,
code: 0,
checksum: 0,
identifier: id,
sequence: seq,
originate_time: time_since_midnight.to_be(),
receive_time: 0,
transmit_time: 0,
};
let buf_v: Vec<u8> = serialize(&icmp).unwrap(); // Construct a header with checksum based on the payload
let payload = &buf_v[4..]; let hdr = Icmpv4Header::with_checksum(
Icmpv4Type::TimestampRequest(TimestampMessage {
id: self.id,
seq,
originate_timestamp: time_since_midnight,
receive_timestamp: 0,
transmit_timestamp: 0,
}),
&payload,
);
packet.populate(&icmp::Icmp { // Create a buffer to hold the result of header + payload
icmp_type: Timestamp, let mut result = Vec::<u8>::with_capacity(hdr.header_len());
icmp_code: icmp::IcmpCode::new(0),
checksum: 0,
payload: Vec::from(payload),
});
packet.set_checksum(icmp::checksum( // Write the header to the buffer
&icmp::IcmpPacket::new(&packet.packet()).unwrap(), hdr.write(&mut result).expect("Error writing packet");
));
self.tx.send_to(packet, *reflector).expect("Error sending"); result
Ok(())
} }
} }
+314
View File
@@ -0,0 +1,314 @@
use crate::netlink::Netlink;
use crate::{Config, ReflectorStats, Utils};
use rand::seq::SliceRandom;
use rand::thread_rng;
use rand::RngCore;
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::Duration;
#[derive(Copy, Clone)]
pub enum StatsDirection {
RX,
TX,
}
fn f64_max(a: f64, b: f64) -> f64 {
*[a, b].iter().max_by(|a, b| a.total_cmp(b)).unwrap()
}
fn f64_min(a: f64, b: f64) -> f64 {
*[a, b].iter().min_by(|a, b| a.total_cmp(b)).unwrap()
}
fn get_interface_stats(
config: Config,
down_direction: StatsDirection,
up_direction: StatsDirection,
) -> (i128, i128) {
let (down_rx, down_tx) = match Netlink::get_interface_stats(config.download_interface.as_str())
{
Ok(stats) => (stats.rx_bytes as i128, stats.tx_bytes as i128),
Err(e) => {
println!("Error while retrieving stats: {}", e.to_string());
(-1, -1)
}
};
let (up_rx, up_tx) = match Netlink::get_interface_stats(config.upload_interface.as_str()) {
Ok(stats) => (stats.rx_bytes as i128, stats.tx_bytes as i128),
Err(e) => {
println!("Error while retrieving stats: {}", e.to_string());
(-1, -1)
}
};
let rx_bytes = match down_direction {
RX => down_rx,
TX => down_tx,
};
let tx_bytes = match up_direction {
RX => up_rx,
TX => up_tx,
};
(rx_bytes, tx_bytes)
}
pub struct Ratecontroller {
pub(crate) config: Config,
pub(crate) owd_baseline: Arc<Mutex<HashMap<IpAddr, ReflectorStats>>>,
pub(crate) owd_recent: Arc<Mutex<HashMap<IpAddr, ReflectorStats>>>,
pub(crate) reflectors_lock: Arc<Mutex<Vec<IpAddr>>>,
pub(crate) reselect_trigger: Sender<bool>,
}
impl Ratecontroller {
pub fn run(&self, down_direction: StatsDirection, up_direction: StatsDirection) {
let sleep_time_s = self.config.min_change_interval.floor() as u64;
let sleep_time_ns = ((self.config.min_change_interval % 1.0) * 1e9) as u32;
let sleep_time = Duration::new(sleep_time_s, sleep_time_ns);
let (start_s, start_ns) = Utils::get_current_time();
let (mut lastchg_s, mut lastchg_ns) = Utils::get_current_time();
let mut lastchg_t = lastchg_s - start_s + lastchg_ns / 1e9;
let mut lastdump_t = lastchg_t - 310.0;
let mut rng = thread_rng();
let base_dl_rate = 45000.0;
let base_ul_rate = 15000.0;
let min_dl_rate = 5000.0;
let min_ul_rate = 1000.0;
let histsize = 100;
let dl_max_delta_owd = 15.0;
let ul_max_delta_owd = 15.0;
let high_load_level = 0.8;
let down_ifindex =
Netlink::find_interface(self.config.download_interface.as_str()).unwrap();
let up_ifindex = Netlink::find_interface(self.config.upload_interface.as_str()).unwrap();
let down_qdisc = Netlink::find_qdisc(down_ifindex).unwrap();
let up_qdisc = Netlink::find_qdisc(up_ifindex).unwrap();
// set qdisc rates to 60% of base rate to make sure we start with sane baselines
let mut cur_dl_rate: f64 = base_dl_rate * 0.6;
let mut cur_ul_rate: f64 = base_ul_rate * 0.6;
Netlink::set_qdisc_rate(down_qdisc, cur_dl_rate.floor() as u64).unwrap();
Netlink::set_qdisc_rate(up_qdisc, cur_ul_rate.floor() as u64).unwrap();
let (mut prev_rx_bytes, mut prev_tx_bytes) =
get_interface_stats(self.config.clone(), down_direction, up_direction);
if prev_rx_bytes == -1 || prev_tx_bytes == -1 {
panic!("Couldn't retrieve stats from interface.");
}
let mut t_prev_bytes = lastchg_t;
let mut safe_dl_rates: Vec<f64> = Vec::new();
let mut safe_ul_rates: Vec<f64> = Vec::new();
for i in 0..histsize {
safe_dl_rates.push((rand::thread_rng().next_u32() as f64 * 0.2 + 0.75) * base_dl_rate);
safe_ul_rates.push((rand::thread_rng().next_u32() as f64 * 0.2 + 0.75) * base_dl_rate);
}
let mut nrate_up = 0;
let mut nrate_down = 0;
loop {
let (mut now_s, now_ns) = Utils::get_current_time();
let now_abstime = now_s + now_ns / 1e9;
now_s = now_s - start_s;
let now_t = now_s + now_ns / 1e9;
if now_t - lastchg_t > self.config.min_change_interval {
let owd_baseline = self.owd_baseline.lock().unwrap();
let owd_recent = self.owd_recent.lock().unwrap();
let reflectors = self.reflectors_lock.lock().unwrap();
let mut down_delta_stat: f64;
let mut up_delta_stat: f64;
if reflectors.len() > 0 {
let mut down_deltas = Vec::new();
let mut up_deltas = Vec::new();
let (
mut next_dl_rate,
mut next_ul_rate,
mut rx_load,
mut tx_load,
mut up_utilisation,
mut down_utilisation,
): (f64, f64, f64, f64, f64, f64);
rx_load = -1.0;
tx_load = -1.0;
down_delta_stat = 0.0;
up_delta_stat = 0.0;
for reflector in reflectors.iter() {
if owd_baseline.contains_key(reflector)
&& owd_recent.contains_key(reflector)
&& owd_recent[reflector].last_receive_time_s
> now_abstime - 2.0 * self.config.tick_duration
{
down_deltas.push(
owd_recent[reflector].down_ewma - owd_baseline[reflector].down_ewma,
);
up_deltas.push(
owd_recent[reflector].up_ewma - owd_baseline[reflector].up_ewma,
);
}
}
if down_deltas.len() < 5 || up_deltas.len() < 5 {
// trigger reselection
let _ = self.reselect_trigger.send(true);
}
next_dl_rate = cur_dl_rate;
next_ul_rate = cur_ul_rate;
let (cur_rx_bytes, cur_tx_bytes) =
get_interface_stats(self.config.clone(), down_direction, up_direction);
if cur_rx_bytes == -1 || cur_tx_bytes == -1 {
println!("One or both Netlink stats could not be read. Skipping rate control algorithm");
} else if down_deltas.len() < 3 || up_deltas.len() < 3 {
next_dl_rate = min_dl_rate;
next_ul_rate = min_ul_rate;
} else {
down_deltas.sort_by(|a, b| a.partial_cmp(b).unwrap());
up_deltas.sort_by(|a, b| a.partial_cmp(b).unwrap());
down_delta_stat = Utils::a_else_b(down_deltas[3], down_deltas[1]);
up_delta_stat = Utils::a_else_b(up_deltas[3], up_deltas[1]);
if down_delta_stat > 0.0 && up_delta_stat > 0.0 {
/*
* TODO - find where the (8 / 1000) comes from and
* i. convert to a pre-computed factor
* ii. ideally, see if it can be defined in terms of constants, eg ticks per second and number of active reflectors
*/
down_utilisation = (8.0 / 1000.0)
* (cur_rx_bytes as f64 - prev_rx_bytes as f64)
/ (now_t - t_prev_bytes);
rx_load = down_utilisation / cur_dl_rate;
up_utilisation = (8.0 / 1000.0)
* (cur_tx_bytes as f64 - prev_tx_bytes as f64)
/ (now_t - t_prev_bytes);
tx_load = up_utilisation / cur_ul_rate;
next_ul_rate = cur_ul_rate;
next_dl_rate = cur_dl_rate;
if down_delta_stat > 0.0
&& down_delta_stat < dl_max_delta_owd
&& rx_load > high_load_level
{
safe_dl_rates[nrate_down] = (cur_dl_rate * rx_load).floor();
let max_dl =
safe_dl_rates.iter().max_by(|a, b| a.total_cmp(b)).unwrap();
next_dl_rate = cur_dl_rate
* (1.0 + 0.1 * f64_max(0.0, 1.0 - cur_dl_rate / max_dl))
+ (base_dl_rate * 0.03);
nrate_down = nrate_down + 1;
nrate_down = nrate_down % histsize;
}
if up_delta_stat > 0.0
&& up_delta_stat < dl_max_delta_owd
&& tx_load > high_load_level
{
safe_ul_rates[nrate_up] = (cur_ul_rate * tx_load).floor();
let max_ul =
safe_ul_rates.iter().max_by(|a, b| a.total_cmp(b)).unwrap();
next_ul_rate = cur_ul_rate
* (1.0 + 0.1 * f64_max(0.0, 1.0 - cur_ul_rate / max_ul))
+ (base_dl_rate * 0.03);
nrate_up = nrate_up + 1;
nrate_up = nrate_up % histsize;
}
if down_delta_stat > dl_max_delta_owd {
if safe_dl_rates.len() > 0 {
next_dl_rate = f64_min(
0.9 * cur_dl_rate * rx_load,
*safe_dl_rates.choose(&mut rng).unwrap(),
)
} else {
next_dl_rate = 0.9 * cur_dl_rate * rx_load;
}
}
if up_delta_stat > ul_max_delta_owd {
if safe_ul_rates.len() > 0 {
next_ul_rate = f64_min(
0.9 * cur_ul_rate * tx_load,
*safe_ul_rates.choose(&mut rng).unwrap(),
)
} else {
next_ul_rate = 0.9 * cur_ul_rate * tx_load;
}
}
}
}
t_prev_bytes = now_t;
prev_rx_bytes = cur_rx_bytes;
prev_tx_bytes = cur_tx_bytes;
next_dl_rate = f64_max(min_dl_rate, next_dl_rate).floor();
next_ul_rate = f64_max(min_ul_rate, next_ul_rate).floor();
if next_dl_rate != cur_dl_rate || next_ul_rate != cur_ul_rate {
println!(
"next_ul_rate {} next_dl_rate {}",
next_ul_rate, next_dl_rate
);
}
if next_dl_rate != cur_dl_rate {
Netlink::set_qdisc_rate(down_qdisc, next_dl_rate as u64).unwrap();
}
if next_ul_rate != cur_ul_rate {
Netlink::set_qdisc_rate(up_qdisc, next_ul_rate as u64).unwrap();
}
cur_dl_rate = next_dl_rate;
cur_ul_rate = next_ul_rate;
(lastchg_s, lastchg_ns) = Utils::get_current_time();
if rx_load > 0.0
&& tx_load > 0.0
&& down_delta_stat > 0.0
&& up_delta_stat > 0.0
{
println!(
"{},{},{},{},{},{},{},{}",
lastchg_s,
lastchg_ns,
rx_load,
tx_load,
down_delta_stat,
up_delta_stat,
cur_dl_rate,
cur_ul_rate
);
}
lastchg_s = lastchg_s - start_s;
lastchg_t = lastchg_s + lastchg_ns / 1e9;
}
}
sleep(sleep_time);
}
}
}
+129
View File
@@ -0,0 +1,129 @@
use crate::{Config, ReflectorStats};
use rand::seq::SliceRandom;
use rand::{thread_rng, Rng};
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::Duration;
pub struct ReflectorSelector {
pub(crate) config: Config,
pub(crate) owd_baseline: Arc<Mutex<HashMap<IpAddr, ReflectorStats>>>,
pub(crate) owd_recent: Arc<Mutex<HashMap<IpAddr, ReflectorStats>>>,
pub(crate) reflector_peers_lock: Arc<Mutex<Vec<IpAddr>>>,
pub(crate) reflector_pool: Vec<IpAddr>,
pub(crate) trigger_channel: Receiver<bool>,
}
impl ReflectorSelector {
pub fn run(&self) {
let mut selector_sleep_time = Duration::new(30, 0);
let mut reselection_count = 0;
let baseline_sleep_time = Duration::new(
(self.config.tick_duration * std::f64::consts::PI) as u64,
(((self.config.tick_duration * std::f64::consts::PI) % 1.0) * 1e9) as u32,
);
let mut rng = thread_rng();
// Initial wait of several seconds to allow some OWD data to build up
sleep(baseline_sleep_time);
loop {
/*
* Selection is triggered either by some other thread triggering it through the channel,
* or it passes the timeout. In any case we don't care about the result of this function,
* so we ignore the result of it.
*/
let _ = self
.trigger_channel
.recv_timeout(selector_sleep_time)
.unwrap_or(true);
reselection_count += 1;
// After 40 reselections, slow down to every 15 minutes
if reselection_count > 40 {
selector_sleep_time = Duration::new(15 * 60, 0);
}
let mut next_peers: Vec<IpAddr> = Vec::new();
let mut reflectors_peers = self.reflector_peers_lock.lock().unwrap();
// Include all current peers
for reflector in reflectors_peers.iter() {
next_peers.push(*reflector);
}
for i in 1..20 {
let next_candidate = self.reflector_pool.choose(&mut rng).unwrap();
next_peers.push(*next_candidate);
}
// Put all the pool members back into the peers for some re-baselining...
*reflectors_peers = next_peers.clone();
// Drop the MutexGuard explicitly, as Rust won't unlock the mutex by default
// until the guard goes out of scope
drop(reflectors_peers);
// Wait for several seconds to allow all reflectors to be re-baselined
sleep(baseline_sleep_time);
// Re-acquire the lock when we wake up again
reflectors_peers = self.reflector_peers_lock.lock().unwrap();
reflectors_peers.len();
let mut candidates = Vec::new();
let owd_baseline = self.owd_baseline.lock().unwrap();
let owd_recent = self.owd_recent.lock().unwrap();
for peer in next_peers {
if owd_recent.contains_key(&peer) {
let rtt = (owd_recent[&peer].down_ewma + owd_recent[&peer].up_ewma) as u64;
candidates.push((peer, rtt));
println!("Candidate reflector: {} RTT: {}", peer.to_string(), rtt);
} else {
println!(
"No data found from candidate reflector: {} - skipping",
peer.to_string()
);
}
}
// Sort the candidates table now by ascending RTT
candidates.sort_by(|a, b| a.1.cmp(&b.1));
// Now we will just limit the candidates down to 2 * num_reflectors
let mut num_reflectors = self.config.num_reflectors.clone();
let candidate_pool_num = (2 * num_reflectors) as usize;
candidates = candidates[0..candidate_pool_num - 1].to_vec();
for (candidate, rtt) in candidates.iter() {
println!("Fastest candidate {}: {}", candidate, rtt);
}
// Shuffle the deck so we avoid overwhelming good reflectors (Fisher-Yates)
for i in (1 as usize..candidates.len()).rev() {
let j = rng.gen_range(0..(i + 1));
candidates.swap(i, j);
}
if (candidates.len() as u8) < num_reflectors {
num_reflectors = candidates.len() as u8;
}
let mut new_peers = Vec::new();
for i in 1..num_reflectors {
new_peers.push(candidates[i as usize].0);
println!(
"New selected peer: {}",
candidates[i as usize].0.to_string()
);
}
*reflectors_peers = new_peers;
}
}
}
+26
View File
@@ -0,0 +1,26 @@
use nix::time::{clock_gettime, ClockId};
pub(crate) struct Utils {}
impl Utils {
pub(crate) fn a_else_b(a: f64, b: f64) -> f64 {
return if a > 0.0 { a } else { b };
}
pub(crate) fn ewma_factor(tick: f64, dur: f64) -> f64 {
((0.5_f64).ln() / (dur / tick)).exp()
}
pub(crate) fn get_current_time() -> (f64, f64) {
let time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
(time.tv_sec() as f64, time.tv_nsec() as f64)
}
#[cfg(target_endian = "big")]
pub(crate) fn to_ne(val: u64) -> u64 {
val.to_be()
}
#[cfg(target_endian = "little")]
pub(crate) fn to_ne(val: u32) -> u32 {
val.to_le()
}
}