Merge pull request #406 from LibreQoE/long_term_stats

Long term stats
This commit is contained in:
Robert Chacón 2023-10-17 13:48:25 -06:00 committed by GitHub
commit bcbda6a262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1468 additions and 291 deletions

1
src/VERSION_STRING Normal file
View File

@ -0,0 +1 @@
1.4-rc10-devel

354
src/rust/Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.20.0" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [ dependencies = [
"gimli", "gimli",
] ]
@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -49,24 +49,23 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.3.2" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
"anstyle-query", "anstyle-query",
"anstyle-wincon", "anstyle-wincon",
"colorchoice", "colorchoice",
"is-terminal",
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
@ -88,9 +87,9 @@ dependencies = [
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "1.0.2" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys 0.48.0", "windows-sys 0.48.0",
@ -98,9 +97,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.72" version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]] [[package]]
name = "async-compression" name = "async-compression"
@ -135,18 +134,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.72" version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -212,9 +211,9 @@ dependencies = [
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.68" version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cc", "cc",
@ -227,9 +226,9 @@ dependencies = [
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.2" version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
[[package]] [[package]]
name = "binascii" name = "binascii"
@ -265,7 +264,7 @@ dependencies = [
"regex", "regex",
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn 2.0.28", "syn 2.0.29",
"which", "which",
] ]
@ -346,9 +345,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.82" version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -430,9 +429,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.3.21" version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -441,9 +440,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.3.21" version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -453,21 +452,21 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.3.12" version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.5.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
@ -709,14 +708,14 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
name = "dashmap" name = "dashmap"
version = "5.5.0" version = "5.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" checksum = "9b101bb8960ab42ada6ae98eb82afcea4452294294c45b681295af26610d6d28"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"hashbrown 0.14.0", "hashbrown 0.14.0",
@ -744,9 +743,9 @@ dependencies = [
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.7" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]] [[package]]
name = "devise" name = "devise"
@ -778,7 +777,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -843,9 +842,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -871,9 +870,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
dependencies = [ dependencies = [
"errno-dragonfly", "errno-dragonfly",
"libc", "libc",
@ -930,9 +929,9 @@ dependencies = [
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.26" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",
@ -1024,7 +1023,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -1093,9 +1092,9 @@ dependencies = [
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.27.3" version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]] [[package]]
name = "glob" name = "glob"
@ -1105,9 +1104,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.20" version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -1182,9 +1181,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]] [[package]]
name = "httpdate" name = "httpdate"
version = "1.0.2" 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 = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "humantime" name = "humantime"
@ -1423,7 +1422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75adb4021282a72ca63ebbc0e4247750ad74ede68ff062d247691072d709ad8b" checksum = "75adb4021282a72ca63ebbc0e4247750ad74ede68ff062d247691072d709ad8b"
dependencies = [ dependencies = [
"cc", "cc",
"nix", "nix 0.26.4",
"num_cpus", "num_cpus",
"pkg-config", "pkg-config",
] ]
@ -1462,9 +1461,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.19" version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]] [[package]]
name = "log-once" name = "log-once"
@ -1515,7 +1514,7 @@ dependencies = [
"lqos_config", "lqos_config",
"lqos_utils", "lqos_utils",
"lts_client", "lts_client",
"nix", "nix 0.27.1",
"serde", "serde",
"serde_cbor", "serde_cbor",
"thiserror", "thiserror",
@ -1573,8 +1572,9 @@ dependencies = [
"lqos_bus", "lqos_bus",
"lqos_config", "lqos_config",
"lqos_utils", "lqos_utils",
"nix", "nix 0.27.1",
"once_cell", "once_cell",
"reqwest",
"rocket", "rocket",
"rocket_async_compression", "rocket_async_compression",
"sysinfo", "sysinfo",
@ -1587,7 +1587,7 @@ dependencies = [
"anyhow", "anyhow",
"lqos_bus", "lqos_bus",
"lqos_utils", "lqos_utils",
"nix", "nix 0.27.1",
"pyo3", "pyo3",
"sysinfo", "sysinfo",
"tokio", "tokio",
@ -1638,7 +1638,7 @@ dependencies = [
"lqos_bus", "lqos_bus",
"lqos_config", "lqos_config",
"lqos_utils", "lqos_utils",
"nix", "nix 0.27.1",
"once_cell", "once_cell",
"thiserror", "thiserror",
"zerocopy", "zerocopy",
@ -1650,7 +1650,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"log", "log",
"nix", "nix 0.27.1",
"notify", "notify",
"serde", "serde",
"thiserror", "thiserror",
@ -1673,7 +1673,7 @@ dependencies = [
"lqos_sys", "lqos_sys",
"lqos_utils", "lqos_utils",
"lts_client", "lts_client",
"nix", "nix 0.27.1",
"num-traits", "num-traits",
"once_cell", "once_cell",
"serde", "serde",
@ -1729,6 +1729,7 @@ dependencies = [
"once_cell", "once_cell",
"serde", "serde",
"serde_cbor", "serde_cbor",
"serde_json",
"sysinfo", "sysinfo",
"thiserror", "thiserror",
"tokio", "tokio",
@ -1758,9 +1759,9 @@ checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@ -1901,16 +1902,26 @@ dependencies = [
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.26.2" version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"cfg-if", "cfg-if",
"libc", "libc",
"memoffset 0.7.1", "memoffset 0.7.1",
"pin-utils", "pin-utils",
"static_assertions", ]
[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
"libc",
] ]
[[package]] [[package]]
@ -1979,9 +1990,9 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.31.1" version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -2000,11 +2011,11 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.56" version = "0.10.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 2.4.0",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -2021,7 +2032,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -2032,9 +2043,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.91" version = "0.9.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" checksum = "db7e971c2c2bba161b2d2fdf37080177eff520b3bc044787c7f1f5f9e78d869b"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -2068,7 +2079,7 @@ dependencies = [
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-targets 0.48.1", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -2097,7 +2108,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -2129,14 +2140,14 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.12" version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -2152,9 +2163,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]] [[package]]
name = "platforms" name = "platforms"
version = "3.0.2" version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8"
[[package]] [[package]]
name = "plotters" name = "plotters"
@ -2197,7 +2208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -2217,7 +2228,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
"version_check", "version_check",
"yansi 1.0.0-rc.1", "yansi 1.0.0-rc.1",
] ]
@ -2284,9 +2295,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.32" version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -2354,34 +2365,34 @@ dependencies = [
[[package]] [[package]]
name = "ref-cast" name = "ref-cast"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ef7e18e8841942ddb1cf845054f8008410030a3997875d9e49b7a363063df1" checksum = "acde58d073e9c79da00f2b5b84eed919c8326832648a5b109b3fce1bb1175280"
dependencies = [ dependencies = [
"ref-cast-impl", "ref-cast-impl",
] ]
[[package]] [[package]]
name = "ref-cast-impl" name = "ref-cast-impl"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfaf0c85b766276c797f3791f5bc6d5bd116b41d53049af2789666b0c0bc9fa" checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.3" version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-automata 0.3.6", "regex-automata 0.3.7",
"regex-syntax 0.7.4", "regex-syntax 0.7.5",
] ]
[[package]] [[package]]
@ -2395,13 +2406,13 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.6" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-syntax 0.7.4", "regex-syntax 0.7.5",
] ]
[[package]] [[package]]
@ -2412,15 +2423,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.18" version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@ -2541,7 +2552,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rocket_http", "rocket_http",
"syn 2.0.28", "syn 2.0.29",
"unicode-xid", "unicode-xid",
] ]
@ -2596,9 +2607,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.8" version = "0.38.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags 2.4.0",
"errno", "errno",
@ -2689,9 +2700,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.183" version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -2708,20 +2719,20 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.183" version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.104" version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -2817,9 +2828,9 @@ dependencies = [
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.8" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -2904,12 +2915,6 @@ dependencies = [
"loom", "loom",
] ]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -2935,9 +2940,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.28" version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2952,9 +2957,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.29.7" version = "0.29.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165d6d8539689e3d3bc8b98ac59541e1f21c7de7c85d60dc80e43ae0ed2113db" checksum = "a8d0e9cc2273cc8d31377bdd638d72e3ac3e5607b18621062b169d02787f1bab"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"core-foundation-sys", "core-foundation-sys",
@ -2994,9 +2999,9 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.7.1" version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
@ -3016,22 +3021,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.44" version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.44" version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -3046,9 +3051,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.25" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
@ -3065,9 +3070,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.11" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
dependencies = [ dependencies = [
"time-core", "time-core",
] ]
@ -3099,9 +3104,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.30.0" version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd" checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -3124,7 +3129,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -3245,7 +3250,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -3388,9 +3393,9 @@ checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
[[package]] [[package]]
name = "url" name = "url"
version = "2.4.0" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
@ -3478,7 +3483,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3512,7 +3517,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3581,7 +3586,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [ dependencies = [
"windows-targets 0.48.1", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -3599,7 +3604,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [ dependencies = [
"windows-targets 0.48.1", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -3619,17 +3624,17 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.1" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.48.0", "windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.0", "windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.0", "windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.0", "windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.0", "windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.0", "windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.0", "windows_x86_64_msvc 0.48.5",
] ]
[[package]] [[package]]
@ -3640,9 +3645,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -3652,9 +3657,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -3664,9 +3669,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -3676,9 +3681,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -3688,9 +3693,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@ -3700,9 +3705,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -3712,26 +3717,27 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.9" version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344c9f03e6918ce61d94ea6b0500964bb42ee9ca9b2c9c8931990e20b481144" checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.10.1" version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [ dependencies = [
"winapi", "cfg-if",
"windows-sys 0.48.0",
] ]
[[package]] [[package]]
@ -3784,7 +3790,7 @@ checksum = "8f7f3a471f98d0a61c34322fbbfd10c384b07687f680d4119813713f72308d91"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -3804,5 +3810,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]

View File

@ -15,7 +15,7 @@ thiserror = "1"
lqos_config = { path = "../lqos_config" } lqos_config = { path = "../lqos_config" }
lqos_utils = { path = "../lqos_utils" } lqos_utils = { path = "../lqos_utils" }
lts_client = { path = "../lts_client" } lts_client = { path = "../lts_client" }
tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ] } tokio = { version = "1", features = [ "full" ] }
log = "0" log = "0"
nix = "0" nix = "0"
serde_cbor = "0" # For RFC8949/7409 format C binary objects serde_cbor = "0" # For RFC8949/7409 format C binary objects

View File

@ -141,7 +141,7 @@ impl WebUsers {
role: UserRole, role: UserRole,
) -> Result<String, AuthenticationError> { ) -> Result<String, AuthenticationError> {
let token; // Assigned in a branch let token; // Assigned in a branch
if let Some(mut user) = if let Some(user) =
self.users.iter_mut().find(|u| u.username == username) self.users.iter_mut().find(|u| u.username == username)
{ {
user.password_hash = Self::hash_password(password); user.password_hash = Self::hash_password(password);

View File

@ -211,6 +211,55 @@ impl EtcLqos {
} }
} }
/// Run this if you've received the OK from the licensing server, and been
/// sent a license key. This appends a [long_term_stats] section to your
/// config file - ONLY if one doesn't already exist.
pub fn enable_long_term_stats(license_key: String) {
if let Ok(raw) = std::fs::read_to_string("/etc/lqos.conf") {
let document = raw.parse::<Document>();
match document {
Err(e) => {
error!("Unable to parse TOML from /etc/lqos.conf");
error!("Full error: {:?}", e);
return;
}
Ok(mut config_doc) => {
let cfg = toml_edit::de::from_document::<EtcLqos>(config_doc.clone());
match cfg {
Ok(cfg) => {
// Now we enable LTS if its not present
if let Ok(isp_config) = crate::LibreQoSConfig::load() {
if cfg.long_term_stats.is_none() {
let mut new_section = toml_edit::table();
new_section["gather_stats"] = value(true);
new_section["collation_period_seconds"] = value(60);
new_section["license_key"] = value(license_key);
if isp_config.automatic_import_uisp {
new_section["uisp_reporting_interval_seconds"] = value(300);
}
config_doc["long_term_stats"] = new_section;
let new_cfg = config_doc.to_string();
if let Err(e) = fs::write(Path::new("/etc/lqos.conf"), new_cfg) {
log::error!("Unable to write to /etc/lqos.conf");
log::error!("{e:?}");
return;
}
}
}
}
Err(e) => {
error!("Unable to parse TOML from /etc/lqos.conf");
error!("Full error: {:?}", e);
return;
}
}
}
}
}
}
fn check_config(cfg_doc: &mut Document, cfg: &mut EtcLqos) { fn check_config(cfg_doc: &mut Document, cfg: &mut EtcLqos) {
use sha2::digest::Update; use sha2::digest::Update;
use sha2::Digest; use sha2::Digest;

View File

@ -14,7 +14,7 @@ mod program_control;
mod shaped_devices; mod shaped_devices;
pub use authentication::{UserRole, WebUsers}; pub use authentication::{UserRole, WebUsers};
pub use etc::{BridgeConfig, BridgeInterface, BridgeVlan, EtcLqos, Tunables}; pub use etc::{BridgeConfig, BridgeInterface, BridgeVlan, EtcLqos, Tunables, enable_long_term_stats};
pub use libre_qos_config::LibreQoSConfig; pub use libre_qos_config::LibreQoSConfig;
pub use network_json::{NetworkJson, NetworkJsonNode, NetworkJsonTransport}; pub use network_json::{NetworkJson, NetworkJsonNode, NetworkJsonTransport};
pub use program_control::load_libreqos; pub use program_control::load_libreqos;

View File

@ -21,6 +21,7 @@ nix = "0"
once_cell = "1" once_cell = "1"
dns-lookup = "1" dns-lookup = "1"
dashmap = "5" dashmap = "5"
reqwest = { version = "0.11.20", features = ["json"] }
# Support JemAlloc on supported platforms # Support JemAlloc on supported platforms
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] [target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]

View File

@ -0,0 +1,7 @@
use std::process::Command;
fn main() {
// Adds a git commit hash to the program
let output = Command::new("git").args(["rev-parse", "HEAD"]).output().unwrap();
let git_hash = String::from_utf8(output.stdout).unwrap();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
}

View File

@ -11,6 +11,7 @@ mod auth_guard;
mod config_control; mod config_control;
mod network_tree; mod network_tree;
mod queue_info; mod queue_info;
mod toasts;
// Use JemAllocator only on supported platforms // Use JemAllocator only on supported platforms
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@ -105,6 +106,9 @@ fn rocket() -> _ {
static_pages::fontawesome_solid, static_pages::fontawesome_solid,
static_pages::fontawesome_webfont, static_pages::fontawesome_webfont,
static_pages::fontawesome_woff, static_pages::fontawesome_woff,
// Front page toast checks
toasts::version_check,
toasts::stats_check,
], ],
); );

View File

@ -0,0 +1,113 @@
use lqos_config::EtcLqos;
use lqos_utils::unix_time::unix_now;
use rocket::serde::json::Json;
use rocket::serde::{Deserialize, Serialize};
static LAST_VERSION_CHECK: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
const ONE_HOUR_SECONDS: u64 = 60 * 60;
const VERSION_STRING: &str = include_str!("../../../VERSION_STRING");
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct VersionCheckRequest {
current_git_hash: String,
version_string: String,
node_id: String,
}
#[derive(Deserialize, Debug)]
#[serde(crate = "rocket::serde")]
pub struct VersionCheckResponse {
update_available: bool,
}
async fn send_version_check() -> anyhow::Result<VersionCheckResponse> {
if let Ok(cfg) = EtcLqos::load() {
let current_hash = env!("GIT_HASH");
let request = VersionCheckRequest {
current_git_hash: current_hash.to_string(),
version_string: VERSION_STRING.to_string(),
node_id: cfg.node_id.unwrap_or("(not configured)".to_string()),
};
let response = reqwest::Client::new()
.post("https://stats.libreqos.io/api/version_check")
.json(&request)
.send()
.await?
.json()
.await?;
Ok(response)
} else {
anyhow::bail!("No config");
}
}
#[get("/api/version_check")]
pub async fn version_check() -> Json<String> {
let last_check = LAST_VERSION_CHECK.load(std::sync::atomic::Ordering::Relaxed);
if let Ok(now) = unix_now() {
if now > last_check + ONE_HOUR_SECONDS {
let res = send_version_check().await;
if let Ok(response) = send_version_check().await {
LAST_VERSION_CHECK.store(now, std::sync::atomic::Ordering::Relaxed);
if response.update_available {
return Json(String::from("Update available"));
}
} else {
error!("Unable to send version check");
error!("{res:?}");
}
}
}
Json(String::from("All Good"))
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub enum StatsCheckResponse {
DoNothing,
NotSetup,
Disabled,
GoodToGo,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct StatsCheckAction {
action: StatsCheckResponse,
node_id: String,
}
#[get("/api/stats_check")]
pub async fn stats_check() -> Json<StatsCheckAction> {
let mut response = StatsCheckAction {
action: StatsCheckResponse::DoNothing,
node_id: String::new(),
};
if let Ok(cfg) = EtcLqos::load() {
if let Some(lts) = &cfg.long_term_stats {
if !lts.gather_stats {
response = StatsCheckAction {
action: StatsCheckResponse::Disabled,
node_id: cfg.node_id.unwrap_or("(not configured)".to_string()),
};
} else {
// Stats are enabled
response = StatsCheckAction {
action: StatsCheckResponse::GoodToGo,
node_id: cfg.node_id.unwrap_or("(not configured)".to_string()),
};
}
} else {
response = StatsCheckAction {
action: StatsCheckResponse::NotSetup,
node_id: cfg.node_id.unwrap_or("(not configured)".to_string()),
};
}
}
Json(response)
}

View File

@ -46,10 +46,7 @@
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li> <li class="nav-item" id="currentLogin"></li>
<li class="nav-item"> <li class="nav-item" id="statsLink"></li>
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth
Test</a>
</li>
<li class="nav-item ms-auto"> <li class="nav-item ms-auto">
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a> <a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li> </li>

View File

@ -37,9 +37,7 @@
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li> <li class="nav-item" id="currentLogin"></li>
<li class="nav-item"> <li class="nav-item" id="statsLink"></li>
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth Test</a>
</li>
<li class="nav-item ms-auto"> <li class="nav-item ms-auto">
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a> <a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li> </li>

View File

@ -160,8 +160,28 @@ function updateHostCounts() {
} }
$("#currentLogin").html(html); $("#currentLogin").html(html);
}); });
$("#startTest").on('click', () => { /*$("#startTest").on('click', () => {
$.get("/api/run_btest", () => { }); $.get("/api/run_btest", () => { });
});*/
// LTS Check
$.get("/api/stats_check", (data) => {
console.log(data);
let template = "<a class='nav-link' href='$URL$'><i class='fa fa-dashboard'></i> $TEXT$</a>";
switch (data.action) {
case "Disabled": {
template = template.replace("$URL$", "#")
.replace("$TEXT$", "<span style='color: red'>Stats Disabled</span>");
}
case "NotSetup": {
template = template.replace("$URL$", "https://stats.libreqos.io/trial1/" + encodeURI(data.node_id))
.replace("$TEXT$", "<span class='badge badge-pill badge-success green-badge'>Statistics Free Trial</span>");
} break;
default: {
template = template.replace("$URL$", "https://stats.libreqos.io/")
.replace("$TEXT$", "Statistics");
}
}
$("#statsLink").html(template);
}); });
} }

View File

@ -44,10 +44,7 @@
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li> <li class="nav-item" id="currentLogin"></li>
<li class="nav-item"> <li class="nav-item" id="statsLink"></li>
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth
Test</a>
</li>
<li class="nav-item ms-auto"> <li class="nav-item ms-auto">
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a> <a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li> </li>
@ -61,6 +58,8 @@
<div id="container" class="pad4"> <div id="container" class="pad4">
<div id="toasts"></div>
<!-- Dashboard Row 1 --> <!-- Dashboard Row 1 -->
<div class="row mbot8"> <div class="row mbot8">
<!-- THROUGHPUT --> <!-- THROUGHPUT -->
@ -351,6 +350,17 @@
updateHostCounts(); updateHostCounts();
updateSiteFunnel(); updateSiteFunnel();
OneSecondCadence(); OneSecondCadence();
// Version Check
$.get("/api/version_check", (data) => {
if (data != "All Good") {
let html = "<div class='alert alert-info alert-dismissible fade show' role='alert'>";
html += "<strong>LibreQoS Update Available!</strong>";
html += "<button type='button' class='btn-close' data-bs-dismiss='alert' aria-label='Close'></button>";
html += "</div>";
$("#toasts").append(html);
}
});
} }
$(document).ready(start); $(document).ready(start);

View File

@ -37,9 +37,7 @@
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li> <li class="nav-item" id="currentLogin"></li>
<li class="nav-item"> <li class="nav-item" id="statsLink"></li>
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth Test</a>
</li>
<li class="nav-item ms-auto"> <li class="nav-item ms-auto">
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a> <a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li> </li>

View File

@ -44,10 +44,7 @@
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li> <li class="nav-item" id="currentLogin"></li>
<li class="nav-item"> <li class="nav-item" id="statsLink"></li>
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth
Test</a>
</li>
<li class="nav-item ms-auto"> <li class="nav-item ms-auto">
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a> <a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li> </li>

View File

@ -12,7 +12,7 @@ crate-type = ["cdylib"]
pyo3 = "0" pyo3 = "0"
lqos_bus = { path = "../lqos_bus" } lqos_bus = { path = "../lqos_bus" }
lqos_utils = { path = "../lqos_utils" } lqos_utils = { path = "../lqos_utils" }
tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ] } tokio = { version = "1", features = [ "full" ] }
anyhow = "1" anyhow = "1"
sysinfo = "0" sysinfo = "0"
nix = "0" nix = "0"

View File

@ -102,7 +102,8 @@ pub(crate) fn xps_setup_default_disable(interface: &str) -> Result<()> {
fn sorted_txq_xps_cpus(interface: &str) -> Result<Vec<String>> { fn sorted_txq_xps_cpus(interface: &str) -> Result<Vec<String>> {
let mut result = Vec::new(); let mut result = Vec::new();
let paths = let paths =
std::fs::read_dir(&format!("/sys/class/net/{interface}/queues/"))?; std::fs::read_dir(&format!("/sys/class/net/{interface}/queues/"))
.map_err(|_| anyhow::anyhow!("/sys/class/net/{interface}/queues/ does not exist. Does this card only support one queue (not supported)?"))?;
for path in paths { for path in paths {
if let Ok(path) = &path { if let Ok(path) = &path {
if path.path().is_dir() { if path.path().is_dir() {

View File

@ -6,7 +6,7 @@ license = "GPL-2.0-only"
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
nix = "0" nix = { version = "0", features = ["time"] }
log = "0" log = "0"
notify = { version = "5.0.0", default-features = false } # Not using crossbeam because of Tokio notify = { version = "5.0.0", default-features = false } # Not using crossbeam because of Tokio
thiserror = "1" thiserror = "1"

View File

@ -44,7 +44,7 @@ pub(crate) fn get_nic_info() -> anyhow::Result<Vec<Nic>> {
current_nic = Some(Nic::default()); current_nic = Some(Nic::default());
} }
if let Some(mut nic) = current_nic.as_mut() { if let Some(nic) = current_nic.as_mut() {
if let Some(d) = trimmed.strip_prefix("description: ") { if let Some(d) = trimmed.strip_prefix("description: ") {
nic.description = d.to_string(); nic.description = d.to_string();
} }

View File

@ -5,6 +5,6 @@ edition = "2021"
license = "GPL-2.0-only" license = "GPL-2.0-only"
[dependencies] [dependencies]
tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ] } tokio = { version = "1", features = [ "full" ] }
anyhow = "1" anyhow = "1"
lqos_bus = { path = "../lqos_bus" } lqos_bus = { path = "../lqos_bus" }

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "GPL-2.0-only" license = "GPL-2.0-only"
[dependencies] [dependencies]
tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ] } tokio = { version = "1", features = [ "full" ] }
lqos_bus = { path = "../lqos_bus" } lqos_bus = { path = "../lqos_bus" }
lqos_utils = { path = "../lqos_utils" } lqos_utils = { path = "../lqos_utils" }
anyhow = "1" anyhow = "1"

View File

@ -12,7 +12,7 @@ uisp = { path = "../uisp" }
dryoc = { version = "0.5", features = ["serde"] } dryoc = { version = "0.5", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
thiserror = "1" thiserror = "1"
tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ] } tokio = { version = "1", features = [ "full" ] }
serde_cbor = "0" # For RFC8949/7409 format C binary objects serde_cbor = "0" # For RFC8949/7409 format C binary objects
log = "0" log = "0"
bincode = "1" bincode = "1"
@ -20,4 +20,5 @@ once_cell = "1"
sysinfo = "0" sysinfo = "0"
num-traits = "0.2" num-traits = "0.2"
miniz_oxide = "0.7.1" miniz_oxide = "0.7.1"
dashmap = "5.4" dashmap = "5.4"
serde_json = "1"

View File

@ -112,8 +112,14 @@ pub(crate) async fn collate_stats(comm_tx: Sender<SenderChannelMessage>) {
tree_entries.push(n); tree_entries.push(n);
} }
// Add to the submissions queue // Obtain the CPU/RAM utilization
let (cpu, ram) = system_stats::get_cpu_ram().await; let (cpu, ram) = system_stats::get_cpu_ram().await;
// Obtain queue stats
let cake_stats = super::update_cake_stats().await;
// Add to the submissions queue
new_submission(StatsSubmission { new_submission(StatsSubmission {
timestamp, timestamp,
totals: Some(StatsTotals { totals: Some(StatsTotals {
@ -138,6 +144,7 @@ pub(crate) async fn collate_stats(comm_tx: Sender<SenderChannelMessage>) {
hosts: Some(stats_hosts), hosts: Some(stats_hosts),
tree: Some(tree_entries), tree: Some(tree_entries),
uisp_devices: None, uisp_devices: None,
cake_stats,
}, comm_tx).await; }, comm_tx).await;
// Clear the collection buffer // Clear the collection buffer

View File

@ -35,10 +35,16 @@ pub async fn start_long_term_stats() -> Sender<StatsUpdateMessage> {
} }
async fn collation_scheduler(tx: Sender<StatsUpdateMessage>) { async fn collation_scheduler(tx: Sender<StatsUpdateMessage>) {
log::info!("Starting collation scheduler");
loop { loop {
let collation_period = get_collation_period(); let collation_period = get_collation_period();
tx.send(StatsUpdateMessage::CollationTime).await.unwrap(); log::info!("Collation period: {}s", collation_period.as_secs());
if tx.send(StatsUpdateMessage::CollationTime).await.is_err() {
log::warn!("Unable to send collation time message");
}
log::info!("Sent collation time message. Sleeping.");
tokio::time::sleep(collation_period).await; tokio::time::sleep(collation_period).await;
log::info!("Collation scheduler woke up.");
} }
} }
@ -107,11 +113,20 @@ fn get_uisp_collation_period() -> Option<Duration> {
} }
async fn uisp_collection_manager(control_tx: Sender<StatsUpdateMessage>) { async fn uisp_collection_manager(control_tx: Sender<StatsUpdateMessage>) {
if let Some(period) = get_uisp_collation_period() { // Outer loop: If UISP is disabled, check hourly to see if it
log::info!("Starting UISP poller with period {:?}", period); // was enabled. If it is enabled, start the inner loop.
loop { loop {
control_tx.send(StatsUpdateMessage::UispCollationTime).await.unwrap(); // Inner loop - if there's a collation period set for UISP,
tokio::time::sleep(period).await; // poll it.
if let Some(period) = get_uisp_collation_period() {
log::info!("Starting UISP poller with period {:?}", period);
loop {
control_tx.send(StatsUpdateMessage::UispCollationTime).await.unwrap();
tokio::time::sleep(period).await;
}
} else {
// Sleep for one hour - then we'll check again
tokio::time::sleep(Duration::from_secs(3600)).await;
} }
} }
} }

View File

@ -6,8 +6,11 @@ mod throughput_summary;
mod collation; mod collation;
mod network_tree; mod network_tree;
mod uisp_ext; mod uisp_ext;
mod quick_drops;
pub use stats_availability::StatsUpdateMessage; pub use stats_availability::StatsUpdateMessage;
pub use collection_manager::start_long_term_stats; pub use collection_manager::start_long_term_stats;
pub use throughput_summary::{ThroughputSummary, HostSummary}; pub use throughput_summary::{ThroughputSummary, HostSummary};
pub(crate) use collation::SESSION_BUFFER; pub(crate) use collation::SESSION_BUFFER;
pub use network_tree::NetworkTreeEntry; pub use network_tree::NetworkTreeEntry;
pub(crate) use quick_drops::*;
pub use quick_drops::CakeStats;

View File

@ -17,16 +17,20 @@ impl From<&NetworkJsonNode> for NetworkTreeEntry {
let mut min = if value.rtts.is_empty() { let mut min = if value.rtts.is_empty() {
0 0
} else { } else {
u16::MAX u64::MAX
}; };
let mut sum = 0; let mut sum: u64 = 0;
let mut count = 0;
for n in value.rtts.iter() { for n in value.rtts.iter() {
let n = *n; let n = *n as u64;
sum += n; if n > 0 {
if n < min { min = n; } sum += n;
if n > max { max = n; } if n < min { min = n; }
if n > max { max = n; }
count += 1;
}
} }
let avg = sum.checked_div(value.rtts.len() as u16).unwrap_or(0); let avg = sum.checked_div(count).unwrap_or(0);
Self { Self {
name: value.name.clone(), name: value.name.clone(),
@ -38,7 +42,7 @@ impl From<&NetworkJsonNode> for NetworkTreeEntry {
value.current_throughput.1.load(std::sync::atomic::Ordering::Relaxed) as u32, value.current_throughput.1.load(std::sync::atomic::Ordering::Relaxed) as u32,
), ),
node_type: value.node_type.clone(), node_type: value.node_type.clone(),
rtts: (min, max, avg), rtts: (min as u16, max as u16, avg as u16),
} }
} }
} }

View File

@ -0,0 +1,17 @@
//! Provides a quick'n'dirty 10 second snapshot of the TC queue
//! status. This is used by the LTS system to provide a quick'n'dirty
//! summary of drops and marks for the last 10 seconds.
mod queue_structure;
mod retriever;
mod stats_diff;
pub(crate) use retriever::*;
use serde::{Serialize, Deserialize};
pub(crate) use stats_diff::*;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CakeStats {
pub circuit_id: String,
pub drops: u64,
pub marks: u64,
}

View File

@ -0,0 +1,39 @@
mod tc_handle;
mod queue_network;
mod queue_node;
use log::error;
use queue_network::QueueNetwork;
use thiserror::Error;
pub(crate) use queue_node::QueueNode;
pub(crate) fn read_queueing_structure(
) -> Result<Vec<QueueNode>, QueueStructureError> {
// Note: the ? is allowed because the sub-types return a QueueStructureError and handle logging.
let network = QueueNetwork::from_json()?;
let flattened = network.to_flat();
Ok(flattened)
}
#[derive(Error, Debug)]
pub enum QueueStructureError {
#[error("unable to parse u64")]
U64Parse(String),
#[error("Unable to retrieve string from JSON")]
StringParse(String),
#[error("Unable to convert string to TC Handle")]
TcHandle(String),
#[error("Unable to convert string to u32 via hex")]
HexParse(String),
#[error("Error reading child circuit")]
Circuit,
#[error("Error reading child device")]
Device,
#[error("Error reading child's children")]
Children,
#[error("Unable to read configuration from /etc/lqos.conf")]
LqosConf,
#[error("Unable to access queueingStructure.json")]
FileNotFound,
#[error("Unable to read JSON")]
JsonError,
}

View File

@ -0,0 +1,77 @@
use super::{queue_node::QueueNode, QueueStructureError};
use log::error;
use lqos_config::EtcLqos;
use serde_json::Value;
use std::path::{Path, PathBuf};
pub struct QueueNetwork {
pub(crate) cpu_node: Vec<QueueNode>,
}
impl QueueNetwork {
pub fn path() -> Result<PathBuf, QueueStructureError> {
let cfg = EtcLqos::load();
if cfg.is_err() {
error!("unable to read /etc/lqos.conf");
return Err(QueueStructureError::LqosConf);
}
let cfg = cfg.unwrap();
let base_path = Path::new(&cfg.lqos_directory);
Ok(base_path.join("queuingStructure.json"))
}
fn exists() -> bool {
if let Ok(path) = QueueNetwork::path() {
path.exists()
} else {
false
}
}
pub(crate) fn from_json() -> Result<Self, QueueStructureError> {
let path = QueueNetwork::path()?;
if !QueueNetwork::exists() {
error!("queueStructure.json does not exist yet. Try running LibreQoS?");
return Err(QueueStructureError::FileNotFound);
}
let raw_string = std::fs::read_to_string(path)
.map_err(|_| QueueStructureError::FileNotFound)?;
let mut result = Self { cpu_node: Vec::new() };
let json: Value = serde_json::from_str(&raw_string)
.map_err(|_| QueueStructureError::FileNotFound)?;
if let Value::Object(map) = &json {
if let Some(network) = map.get("Network") {
if let Value::Object(map) = network {
for (key, value) in map.iter() {
result.cpu_node.push(QueueNode::from_json(key, value)?);
}
} else {
error!("Unable to parse JSON for queueStructure");
return Err(QueueStructureError::JsonError);
}
} else {
error!("Unable to parse JSON for queueStructure");
return Err(QueueStructureError::JsonError);
}
} else {
error!("Unable to parse JSON for queueStructure");
return Err(QueueStructureError::JsonError);
}
Ok(result)
}
pub fn to_flat(&self) -> Vec<QueueNode> {
let mut result = Vec::new();
for cpu in self.cpu_node.iter() {
result.push(cpu.clone());
let children = cpu.to_flat();
result.extend_from_slice(&children);
}
for c in result.iter_mut() {
c.circuits.clear();
c.devices.clear();
}
result
}
}

View File

@ -0,0 +1,253 @@
use super::{QueueStructureError, tc_handle::TcHandle};
use log::error;
use lqos_utils::hex_string::read_hex_string;
use serde_json::Value;
#[derive(Default, Clone, Debug)]
pub struct QueueNode {
pub download_bandwidth_mbps: u64,
pub upload_bandwidth_mbps: u64,
pub download_bandwidth_mbps_min: u64,
pub upload_bandwidth_mbps_min: u64,
pub class_id: TcHandle,
pub up_class_id: TcHandle,
pub parent_class_id: TcHandle,
pub up_parent_class_id: TcHandle,
pub class_major: u32,
pub up_class_major: u32,
pub class_minor: u32,
pub cpu_num: u32,
pub up_cpu_num: u32,
pub circuits: Vec<QueueNode>,
pub circuit_id: Option<String>,
pub circuit_name: Option<String>,
pub parent_node: Option<String>,
pub devices: Vec<QueueNode>,
pub comment: String,
pub device_id: Option<String>,
pub device_name: Option<String>,
pub mac: Option<String>,
pub children: Vec<QueueNode>,
}
/// Provides a convenient wrapper that attempts to decode a u64 from a JSON
/// value, and returns an error if decoding fails.
macro_rules! grab_u64 {
($target: expr, $key: expr, $value: expr) => {
let tmp = $value.as_u64().ok_or(QueueStructureError::U64Parse(format!("{} => {:?}", $key, $value)));
match tmp {
Err(e) => {
error!("Error decoding JSON. Key: {}, Value: {:?} is not readily convertible to a u64.", $key, $value);
return Err(e);
}
Ok(data) => $target = data,
}
};
}
/// Provides a macro to safely unwrap TC Handles and issue an error if they didn't parse
/// correctly.
macro_rules! grab_tc_handle {
($target: expr, $key: expr, $value: expr) => {
let s = $value.as_str();
if s.is_none() {
error!("Unable to parse {:?} as a string from JSON", s);
return Err(QueueStructureError::StringParse(format!("{:?}", $value)));
}
let s = s.unwrap();
let tmp = TcHandle::from_string(s);
if tmp.is_err() {
error!("Unable to parse {:?} as a TC Handle", s);
return Err(QueueStructureError::TcHandle(format!("{:?}", tmp)));
}
$target = tmp.unwrap();
};
}
/// Macro to convert hex strings (e.g. 0xff) to a u32
macro_rules! grab_hex {
($target: expr, $key: expr, $value: expr) => {
let s = $value.as_str();
if s.is_none() {
error!("Unable to parse {:?} as a string from JSON", $value);
return Err(QueueStructureError::StringParse(format!("{:?}", s)));
}
let s = s.unwrap();
let tmp = read_hex_string(s);
if tmp.is_err() {
error!("Unable to parse {:?} as a hex string", $value);
return Err(QueueStructureError::HexParse(format!("{:?}", tmp)));
}
$target = tmp.unwrap();
};
}
/// Macro to extract an option<string>
macro_rules! grab_string_option {
($target: expr, $key: expr, $value: expr) => {
let s = $value.as_str();
if s.is_none() {
error!("Unable to parse {:?} as a string from JSON", $value);
return Err(QueueStructureError::StringParse(format!("{:?}", s)));
}
$target = Some(s.unwrap().to_string());
};
}
/// Macro to extract a string
macro_rules! grab_string {
($target: expr, $key: expr, $value: expr) => {
let s = $value.as_str();
if s.is_none() {
error!("Unable to parse {:?} as a string from JSON", $value);
return Err(QueueStructureError::StringParse(format!("{:?}", s)));
}
$target = s.unwrap().to_string();
};
}
impl QueueNode {
pub(crate) fn from_json(
key: &str,
value: &Value,
) -> Result<Self, QueueStructureError> {
let mut result = Self::default();
if let Value::Object(map) = value {
for (key, value) in map.iter() {
match key.as_str() {
"downloadBandwidthMbps" | "maxDownload" => {
grab_u64!(result.download_bandwidth_mbps, key.as_str(), value);
}
"uploadBandwidthMbps" | "maxUpload" => {
grab_u64!(result.upload_bandwidth_mbps, key.as_str(), value);
}
"downloadBandwidthMbpsMin" | "minDownload" => {
grab_u64!(result.download_bandwidth_mbps_min, key.as_str(), value);
}
"uploadBandwidthMbpsMin" | "minUpload" => {
grab_u64!(result.upload_bandwidth_mbps_min, key.as_str(), value);
}
"classid" => {
grab_tc_handle!(result.class_id, key.as_str(), value);
}
"up_classid" => {
grab_tc_handle!(result.up_class_id, key.as_str(), value);
}
"classMajor" => {
grab_hex!(result.class_major, key.as_str(), value);
}
"up_classMajor" => {
grab_hex!(result.up_class_major, key.as_str(), value);
}
"classMinor" => {
grab_hex!(result.class_minor, key.as_str(), value);
}
"cpuNum" => {
grab_hex!(result.cpu_num, key.as_str(), value);
}
"up_cpuNum" => {
grab_hex!(result.up_cpu_num, key.as_str(), value);
}
"parentClassID" => {
grab_tc_handle!(result.parent_class_id, key.as_str(), value);
}
"up_parentClassID" => {
grab_tc_handle!(result.up_parent_class_id, key.as_str(), value);
}
"circuitId" | "circuitID" => {
grab_string_option!(result.circuit_id, key.as_str(), value);
}
"circuitName" => {
grab_string_option!(result.circuit_name, key.as_str(), value);
}
"parentNode" | "ParentNode" => {
grab_string_option!(result.parent_node, key.as_str(), value);
}
"comment" => {
grab_string!(result.comment, key.as_str(), value);
}
"deviceId" | "deviceID" => {
grab_string_option!(result.device_id, key.as_str(), value);
}
"deviceName" => {
grab_string_option!(result.device_name, key.as_str(), value);
}
"mac" => {
grab_string_option!(result.mac, key.as_str(), value);
}
"ipv4s" => {} // Ignore
"ipv6s" => {}
"circuits" => {
if let Value::Array(array) = value {
for c in array.iter() {
let n = QueueNode::from_json(key, c);
if n.is_err() {
error!("Unable to read circuit children");
error!("{:?}", n);
return Err(QueueStructureError::Circuit);
}
result.circuits.push(n.unwrap());
}
}
}
"devices" => {
if let Value::Array(array) = value {
for c in array.iter() {
let n = QueueNode::from_json(key, c);
if n.is_err() {
error!("Unable to read device children");
error!("{:?}", n);
return Err(QueueStructureError::Device);
}
result.devices.push(n.unwrap());
}
}
}
"children" => {
if let Value::Object(map) = value {
for (key, c) in map.iter() {
let n = QueueNode::from_json(key, c);
if n.is_err() {
error!("Unable to read children. Don't worry, we all feel that way sometimes.");
error!("{:?}", n);
return Err(QueueStructureError::Children);
}
result.circuits.push(n.unwrap());
}
} else {
log::warn!("Children was not an object");
log::warn!("{:?}", value);
}
}
"idForCircuitsWithoutParentNodes" | "type" => {
// Ignore
}
_ => log::error!("I don't know how to parse key: [{key}]"),
}
}
} else {
log::warn!("Unable to parse node structure for [{key}]");
}
Ok(result)
}
pub(crate) fn to_flat(&self) -> Vec<QueueNode> {
let mut result = Vec::new();
for c in self.circuits.iter() {
result.push(c.clone());
let children = c.to_flat();
result.extend_from_slice(&children);
}
for c in self.devices.iter() {
result.push(c.clone());
let children = c.to_flat();
result.extend_from_slice(&children);
}
for c in self.children.iter() {
result.push(c.clone());
let children = c.to_flat();
result.extend_from_slice(&children);
}
result
}
}

View File

@ -0,0 +1,69 @@
use log::error;
use lqos_utils::hex_string::read_hex_string;
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// Provides consistent handling of TC handle types.
#[derive(
Copy, Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq, Hash
)]
pub struct TcHandle(u32);
const TC_H_ROOT: u32 = 4294967295;
const TC_H_UNSPEC: u32 = 0;
impl TcHandle {
/// Returns the TC handle as two values, indicating major and minor
/// TC handle values.
#[inline(always)]
pub fn get_major_minor(&self) -> (u16, u16) {
// According to xdp_pping.c handles are minor:major u16s inside
// a u32.
((self.0 >> 16) as u16, (self.0 & 0xFFFF) as u16)
}
/// Build a TC handle from a string. This is actually a complicated
/// operation, since it has to handle "root" and other strings as well
/// as simple "1:2" mappings. Calls a C function to handle this gracefully.
pub fn from_string(
handle: &str,
) -> Result<Self, TcHandleParseError> {
let handle = handle.trim();
match handle {
"root" => Ok(Self(TC_H_ROOT)),
"none" => Ok(Self(TC_H_UNSPEC)),
_ => {
if !handle.contains(':') {
if let Ok(major) = read_hex_string(handle) {
let minor = 0;
return Ok(Self((major << 16) | minor));
} else {
error!("Unable to parse TC handle {handle}. Must contain a colon.");
return Err(TcHandleParseError::InvalidInput(handle.to_string()));
}
}
let parts: Vec<&str> = handle.split(':').collect();
let major = read_hex_string(parts[0]).map_err(|_| TcHandleParseError::InvalidInput(handle.to_string()))?;
let minor = read_hex_string(parts[1]).map_err(|_| TcHandleParseError::InvalidInput(handle.to_string()))?;
if major >= (1<<16) || minor >= (1<<16) {
return Err(TcHandleParseError::InvalidInput(handle.to_string()));
}
Ok(Self((major << 16) | minor))
}
}
}
}
impl ToString for TcHandle {
fn to_string(&self) -> String {
let (major, minor) = self.get_major_minor();
format!("{major:x}:{minor:x}")
}
}
#[derive(Error, Debug)]
pub enum TcHandleParseError {
#[error("Invalid input")]
InvalidInput(String),
}

View File

@ -0,0 +1,171 @@
//! Async reader/parser for tc -s -j qdisc show dev (whatever)
use thiserror::Error;
use tokio::process::Command;
pub use crate::collector::CakeStats;
use super::queue_structure::{read_queueing_structure, QueueNode};
#[derive(Debug, Error)]
pub(crate) enum AsyncQueueReaderMessage {
#[error("Unable to figure out the current queue structure")]
QueueStructure,
#[error("Unable to query the interface with tc")]
FetchRawFail,
#[error("Unable to fetch stdout")]
FetchStdout,
#[error("JSON decode error")]
JsonDecode,
}
pub(crate) struct AsyncQueueReader {
pub(crate) interface: String,
}
impl AsyncQueueReader {
pub(crate) fn new<S: ToString>(interface: S) -> Self {
Self {
interface: interface.to_string(),
}
}
pub(crate) async fn run(&self) -> Result<Option<Vec<CakeStats>>, AsyncQueueReaderMessage> {
let mut result = None;
if let Ok(queue_map) =
read_queueing_structure().map_err(|_| AsyncQueueReaderMessage::QueueStructure)
{
if let Ok(raw) = self.fetch_raw().await {
let stats = self.quick_parse(&raw, &queue_map).await?;
result = Some(stats);
} else {
log::error!("Unable to fetch raw tc output");
}
}
Ok(result)
}
pub(crate) async fn run_on_a_stick(&self) -> Result<(Option<Vec<CakeStats>>, Option<Vec<CakeStats>>), AsyncQueueReaderMessage> {
let mut result = (None, None);
if let Ok(queue_map) =
read_queueing_structure().map_err(|_| AsyncQueueReaderMessage::QueueStructure)
{
if let Ok(raw) = self.fetch_raw().await {
let stats = self.quick_parse_stick(&raw, &queue_map).await?;
result = (Some(stats.0), Some(stats.1));
} else {
log::error!("Unable to fetch raw tc output");
}
}
Ok(result)
}
async fn fetch_raw(&self) -> Result<String, AsyncQueueReaderMessage> {
let command_output = Command::new("/sbin/tc")
.args(["-s", "-j", "qdisc", "show", "dev", &self.interface])
.output()
.await
.map_err(|_| AsyncQueueReaderMessage::FetchRawFail)?;
let json = String::from_utf8(command_output.stdout)
.map_err(|_| AsyncQueueReaderMessage::FetchStdout)?;
Ok(json)
}
async fn quick_parse(&self, raw: &str, structure: &[QueueNode]) -> Result<Vec<CakeStats>, AsyncQueueReaderMessage> {
let mut result = Vec::with_capacity(structure.len());
let json = serde_json::from_str::<serde_json::Value>(raw)
.map_err(|_| AsyncQueueReaderMessage::JsonDecode)?;
if let Some(array) = json.as_array() {
for entry in array.iter() {
if let Some(map) = entry.as_object() {
if let (Some(kind), Some(handle)) =
(map.get_key_value("kind"), map.get_key_value("parent"))
{
if let (Some("cake"), Some(handle)) = (kind.1.as_str(), handle.1.as_str()) {
structure.iter().for_each(|node| {
if node.class_id.to_string() == handle.to_string() {
if let Some(circuit_id) = &node.circuit_id {
let mut stats = CakeStats {
circuit_id: circuit_id.to_string(),
..Default::default()
};
if let Some(serde_json::Value::Number(drops)) = map.get("drops") {
stats.drops = drops.as_u64().unwrap_or(0);
}
if let Some(serde_json::Value::Number(marks)) = map.get("ecn_mark") {
stats.marks = marks.as_u64().unwrap_or(0);
}
result.push(stats);
}
}
})
}
}
}
// Be good async citizens and don't eat the CPU
tokio::task::yield_now().await;
}
}
Ok(result)
}
async fn quick_parse_stick(&self, raw: &str, structure: &[QueueNode]) -> Result<(Vec<CakeStats>, Vec<CakeStats>), AsyncQueueReaderMessage> {
let mut down = Vec::with_capacity(structure.len());
let mut up = Vec::with_capacity(structure.len());
let json = serde_json::from_str::<serde_json::Value>(raw)
.map_err(|_| AsyncQueueReaderMessage::JsonDecode)?;
if let Some(array) = json.as_array() {
for entry in array.iter() {
if let Some(map) = entry.as_object() {
if let (Some(kind), Some(handle)) =
(map.get_key_value("kind"), map.get_key_value("parent"))
{
if let (Some("cake"), Some(handle)) = (kind.1.as_str(), handle.1.as_str()) {
structure.iter().for_each(|node| {
if node.class_id.to_string() == handle.to_string() {
if let Some(circuit_id) = &node.circuit_id {
let mut stats = CakeStats {
circuit_id: circuit_id.to_string(),
..Default::default()
};
if let Some(serde_json::Value::Number(drops)) = map.get("drops") {
stats.drops = drops.as_u64().unwrap_or(0);
}
if let Some(serde_json::Value::Number(marks)) = map.get("ecn_mark") {
stats.marks = marks.as_u64().unwrap_or(0);
}
down.push(stats);
}
} else if node.up_class_id.to_string() == handle.to_string() {
if let Some(circuit_id) = &node.circuit_id {
let mut stats = CakeStats {
circuit_id: circuit_id.to_string(),
..Default::default()
};
if let Some(serde_json::Value::Number(drops)) = map.get("drops") {
stats.drops = drops.as_u64().unwrap_or(0);
}
if let Some(serde_json::Value::Number(marks)) = map.get("ecn_mark") {
stats.marks = marks.as_u64().unwrap_or(0);
}
up.push(stats);
}
}
})
}
}
}
// Be good async citizens and don't eat the CPU
tokio::task::yield_now().await;
}
}
Ok((down, up))
}
}

View File

@ -0,0 +1,76 @@
use tokio::sync::Mutex;
use once_cell::sync::Lazy;
use super::CakeStats;
static CAKE_TRACKER: Lazy<Mutex<CakeTracker>> = Lazy::new(|| Mutex::new(CakeTracker::new()));
pub(crate) async fn update_cake_stats() -> Option<(Vec<CakeStats>, Vec<CakeStats>)> {
let mut tracker = CAKE_TRACKER.lock().await;
tracker.update().await
}
pub(crate) struct CakeTracker {
prev: Option<(Vec<CakeStats>, Vec<CakeStats>)>,
current: Option<(Vec<CakeStats>, Vec<CakeStats>)>,
}
impl CakeTracker {
pub(crate) fn new() -> Self {
Self {
prev: None,
current: None,
}
}
pub(crate) async fn update(&mut self) -> Option<(Vec<CakeStats>, Vec<CakeStats>)> {
if let Ok(cfg) = lqos_config::LibreQoSConfig::load() {
let outbound = &cfg.internet_interface;
let inbound = &cfg.isp_interface;
if cfg.on_a_stick_mode {
let reader = super::AsyncQueueReader::new(outbound);
if let Ok((Some(up), Some(down))) = reader.run_on_a_stick().await {
return self.read_up_down(up, down);
}
} else {
let out_reader = super::AsyncQueueReader::new(outbound);
let in_reader = super::AsyncQueueReader::new(inbound);
let (up, down) = tokio::join!(
out_reader.run(),
in_reader.run(),
);
if let (Ok(Some(up)), Ok(Some(down))) = (up, down) {
return self.read_up_down(up, down);
}
}
}
None
}
fn read_up_down(&mut self, up: Vec<CakeStats>, down: Vec<CakeStats>) -> Option<(Vec<CakeStats>, Vec<CakeStats>)> {
if self.prev.is_none() {
self.prev = Some((up, down));
None
} else {
// Delta time
if let Some((down, up)) = &mut self.current {
down.iter_mut().for_each(|d| {
if let Some(prev) = self.prev.as_ref().unwrap().0.iter().find(|p| p.circuit_id == d.circuit_id) {
d.drops = d.drops.saturating_sub(prev.drops);
d.marks = d.marks.saturating_sub(prev.marks);
}
});
up.iter_mut().for_each(|d| {
if let Some(prev) = self.prev.as_ref().unwrap().1.iter().find(|p| p.circuit_id == d.circuit_id) {
d.drops = d.drops.saturating_sub(prev.drops);
d.marks = d.marks.saturating_sub(prev.marks);
}
});
}
// Advance the previous
self.prev = self.current.take();
Some((up, down))
}
}
}

View File

@ -31,6 +31,7 @@ pub(crate) async fn gather_uisp_data(comm_tx: Sender<SenderChannelMessage>) {
cpu_usage: None, cpu_usage: None,
ram_percent: None, ram_percent: None,
uisp_devices: Some(uisp_devices), uisp_devices: Some(uisp_devices),
cake_stats: None,
}; };
new_submission(submission, comm_tx).await; new_submission(submission, comm_tx).await;
} else { } else {

View File

@ -11,6 +11,7 @@ pub mod collector;
/// Submissions system for `lqosd` /// Submissions system for `lqosd`
pub mod submission_queue; pub mod submission_queue;
pub use collector::CakeStats;
/// Re-export bincode /// Re-export bincode
pub mod bincode { pub mod bincode {

View File

@ -1,4 +1,3 @@
use std::path::Path;
use dryoc::dryocbox::*; use dryoc::dryocbox::*;
/// Genereate a new keypair and store it in a file. If the file exists, /// Genereate a new keypair and store it in a file. If the file exists,
@ -11,20 +10,9 @@ use dryoc::dryocbox::*;
/// # Returns /// # Returns
/// ///
/// The generated or loaded keypair /// The generated or loaded keypair
pub fn generate_new_keypair(key_path: &str) -> KeyPair { pub fn generate_new_keypair() -> KeyPair {
let path = Path::new(key_path);
if path.exists() {
if let Ok(bytes) = std::fs::read(path) {
if let Ok(keypair) = bincode::deserialize(&bytes) {
log::info!("Loaded keypair from {}", path.display());
return keypair;
}
}
}
let keypair = KeyPair::gen(); let keypair = KeyPair::gen();
let bytes = bincode::serialize(&keypair).unwrap(); log::info!("Generated new keypair");
std::fs::write(path, bytes).unwrap();
log::info!("Generated new keypair and stored it at {}", path.display());
keypair keypair
} }

View File

@ -1,8 +1,68 @@
use dryoc::{dryocbox::{Nonce, DryocBox}, types::{NewByteArray, ByteArray}}; use dryoc::{dryocbox::{Nonce, DryocBox}, types::{NewByteArray, ByteArray}};
use lqos_config::EtcLqos; use lqos_config::EtcLqos;
use crate::{transport_data::{LtsCommand, NodeIdAndLicense}, submission_queue::queue::QueueError}; use thiserror::Error;
use crate::{transport_data::{LtsCommand, NodeIdAndLicense, HelloVersion2}, submission_queue::queue::QueueError};
use super::keys::{SERVER_PUBLIC_KEY, KEYPAIR}; use super::keys::{SERVER_PUBLIC_KEY, KEYPAIR};
pub(crate) async fn encode_submission_hello(license_key: &str, node_id: &str, node_name: &str) -> Result<Vec<u8>, QueueError> {
let mut result = Vec::new();
// Build the body
let hello_message = HelloVersion2 {
license_key: license_key.to_string(),
node_id: node_id.to_string(),
node_name: node_name.to_string(),
client_public_key: KEYPAIR.read().await.public_key.clone().to_vec(),
};
// Add the version
result.extend(2u16.to_be_bytes());
// Pad to 32-bit boundary
result.extend(3u16.to_be_bytes());
// Serialize the body
let hello_bytes = serde_cbor::to_vec(&hello_message).map_err(|_| QueueError::SendFail)?;
// Add the length
result.extend((hello_bytes.len() as u64).to_be_bytes());
// Add the body
result.extend(hello_bytes);
Ok(result)
}
#[allow(dead_code)]
#[derive(Debug, Error)]
pub enum SubmissionDecodeError {
#[error("Invalid version")]
InvalidVersion,
#[error("Invalid padding")]
InvalidPadding,
#[error("Failed to deserialize")]
Deserialize,
}
#[allow(dead_code)]
pub(crate) fn decode_submission_hello(bytes: &[u8]) -> Result<HelloVersion2, SubmissionDecodeError> {
let version = u16::from_be_bytes([bytes[0], bytes[1]]);
if version != 2 {
log::error!("Received an invalid version from the server: {}", version);
return Err(SubmissionDecodeError::InvalidVersion);
}
let padding = u16::from_be_bytes([bytes[2], bytes[3]]);
if padding != 3 {
log::error!("Received an invalid padding from the server: {}", padding);
return Err(SubmissionDecodeError::InvalidPadding);
}
let size = u64::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11]]);
let hello_bytes = &bytes[12..12 + size as usize];
let hello: HelloVersion2 = serde_cbor::from_slice(hello_bytes).map_err(|_| SubmissionDecodeError::Deserialize)?;
Ok(hello)
}
pub(crate) async fn encode_submission(submission: &LtsCommand) -> Result<Vec<u8>, QueueError> { pub(crate) async fn encode_submission(submission: &LtsCommand) -> Result<Vec<u8>, QueueError> {
let nonce = Nonce::gen(); let nonce = Nonce::gen();
let mut result = Vec::new(); let mut result = Vec::new();
@ -57,4 +117,19 @@ fn get_license_key_and_node_id(nonce: &Nonce) -> Result<NodeIdAndLicense, QueueE
} }
} }
Err(QueueError::SendFail) Err(QueueError::SendFail)
}
#[cfg(test)]
mod test {
#[tokio::test]
async fn hello_submission_roundtrip() {
let license_key = "1234567890";
let node_id = "node_id";
let node_name = "node_name";
let hello = super::encode_submission_hello(license_key, node_id, node_name).await.unwrap();
let hello = super::decode_submission_hello(&hello).unwrap();
assert_eq!(hello.license_key, license_key);
assert_eq!(hello.node_id, node_id);
assert_eq!(hello.node_name, node_name);
}
} }

View File

@ -3,10 +3,10 @@ use lqos_config::EtcLqos;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use tokio::sync::RwLock; use tokio::sync::RwLock;
pub(crate) static KEYPAIR: Lazy<RwLock<KeyPair>> = Lazy::new(|| RwLock::new(generate_new_keypair("lts_keys.bin"))); pub(crate) static KEYPAIR: Lazy<RwLock<KeyPair>> = Lazy::new(|| RwLock::new(generate_new_keypair()));
pub(crate) static SERVER_PUBLIC_KEY: Lazy<RwLock<Option<PublicKey>>> = Lazy::new(|| RwLock::new(None)); pub(crate) static SERVER_PUBLIC_KEY: Lazy<RwLock<Option<PublicKey>>> = Lazy::new(|| RwLock::new(None));
async fn store_server_public_key(key: &PublicKey) { pub(crate) async fn store_server_public_key(key: &PublicKey) {
*SERVER_PUBLIC_KEY.write().await = Some(key.clone()); *SERVER_PUBLIC_KEY.write().await = Some(key.clone());
} }

View File

@ -1,8 +1,11 @@
use std::time::Duration; use std::time::Duration;
use tokio::{sync::mpsc::Receiver, time::sleep, net::TcpStream}; use lqos_config::EtcLqos;
use self::keys::key_exchange; use tokio::{sync::mpsc::Receiver, time::sleep, net::TcpStream, io::{AsyncWriteExt, AsyncReadExt}};
use super::{licensing::{get_license_status, LicenseState}, queue::send_queue}; use crate::submission_queue::comm_channel::keys::store_server_public_key;
use self::encode::encode_submission_hello;
use super::queue::{send_queue, QueueError};
mod keys; mod keys;
pub(crate) use keys::key_exchange;
mod encode; mod encode;
pub(crate) use encode::encode_submission; pub(crate) use encode::encode_submission;
@ -12,38 +15,25 @@ pub(crate) enum SenderChannelMessage {
} }
pub(crate) async fn start_communication_channel(mut rx: Receiver<SenderChannelMessage>) { pub(crate) async fn start_communication_channel(mut rx: Receiver<SenderChannelMessage>) {
let mut connected = false; // let mut connected = false;
let mut stream: Option<TcpStream> = None; // let mut stream: Option<TcpStream> = None;
loop { loop {
match rx.try_recv() { match rx.try_recv() {
Ok(SenderChannelMessage::QueueReady) => { Ok(SenderChannelMessage::QueueReady) => {
// If not connected, see if we are allowed to connect and get a target log::info!("Trying to connect to stats.libreqos.io");
if !connected || stream.is_none() { let mut stream = connect_if_permitted().await;
log::info!("Establishing LTS TCP channel."); log::info!("Connection to stats.libreqos.io established");
stream = connect_if_permitted().await;
if stream.is_some() {
connected = true;
}
}
// If we're still not connected, skip - otherwise, send the // If we're still not connected, skip - otherwise, send the
// queued data // queued data
if let Some(tcpstream) = &mut stream { if let Ok(tcpstream) = &mut stream {
if connected && tcpstream.writable().await.is_ok() { // Send the data
// Send the data let all_good = send_queue(tcpstream).await;
let all_good = send_queue(tcpstream).await; if all_good.is_err() {
if all_good.is_err() { log::error!("Stream fail during send. Will re-send");
log::error!("Stream fail during send. Will re-send");
connected = false;
stream = None;
}
} else {
stream = None;
connected = false;
} }
} else { } else {
connected = false; log::error!("Unable to submit data to stats.libreqos.io: {stream:?}");
stream = None;
} }
} }
Ok(SenderChannelMessage::Quit) => { Ok(SenderChannelMessage::Quit) => {
@ -56,28 +46,85 @@ pub(crate) async fn start_communication_channel(mut rx: Receiver<SenderChannelMe
} }
} }
async fn connect_if_permitted() -> Option<TcpStream> { async fn connect_if_permitted() -> Result<TcpStream, QueueError> {
let license = get_license_status().await; log::info!("Connecting to stats.libreqos.io");
if let LicenseState::Valid { stats_host, .. } = license { // Check that we have a local license key and are enabled
if !key_exchange().await { let cfg = EtcLqos::load().map_err(|_| {
return None; log::error!("Unable to load config file.");
} QueueError::NoLocalLicenseKey
})?;
let node_id = cfg.node_id.ok_or_else(|| {
log::warn!("No node ID configured.");
QueueError::NoLocalLicenseKey
})?;
let node_name = cfg.node_name.unwrap_or(node_id.clone());
let usage_cfg = cfg.long_term_stats.ok_or_else(|| {
log::warn!("Long-term stats are not configured.");
QueueError::NoLocalLicenseKey
})?;
if !usage_cfg.gather_stats {
log::warn!("Gathering long-term stats is disabled.");
return Err(QueueError::StatsDisabled);
}
let license_key = usage_cfg.license_key.ok_or_else(|| {
log::warn!("No license key configured.");
QueueError::NoLocalLicenseKey
})?;
// Connect
let host = "stats.libreqos.io:9128";
let mut stream = TcpStream::connect(&host).await
.map_err(|e| {
log::error!("Unable to connect to {host}: {e:?}");
QueueError::SendFail
})?;
let host = format!("{stats_host}:9128"); // Send Hello
let stream = TcpStream::connect(&host).await; let bytes = encode_submission_hello(&license_key, &node_id, &node_name).await?;
match stream { stream.write_all(&bytes).await
Err(e) => { .map_err(|e| {
log::error!("Unable to connect to {host}: {e}"); log::error!("Unable to write to {host}: {e:?}");
return None; QueueError::SendFail
} })?;
Ok(stream) => {
if stream.writable().await.is_err() { // Receive Server Public Key or Denied
log::error!("Unable to write to {host}"); let result = stream.read_u16().await
return None; .map_err(|e| {
} log::error!("Unable to read reply from {host}, {e:?}");
return Some(stream); QueueError::SendFail
} })?;
match result {
0 => {
log::error!("License validation failure.");
return Err(QueueError::SendFail);
}
1 => {
// We received validation. Now to decode the public key.
let key_size = stream.read_u64().await
.map_err(|e| {
log::error!("Unable to read reply from {host}, {e:?}");
QueueError::SendFail
})?;
let mut key_buffer = vec![0u8; key_size as usize];
stream.read_exact(&mut key_buffer).await
.map_err(|e| {
log::error!("Unable to read reply from {host}, {e:?}");
QueueError::SendFail
})?;
let server_public_key = serde_cbor::from_slice(&key_buffer)
.map_err(|e| {
log::error!("Unable to decode key from {host}, {e:?}");
QueueError::SendFail
})?;
store_server_public_key(&server_public_key).await;
log::info!("Received server public key.");
}
_ => {
log::error!("Unexpected reply from server.");
return Err(QueueError::SendFail);
} }
} }
None
// Proceed
Ok(stream)
} }

View File

@ -1,4 +1,4 @@
use crate::transport_data::{ask_license_server, LicenseReply}; use crate::transport_data::{ask_license_server, LicenseReply, ask_license_server_for_new_account};
use lqos_config::EtcLqos; use lqos_config::EtcLqos;
use lqos_utils::unix_time::unix_now; use lqos_utils::unix_time::unix_now;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -28,6 +28,7 @@ static LICENSE_STATUS: Lazy<RwLock<LicenseStatus>> =
Lazy::new(|| RwLock::new(LicenseStatus::default())); Lazy::new(|| RwLock::new(LicenseStatus::default()));
pub(crate) async fn get_license_status() -> LicenseState { pub(crate) async fn get_license_status() -> LicenseState {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
if let Ok(unix_time) = unix_now() { if let Ok(unix_time) = unix_now() {
let license_status = { let license_status = {
LICENSE_STATUS.read().await.clone() LICENSE_STATUS.read().await.clone()
@ -43,7 +44,11 @@ pub(crate) async fn get_license_status() -> LicenseState {
const MISERLY_NO_KEY: &str = "IDontSupportDevelopersAndShouldFeelBad"; const MISERLY_NO_KEY: &str = "IDontSupportDevelopersAndShouldFeelBad";
async fn check_license(unix_time: u64) -> LicenseState { async fn check_license(unix_time: u64) -> LicenseState {
log::info!("Checking LTS stats license");
if let Ok(cfg) = EtcLqos::load() { if let Ok(cfg) = EtcLqos::load() {
// The config file is good. Is LTS enabled?
// If it isn't, we need to try very gently to see if a pending
// request has been submitted.
if let Some(cfg) = cfg.long_term_stats { if let Some(cfg) = cfg.long_term_stats {
if let Some(key) = cfg.license_key { if let Some(key) = cfg.license_key {
if key == MISERLY_NO_KEY { if key == MISERLY_NO_KEY {
@ -80,7 +85,29 @@ async fn check_license(unix_time: u64) -> LicenseState {
} }
} }
} }
} else {
// LTS is unconfigured - but not explicitly disabled.
// So we need to check if we have a pending request.
// If a license key has been assigned, then we'll setup
// LTS. If it hasn't, we'll just return Unknown.
if let Some(node_id) = &cfg.node_id {
if let Ok(result) = ask_license_server_for_new_account(node_id.to_string()).await {
if let LicenseReply::NewActivation { license_key } = result {
// We have a new license!
let _ = lqos_config::enable_long_term_stats(license_key);
// Note that we're not doing anything beyond this - the next cycle
// will pick up on there actually being a license
} else {
log::info!("No pending LTS license found");
}
}
} else {
// There's no node ID either - we can't talk to this
log::warn!("No NodeID is configured. No online services are possible.");
}
} }
} else {
log::error!("Unable to load lqosd configuration. Not going to try.");
} }
LicenseState::Unknown LicenseState::Unknown
} }

View File

@ -15,8 +15,11 @@ pub(crate) async fn enqueue_if_allowed(data: StatsSubmission, comm_tx: Sender<Se
log::error!("Your license is invalid. Please contact support."); log::error!("Your license is invalid. Please contact support.");
} }
LicenseState::Valid{ .. } => { LicenseState::Valid{ .. } => {
log::info!("Sending data to the queue.");
QUEUE.push(LtsCommand::Submit(Box::new(data))).await; QUEUE.push(LtsCommand::Submit(Box::new(data))).await;
let _ = comm_tx.send(SenderChannelMessage::QueueReady).await; if let Err(e) = comm_tx.send(SenderChannelMessage::QueueReady).await {
log::error!("Unable to send queue ready message: {}", e);
}
} }
} }
} }
@ -72,12 +75,20 @@ pub(crate) async fn send_queue(stream: &mut TcpStream) -> Result<(), QueueError>
let mut lock = QUEUE.queue.lock().await; let mut lock = QUEUE.queue.lock().await;
for message in lock.iter_mut() { for message in lock.iter_mut() {
let submission_buffer = encode_submission(&message.body).await?; let submission_buffer = encode_submission(&message.body).await?;
let ret = stream.write(&submission_buffer).await; let ret = stream.write_all(&submission_buffer).await;
log::info!("Sent submission: {} bytes.", submission_buffer.len()); log::info!("Sent submission: {} bytes.", submission_buffer.len());
if ret.is_err() { if ret.is_err() {
log::error!("Unable to write to TCP stream."); log::error!("Unable to write to TCP stream.");
log::error!("{:?}", ret); log::error!("{:?}", ret);
message.sent = false; message.sent = false;
match crate::submission_queue::comm_channel::key_exchange().await {
true => {
log::info!("Successfully exchanged license keys.");
}
false => {
log::error!("Unable to talk to the licensing system to fix keys.");
}
}
return Err(QueueError::SendFail); return Err(QueueError::SendFail);
} else { } else {
message.sent = true; message.sent = true;
@ -91,6 +102,10 @@ pub(crate) async fn send_queue(stream: &mut TcpStream) -> Result<(), QueueError>
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub(crate) enum QueueError { pub(crate) enum QueueError {
#[error("No local license key")]
NoLocalLicenseKey,
#[error("Stats are disabled")]
StatsDisabled,
#[error("Unable to send")] #[error("Unable to send")]
SendFail, SendFail,
} }

View File

@ -24,6 +24,11 @@ pub enum LicenseRequest {
/// The sodium-style public key of the requesting shaper node /// The sodium-style public key of the requesting shaper node
public_key: PublicKey, public_key: PublicKey,
}, },
/// Check to see if this node has been newly approved
PendingLicenseRequest {
/// The local node id
node_id: String,
}
} }
/// License server responses for a key /// License server responses for a key
@ -43,6 +48,11 @@ pub enum LicenseReply {
/// The server's public key /// The server's public key
public_key: PublicKey, public_key: PublicKey,
}, },
/// New Activation
NewActivation {
/// The license key to apply
license_key: String,
}
} }
/// Errors that can occur when checking licenses /// Errors that can occur when checking licenses
@ -71,4 +81,17 @@ pub struct NodeIdAndLicense {
pub license_key: String, pub license_key: String,
/// The Sodium Nonce /// The Sodium Nonce
pub nonce: [u8; 24], pub nonce: [u8; 24],
}
/// For the new V2 hello license system, encodes a greeting
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct HelloVersion2 {
/// The node id
pub node_id: String,
/// The license key
pub license_key: String,
// The name of the node requesting service
pub node_name: String,
/// The Sodium Public Key
pub client_public_key: Vec<u8>,
} }

View File

@ -17,7 +17,27 @@ fn build_license_request(key: String) -> Result<Vec<u8>, LicenseCheckError> {
let mut result = Vec::new(); let mut result = Vec::new();
let payload = serde_cbor::to_vec(&LicenseRequest::LicenseCheck { key }); let payload = serde_cbor::to_vec(&LicenseRequest::LicenseCheck { key });
if let Err(e) = payload { if let Err(e) = payload {
log::warn!("Unable to serialize statistics. Not sending them."); log::warn!("Unable to serialize license request. Not sending them.");
log::warn!("{e:?}");
return Err(LicenseCheckError::SerializeFail);
}
let payload = payload.unwrap();
// Store the version as network order
result.extend(1u16.to_be_bytes());
// Store the payload size as network order
result.extend((payload.len() as u64).to_be_bytes());
// Store the payload itself
result.extend(payload);
Ok(result)
}
fn build_activation_query(node_id: String) -> Result<Vec<u8>, LicenseCheckError> {
let mut result = Vec::new();
let payload = serde_cbor::to_vec(&LicenseRequest::PendingLicenseRequest { node_id } );
if let Err(e) = payload {
log::warn!("Unable to serialize license request. Not sending them.");
log::warn!("{e:?}"); log::warn!("{e:?}");
return Err(LicenseCheckError::SerializeFail); return Err(LicenseCheckError::SerializeFail);
} }
@ -107,6 +127,47 @@ pub async fn ask_license_server(key: String) -> Result<LicenseReply, LicenseChec
} }
} }
pub async fn ask_license_server_for_new_account(
node_id: String,
) -> Result<LicenseReply, LicenseCheckError>
{
if let Ok(buffer) = build_activation_query(node_id) {
let stream = TcpStream::connect(LICENSE_SERVER).await;
if let Err(e) = &stream {
if e.kind() == std::io::ErrorKind::NotFound {
log::error!("Unable to access {LICENSE_SERVER}. Check that lqosd is running and you have appropriate permissions.");
return Err(LicenseCheckError::SendFail);
}
}
let stream = stream;
match stream {
Ok(mut stream) => {
let ret = stream.write(&buffer).await;
if ret.is_err() {
log::error!("Unable to write to {LICENSE_SERVER} stream.");
log::error!("{:?}", ret);
return Err(LicenseCheckError::SendFail);
}
let mut buf = Vec::with_capacity(10240);
let ret = stream.read_to_end(&mut buf).await;
if ret.is_err() {
log::error!("Unable to read from {LICENSE_SERVER} stream.");
log::error!("{:?}", ret);
return Err(LicenseCheckError::SendFail);
}
decode_response(&buf)
}
Err(e) => {
log::warn!("TCP stream failed to connect: {:?}", e);
Err(LicenseCheckError::ReceiveFail)
}
}
} else {
Err(LicenseCheckError::SerializeFail)
}
}
/// Ask the license server for the public key /// Ask the license server for the public key
pub async fn exchange_keys_with_license_server( pub async fn exchange_keys_with_license_server(
node_id: String, node_id: String,

View File

@ -5,6 +5,8 @@ use lqos_config::ShapedDevice;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uisp::Device; use uisp::Device;
use crate::collector::CakeStats;
/// Type that provides a minimum, maximum and average value /// Type that provides a minimum, maximum and average value
/// for a given statistic within the associated time period. /// for a given statistic within the associated time period.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
@ -92,6 +94,9 @@ pub struct StatsSubmission {
pub ram_percent: Option<u32>, pub ram_percent: Option<u32>,
/// UISP Device Information /// UISP Device Information
pub uisp_devices: Option<Vec<UispExtDevice>>, pub uisp_devices: Option<Vec<UispExtDevice>>,
/// Queue Stats
pub cake_stats: Option<(Vec<CakeStats>, Vec<CakeStats>)>,
} }
/// Submission to the `lts_node` process /// Submission to the `lts_node` process

View File

@ -6,7 +6,7 @@ license = "GPL-2.0-only"
[dependencies] [dependencies]
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ] } tokio = { version = "1", features = [ "full" ] }
anyhow = "1" anyhow = "1"
lqos_bus = { path = "../lqos_bus" } lqos_bus = { path = "../lqos_bus" }
lqos_utils = { path = "../lqos_utils" } lqos_utils = { path = "../lqos_utils" }

View File

@ -5,6 +5,6 @@ edition = "2021"
license = "GPL-2.0-only" license = "GPL-2.0-only"
[dependencies] [dependencies]
tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ] } tokio = { version = "1", features = [ "full" ] }
anyhow = "1" anyhow = "1"
lqos_bus = { path = "../lqos_bus" } lqos_bus = { path = "../lqos_bus" }