diff --git a/Cargo.lock b/Cargo.lock index 49b0140..d3e2295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,9 +82,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -156,24 +156,24 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "asn1-rs" @@ -215,7 +215,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -227,7 +227,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -239,7 +239,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -274,7 +274,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -285,15 +285,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-credential-types" -version = "1.2.14" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" +checksum = "e93964ffdaf57857f544be3666a5f57570bb699e934700f11b49708f61bb556e" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -314,21 +314,22 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] name = "aws-runtime" -version = "1.7.4" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ed8e8c52d2dc2390ad9f15647fe663f71e9780b4262c190fbb823a32721566" +checksum = "7816e98ee912159f45d307e5ee6bfea4a335a55aee15f7f3e32f81a6f3000f1d" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -341,8 +342,8 @@ dependencies = [ "bytes", "bytes-utils", "fastrand", - "http 1.4.0", - "http-body 1.0.1", + "http 1.4.2", + "http-body 1.1.0", "percent-encoding", "pin-project-lite", "tracing", @@ -351,9 +352,9 @@ dependencies = [ [[package]] name = "aws-sdk-route53" -version = "1.114.0" +version = "1.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4b3ded4d6f91aefec370eac63d8522786f17c0763ef531e6a3be88f459219bc" +checksum = "6e80f600bb96d1dca90a78681252685942e81e777b8e8ba653369ff48b6698c1" dependencies = [ "arc-swap", "aws-credential-types", @@ -364,21 +365,22 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-smithy-xml", "aws-types", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.4.5" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71" +checksum = "723c2234ad7511ceef63eab016b7ba6ff7c55590fefb96fa8467af014a07309f" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -389,18 +391,18 @@ dependencies = [ "hex", "hmac", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "percent-encoding", - "sha2 0.11.0", + "sha2", "time", "tracing", ] [[package]] name = "aws-smithy-async" -version = "1.2.14" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" +checksum = "f02e407fb3b54891734224b9ffac8a71fdd35f542500fa1af95754a6b2beb316" dependencies = [ "futures-util", "pin-project-lite", @@ -409,9 +411,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.63.6" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" +checksum = "37843d9add67c3aff5856f409c6dc315d3cdff60f9c0cb5b670dab1e9920306d" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -419,8 +421,8 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", "percent-encoding", "pin-project-lite", @@ -430,26 +432,26 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.1.13" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3ef8931ad1c98aa6a55b4256f847f3116090819844e0dd41ea682cac5dd2d3" +checksum = "635d23afda0a6ab48d666c4d447c4873e8d1e83518a2be2093122397e50b838e" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", "h2 0.3.27", - "h2 0.4.13", + "h2 0.4.15", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "http-body 0.4.6", "hyper 0.14.32", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-rustls 0.24.2", "hyper-rustls 0.27.9", "hyper-util", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.37", + "rustls 0.23.42", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -460,9 +462,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.7" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa" +checksum = "3dc65a121adb4b33729919fcfa14fa36fb33c1555a8f06bb0e2188dbfdc1d9ef" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -471,18 +473,18 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" +checksum = "8e86338c869539a581bf161247762a6e87f92c5c075060057b5ed6d06632ed0c" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-runtime" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" +checksum = "bea94a9ff8464016338c851e24b472d7131c388c88898a502e781815b2ee6045" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -494,9 +496,9 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "http-body 0.4.6", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "pin-project-lite", "pin-utils", @@ -506,16 +508,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c" +checksum = "22ed1ebe6e0a95ea84570225f5a8208dec4b8f77e61a9b0d6f51773fcb4612f0" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "pin-project-lite", "tokio", "tracing", @@ -524,40 +526,40 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api-macros" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +checksum = "221eaa237ddf1ca79b60d1372aad77e47f9c0ea5b3ce5099da8c61d027dc77b3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "aws-smithy-schema" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", - "http 1.4.0", + "http 1.4.2", ] [[package]] name = "aws-smithy-types" -version = "1.4.9" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f93074121a1be41317b9aa607143ae17900631f7f59a99f2b905d519d6783b" +checksum = "d6dc683efb34b9e755675b37fedbe0103141e5b6df7bdc9eb6967756a8c167d8" dependencies = [ "base64-simd", "bytes", "bytes-utils", "futures-core", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "http-body 0.4.6", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "itoa", "num-integer", @@ -572,18 +574,21 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.15" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +checksum = "ea3f68eec3607f02acd24067969ce2abc6ba16aa7d5ce59ca450ed2fb5f78957" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.16" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" +checksum = "e957a6c6dbce82b7a91f44231c09273159703769f447cbe85e854dfe9cf67f86" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -604,10 +609,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-util", "itoa", "matchit", @@ -635,8 +640,8 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", "mime", "pin-project-lite", @@ -656,12 +661,12 @@ dependencies = [ "bytes", "either", "fs-err", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.9.0", + "http 1.4.2", + "http-body 1.1.0", + "hyper 1.10.1", "hyper-util", "pin-project-lite", - "rustls 0.23.37", + "rustls 0.23.42", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", @@ -676,7 +681,7 @@ checksum = "d18bc4e506fbb85ab7392ed993a7db4d1a452c71b75a246af4a80ab8c9d2dd50" dependencies = [ "assert_matches", "aya-obj", - "bitflags 2.11.0", + "bitflags 2.13.0", "bytes", "libc", "log", @@ -747,9 +752,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "blake2" @@ -771,9 +776,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] @@ -823,20 +828,20 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -846,9 +851,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bytes-utils" @@ -860,31 +865,11 @@ dependencies = [ "either", ] -[[package]] -name = "c2rust-bitfields" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcee50917f9de1a018e3f4f9a8f2ff3d030a288cffa4b18d9b391e97c12e4cfb" -dependencies = [ - "c2rust-bitfields-derive", -] - -[[package]] -name = "c2rust-bitfields-derive" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b457277798202ccd365b9c112ebee08ddd57f1033916c8b8ea52f222e5b715d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "cc" -version = "1.2.59" +version = "1.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ "find-msvc-tools", "jobserver", @@ -894,27 +879,27 @@ dependencies = [ [[package]] name = "cf-rustracing" -version = "1.3.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6565523d8145e63e0cf1b397a5f1bd4e90d5652a7dffb2de8cec460ff23ef6b1" +checksum = "93f85c3824e4191621dec0551e3cef3d511f329da9a8990bf3e450a85651d97e" dependencies = [ "backtrace", - "rand 0.10.1", + "rand 0.8.7", "tokio", "trackable", ] [[package]] name = "cf-rustracing-jaeger" -version = "1.3.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c0e4d8cce27f6a6eaff58d2b66f063a18b8ed0d6ef0947ae7a263afa3b7c08" +checksum = "a6a5f80d44c257c3300a7f45ada676c211e64bbbac591bbec19344a8f61fbcab" dependencies = [ "cf-rustracing", "hostname", "local-ip-address", "percent-encoding", - "rand 0.10.1", + "rand 0.9.5", "thrift_codec", "tokio", "trackable", @@ -934,9 +919,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -945,9 +930,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -968,9 +953,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -990,14 +975,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1116,18 +1101,18 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crypto-common" @@ -1165,7 +1150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.31.2", + "nix 0.31.3", "windows-sys 0.61.2", ] @@ -1217,7 +1202,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1228,7 +1213,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1270,9 +1255,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derivative" @@ -1303,7 +1285,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1313,7 +1295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1333,7 +1315,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid", "crypto-common 0.2.2", "ctutils", @@ -1345,7 +1327,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2", "libc", "objc2", @@ -1359,7 +1341,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1370,9 +1352,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "encoding_rs" @@ -1528,9 +1510,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.3.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" +checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" dependencies = [ "autocfg", "tokio", @@ -1617,7 +1599,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1671,13 +1653,13 @@ dependencies = [ [[package]] name = "getifaddrs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "802c6e75f730346652928a55c200ca680741aaaa60c4973d52a999f520c4fde4" +checksum = "a542e1b7ac1f3d62c5777d430d66eca9cb59e813c46b86e29fa9ce94ff9a4810" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1709,28 +1691,27 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", + "wasm-bindgen", ] [[package]] name = "getset" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" dependencies = [ - "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1761,7 +1742,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.13.1", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1770,17 +1751,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.0", - "indexmap 2.13.1", + "http 1.4.2", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1815,6 +1796,17 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + [[package]] name = "heck" version = "0.5.0" @@ -1860,9 +1852,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1881,24 +1873,24 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.2", ] [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http 1.4.2", + "http-body 1.1.0", "pin-project-lite", ] @@ -1916,9 +1908,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] @@ -1949,17 +1941,17 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2 0.4.15", + "http 1.4.2", + "http-body 1.1.0", "httparse", "httpdate", "itoa", @@ -1990,10 +1982,10 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.0", - "hyper 1.9.0", + "http 1.4.2", + "hyper 1.10.1", "hyper-util", - "rustls 0.23.37", + "rustls 0.23.42", "rustls-native-certs", "rustls-platform-verifier", "tokio", @@ -2007,7 +1999,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.9.0", + "hyper 1.10.1", "hyper-util", "pin-project-lite", "tokio", @@ -2024,14 +2016,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.9.0", + "http 1.4.2", + "http-body 1.1.0", + "hyper 1.10.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.6.5", "system-configuration", "tokio", "tower-service", @@ -2145,12 +2137,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -2190,32 +2176,30 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", + "hashbrown 0.17.1", ] [[package]] name = "inotify" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "inotify-sys", "libc", ] [[package]] name = "inotify-sys" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" dependencies = [ "libc", ] @@ -2239,15 +2223,15 @@ dependencies = [ "aws-lc-rs", "base64", "bytes", - "http 1.4.0", - "http-body 1.0.1", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", "httpdate", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-rustls 0.27.9", "hyper-util", "rcgen", - "rustls 0.23.37", + "rustls 0.23.42", "rustls-pki-types", "serde", "serde_json", @@ -2318,7 +2302,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2337,36 +2321,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] [[package]] name = "kqueue" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ "kqueue-sys", "libc", @@ -2374,11 +2357,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.0", "libc", ] @@ -2388,17 +2371,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -2412,9 +2389,9 @@ dependencies = [ [[package]] name = "libz-ng-sys" -version = "1.1.28" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be734b33b7bc6a42d92d23e25e69758f866cf564a88d0bf80866fcf5a52c2255" +checksum = "879917b256f6317769b9f374b435805a8697013098aacce5a38ac106cd6a9469" dependencies = [ "cmake", "libc", @@ -2447,9 +2424,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "local-ip-address" -version = "0.6.12" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b0187df4e614e42405b49511b82ff7a1774fbd9a816060ee465067847cac22" +checksum = "aa08fb2b1ec3ea84575e94b489d06d4ce0cbf052d12acd515838f50e3c3d63e3" dependencies = [ "libc", "neli", @@ -2467,9 +2444,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" @@ -2494,9 +2471,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memoffset" @@ -2550,9 +2527,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "log", @@ -2572,7 +2549,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "byteorder", "derive_builder", "getset", @@ -2592,7 +2569,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2631,7 +2608,7 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "libc", "log", "netlink-packet-core", @@ -2643,7 +2620,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "libc", "log", "netlink-packet-core", @@ -2715,7 +2692,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -2724,11 +2701,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -2751,7 +2728,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "fsevent-sys", "inotify", "kqueue", @@ -2769,13 +2746,14 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", ] [[package]] name = "nullnet-client" version = "0.1.0" dependencies = [ + "aes-gcm", "aya", "chrono", "clap", @@ -2873,9 +2851,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -2928,7 +2906,7 @@ checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "crc32fast", "hashbrown 0.15.5", - "indexmap 2.13.1", + "indexmap 2.14.0", "memchr", ] @@ -2979,11 +2957,11 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.80" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "cfg-if", "foreign-types", "libc", @@ -2999,7 +2977,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3016,18 +2994,18 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.6.0+3.6.2" +version = "300.6.1+3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" +checksum = "46eb8fb9fb3b61ce1c0f8a026c4c1a0714d3a9e138e7fbde78753ce2babc3846" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -3100,7 +3078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.13.1", + "indexmap 2.14.0", ] [[package]] @@ -3111,27 +3089,27 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.1", + "indexmap 2.14.0", ] [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3148,9 +3126,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pingora-cache" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59d8c4c939a3a193a3da0e061aa7acf7432431f92ee62a26f5a9e5167a0ade2" +checksum = "527735ac204efb9fa3884bfd9224d016c5735fabe1d394ebed145b40e7545b99" dependencies = [ "ahash", "async-trait", @@ -3160,7 +3138,7 @@ dependencies = [ "cf-rustracing", "cf-rustracing-jaeger", "hex", - "http 1.4.0", + "http 1.4.2", "httparse", "httpdate", "indexmap 1.9.3", @@ -3174,7 +3152,7 @@ dependencies = [ "pingora-http", "pingora-lru", "pingora-timeout", - "rand 0.8.6", + "rand 0.8.7", "regex", "rmp", "rmp-serde", @@ -3185,9 +3163,9 @@ dependencies = [ [[package]] name = "pingora-core" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08973c4853cef4c682f7a592907e81a32dcad69476c4846e5de079f16448b177" +checksum = "6a7ffe2f5acf9f94fd255cfd1438866bc9124f8f0c7d42562bd3f853df2094b7" dependencies = [ "ahash", "async-trait", @@ -3201,8 +3179,8 @@ dependencies = [ "derivative", "flate2", "futures", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.15", + "http 1.4.2", "httparse", "httpdate", "libc", @@ -3219,12 +3197,12 @@ dependencies = [ "pingora-runtime", "pingora-timeout", "prometheus", - "rand 0.8.6", + "rand 0.8.7", "regex", "serde", "serde_yaml", "sfv", - "socket2 0.6.3", + "socket2 0.6.5", "strum", "strum_macros", "tokio", @@ -3237,18 +3215,18 @@ dependencies = [ [[package]] name = "pingora-error" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fa97a500e7e5c27a7b8609b9294c8922c9656322285268bfad9520f12feb38" +checksum = "b23f7bc013de67e44ed902a82843f6157460b89d11da882bcc6f09f8ae380af1" [[package]] name = "pingora-header-serde" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2705feb8b50d4e734e0c7d3879aa040e655a45656276323ff530e254585dd816" +checksum = "828c0e53e74160cbfe8e67dd3a811eb6a253c36acbaf7a39a01d9aacfb9ac139" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.2", "httparse", "pingora-error", "pingora-http", @@ -3259,25 +3237,25 @@ dependencies = [ [[package]] name = "pingora-http" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb52d4651b687fab6abf669539cfd97b7cd94b301fde8f57c63354f9c9cc5e2" +checksum = "d553d310a15ec88107b9388a02885f798efc57764d8e9bdaaf32a76722927a10" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.2", "pingora-error", ] [[package]] name = "pingora-lru" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91bb5030596a3d442c0866ac68afe29c14ba558e77c726dcdf7016b0dbb359d9" +checksum = "6705a26ad89d241a989a5395641931ba37076f5ab5fbd19ee92402414a43af32" dependencies = [ "arrayvec", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "parking_lot", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] @@ -3295,9 +3273,9 @@ dependencies = [ [[package]] name = "pingora-pool" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67f034be36772f318370d058913db43dbd22c3763ad974c995ba2e4afb2bb52a" +checksum = "feb1237893b15a9cf6b371bee8d7e2e1c10742e4be6eb00ed38cfe87fd1363f8" dependencies = [ "crossbeam-queue", "log", @@ -3310,44 +3288,44 @@ dependencies = [ [[package]] name = "pingora-proxy" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1e070a98a70d0d05f2fdcfb706237e06a043b2fbc9261e8772a3459cc2175e" +checksum = "8a92ee756ecf6ecb6419864da651cad6cecd933b6d420a26877031efa16bef57" dependencies = [ "async-trait", "bytes", "clap", "futures", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.15", + "http 1.4.2", "log", "once_cell", "pingora-cache", "pingora-core", "pingora-error", "pingora-http", - "rand 0.8.6", + "rand 0.8.7", "regex", "tokio", ] [[package]] name = "pingora-runtime" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e371315b1c44c2e5a8788fdc61577527b785e121e6ff49144755f40d86511430" +checksum = "41815a13691a3e7d9ad0e34767d4140284132e31b95a4481f5e73ab6f407f834" dependencies = [ "once_cell", - "rand 0.8.6", + "rand 0.8.7", "thread_local", "tokio", ] [[package]] name = "pingora-timeout" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a853fee5ce510a7f5db2561f99c752724112ed13fc3820e70d462d278d704ea" +checksum = "8e3e321452eaa461e0b6c5aaa35b7e42527ee89df33710279f37fae7f066b68e" dependencies = [ "once_cell", "parking_lot", @@ -3369,9 +3347,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "polyval" @@ -3416,29 +3394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3467,9 +3423,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -3477,9 +3433,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ "heck", "itertools", @@ -3492,28 +3448,28 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.117", + "syn 2.0.118", "tempfile", ] [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "prost-types" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ "prost", ] @@ -3526,11 +3482,11 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "pulldown-cmark" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "memchr", "unicase", ] @@ -3555,9 +3511,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -3565,8 +3521,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.37", - "socket2 0.6.3", + "rustls 0.23.42", + "socket2 0.6.5", "thiserror 2.0.18", "tokio", "tracing", @@ -3575,18 +3531,19 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "aws-lc-rs", "bytes", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.4", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", - "rustls 0.23.37", + "rustls 0.23.42", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -3597,23 +3554,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.6.5", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -3632,9 +3589,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -3643,9 +3600,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -3653,12 +3610,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -3706,6 +3663,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rcgen" version = "0.14.8" @@ -3727,14 +3693,14 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -3744,9 +3710,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", @@ -3761,9 +3727,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -3775,11 +3741,11 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2 0.4.15", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-rustls 0.27.9", "hyper-util", "js-sys", @@ -3788,7 +3754,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls 0.23.42", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -3841,9 +3807,9 @@ dependencies = [ [[package]] name = "route_manager" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "319bb478ff9aae1dc7a544fa599e9eb47e2521621dc3921fba6abcaaa312092c" +checksum = "1de4a73da5e20c4e521c395e21918034624165c31511e86b03e494bf0afa56ef" dependencies = [ "flume", "libc", @@ -3873,9 +3839,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.11.0" +version = "8.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" dependencies = [ "axum", "rust-embed-impl", @@ -3885,32 +3851,33 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.11.0" +version = "8.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" dependencies = [ + "mime_guess", "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.117", + "syn 2.0.118", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.11.0" +version = "8.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" dependencies = [ - "sha2 0.10.9", + "sha2", "walkdir", ] [[package]] name = "rust_decimal" -version = "1.42.0" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c5108e3d4d903e21aac27f12ba5377b6b34f9f44b325e4894c7924169d06995" +checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" dependencies = [ "arrayvec", "num-traits", @@ -3918,15 +3885,15 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -3952,7 +3919,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -3973,24 +3940,24 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", "once_cell", "rustls-pki-types", - "rustls-webpki 0.103.10", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe 0.2.1", "rustls-pki-types", @@ -4000,9 +3967,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -4019,10 +3986,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.37", + "rustls 0.23.42", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.10", + "rustls-webpki 0.103.13", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -4047,9 +4014,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -4059,9 +4026,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -4109,7 +4076,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4159,14 +4126,14 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -4213,7 +4180,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -4227,21 +4194,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fa1f336066b758b7c9df34ed049c0e693a426afe2b27ff7d5b14f410ab1a132" dependencies = [ "base64", - "indexmap 2.13.1", + "indexmap 2.14.0", "rust_decimal", ] -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - [[package]] name = "sha2" version = "0.11.0" @@ -4255,9 +4211,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -4277,9 +4233,9 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -4299,9 +4255,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" @@ -4315,9 +4271,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4325,9 +4281,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ "lock_api", ] @@ -4363,7 +4319,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4385,9 +4341,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -4411,7 +4367,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4420,7 +4376,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4442,7 +4398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4474,7 +4430,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4485,14 +4441,14 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] @@ -4509,12 +4465,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -4524,15 +4479,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -4550,9 +4505,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -4565,16 +4520,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2 0.6.5", "tokio-macros", "windows-sys 0.61.2", ] @@ -4587,7 +4542,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4617,7 +4572,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls 0.23.42", "tokio", ] @@ -4663,7 +4618,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ - "indexmap 2.13.1", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime", @@ -4698,25 +4653,25 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tonic" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "axum", "base64", "bytes", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2 0.4.15", + "http 1.4.2", + "http-body 1.1.0", "http-body-util", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", "rustls-native-certs", - "socket2 0.6.3", + "socket2 0.6.5", "sync_wrapper", "tokio", "tokio-rustls 0.26.4", @@ -4736,14 +4691,14 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost", @@ -4761,7 +4716,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.117", + "syn 2.0.118", "tempfile", "tonic-build", ] @@ -4774,7 +4729,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.1", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -4791,11 +4746,11 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http 1.4.2", + "http-body 1.1.0", "pin-project-lite", "tower", "tower-layer", @@ -4835,7 +4790,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4874,14 +4829,13 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tun-rs" -version = "2.8.1" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88c6aea62111e48fb67b733d6bbdc2019272e6d6f7108c9b0f4cd25d715d38a" +checksum = "ea75f145e8f32c72b1afdf137f2181810b0232be9930519e8d82071b4a3b3bdf" dependencies = [ "blocking", "byteorder", "bytes", - "c2rust-bitfields", "encoding_rs", "getifaddrs", "ipnet", @@ -4889,7 +4843,7 @@ dependencies = [ "libloading", "log", "netconfig-rs", - "nix 0.31.2", + "nix 0.31.3", "route_manager", "scopeguard", "tokio", @@ -4900,9 +4854,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicase" @@ -4916,12 +4870,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" @@ -4976,11 +4924,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -5030,27 +4978,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -5061,9 +5000,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -5071,9 +5010,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5081,65 +5020,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.13.1", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap 2.13.1", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -5157,9 +5062,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] @@ -5300,7 +5205,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5311,7 +5216,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5578,107 +5483,25 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] name = "winreg" -version = "0.55.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" dependencies = [ "cfg-if", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.13.1", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.1", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.13.1", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" @@ -5765,28 +5588,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5806,15 +5629,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" @@ -5846,14 +5669,14 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" diff --git a/README.md b/README.md index 1f712ee..fe39902 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The repository should be cloned under `/root` so the provided `setup-*.sh` scrip NET_TYPE=VXLAN CERT_ENCRYPTION_KEY=<32 raw bytes or 64 hex chars> PROXY_IP=192.168.1.100 + ENCRYPTION_ENABLED=true ``` `CERT_ENCRYPTION_KEY` is **required** — the server refuses to start without it. It encrypts TLS certificate private keys (and the DNS-provider credentials of ACME-issued certs) at rest; @@ -50,6 +51,10 @@ The repository should be cloned under `/root` so the provided `setup-*.sh` scrip a per-initiator egress edge to this host. If unset, egress is disabled (the trigger is rejected with "PROXY_IP is not configured") — ingress still works. + `ENCRYPTION_ENABLED` toggles per-tunnel VLAN/VXLAN encryption (AES-256-GCM for VLAN, XFRM/MACsec + for VXLAN) and **defaults to `true`** — omit it to keep encryption on. Set it to `false`/`0`/`no` + to run tunnels unencrypted instead (a bare vxlan/veth link, no XFRM SA/policy or MACsec). + - TLS certificates are issued from Let's Encrypt via a DNS-01 challenge (UI: *Certificates* page). Each cert stores its DNS-provider credentials encrypted at rest and is **renewed automatically** before expiry. The renewal scan is tunable via optional env vars (defaults shown): diff --git a/ebpf/src/main.rs b/ebpf/src/main.rs index 046a440..2b2a243 100644 --- a/ebpf/src/main.rs +++ b/ebpf/src/main.rs @@ -25,6 +25,16 @@ use network_types::{ // - ARP (next-hop resolution) // - TCP to/from SERVER_IP:PORT (nullnet control plane / gRPC) // - UDP 4789/9999 to/from a peer (nullnet data plane: VXLAN / forward) +// - UDP on a live tunnel's per-tunnel VXLAN dstport, to/from the SPECIFIC +// peer that port was allocated to (each VXLAN tunnel gets its own +// dynamically-allocated dstport instead of the shared 4789 default; +// VXLAN_PORTS maps port -> peer IP, populated/depopulated by the control +// channel alongside PEERS as tunnels come and go). Paired rather than two +// independent sets, so one tunnel's port can't be satisfied by a +// different concurrent tunnel's peer IP. +// - ESP (proto 50) to/from a peer: cross-host VXLAN tunnels are wrapped in +// kernel IPsec/ESP (see vxlan-setup.sh); ESP is portless like ICMP, so it's +// scoped to known peers instead of a port check // Stateful additions: // - any packet whose flow is already in the CT map is allowed (established // return); every allowed non-ARP packet (re)inserts its canonical 5-tuple, @@ -45,11 +55,22 @@ const FORWARD_PORT: u16 = 9999; const PROTO_TCP: u8 = 6; const PROTO_UDP: u8 = 17; +const PROTO_ESP: u8 = 50; // Allowlist of peer underlay IPs (host-order `u32::from(Ipv4Addr)` keys). #[map] static PEERS: HashMap = HashMap::with_max_entries(4096, 0); +// Per-tunnel VXLAN dstport -> the specific peer underlay IP it was allocated +// to (host-order `u32::from(Ipv4Addr)`, same encoding as PEERS). Each live +// tunnel gets its own dynamically-allocated dstport (see nullnet-server's +// `UdpPortPool`) instead of the shared 4789 default, so this is what lets +// `data_plane()` recognize that traffic without widening the static +// ALLOW_PORTS operator policy — keyed by port -> peer (not two independent +// sets) so a packet must match the port its OWN tunnel was assigned. +#[map] +static VXLAN_PORTS: HashMap = HashMap::with_max_entries(4096, 0); + // Per-direction, per-proto destination-port allowlist. Key packs direction and // protocol alongside the port (see `allow_key`) so ingress/egress and TCP/UDP of // the same number are distinct. Populated by userspace from the four @@ -145,6 +166,10 @@ fn try_firewall(ctx: &TcContext, is_egress: bool) -> Result { // ICMP is portless and always allowed (echo + PMTUD/errors, both // directions). Not CT-tracked. Simpler than gating it for now. IpProto::Icmp => return Ok(TC_ACT_OK), + // ESP (cross-host VXLAN IPsec) is portless too, but unlike ICMP + // it carries real tunnel data, so it stays peer-scoped rather + // than unconditionally allowed — see the PROTO_ESP check below. + IpProto::Esp => (PROTO_ESP, 0u16, 0u16), _ => return Ok(TC_ACT_SHOT), }; @@ -161,7 +186,7 @@ fn try_firewall(ctx: &TcContext, is_egress: bool) -> Result { } // Base (stateless) allow decision, scoped by direction and node role. -#[inline] +#[inline(always)] fn base_allow( is_egress: bool, proto: u8, @@ -176,6 +201,9 @@ fn base_allow( if proto == PROTO_UDP && data_plane(src, dst, src_port, dst_port) { return true; } + if proto == PROTO_ESP && (is_peer(src) || is_peer(dst)) { + return true; + } // Gateway outbound: it is the internet boundary — allow all, track it. if is_gateway() && is_egress { return true; @@ -193,14 +221,24 @@ fn control_plane(src: u32, dst: u32, src_port: u16, dst_port: u16) -> bool { (dst == server && dst_port == ctrl_port) || (src == server && src_port == ctrl_port) } -// Data plane: UDP on the VXLAN (4789) or forward (9999) port with a known peer. +// Data plane: UDP on the VXLAN (4789) or forward (9999) shared ports with any +// known peer, OR on a live tunnel's own per-tunnel dstport with the SPECIFIC +// peer that port was allocated to. The shared ports aren't tunnel-exclusive +// (FORWARD_PORT is one listening socket for every VLAN tunnel on this host), +// so "any known peer" is correct there; a per-tunnel dstport is exclusive to +// one tunnel, so it's paired with that tunnel's own peer instead of checked +// against the flat peer set — otherwise a packet spoofing a *different* +// concurrent tunnel's peer IP would satisfy this tunnel's port. #[inline] fn data_plane(src: u32, dst: u32, src_port: u16, dst_port: u16) -> bool { - let on_data_port = dst_port == VXLAN_PORT + let on_fixed_port = dst_port == VXLAN_PORT || src_port == VXLAN_PORT || dst_port == FORWARD_PORT || src_port == FORWARD_PORT; - on_data_port && (is_peer(src) || is_peer(dst)) + if on_fixed_port && (is_peer(src) || is_peer(dst)) { + return true; + } + vxlan_port_peer(dst_port) == Some(src) || vxlan_port_peer(src_port) == Some(dst) } #[inline] @@ -208,6 +246,11 @@ fn is_peer(ip: u32) -> bool { unsafe { PEERS.get(&ip) }.is_some() } +#[inline] +fn vxlan_port_peer(port: u16) -> Option { + unsafe { VXLAN_PORTS.get(&port) }.copied() +} + // Is `port` allowed as a destination in this direction/proto? (ALLOW_PORTS key // layout must match userspace `allow_key` in members/nullnet-client/src/ebpf.) #[inline] diff --git a/members/nullnet-client/Cargo.toml b/members/nullnet-client/Cargo.toml index 825b496..dcbb664 100644 --- a/members/nullnet-client/Cargo.toml +++ b/members/nullnet-client/Cargo.toml @@ -22,5 +22,6 @@ network-interface = "2.0.5" gag.workspace = true chrono.workspace = true nfq = "0.2" +aes-gcm = "0.10" aya = "0.13" -libc = "0.2" \ No newline at end of file +libc = "0.2" diff --git a/members/nullnet-client/src/commands/mod.rs b/members/nullnet-client/src/commands/mod.rs index 3a55d42..9a72b4d 100644 --- a/members/nullnet-client/src/commands/mod.rs +++ b/members/nullnet-client/src/commands/mod.rs @@ -26,11 +26,20 @@ pub(crate) async fn setup_br0(rtnetlink_handle: &RtNetLinkHandle) { // delete existing OpenFlow rules OvsCommand::DeleteFlows.execute(); - // use the built-in switching logic - OvsCommand::AddFlow.execute(); - - // add our TAP to the bridge as a trunk port + // add our TAP to the bridge as a trunk port first, so the flow rules + // below can reference it by name OvsCommand::AddTrunkPort.execute(); + + // Safe fallback (same as OVS's original single default rule) for + // anything not covered by a more specific rule — mainly the brief + // window between an access port being created and its own redirect + // rule (installed in `configure_access_port`) landing. + OvsCommand::AddDefaultFlow.execute(); + + // Traffic arriving from the trunk (i.e. already decrypted by + // nullnet-client's userspace forwarder) is delivered by normal + // VLAN-aware switching. + OvsCommand::AddTrunkDeliveryFlow.execute(); } pub(crate) async fn configure_access_port( @@ -52,9 +61,22 @@ pub(crate) async fn configure_access_port( // add the peer interface to the bridge as an access port OvsCommand::AddAccessPort(&veth_peer_name, vlan_id).execute(); + + // Redirect this port's traffic to the trunk instead of letting OVS + // switch it directly to another local access port — and re-add the + // 802.1Q tag that gets stripped along the way, since the raw `output` + // action used to reach the trunk doesn't do that automatically the way + // `actions=normal` would. + OvsCommand::AddAccessRedirectFlow(&veth_peer_name, vlan_id).execute(); } pub(crate) async fn remove_vlan(rtnetlink_handle: &RtNetLinkHandle, vlan_id: u16) { + // remove this port's redirect flow before the port itself disappears, + // so no stale rule is left behind that could later match a different, + // unrelated port reusing the same OVS port number + let veth_peer_name = format!("veth-{vlan_id}p"); + OvsCommand::DeleteAccessRedirectFlow(&veth_peer_name).execute(); + // delete the veth pair rtnetlink_handle .execute(NetLinkCommand::DeleteVeth(vlan_id)) diff --git a/members/nullnet-client/src/commands/netlink.rs b/members/nullnet-client/src/commands/netlink.rs index 3bd7d3f..259735b 100644 --- a/members/nullnet-client/src/commands/netlink.rs +++ b/members/nullnet-client/src/commands/netlink.rs @@ -8,6 +8,15 @@ use rtnetlink::packet_route::route::{RouteAttribute, RouteHeader}; use rtnetlink::{Handle, LinkUnspec, LinkVeth, RouteMessageBuilder}; use std::net::{IpAddr, Ipv4Addr}; +/// Matches `vxlan-setup.sh`'s `OVERLAY_MTU`: this environment's physical +/// underlay (likely the host's own SDN/overlay layer) adds enough hidden +/// overhead that a full 1500-byte frame gets silently dropped past a +/// measured ~1104-byte ceiling. VLAN tunnels share the same physical path, +/// so the veth pair carrying a tunnel's traffic needs the same shrink — +/// otherwise a large enough frame (e.g. an SSH KEX packet) vanishes with no +/// error on either end. +const VLAN_VETH_MTU: u32 = 1080; + #[derive(Debug)] pub(super) enum NetLinkCommand<'a> { HandleVethPairCreation(Ipv4Network, &'a str, &'a str), @@ -68,9 +77,9 @@ async fn handle_veth_pair_creation( let veth = get_link_by_name(handle, veth_name).await?; let veth_peer = get_link_by_name(handle, veth_peer_name).await?; - // set both ends of the veth pair up + // shrink both ends to VLAN_VETH_MTU and set them up for link in [&veth, &veth_peer] { - set_link_up(handle, link).await?; + set_link_mtu_up(handle, link, VLAN_VETH_MTU).await?; } // assign the IP address to veth_name @@ -214,6 +223,21 @@ async fn set_link_up(handle: &Handle, link: &LinkMessage) -> Result<(), Error> { Ok(()) } +async fn set_link_mtu_up(handle: &Handle, link: &LinkMessage, mtu: u32) -> Result<(), Error> { + let req = LinkUnspec::new_with_index(link.header.index) + .mtu(mtu) + .up() + .build(); + handle + .link() + .set(req) + .execute() + .await + .handle_err(location!())?; + + Ok(()) +} + async fn delete_link(handle: &Handle, link: LinkMessage) -> Result<(), Error> { handle .link() diff --git a/members/nullnet-client/src/commands/ovs.rs b/members/nullnet-client/src/commands/ovs.rs index 50ad74b..2bab949 100644 --- a/members/nullnet-client/src/commands/ovs.rs +++ b/members/nullnet-client/src/commands/ovs.rs @@ -7,7 +7,30 @@ pub(super) enum OvsCommand<'a> { DeleteBridge, AddBridge, DeleteFlows, - AddFlow, + /// Fallback for anything not covered by a more specific rule below — + /// same behavior as OVS's original single default flow. Mainly covers + /// the brief startup window between an access port being created and + /// its own redirect flow (below) landing. + AddDefaultFlow, + /// Traffic arriving from the trunk (already decrypted by nullnet-client's + /// userspace forwarder) gets delivered by normal VLAN-aware L2 switching. + AddTrunkDeliveryFlow, + /// One rule per access port, installed alongside it: redirect this + /// port's traffic to the trunk instead of letting OVS switch it + /// directly to another local access port (which would bypass the TAP + /// and the encrypting userspace forwarder entirely when a tunnel's two + /// endpoints happen to be colocated on this host). `output:` is a + /// raw action — unlike `actions=normal`, it does *not* re-add the + /// 802.1Q tag that access ports carry only internally, so this + /// explicitly pushes the tag back on first: without that, packets would + /// arrive at nullnet-client's TAP already stripped of their VLAN tag + /// and get silently dropped as malformed. + AddAccessRedirectFlow(&'a str, u16), + /// Removes exactly the rule `AddAccessRedirectFlow` installed for this + /// port, so a torn-down tunnel doesn't leave a stale flow entry that + /// could wrongly match a future, unrelated port reusing the same + /// OVS port number. + DeleteAccessRedirectFlow(&'a str), AddTrunkPort, AddAccessPort(&'a str, u16), } @@ -33,7 +56,11 @@ impl OvsCommand<'_> { | OvsCommand::DeleteBridge | OvsCommand::AddAccessPort(_, _) | OvsCommand::AddTrunkPort => "ovs-vsctl", - OvsCommand::DeleteFlows | OvsCommand::AddFlow => "ovs-ofctl", + OvsCommand::DeleteFlows + | OvsCommand::AddDefaultFlow + | OvsCommand::AddTrunkDeliveryFlow + | OvsCommand::AddAccessRedirectFlow(_, _) + | OvsCommand::DeleteAccessRedirectFlow(_) => "ovs-ofctl", } } @@ -45,10 +72,36 @@ impl OvsCommand<'_> { .iter() .map(ToString::to_string) .collect(), - OvsCommand::AddFlow => ["add-flow", "br0", "priority=0,actions=normal"] + OvsCommand::AddDefaultFlow => ["add-flow", "br0", "priority=0,actions=normal"] .iter() .map(ToString::to_string) .collect(), + OvsCommand::AddTrunkDeliveryFlow => [ + "add-flow", + "br0", + &format!("priority=200,in_port={TAP_NAME},actions=normal"), + ] + .iter() + .map(ToString::to_string) + .collect(), + OvsCommand::AddAccessRedirectFlow(dev, vlan) => [ + "-O", + "OpenFlow13", + "add-flow", + "br0", + &format!( + "priority=150,in_port={dev},actions=push_vlan:0x8100,mod_vlan_vid:{vlan},output:{TAP_NAME}" + ), + ] + .iter() + .map(ToString::to_string) + .collect(), + OvsCommand::DeleteAccessRedirectFlow(dev) => { + ["del-flows", "br0", &format!("in_port={dev}")] + .iter() + .map(ToString::to_string) + .collect() + } OvsCommand::AddTrunkPort => ["add-port", "br0", TAP_NAME] .iter() .map(ToString::to_string) diff --git a/members/nullnet-client/src/control_channel.rs b/members/nullnet-client/src/control_channel.rs index f657acd..64c6110 100644 --- a/members/nullnet-client/src/control_channel.rs +++ b/members/nullnet-client/src/control_channel.rs @@ -1,5 +1,5 @@ use crate::commands::{RtNetLinkHandle, configure_access_port, dnat, egress, remove_vlan}; -use crate::ebpf::{FirewallPeers, NetId}; +use crate::ebpf::{FirewallPeers, FirewallVxlanPorts, NetId}; use crate::egress_state::{EgressRecord, EgressState}; use crate::host_mappings::HostMappingsState; use crate::peers::peer::{Peers, VethKey}; @@ -32,6 +32,7 @@ fn fire_event(grpc: &NullnetGrpcInterface, kind: AgentEventKind) { }); } +#[allow(clippy::too_many_arguments)] pub(crate) async fn control_channel( server: NullnetGrpcInterface, peers: Arc>, @@ -39,6 +40,7 @@ pub(crate) async fn control_channel( triggers_state: Arc, host_mappings_state: Arc, firewall_peers: Arc, + firewall_vxlan_ports: Arc, egress_state: Arc, ) -> Result<(), Error> { let (outbound, grpc_rx) = mpsc::channel(64); @@ -59,6 +61,7 @@ pub(crate) async fn control_channel( let host_mappings_state = host_mappings_state.clone(); let server = server.clone(); let firewall_peers = firewall_peers.clone(); + let firewall_vxlan_ports = firewall_vxlan_ports.clone(); match message.message { Some(net_message::Message::VlanSetup(vlan_setup)) => { tokio::spawn(async move { @@ -98,6 +101,7 @@ pub(crate) async fn control_channel( host_mappings_state, server, firewall_peers, + firewall_vxlan_ports, egress_state, ) .await; @@ -113,6 +117,7 @@ pub(crate) async fn control_channel( host_mappings_state, server, firewall_peers, + firewall_vxlan_ports, egress_state, ); }); @@ -176,6 +181,32 @@ async fn handle_vlan_setup( }), ); })?; + // Fail before touching the network if the key is malformed — running + // this tunnel without a valid key would mean forwarding traffic in the + // clear instead of encrypted. Skipped entirely when the server had + // encryption disabled (ENCRYPTION_ENABLED=false) — `encryption_key` is + // just placeholder zero bytes in that case, not a real key to validate. + let encryption_key: Option<[u8; 32]> = if message.encrypted { + Some( + message + .encryption_key + .try_into() + .map_err(|_| "VLAN setup message carried a malformed encryption key") + .handle_err(location!()) + .inspect_err(|e| { + fire_event( + &grpc, + AgentEventKind::VlanSetupFailed(AgentVlanSetupFailed { + vlan_id: message.vlan_id, + local_veth: local_veth.to_string(), + error_reason: e.to_str().to_string(), + }), + ); + })?, + ) + } else { + None + }; // setup VLAN on this machine let init_t = std::time::Instant::now(); @@ -190,11 +221,15 @@ async fn handle_vlan_setup( init_t.elapsed().as_millis() ); - // register peer - peers - .write() - .await - .insert(VethKey::new(remote_veth, vlan_id), remote_ip); + // register peer + this tunnel's encryption state + { + let mut peers = peers.write().await; + peers.insert(VethKey::new(remote_veth, vlan_id), remote_ip); + match encryption_key { + Some(key) => peers.insert_key(vlan_id, &key), + None => peers.insert_plaintext(vlan_id), + } + } // allow this peer's data-plane traffic through the host firewall firewall_peers.add(NetId::Vlan(vlan_id), remote_ip); @@ -287,6 +322,7 @@ async fn handle_vxlan_setup( host_mappings_state: Arc, grpc: NullnetGrpcInterface, firewall_peers: Arc, + firewall_vxlan_ports: Arc, egress_state: Arc, ) -> Result<(), Error> { let egress_steer = message.egress_steer.unwrap_or(false); @@ -314,11 +350,57 @@ async fn handle_vxlan_setup( .remote_ip .parse::() .handle_err(location!())?; + // Fail before touching the network if the key is malformed — running + // this tunnel without a valid key would mean forwarding traffic in the + // clear instead of encrypted. Skipped entirely when the server had + // encryption disabled (ENCRYPTION_ENABLED=false) — `encryption_key` is + // just placeholder zero bytes in that case, and vxlan-setup.sh ignores + // it (see the `encrypted` arg passed below). + let encryption_key: [u8; 32] = if message.encrypted { + message + .encryption_key + .try_into() + .map_err(|_| "VXLAN setup message carried a malformed encryption key") + .handle_err(location!()) + .inspect_err(|e| { + fire_event( + &grpc, + AgentEventKind::VxlanSetupFailed(AgentVxlanSetupFailed { + vxlan_id, + ns_name: ns_name.clone(), + error_code: -1, + }), + ); + eprintln!("[vxlan_setup] {}", e.to_str()); + })? + } else { + [0u8; 32] + }; // allow this peer's data-plane (VXLAN underlay) traffic through the host // firewall before the tunnel comes up, so the first packets aren't dropped. firewall_peers.add(NetId::Vxlan(vxlan_id), remote_ip); + // also allow this tunnel's own per-tunnel dstport: the eBPF firewall's + // static data-plane check only knows the fixed 4789/9999 ports, not the + // dynamically-allocated dstport each VXLAN tunnel gets (see + // nullnet-server's `UdpPortPool`), so without this every packet on a real + // tunnel — cross-host VXLAN encapsulation, including the overlay ARP that + // has to succeed before any TCP connection can be routed — is silently + // dropped at this host's NIC. Paired with `remote_ip` specifically, so a + // different concurrent tunnel's peer can't satisfy this port. + // + // Skipped for the shared default port (same-host or unencrypted edges — + // see nullnet-server's DEFAULT_VXLAN_DSTPORT doc comment): that port is + // legitimately reused by many concurrent tunnels at once, so it can't be + // paired to one specific peer here — it's already allowed via the eBPF + // firewall's own VXLAN_PORT constant check instead (any known peer). + if let Ok(dstport) = u16::try_from(message.dstport) + && dstport != crate::DEFAULT_VXLAN_DSTPORT + { + firewall_vxlan_ports.add(vxlan_id, dstport, remote_ip); + } + // setup VXLAN on this machine (optionally attaching a Docker container) let init_t = std::time::Instant::now(); let mut cmd = std::process::Command::new("./vxlan_scripts/vxlan-setup.sh"); @@ -328,7 +410,10 @@ async fn handle_vxlan_setup( .arg(&br_name) .arg(br_net.to_string()) .arg(local_ip.to_string()) - .arg(remote_ip.to_string()); + .arg(remote_ip.to_string()) + .arg(hex_encode(&encryption_key)) + .arg(message.dstport.to_string()) + .arg(if message.encrypted { "true" } else { "false" }); // Egress-steer edges keep their tunnel endpoint in the host root namespace // (no `docker_container` arg) so the initiator container's *forwarded* // external traffic can be policy-routed into the bridge. Other edges attach @@ -529,6 +614,7 @@ fn handle_vxlan_teardown( host_mappings_state: Arc, grpc: NullnetGrpcInterface, firewall_peers: Arc, + firewall_vxlan_ports: Arc, egress_state: Arc, ) { // reverse egress steering/interception if this was an egress edge @@ -548,6 +634,9 @@ fn handle_vxlan_teardown( // drop this peer's firewall allowance (refcounted; only removed if unused) firewall_peers.remove(NetId::Vxlan(message.vxlan_id)); + // drop this tunnel's per-tunnel dstport allowance + firewall_vxlan_ports.remove(message.vxlan_id); + // remove DNAT before tearing the tunnel down so existing flows reset // cleanly. The `container_ip` matches the `-s` we used at install time. // `remove_by_vxlan` also matches the egress steer's sentinel-port entry @@ -583,7 +672,12 @@ fn handle_vxlan_teardown( let br_name = message.br_name; let mut cmd = std::process::Command::new("./vxlan_scripts/vxlan-teardown.sh"); - cmd.arg(vxlan_id.to_string()).arg(&ns_name).arg(&br_name); + cmd.arg(vxlan_id.to_string()) + .arg(&ns_name) + .arg(&br_name) + .arg(&message.local_ip) + .arg(&message.remote_ip) + .arg(message.dstport.to_string()); if let Some(container) = &message.docker_container { cmd.arg(container); } @@ -834,3 +928,9 @@ fn remove_hosts_entry(content: &str, name: &str) -> String { .collect(); lines.join("\n") + "\n" } + +/// Lowercase hex encoding, used to pass the tunnel's AES key to +/// `vxlan-setup.sh`/`vxlan-teardown.sh` as a shell argument. +fn hex_encode(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{b:02x}")).collect() +} diff --git a/members/nullnet-client/src/crypto.rs b/members/nullnet-client/src/crypto.rs new file mode 100644 index 0000000..32a9e1b --- /dev/null +++ b/members/nullnet-client/src/crypto.rs @@ -0,0 +1,139 @@ +use aes_gcm::aead::rand_core::RngCore; +use aes_gcm::aead::{Aead, KeyInit, OsRng}; +use aes_gcm::{Aes256Gcm, Key, Nonce}; + +/// AES-256-GCM cipher for one VLAN tunnel's traffic. Wire format produced by +/// [`Self::encrypt`] / consumed by [`Self::decrypt`] is `nonce[12] || ciphertext+tag`. +/// Unlike `nullnet-server`'s cert-at-rest `Encryptor` (keyed once, process-wide, +/// operates on UTF-8 strings), this is constructed per-tunnel from the key the +/// server hands out at `VlanSetup` time and operates on raw Ethernet frames. +pub(crate) struct TunnelCipher { + cipher: Aes256Gcm, +} + +impl TunnelCipher { + pub(crate) fn new(key: &[u8; 32]) -> Self { + Self { + cipher: Aes256Gcm::new(Key::::from_slice(key)), + } + } + + pub(crate) fn encrypt(&self, plaintext: &[u8]) -> Option> { + let mut nonce_bytes = [0u8; 12]; + OsRng.fill_bytes(&mut nonce_bytes); + let mut out = nonce_bytes.to_vec(); + out.extend( + self.cipher + .encrypt(Nonce::from_slice(&nonce_bytes), plaintext) + .ok()?, + ); + Some(out) + } + + pub(crate) fn decrypt(&self, data: &[u8]) -> Option> { + if data.len() < 12 { + return None; + } + let (nonce_bytes, ciphertext) = data.split_at(12); + self.cipher + .decrypt(Nonce::from_slice(nonce_bytes), ciphertext) + .ok() + } +} + +/// Wire framing for the VLAN userspace forwarder (`forward/send.rs`, +/// `forward/receive.rs`): every datagram on the forward socket is +/// `vlan_id[2, big-endian] || nonce[12] || ciphertext+tag`. The vlan_id has +/// to be readable in the clear so the receiver knows which tunnel's key to +/// decrypt with before it can read anything else. +pub(crate) fn seal(vlan_id: u16, cipher: &TunnelCipher, plaintext: &[u8]) -> Option> { + let mut out = vlan_id.to_be_bytes().to_vec(); + out.extend(cipher.encrypt(plaintext)?); + Some(out) +} + +/// Wire framing for a VLAN tunnel with encryption disabled: the same +/// `vlan_id[2, big-endian] || ...` prefix as `seal`, but the frame follows +/// as-is afterward — no nonce, no AEAD transform. Both ends agree on which +/// framing applies to a given `vlan_id` from the same `VlanSetup.encrypted` +/// flag, so there's no ambiguity despite the shorter format. +pub(crate) fn seal_plain(vlan_id: u16, plaintext: &[u8]) -> Vec { + let mut out = vlan_id.to_be_bytes().to_vec(); + out.extend_from_slice(plaintext); + out +} + +/// Splits a raw forward-socket datagram into its cleartext `vlan_id` and the +/// remaining `nonce || ciphertext+tag` slice, ready for `TunnelCipher::decrypt`. +pub(crate) fn open_vlan_id(datagram: &[u8]) -> Option<(u16, &[u8])> { + if datagram.len() < 2 { + return None; + } + let (vlan_id_bytes, rest) = datagram.split_at(2); + Some(( + u16::from_be_bytes([vlan_id_bytes[0], vlan_id_bytes[1]]), + rest, + )) +} + +#[cfg(test)] +mod tests { + use super::{TunnelCipher, open_vlan_id, seal, seal_plain}; + + #[test] + fn round_trip() { + let cipher = TunnelCipher::new(&[7u8; 32]); + let frame = b"pretend this is an ethernet frame"; + let ct = cipher.encrypt(frame).unwrap(); + assert_ne!(ct, frame); + assert_eq!(cipher.decrypt(&ct).unwrap(), frame); + } + + #[test] + fn nonce_randomizes_ciphertext() { + let cipher = TunnelCipher::new(&[7u8; 32]); + assert_ne!( + cipher.encrypt(b"same").unwrap(), + cipher.encrypt(b"same").unwrap() + ); + } + + #[test] + fn wrong_key_fails() { + let ct = TunnelCipher::new(&[1u8; 32]).encrypt(b"secret").unwrap(); + assert!(TunnelCipher::new(&[2u8; 32]).decrypt(&ct).is_none()); + } + + #[test] + fn truncated_data_fails_without_panicking() { + let cipher = TunnelCipher::new(&[3u8; 32]); + assert!(cipher.decrypt(&[0u8; 5]).is_none()); + } + + #[test] + fn seal_then_open_round_trips_vlan_id_and_plaintext() { + let cipher = TunnelCipher::new(&[9u8; 32]); + let frame = b"ethernet frame payload"; + let datagram = seal(4242, &cipher, frame).unwrap(); + + let (vlan_id, rest) = open_vlan_id(&datagram).unwrap(); + assert_eq!(vlan_id, 4242); + assert_eq!(cipher.decrypt(rest).unwrap(), frame); + } + + #[test] + fn seal_plain_then_open_round_trips_vlan_id_and_frame_untransformed() { + let frame = b"ethernet frame payload"; + let datagram = seal_plain(4242, frame); + + let (vlan_id, rest) = open_vlan_id(&datagram).unwrap(); + assert_eq!(vlan_id, 4242); + assert_eq!(rest, frame); + } + + #[test] + fn open_vlan_id_rejects_short_datagrams() { + assert!(open_vlan_id(&[0u8]).is_none()); + assert!(open_vlan_id(&[]).is_none()); + } +} diff --git a/members/nullnet-client/src/ebpf/mod.rs b/members/nullnet-client/src/ebpf/mod.rs index 20cf38a..cd71a6b 100644 --- a/members/nullnet-client/src/ebpf/mod.rs +++ b/members/nullnet-client/src/ebpf/mod.rs @@ -7,8 +7,9 @@ //! returns. ICMP is always allowed (both directions). Everything else is an //! explicit, env-driven allow: the four `{INGRESS,EGRESS}_ALLOW_{TCP,UDP}_PORTS` //! lists (→ `ALLOW_PORTS` map). On the egress-gateway host all outbound is -//! additionally allowed and tracked. Peers are added/removed from the `PEERS` map -//! by the control channel as edges come and go. +//! additionally allowed and tracked. Peers are added/removed from the `PEERS` map, +//! and each VXLAN tunnel's per-tunnel dstport (paired with its specific peer) from +//! the `VXLAN_PORTS` map, by the control channel as edges come and go. //! //! Loading: raise the memlock rlimit, `EbpfLoader` with `set_global`, ensure a //! clsact qdisc, load+attach each `SchedClassifier` to its hook, then populate @@ -55,7 +56,7 @@ pub enum NetId { } /// Live handle to the attached firewall. Holds the loaded `Ebpf` (dropping it -/// detaches the program) and the peer allowlist shared with the control +/// detaches the program) and the peer/port allowlists shared with the control /// channel. Keep it alive for the whole run. pub struct Firewall { // held only to keep the loaded program + attached links alive (drop = @@ -63,6 +64,7 @@ pub struct Firewall { #[allow(dead_code)] bpf: Ebpf, pub peers: Arc, + pub vxlan_ports: Arc, } /// Refcounted peer allowlist backing the `PEERS` BPF map. Multiple edges can @@ -134,6 +136,57 @@ impl FirewallPeers { } } +/// Live per-tunnel VXLAN dstport -> peer allowlist backing the `VXLAN_PORTS` +/// BPF map, added/removed by the control channel alongside `FirewallPeers` as +/// VXLAN edges come and go. Maps port -> the specific peer it was allocated +/// to (not just a marker), so the eBPF side can reject a packet that reuses a +/// live port but claims a *different* concurrent tunnel's peer IP. Unlike +/// peers (which can be shared by multiple edges), the server's `UdpPortPool` +/// guarantees a dstport belongs to at most one live tunnel at a time, so this +/// needs no refcounting — just insert on setup, remove on teardown, keyed by +/// `vxlan_id` so a re-point to a different port doesn't leak the old one. +pub struct FirewallVxlanPorts { + inner: Mutex, +} + +struct PortInner { + map: AyaHashMap, + by_id: HashMap, +} + +impl FirewallVxlanPorts { + fn new(map: AyaHashMap) -> Self { + Self { + inner: Mutex::new(PortInner { + map, + by_id: HashMap::new(), + }), + } + } + + /// Allow data-plane traffic on `port` for tunnel `vxlan_id`, scoped to + /// `peer` — the eBPF side checks this port's packets against the actual + /// peer address, not just "any known peer," so a different concurrent + /// tunnel's peer can't satisfy this one's port. + pub fn add(&self, vxlan_id: u32, port: u16, peer: Ipv4Addr) { + let mut inner = self.inner.lock().unwrap(); + if let Some(old_port) = inner.by_id.insert(vxlan_id, port) + && old_port != port + { + let _ = inner.map.remove(&old_port); + } + let _ = inner.map.insert(port, u32::from(peer), 0); + } + + /// Drop tunnel `vxlan_id`'s dstport allowance. + pub fn remove(&self, vxlan_id: u32) { + let mut inner = self.inner.lock().unwrap(); + if let Some(port) = inner.by_id.remove(&vxlan_id) { + let _ = inner.map.remove(&port); + } + } +} + pub fn enable(iface: &str, cfg: &FirewallConfig) -> Result { use aya::EbpfLoader; use aya::programs::{TcAttachType, tc}; @@ -180,9 +233,17 @@ pub fn enable(iface: &str, cfg: &FirewallConfig) -> Result { .try_into() .handle_err(location!())?; + let vxlan_ports_map: AyaHashMap = bpf + .take_map("VXLAN_PORTS") + .ok_or("VXLAN_PORTS map not found in bytecode") + .handle_err(location!())? + .try_into() + .handle_err(location!())?; + Ok(Firewall { bpf, peers: Arc::new(FirewallPeers::new(peers_map)), + vxlan_ports: Arc::new(FirewallVxlanPorts::new(vxlan_ports_map)), }) } diff --git a/members/nullnet-client/src/forward/receive.rs b/members/nullnet-client/src/forward/receive.rs index 66e0944..8f11de6 100644 --- a/members/nullnet-client/src/forward/receive.rs +++ b/members/nullnet-client/src/forward/receive.rs @@ -1,24 +1,53 @@ use std::sync::Arc; use tokio::net::UdpSocket; +use tokio::sync::RwLock; use tun_rs::AsyncDevice; +use crate::crypto; use crate::forward::frame::Frame; +use crate::peers::peer::{Peers, VlanCipher}; /// Handles incoming network packets (receives packets from the socket and /// writes them to the TAP interface). -pub async fn receive(device: &Arc, socket: &Arc) { +pub async fn receive( + device: &Arc, + socket: &Arc, + peers: &Arc>, +) { let mut frame = Frame::new(); loop { - // wait until there is an incoming packet on the socket (packets on the socket are raw IP) + // wait until there is an incoming datagram on the socket let Ok((s, _)) = socket.recv_from(&mut frame.frame).await else { continue; }; frame.size = s; if frame.size > 0 { + let datagram = &frame.frame[..frame.size]; + // the vlan_id has to be readable before decryption so we know + // which tunnel's key to decrypt with + let Some((vlan_id, sealed)) = crypto::open_vlan_id(datagram) else { + continue; + }; + let Some(vlan_key) = peers.read().await.get_key(vlan_id) else { + continue; + }; + // decrypt as the packet exits the tunnel; auth failure (wrong + // key, corrupted/spoofed datagram) drops it here. Plaintext + // tunnels have nothing to authenticate — the bytes are the frame. + let pkt_data = match vlan_key { + VlanCipher::Encrypted(cipher) => { + let Some(data) = cipher.decrypt(sealed) else { + continue; + }; + data + } + VlanCipher::Plaintext => sealed.to_vec(), + }; + // write packet to the kernel - device.send(frame.pkt_data()).await.unwrap_or(0); + device.send(&pkt_data).await.unwrap_or(0); } } } diff --git a/members/nullnet-client/src/forward/send.rs b/members/nullnet-client/src/forward/send.rs index 26fad06..0563386 100644 --- a/members/nullnet-client/src/forward/send.rs +++ b/members/nullnet-client/src/forward/send.rs @@ -6,8 +6,9 @@ use tokio::net::UdpSocket; use tokio::sync::RwLock; use tun_rs::AsyncDevice; +use crate::crypto; use crate::forward::frame::Frame; -use crate::peers::peer::{Peers, VethKey}; +use crate::peers::peer::{Peers, VethKey, VlanCipher}; /// Handles outgoing network packets (receives packets from the TAP interface /// and sends them to the socket). @@ -20,15 +21,30 @@ pub async fn send(device: &Arc, socket: &Arc, peers: Arc if frame.size > 0 { // send the packet to the socket let pkt_data = frame.pkt_data(); - let Ok(dst_socket) = get_dst_socket(pkt_data, &peers).await else { + let Ok((dst_socket, vlan_id)) = get_dst_socket(pkt_data, &peers).await else { continue; }; - socket.send_to(pkt_data, dst_socket).await.unwrap_or(0); + // encrypt (or frame plain) as the packet enters the tunnel: + // without a registered state for this vlan_id (tunnel torn down + // mid-flight, or setup never landed) there is nothing safe to send. + let Some(vlan_key) = peers.read().await.get_key(vlan_id) else { + continue; + }; + let datagram = match vlan_key { + VlanCipher::Encrypted(cipher) => crypto::seal(vlan_id, &cipher, pkt_data), + VlanCipher::Plaintext => Some(crypto::seal_plain(vlan_id, pkt_data)), + }; + if let Some(datagram) = datagram { + socket.send_to(&datagram, dst_socket).await.unwrap_or(0); + } } } } -async fn get_dst_socket(pkt_data: &[u8], peers: &Arc>) -> Result { +async fn get_dst_socket( + pkt_data: &[u8], + peers: &Arc>, +) -> Result<(SocketAddr, u16), Error> { let headers = LaxPacketHeaders::from_ethernet(pkt_data).handle_err(location!())?; let vlan_id = headers .vlan_ids() @@ -48,10 +64,11 @@ async fn get_dst_socket(pkt_data: &[u8], peers: &Arc>) -> Result Result<(), Error> { @@ -104,6 +109,7 @@ async fn main() -> Result<(), Error> { } }; let firewall_peers = ebpf_firewall.peers.clone(); + let firewall_vxlan_ports = ebpf_firewall.vxlan_ports.clone(); // shared dedup + waiter state, keyed by (initiator_container, port). // The NFQUEUE listener marks Pending and awaits the Notify; the control @@ -126,6 +132,7 @@ async fn main() -> Result<(), Error> { triggers_state_cc, host_mappings_state, firewall_peers, + firewall_vxlan_ports, egress_state, ) .await @@ -440,11 +447,12 @@ async fn setup_tap( let reader = reader_shared.clone(); let socket_1 = forward_socket.clone(); let socket_2 = socket_1.clone(); + let peers_1 = peers.clone(); let peers_2 = peers.clone(); // handle incoming traffic tokio::spawn(async move { - Box::pin(receive(&writer, &socket_1)).await; + Box::pin(receive(&writer, &socket_1, &peers_1)).await; }); // handle outgoing traffic diff --git a/members/nullnet-client/src/peers/peer.rs b/members/nullnet-client/src/peers/peer.rs index 813063d..b7ffab0 100644 --- a/members/nullnet-client/src/peers/peer.rs +++ b/members/nullnet-client/src/peers/peer.rs @@ -2,15 +2,34 @@ use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::Arc; use crate::FORWARD_PORT; +use crate::crypto::TunnelCipher; use serde::{Deserialize, Serialize}; +/// A VLAN tunnel's traffic is either AES-256-GCM encrypted (server's +/// `ENCRYPTION_ENABLED` was on when the tunnel was set up) or forwarded as-is. +/// Distinct from "unknown vlan_id" (`Peers::get_key` returning `None`), which +/// still means drop — this only ever comes from an explicit `VlanSetup`. +#[derive(Clone)] +pub enum VlanCipher { + Encrypted(Arc), + Plaintext, +} + /// Struct containing all peers information. #[derive(Default)] pub struct Peers { /// Mapping from veth addresses to Ethernet IPs. ips: HashMap, + /// Per-tunnel encryption state, keyed by `vlan_id`. Populated from the + /// `VlanSetup` the server hands out and used by the userspace forwarder + /// (`forward/send.rs`, `forward/receive.rs`) to encrypt/decrypt (or pass + /// through) this tunnel's traffic. `TunnelCipher` is `Arc`-wrapped so + /// callers can clone a handle out without holding the `Peers` lock across + /// the actual crypto work. + keys: HashMap, } impl Peers { @@ -24,8 +43,26 @@ impl Peers { self.ips.insert(veth_key, eth_ip); } + pub fn insert_key(&mut self, vlan_id: u16, key: &[u8; 32]) { + self.keys.insert( + vlan_id, + VlanCipher::Encrypted(Arc::new(TunnelCipher::new(key))), + ); + } + + /// Register `vlan_id` as deliberately unencrypted (server's + /// `ENCRYPTION_ENABLED` was off for this tunnel). + pub fn insert_plaintext(&mut self, vlan_id: u16) { + self.keys.insert(vlan_id, VlanCipher::Plaintext); + } + + pub fn get_key(&self, vlan_id: u16) -> Option { + self.keys.get(&vlan_id).cloned() + } + pub fn remove(&mut self, vlan_id: u16) { self.ips.retain(|key, _| key.vlan_id != vlan_id); + self.keys.remove(&vlan_id); } } diff --git a/members/nullnet-client/vxlan_scripts/vxlan-setup.sh b/members/nullnet-client/vxlan_scripts/vxlan-setup.sh index 366b734..339a39f 100755 --- a/members/nullnet-client/vxlan_scripts/vxlan-setup.sh +++ b/members/nullnet-client/vxlan_scripts/vxlan-setup.sh @@ -1,10 +1,10 @@ #!/bin/bash # Read CLI arguments: -if [ "$#" -lt 7 ] || [ "$#" -gt 8 ]; then - echo "Usage: $0 [docker_container]" - echo "Example (standalone): $0 100 ns_100_s 10.0.0.1/29 br_100_s 10.0.0.2/29 192.168.1.102 192.168.1.104" - echo "Example (docker): $0 100 ns_100_s 10.0.0.1/29 br_100_s 10.0.0.2/29 192.168.1.102 192.168.1.104 my_container" +if [ "$#" -lt 10 ] || [ "$#" -gt 11 ]; then + echo "Usage: $0 [docker_container]" + echo "Example (standalone): $0 100 ns_100_s 10.0.0.1/29 br_100_s 10.0.0.2/29 192.168.1.102 192.168.1.104 <64 hex chars> 20100 true" + echo "Example (docker): $0 100 ns_100_s 10.0.0.1/29 br_100_s 10.0.0.2/29 192.168.1.102 192.168.1.104 <64 hex chars> 20100 true my_container" exit 1 fi @@ -15,15 +15,27 @@ BR_NAME=$4 BR_NET=$5 LOCAL_IP=$6 REMOTE_IP=$7 -DOCKER_CONTAINER=$8 +KEY_HEX=$8 +DSTPORT=$9 +ENCRYPTED=${10} +DOCKER_CONTAINER=${11} BR_IP=$(echo $BR_NET | cut -d'/' -f1) -# Overlay MTU: underlay 1500 - 50 (VXLAN encap) = 1450. Set on every interface -# in the chain so the path is uniformly sized and the kernel advertises a -# correct TCP MSS / fragments UDP at the right boundary. Complements the -# TCPMSS clamp installed by the client (mss 1400) — both target the same path. -OVERLAY_MTU=1450 +# Overlay MTU: empirically measured via `ping -M do -s ` against a live +# cross-host tunnel — an IP packet of 1104 bytes gets through cleanly, 1105 +# never does, every time (a hard, reproducible ceiling, not a fragmentation +# reliability issue). That's ~400 bytes tighter than a naive 1500 - VXLAN(50) +# - ESP(~35) estimate accounts for, which means something in this specific +# underlay path (likely the Proxmox host network's own overlay/SDN layer) +# adds overhead this VM's own `ens18` MTU doesn't reflect. Rather than keep +# guessing at a theoretical budget, 1080 just stays safely under the measured +# 1104-byte ceiling. Set on every interface in the chain so the path is +# uniformly sized and the kernel advertises a correct TCP MSS / fragments UDP +# at the right boundary. Same-host (MACsec veth) tunnels don't need this — no +# physical MTU applies to a local veth pair — but sharing one constant is +# simpler and costs nothing. +OVERLAY_MTU=1080 if [ -n "$DOCKER_CONTAINER" ]; then # Docker mode: get the container's PID to enter its network namespace via nsenter @@ -56,7 +68,15 @@ if [ -z "$DOCKER_CONTAINER" ]; then fi if [ "$LOCAL_IP" == "$REMOTE_IP" ]; then - # Same host: connect bridges with a veth pair instead of a VXLAN tunnel + # Same host: connect bridges with a veth pair instead of a VXLAN tunnel. + # This traffic never leaves the host, so there's no physical-network + # sniffer to defend against — but it's still worth encrypting for + # defense-in-depth against another, differently-privileged + # container/process on the SAME host that could otherwise read this + # veth's or bridge's plaintext traffic directly. MACsec (802.1AE) wraps + # the veth link itself in AES-256-GCM, keyed with this tunnel's key — + # no IP addressing involved, so it works regardless of what the + # containers on either side are doing. VETH_S="veth-${VXLAN_ID}-s" VETH_C="veth-${VXLAN_ID}-c" # Both ends are created atomically; the losing task's EEXIST is harmless @@ -64,17 +84,111 @@ if [ "$LOCAL_IP" == "$REMOTE_IP" ]; then # Attach our end to our bridge if [[ "$BR_NAME" == *_s ]]; then LOCAL_VETH="$VETH_S" + PEER_VETH="$VETH_C" + MACSEC_IF="macsec-${VXLAN_ID}-s" else LOCAL_VETH="$VETH_C" + PEER_VETH="$VETH_S" + MACSEC_IF="macsec-${VXLAN_ID}-c" + fi + + if [ "$ENCRYPTED" == "true" ]; then + # The peer's MAC is available immediately: `ip link add ... peer + # name ...` creates both ends atomically in one kernel call, + # whether this invocation won the race above or lost it to the + # sibling script. + PEER_MAC=$(cat /sys/class/net/$PEER_VETH/address) + KEY_ID=$(printf '%032x' $VXLAN_ID) + + # MACsec adds up to 32 bytes of overhead (SecTAG + ICV for + # GCM-AES-256). Give the underlying veth the extra room — it's a + # virtual, host-only link with no physical MTU constraint — so the + # macsec interface on top of it can still carry a full + # OVERLAY_MTU-sized frame. + sudo ip link set "$LOCAL_VETH" mtu $((OVERLAY_MTU + 32)) up + + # Note the argument order: `port` (part of this device's own SCI) + # has to come before `cipher` — iproute2's macsec option parser is + # positional here, not a free-order keyword scanner, and silently + # rejects `port` if it comes after `cipher` ("unknown command + # \"port\"?"). Unlike the veth-pair creation above, none of these + # four commands race against the sibling script invocation (each + # side only ever touches its own uniquely-named macsec interface), + # so their stderr is deliberately left unsuppressed — a real + # failure here should be loud, not silently swallowed. + sudo ip link add link "$LOCAL_VETH" "$MACSEC_IF" type macsec port 1 cipher gcm-aes-256 encrypt on + sudo ip macsec add "$MACSEC_IF" tx sa 0 pn 1 on key "$KEY_ID" "$KEY_HEX" + sudo ip macsec add "$MACSEC_IF" rx port 1 address "$PEER_MAC" on + sudo ip macsec add "$MACSEC_IF" rx port 1 address "$PEER_MAC" sa 0 pn 1 on key "$KEY_ID" "$KEY_HEX" + + sudo ip link set "$MACSEC_IF" master "$BR_NAME" + sudo ip link set "$MACSEC_IF" mtu $OVERLAY_MTU up + else + # Encryption disabled: attach the veth straight to the bridge, no + # macsec interface in between. + sudo ip link set "$LOCAL_VETH" master "$BR_NAME" + sudo ip link set "$LOCAL_VETH" mtu $OVERLAY_MTU up fi - sudo ip link set "$LOCAL_VETH" master "$BR_NAME" - sudo ip link set "$LOCAL_VETH" mtu $OVERLAY_MTU up else - # Create the VXLAN tunnel using your physical IP and interface: - sudo ip link add vxlan-$NS_NAME type vxlan id $VXLAN_ID local $LOCAL_IP remote $REMOTE_IP dstport 4789 # dev ens18 + # Create the VXLAN tunnel using your physical IP and interface. Each + # tunnel gets its own dstport (instead of the IANA-standard 4789) so + # the XFRM policies below can tell concurrent tunnels between the same + # host pair apart. + sudo ip link add vxlan-$NS_NAME type vxlan id $VXLAN_ID local $LOCAL_IP remote $REMOTE_IP dstport $DSTPORT # dev ens18 # Attach the VXLAN to the bridge: sudo ip link set vxlan-$NS_NAME master $BR_NAME sudo ip link set vxlan-$NS_NAME mtu $OVERLAY_MTU up + + if [ "$ENCRYPTED" == "true" ]; then + # Encrypt this tunnel's traffic at the kernel level (AES-256-GCM via + # IPsec/ESP, transport mode) between the two hosts' physical IPs, + # scoped to this tunnel's dstport so it doesn't collide with any + # other concurrent VXLAN tunnel between the same host pair. + # + # RFC4106 GCM keys are "AES key || 4-byte salt". The server only + # hands out a 32-byte AES key (shared verbatim by both VLAN's + # software AEAD and this XFRM SA), so the salt is derived here, + # identically on both ends, from that same key — it doesn't need + # to be secret on its own, only reproducible from the shared + # secret both sides already have. + SALT_HEX=$(printf '%s' "$KEY_HEX" | sha256sum | cut -c1-8) + # `ip xfrm state add`'s ALGO-KEYMAT requires a "0x" prefix — a bare + # hex string is rejected outright with a bare "RTNETLINK answers: + # Invalid argument", confirmed by extensive live testing (see + # commit history). + AEAD_KEY_HEX="0x${KEY_HEX}${SALT_HEX}" + # SPI values 1-255 are IANA-reserved (RFC 4301) and the kernel's + # XFRM code rejects them outright ("Invalid argument"). vxlan_id + # starts at 101 (see net_id_pool.rs), which falls straight into + # that reserved range — offset it well clear of 255 rather than + # using the raw ID. + SPI=$(printf '0x%08x' $((VXLAN_ID + 1000))) + + # Outbound: this host -> remote. + # Note the argument order in both commands below — same lesson as + # the macsec argument-order bug, `ip xfrm` is positional, not a + # free-order keyword scanner: + # - `state add`: the ALGO-LIST (`aead ...`) must come before + # `mode`, not after — "ID [ALGO-LIST] [mode MODE] ..." per + # `ip xfrm state help`. Reversed, it fails with a bare + # "RTNETLINK answers: Invalid argument". + # - `policy add`: the selector (src/dst/proto/dport) must stay + # contiguous, with `dir` only appearing after it's complete — + # "SELECTOR dir DIR ..." per `ip xfrm policy help`. Splitting + # it by putting `dir` in the middle confuses the parser into + # thinking `proto` was given twice ("duplicate \"unknown\": + # \"proto\" is the second value"). + sudo ip xfrm state add src $LOCAL_IP dst $REMOTE_IP proto esp spi $SPI \ + aead 'rfc4106(gcm(aes))' $AEAD_KEY_HEX 128 mode transport + sudo ip xfrm policy add src $LOCAL_IP dst $REMOTE_IP proto udp dport $DSTPORT dir out \ + tmpl src $LOCAL_IP dst $REMOTE_IP proto esp spi $SPI mode transport + + # Inbound: remote -> this host. + sudo ip xfrm state add src $REMOTE_IP dst $LOCAL_IP proto esp spi $SPI \ + aead 'rfc4106(gcm(aes))' $AEAD_KEY_HEX 128 mode transport + sudo ip xfrm policy add src $REMOTE_IP dst $LOCAL_IP proto udp dport $DSTPORT dir in \ + tmpl src $REMOTE_IP dst $LOCAL_IP proto esp spi $SPI mode transport + fi fi # Enable IP forwarding: diff --git a/members/nullnet-client/vxlan_scripts/vxlan-teardown.sh b/members/nullnet-client/vxlan_scripts/vxlan-teardown.sh index 49a938a..f64ae11 100755 --- a/members/nullnet-client/vxlan_scripts/vxlan-teardown.sh +++ b/members/nullnet-client/vxlan_scripts/vxlan-teardown.sh @@ -1,19 +1,42 @@ #!/bin/bash # Read CLI arguments: -if [ "$#" -lt 3 ] || [ "$#" -gt 4 ]; then - echo "Usage: $0 [docker_container]" - echo "Example (standalone): $0 100 ns_100_s br_100_s" - echo "Example (docker): $0 100 ns_100_s br_100_s my_container" +if [ "$#" -lt 6 ] || [ "$#" -gt 7 ]; then + echo "Usage: $0 [docker_container]" + echo "Example (standalone): $0 100 ns_100_s br_100_s 192.168.1.102 192.168.1.104 20100" + echo "Example (docker): $0 100 ns_100_s br_100_s 192.168.1.102 192.168.1.104 20100 my_container" exit 1 fi VXLAN_ID=$1 NS_NAME=$2 BR_NAME=$3 -DOCKER_CONTAINER=$4 +LOCAL_IP=$4 +REMOTE_IP=$5 +DSTPORT=$6 +DOCKER_CONTAINER=$7 -# Remove the VXLAN tunnel or same-host veth pair: +# Remove this tunnel's XFRM state + policy pair, if any was installed (the +# same-host branch of vxlan-setup.sh never creates one). +if [ "$LOCAL_IP" != "$REMOTE_IP" ]; then + # Must match the same offset vxlan-setup.sh uses, to delete the actual + # installed SPI rather than the raw (and IANA-reserved) vxlan_id. + SPI=$(printf '0x%08x' $((VXLAN_ID + 1000))) + # Same argument-order requirement as vxlan-setup.sh: selector fields + # (src/dst/proto/dport) must stay contiguous, with `dir` only after. + sudo ip xfrm policy delete src $LOCAL_IP dst $REMOTE_IP proto udp dport $DSTPORT dir out 2>/dev/null + sudo ip xfrm state delete src $LOCAL_IP dst $REMOTE_IP proto esp spi $SPI 2>/dev/null + sudo ip xfrm policy delete src $REMOTE_IP dst $LOCAL_IP proto udp dport $DSTPORT dir in 2>/dev/null + sudo ip xfrm state delete src $REMOTE_IP dst $LOCAL_IP proto esp spi $SPI 2>/dev/null +fi + +# Remove the VXLAN tunnel or same-host veth pair. Deleting a veth end also +# destroys its peer and cascades to remove any macsec interface stacked on +# either end (the same-host branch of vxlan-setup.sh wraps each end in one), +# but delete both macsec names explicitly too rather than depend solely on +# that cascade. +sudo ip link del macsec-${VXLAN_ID}-s 2>/dev/null +sudo ip link del macsec-${VXLAN_ID}-c 2>/dev/null sudo ip link set vxlan-$NS_NAME down && sudo ip link del vxlan-$NS_NAME sudo ip link set veth-${VXLAN_ID}-s down && sudo ip link del veth-${VXLAN_ID}-s diff --git a/members/nullnet-grpc-lib/proto/nullnet_grpc.proto b/members/nullnet-grpc-lib/proto/nullnet_grpc.proto index 0f57bc6..3d6d501 100644 --- a/members/nullnet-grpc-lib/proto/nullnet_grpc.proto +++ b/members/nullnet-grpc-lib/proto/nullnet_grpc.proto @@ -94,6 +94,15 @@ message VlanSetup { string local_ip = 5; string remote_ip = 6; optional HostMapping host_mapping = 7; + // Per-tunnel AES-256 key (32 raw bytes), generated once by the server and + // sent identically to both endpoints. Used to encrypt/decrypt traffic in + // the client's userspace VLAN forwarder (see forward/send.rs, forward/receive.rs). + // Meaningless (placeholder zero bytes) when `encrypted` is false. + bytes encryption_key = 8; + // Whether this tunnel is encrypted, mirroring the server's ENCRYPTION_ENABLED + // env var at the time this edge was set up. When false, the client skips + // `encryption_key` entirely and forwards this VLAN's traffic in the clear. + bool encrypted = 9; } message VlanTeardown { @@ -123,6 +132,22 @@ message VxlanSetup { // Set on the gateway's edge: forward tunnelled external-bound packets to the // internet (kernel ip_forward + MASQUERADE); the Cilium egress-gateway model. optional bool egress_intercept = 13; + // Per-tunnel AES-256 key (32 raw bytes), generated once by the server and + // sent identically to both endpoints. Used as the XFRM/ESP SA key that + // encrypts this tunnel's traffic at the kernel level. Meaningless + // (placeholder zero bytes) when `encrypted` is false. + bytes encryption_key = 14; + // Per-tunnel VXLAN UDP destination port (replaces the IANA-standard 4789 + // default). Each tunnel gets a distinct port so an XFRM policy — which + // selects by src/dst IP and port, not by VNI — can tell concurrent + // tunnels between the same host pair apart. Allocated regardless of + // `encrypted`, for simplicity. + uint32 dstport = 15; + // Whether this tunnel is encrypted, mirroring the server's ENCRYPTION_ENABLED + // env var at the time this edge was set up. When false, the client skips + // `encryption_key` and vxlan-setup.sh sets up a bare vxlan/veth link with no + // XFRM SA/policy or MACsec. + bool encrypted = 16; } message VxlanTeardown { @@ -130,6 +155,11 @@ message VxlanTeardown { string ns_name = 2; string br_name = 3; optional string docker_container = 4; + // local_ip/remote_ip/dstport: needed to remove this tunnel's XFRM SA + + // policy pair (same values used to install them in VxlanSetup). + string local_ip = 5; + string remote_ip = 6; + uint32 dstport = 7; } message MsgId { diff --git a/members/nullnet-grpc-lib/src/proto/nullnet_grpc.rs b/members/nullnet-grpc-lib/src/proto/nullnet_grpc.rs index c614476..b55b070 100644 --- a/members/nullnet-grpc-lib/src/proto/nullnet_grpc.rs +++ b/members/nullnet-grpc-lib/src/proto/nullnet_grpc.rs @@ -63,6 +63,17 @@ pub struct VlanSetup { pub remote_ip: ::prost::alloc::string::String, #[prost(message, optional, tag = "7")] pub host_mapping: ::core::option::Option, + /// Per-tunnel AES-256 key (32 raw bytes), generated once by the server and + /// sent identically to both endpoints. Used to encrypt/decrypt traffic in + /// the client's userspace VLAN forwarder (see forward/send.rs, forward/receive.rs). + /// Meaningless (placeholder zero bytes) when `encrypted` is false. + #[prost(bytes = "vec", tag = "8")] + pub encryption_key: ::prost::alloc::vec::Vec, + /// Whether this tunnel is encrypted, mirroring the server's ENCRYPTION_ENABLED + /// env var at the time this edge was set up. When false, the client skips + /// `encryption_key` entirely and forwards this VLAN's traffic in the clear. + #[prost(bool, tag = "9")] + pub encrypted: bool, } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct VlanTeardown { @@ -106,6 +117,25 @@ pub struct VxlanSetup { /// internet (kernel ip_forward + MASQUERADE); the Cilium egress-gateway model. #[prost(bool, optional, tag = "13")] pub egress_intercept: ::core::option::Option, + /// Per-tunnel AES-256 key (32 raw bytes), generated once by the server and + /// sent identically to both endpoints. Used as the XFRM/ESP SA key that + /// encrypts this tunnel's traffic at the kernel level. Meaningless + /// (placeholder zero bytes) when `encrypted` is false. + #[prost(bytes = "vec", tag = "14")] + pub encryption_key: ::prost::alloc::vec::Vec, + /// Per-tunnel VXLAN UDP destination port (replaces the IANA-standard 4789 + /// default). Each tunnel gets a distinct port so an XFRM policy — which + /// selects by src/dst IP and port, not by VNI — can tell concurrent + /// tunnels between the same host pair apart. Allocated regardless of + /// `encrypted`, for simplicity. + #[prost(uint32, tag = "15")] + pub dstport: u32, + /// Whether this tunnel is encrypted, mirroring the server's ENCRYPTION_ENABLED + /// env var at the time this edge was set up. When false, the client skips + /// `encryption_key` and vxlan-setup.sh sets up a bare vxlan/veth link with no + /// XFRM SA/policy or MACsec. + #[prost(bool, tag = "16")] + pub encrypted: bool, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct VxlanTeardown { @@ -117,6 +147,14 @@ pub struct VxlanTeardown { pub br_name: ::prost::alloc::string::String, #[prost(string, optional, tag = "4")] pub docker_container: ::core::option::Option<::prost::alloc::string::String>, + /// local_ip/remote_ip/dstport: needed to remove this tunnel's XFRM SA + + /// policy pair (same values used to install them in VxlanSetup). + #[prost(string, tag = "5")] + pub local_ip: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub remote_ip: ::prost::alloc::string::String, + #[prost(uint32, tag = "7")] + pub dstport: u32, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct MsgId { diff --git a/members/nullnet-proxy/src/tls.rs b/members/nullnet-proxy/src/tls.rs index 7076c1c..3f8af69 100644 --- a/members/nullnet-proxy/src/tls.rs +++ b/members/nullnet-proxy/src/tls.rs @@ -89,7 +89,7 @@ fn cert_covers_domain(leaf: &X509Ref, domain: &str) -> bool { leaf.subject_name() .entries_by_nid(Nid::COMMONNAME) .next() - .and_then(|e| e.data().as_utf8().ok()) + .and_then(|e| e.data().to_string().ok()) .is_some_and(|cn| name_matches(&cn, domain)) } diff --git a/members/nullnet-server/src/env.rs b/members/nullnet-server/src/env.rs index 5b765fb..443d38f 100644 --- a/members/nullnet-server/src/env.rs +++ b/members/nullnet-server/src/env.rs @@ -27,3 +27,15 @@ pub static NET_TYPE: std::sync::LazyLock = std::sync::LazyLock::new(|| { _ => Net::default(), } }); + +/// Set to `false`/`0`/`no` to disable per-tunnel VLAN/VXLAN encryption +/// entirely: VLAN falls back to plain (unencrypted) userspace forwarding, +/// VXLAN falls back to a bare vxlan/veth link with no XFRM SA/policy or +/// MACsec. Defaults to enabled, preserving existing behavior when unset — +/// this is an opt-out, not an opt-in, so it needs its own parser rather than +/// nullnet-client's `env_bool` (which defaults false/opt-in). +pub static ENCRYPTION_ENABLED: std::sync::LazyLock = + std::sync::LazyLock::new(|| match std::env::var("ENCRYPTION_ENABLED") { + Ok(s) => !matches!(s.trim().to_ascii_lowercase().as_str(), "0" | "false" | "no"), + Err(_) => true, + }); diff --git a/members/nullnet-server/src/http_server/mod.rs b/members/nullnet-server/src/http_server/mod.rs index 3d1ed61..88fb7e5 100644 --- a/members/nullnet-server/src/http_server/mod.rs +++ b/members/nullnet-server/src/http_server/mod.rs @@ -15,7 +15,6 @@ mod events_stream; mod graph; mod health; mod nodes; -mod pool; mod services; mod sessions; mod stacks; @@ -36,7 +35,6 @@ pub async fn serve(state: AppState) { .route("/api/stacks", get(stacks::stacks_handler)) .route("/api/services/{stack}", get(services::services_handler)) .route("/api/nodes/{stack}", get(nodes::nodes_handler)) - .route("/api/pool", get(pool::pool_handler)) .route("/api/config/{stack}", get(config::config_handler)) .route("/api/graph/{stack}", get(graph::graph_handler)) .route("/api/sessions/{stack}", get(sessions::list_handler)) diff --git a/members/nullnet-server/src/http_server/pool.rs b/members/nullnet-server/src/http_server/pool.rs deleted file mode 100644 index 5181295..0000000 --- a/members/nullnet-server/src/http_server/pool.rs +++ /dev/null @@ -1,20 +0,0 @@ -use super::AppState; -use axum::extract::State; -use axum::response::IntoResponse; -use serde::Serialize; - -#[derive(Serialize)] -struct PoolJson { - total: u32, - in_use: u32, - free: u32, -} - -pub(super) async fn pool_handler(State(state): State) -> impl IntoResponse { - let (total, in_use) = state.orchestrator.pool_stats().await; - axum::Json(PoolJson { - total, - in_use, - free: total - in_use, - }) -} diff --git a/members/nullnet-server/src/net.rs b/members/nullnet-server/src/net.rs index 22a5b61..6c86306 100644 --- a/members/nullnet-server/src/net.rs +++ b/members/nullnet-server/src/net.rs @@ -1,3 +1,4 @@ +use crate::net_id_pool::DEFAULT_VXLAN_DSTPORT; use ipnetwork::Ipv4Network; use nullnet_grpc_lib::nullnet_grpc::{ HostMapping, MsgId, Net, NetMessage, VlanSetup, VlanTeardown, VxlanSetup, VxlanTeardown, @@ -32,10 +33,22 @@ pub(crate) trait NetExt { remote: IpAddr, docker_containers: (Option, Option), dnat_port: Option, + encryption_key: [u8; 32], + dstport: Option, + encrypted: bool, egress: EgressRole, ) -> Option<(Ipv4Addr, NetMessage)>; - fn teardown(self, net_id: u32, side: &str, docker_container: Option) -> NetMessage; + #[allow(clippy::too_many_arguments)] + fn teardown( + self, + net_id: u32, + side: &str, + docker_container: Option, + local_ip: IpAddr, + remote_ip: IpAddr, + dstport: Option, + ) -> NetMessage; } impl NetExt for Net { @@ -48,10 +61,21 @@ impl NetExt for Net { remote: IpAddr, docker_containers: (Option, Option), dnat_port: Option, + encryption_key: [u8; 32], + dstport: Option, + encrypted: bool, egress: EgressRole, ) -> Option<(Ipv4Addr, NetMessage)> { match self { - Net::Vlan => vlan_setup(msg_id, dest, remote_server_name, net_id, remote), + Net::Vlan => vlan_setup( + msg_id, + dest, + remote_server_name, + net_id, + remote, + encryption_key, + encrypted, + ), Net::Vxlan => vxlan_setup( msg_id, dest, @@ -60,12 +84,23 @@ impl NetExt for Net { remote, docker_containers, dnat_port, + encryption_key, + dstport.unwrap_or(u32::from(DEFAULT_VXLAN_DSTPORT)), + encrypted, egress, ), } } - fn teardown(self, net_id: u32, side: &str, docker_container: Option) -> NetMessage { + fn teardown( + self, + net_id: u32, + side: &str, + docker_container: Option, + local_ip: IpAddr, + remote_ip: IpAddr, + dstport: Option, + ) -> NetMessage { match self { Net::Vlan => NetMessage { message: Some(net_message::Message::VlanTeardown(VlanTeardown { @@ -78,6 +113,9 @@ impl NetExt for Net { ns_name: format!("ns_{net_id}_{side}"), br_name: format!("br_{net_id}_{side}"), docker_container, + local_ip: local_ip.to_string(), + remote_ip: remote_ip.to_string(), + dstport: u32::from(dstport.unwrap_or(DEFAULT_VXLAN_DSTPORT)), })), }, } @@ -91,6 +129,8 @@ fn vlan_setup( remote_server_name: Option, vlan_id: u32, remote: IpAddr, + encryption_key: [u8; 32], + encrypted: bool, ) -> Option<(Ipv4Addr, NetMessage)> { // Map vlan_id to a /30 block within 10.0.0.0/8. // Each ID gets 4 IPs (2 usable), with 2 IPs used for server/client veth. @@ -124,6 +164,8 @@ fn vlan_setup( local_ip: dest.to_string(), remote_ip: remote.to_string(), host_mapping, + encryption_key: encryption_key.to_vec(), + encrypted, })), }, )) @@ -138,6 +180,9 @@ fn vxlan_setup( remote: IpAddr, docker_containers: (Option, Option), dnat_port: Option, + encryption_key: [u8; 32], + dstport: u32, + encrypted: bool, egress: EgressRole, ) -> Option<(Ipv4Addr, NetMessage)> { // Map vxlan_id to a /29 block within 10.0.0.0/8. @@ -204,8 +249,11 @@ fn vxlan_setup( host_mapping, docker_container, dnat_port, + encryption_key: encryption_key.to_vec(), + dstport, egress_steer, egress_intercept, + encrypted, })), }, )) diff --git a/members/nullnet-server/src/net_id_pool.rs b/members/nullnet-server/src/net_id_pool.rs index 291fa22..94d297f 100644 --- a/members/nullnet-server/src/net_id_pool.rs +++ b/members/nullnet-server/src/net_id_pool.rs @@ -1,3 +1,5 @@ +use aes_gcm::aead::OsRng; +use aes_gcm::aead::rand_core::RngCore; use std::collections::BTreeSet; use std::sync::LazyLock; @@ -59,15 +61,76 @@ impl NetIdPool { self.freed.insert(id); } } +} + +/// Shared VXLAN dstport for a tunnel that doesn't need a dedicated one from +/// `UdpPortPool` below — same-host tunnels (MACsec on a veth pair, no XFRM at +/// all) and unencrypted cross-host tunnels (no XFRM either). A dedicated port +/// only exists to let an XFRM policy — which selects by IP + port, not VNI — +/// tell concurrent *encrypted* tunnels between the same host pair apart; the +/// VNI alone already disambiguates tunnels sharing this port otherwise, so +/// falling back to it keeps `UdpPortPool`'s 40k entries scoped to only the +/// tunnels that actually need one, instead of capping total concurrent VXLAN +/// tunnels at 40k regardless of encryption. Matches the IANA default and the +/// eBPF firewall's own `VXLAN_PORT` constant (`ebpf/src/main.rs`), which +/// structurally allows this exact port for any known peer. +pub(crate) const DEFAULT_VXLAN_DSTPORT: u16 = 4789; + +/// Minimum/maximum allocatable UDP port for per-tunnel VXLAN dstports. +/// Kept out of the IANA ephemeral range (32768-60999) and away from 4789 +/// (the VXLAN default) to avoid colliding with unrelated local sockets. +const MIN_VXLAN_PORT: u16 = 20000; +const MAX_VXLAN_PORT: u16 = 60000; + +/// Pool of per-tunnel UDP destination ports, used so concurrent VXLAN +/// tunnels between the same physical host pair each get a distinct dstport. +/// This is what lets an XFRM policy (which selects by IP + port, not VNI) +/// tell those tunnels apart. Same allocate/free-with-reuse shape as `NetIdPool`. +#[derive(Debug)] +pub(crate) struct UdpPortPool { + next_fresh: u16, + freed: BTreeSet, +} + +impl UdpPortPool { + pub(crate) fn new() -> Self { + Self { + next_fresh: MIN_VXLAN_PORT, + freed: BTreeSet::new(), + } + } - /// Returns (total_capacity, in_use). - pub(crate) fn stats(&self) -> (u32, u32) { - let capacity = *MAX_NET_ID - MIN_NET_ID + 1; - let in_use = (self.next_fresh - MIN_NET_ID) - self.freed.len() as u32; - (capacity, in_use) + pub(crate) fn allocate(&mut self) -> Option { + if let Some(&port) = self.freed.iter().next() { + self.freed.remove(&port); + return Some(port); + } + + if self.next_fresh <= MAX_VXLAN_PORT { + let port = self.next_fresh; + self.next_fresh += 1; + Some(port) + } else { + None + } + } + + pub(crate) fn free(&mut self, port: u16) { + if (MIN_VXLAN_PORT..=MAX_VXLAN_PORT).contains(&port) { + self.freed.insert(port); + } } } +/// Generate a fresh random 32-byte AES-256 key for one tunnel. Called once +/// per net_id allocation; the same bytes are sent to both endpoints so they +/// share a single symmetric key for that tunnel only. +pub(crate) fn generate_key() -> [u8; 32] { + let mut key = [0u8; 32]; + OsRng.fill_bytes(&mut key); + key +} + #[cfg(test)] impl NetIdPool { /// Number of IDs currently in use (allocated but not freed). @@ -132,59 +195,54 @@ mod tests { } #[test] - fn test_stats_fresh_pool() { - let pool = NetIdPool::new(); - let (total, in_use) = pool.stats(); - let free = total - in_use; - assert!(total > 0); - assert_eq!(in_use, 0); - assert_eq!(free, total); + fn test_udp_port_pool_allocate_sequential() { + let mut pool = UdpPortPool::new(); + assert_eq!(pool.allocate(), Some(MIN_VXLAN_PORT)); + assert_eq!(pool.allocate(), Some(MIN_VXLAN_PORT + 1)); + assert_eq!(pool.allocate(), Some(MIN_VXLAN_PORT + 2)); } #[test] - fn test_stats_after_allocations() { - let mut pool = NetIdPool::new(); - pool.allocate(); + fn test_udp_port_pool_reuse_freed() { + let mut pool = UdpPortPool::new(); + let p1 = pool.allocate().unwrap(); + let p2 = pool.allocate().unwrap(); pool.allocate(); - pool.allocate(); - let (total, in_use) = pool.stats(); - let free = total - in_use; - assert_eq!(in_use, 3); - assert_eq!(free, total - 3); + + pool.free(p2); + pool.free(p1); + + assert_eq!(pool.allocate(), Some(p1)); + assert_eq!(pool.allocate(), Some(p2)); } #[test] - fn test_stats_free_reduces_in_use() { - let mut pool = NetIdPool::new(); - let id = pool.allocate().unwrap(); - pool.allocate(); - pool.allocate(); - pool.free(id); - let (total, in_use) = pool.stats(); - let free = total - in_use; - assert_eq!(in_use, 2); - assert_eq!(free, total - 2); + fn test_udp_port_pool_exhaustion() { + let mut pool = UdpPortPool::new(); + pool.next_fresh = MAX_VXLAN_PORT; + + assert_eq!(pool.allocate(), Some(MAX_VXLAN_PORT)); + assert_eq!(pool.allocate(), None); + + pool.free(MAX_VXLAN_PORT); + assert_eq!(pool.allocate(), Some(MAX_VXLAN_PORT)); + assert_eq!(pool.allocate(), None); } #[test] - fn test_stats_exhausted_pool() { - let mut pool = NetIdPool::new(); - pool.next_fresh = *MAX_NET_ID + 1; - let (total, in_use) = pool.stats(); - let free = total - in_use; - assert_eq!(in_use, total); - assert_eq!(free, 0); + fn test_udp_port_pool_free_ignores_out_of_range() { + let mut pool = UdpPortPool::new(); + pool.free(0); + pool.free(MIN_VXLAN_PORT - 1); + pool.free(MAX_VXLAN_PORT + 1); + assert!(pool.freed.is_empty()); } #[test] - fn test_stats_total_equals_in_use_plus_free() { - let mut pool = NetIdPool::new(); - pool.allocate(); - let id = pool.allocate().unwrap(); - pool.allocate(); - pool.free(id); - let (total, in_use) = pool.stats(); - let free = total - in_use; - assert_eq!(total, in_use + free); + fn test_generate_key_is_random_and_full_length() { + let k1 = generate_key(); + let k2 = generate_key(); + assert_eq!(k1.len(), 32); + assert_ne!(k1, k2); } } diff --git a/members/nullnet-server/src/nullnet_grpc_impl.rs b/members/nullnet-server/src/nullnet_grpc_impl.rs index 49b266c..a3cad79 100644 --- a/members/nullnet-server/src/nullnet_grpc_impl.rs +++ b/members/nullnet-server/src/nullnet_grpc_impl.rs @@ -1,7 +1,8 @@ -use crate::env::{NET_TYPE, PROXY_IP}; +use crate::env::{ENCRYPTION_ENABLED, NET_TYPE, PROXY_IP}; use crate::events::Event; use crate::graphviz::generate_graphviz; use crate::net::EgressRole; +use crate::net_id_pool::generate_key; use crate::orchestrator::Orchestrator; use crate::services::changes::{ apply_changes, collect_dep_chain_edges, detect_services_list_changes, @@ -993,6 +994,43 @@ impl NullnetGrpcImpl { .await; } + // One AES-256 key per tunnel, handed identically to both + // endpoints below (skipped when encryption is globally + // disabled). A dedicated per-tunnel UDP dstport is only + // needed so the two hosts' XFRM policies can tell this + // tunnel apart from other concurrent *encrypted* tunnels + // between the same host pair — same-host tunnels (MACsec on + // a veth, no XFRM) and unencrypted ones (no XFRM either) fall + // back to the shared default port instead. The 40k-entry pool + // is also scoped per host pair (see `Orchestrator::allocate_vxlan_port`), + // not global, so it only actually caps concurrent encrypted + // tunnels between the same two hosts. + let encrypted = *ENCRYPTION_ENABLED; + let encryption_key = if encrypted { generate_key() } else { [0u8; 32] }; + let needs_dedicated_port = + *NET_TYPE == Net::Vxlan && encrypted && server_ethernet != client_ethernet; + let dstport = if needs_dedicated_port { + match orchestrator + .allocate_vxlan_port(net_id, server_ethernet, client_ethernet) + .await + { + Some(port) => Some(u32::from(port)), + None => { + eprintln!("UDP port pool exhausted"); + orchestrator.free_net_id(net_id).await; + if let Some(stack_map) = services.write().await.get_mut(&stack) + && let Some(ServiceInfo::Registered(reg)) = + stack_map.get_mut(server.name()) + { + reg.remove_client(&client); + } + return EdgeOutcome::Failed; + } + } + } else { + None + }; + let orch = orchestrator.clone(); let cd = client_docker.clone(); let sd = server_docker.clone(); @@ -1003,6 +1041,9 @@ impl NullnetGrpcImpl { client_ethernet, (cd, sd), None, + encryption_key, + dstport, + encrypted, server_egress, ); let orch2 = orchestrator.clone(); @@ -1015,6 +1056,9 @@ impl NullnetGrpcImpl { server_ethernet, (cd, sd), backend_entry_port, + encryption_key, + dstport, + encrypted, client_egress, ); diff --git a/members/nullnet-server/src/orchestrator.rs b/members/nullnet-server/src/orchestrator.rs index ab07b2b..e0e91bb 100644 --- a/members/nullnet-server/src/orchestrator.rs +++ b/members/nullnet-server/src/orchestrator.rs @@ -1,11 +1,11 @@ -use crate::env::NET_TYPE; +use crate::env::{ENCRYPTION_ENABLED, NET_TYPE}; use crate::events::{Event, EventStore}; use crate::net::{EgressRole, NetExt}; -use crate::net_id_pool::NetIdPool; +use crate::net_id_pool::{NetIdPool, UdpPortPool, generate_key}; use crate::services::changes::{apply_changes, detect_node_disconnect_changes}; use crate::services::input::StackMap; use nullnet_grpc_lib::nullnet_grpc::{ - ContainerResume, ContainerSuspend, MsgId, NetMessage, net_message, + ContainerResume, ContainerSuspend, MsgId, Net, NetMessage, net_message, }; use nullnet_liberror::{Error, ErrorHandler, Location, location}; use std::collections::HashMap; @@ -40,11 +40,30 @@ pub(crate) struct EgressEdgeInfo { pub(crate) proxy_ip: IpAddr, } +/// An order-independent pair of underlay host IPs, used to scope per-tunnel +/// VXLAN dstport allocation (see `Orchestrator::udp_port_pools`) — always +/// produced via `host_pair()` so both call orders land on the same key. +type HostPair = (IpAddr, IpAddr); + +/// An allocated VXLAN dstport, tagged with which host pair's pool it came +/// from, so `send_net_teardown` can free it back into the right pool. +type AllocatedPort = (HostPair, u16); + #[derive(Debug, Clone)] pub struct Orchestrator { clients: Arc>>, pending: Arc>>>, net_id_pool: Arc>, + /// Per-tunnel VXLAN UDP dstport pools, one per host pair rather than one + /// global pool — XFRM policies already select by the full (src, dst, + /// proto, dport) tuple, so two different host pairs can safely reuse the + /// same port number; only concurrent tunnels *between the same two hosts* + /// need distinct ports. Unused in VLAN mode. + udp_port_pools: Arc>>, + /// net_id -> allocated dstport (with its host pair), for VXLAN tunnels + /// only. Lets `send_net_teardown` free the port back into the right + /// pair's pool without every call site having to carry it around. + net_id_ports: Arc>>, /// Live egress edges, keyed by initiator replica. Separate from the service /// StackMap because the proxy end is infrastructure, not a registered service. egress_edges: Arc>>, @@ -57,6 +76,8 @@ impl Orchestrator { clients: Arc::new(RwLock::new(HashMap::new())), pending: Arc::new(Mutex::new(HashMap::new())), net_id_pool: Arc::new(Mutex::new(NetIdPool::new())), + udp_port_pools: Arc::new(Mutex::new(HashMap::new())), + net_id_ports: Arc::new(Mutex::new(HashMap::new())), egress_edges: Arc::new(RwLock::new(HashMap::new())), events: EventStore::new(), } @@ -165,6 +186,32 @@ impl Orchestrator { return Err("NET ID pool exhausted").handle_err(location!()); }; + // One AES-256 key per tunnel, shared by both ends, same as any other + // chain edge (skipped when encryption is globally disabled). A + // dedicated per-tunnel UDP dstport is only needed for XFRM + // disambiguation between concurrent *encrypted* tunnels sharing a + // host pair; same-host and unencrypted edges fall back to the shared + // default port instead (mirrors net_chain_setup's gating in + // nullnet_grpc_impl.rs — see DEFAULT_VXLAN_DSTPORT's doc comment). + let encrypted = *ENCRYPTION_ENABLED; + let encryption_key = if encrypted { generate_key() } else { [0u8; 32] }; + let needs_dedicated_port = *NET_TYPE == Net::Vxlan && encrypted && proxy_ip != initiator_ip; + let dstport = if needs_dedicated_port { + match self + .allocate_vxlan_port(net_id, proxy_ip, initiator_ip) + .await + { + Some(port) => Some(u32::from(port)), + None => { + self.free_net_id(net_id).await; + self.egress_edges.write().await.remove(&key); + return Err("UDP port pool exhausted").handle_err(location!()); + } + } + } else { + None + }; + // Gateway is the server side (Intercept -> forward/MASQUERADE); initiator // is the client side (Steer -> policy-route + SNAT). docker tuple is (client, server). let dockers = (initiator_docker.clone(), None); @@ -175,6 +222,9 @@ impl Orchestrator { initiator_ip, dockers.clone(), None, + encryption_key, + dstport, + encrypted, EgressRole::Intercept, ); let init_res = self.send_net_setup( @@ -184,6 +234,9 @@ impl Orchestrator { proxy_ip, dockers, None, + encryption_key, + dstport, + encrypted, EgressRole::Steer, ); let (proxy_ok, init_ok) = tokio::join!(proxy_res, init_res); @@ -312,6 +365,9 @@ impl Orchestrator { remote: IpAddr, docker_containers: (Option, Option), dnat_port: Option, + encryption_key: [u8; 32], + dstport: Option, + encrypted: bool, egress: EgressRole, ) -> Option { let outbound = self.clients.read().await.get(&dest).cloned(); @@ -328,6 +384,9 @@ impl Orchestrator { remote, docker_containers, dnat_port, + encryption_key, + dstport, + encrypted, egress, )?; @@ -404,12 +463,39 @@ impl Orchestrator { self.net_id_pool.lock().await.allocate() } - pub(crate) async fn connected_node_ips(&self) -> Vec { - self.clients.read().await.keys().copied().collect() + /// Release a `net_id` that was allocated but never dispatched to either + /// endpoint (e.g. a follow-up allocation failed). No teardown messages + /// are sent — nothing was ever set up on either client. + pub(crate) async fn free_net_id(&self, net_id: u32) { + self.net_id_pool.lock().await.free(net_id); + } + + /// Allocate a per-tunnel VXLAN dstport from the pool scoped to this + /// specific host pair (`host_a`/`host_b`, order-independent — not a + /// global pool, see the field doc on `udp_port_pools`), and remember it + /// against `net_id` so `send_net_teardown` can free it later without the + /// caller having to carry it around. Only meaningful when + /// `NET_TYPE == Net::Vxlan`. + pub(crate) async fn allocate_vxlan_port( + &self, + net_id: u32, + host_a: IpAddr, + host_b: IpAddr, + ) -> Option { + let pair = host_pair(host_a, host_b); + let port = self + .udp_port_pools + .lock() + .await + .entry(pair) + .or_insert_with(UdpPortPool::new) + .allocate()?; + self.net_id_ports.lock().await.insert(net_id, (pair, port)); + Some(port) } - pub(crate) async fn pool_stats(&self) -> (u32, u32) { - self.net_id_pool.lock().await.stats() + pub(crate) async fn connected_node_ips(&self) -> Vec { + self.clients.read().await.keys().copied().collect() } pub(crate) async fn send_net_teardown( @@ -420,20 +506,43 @@ impl Orchestrator { server_docker: Option, net_id: u32, ) { - for (dest, side, docker) in [(client, "c", client_docker), (server, "s", server_docker)] { + // Peeked (not removed yet) so both teardown messages can carry the + // same dstport that was used to install this tunnel's XFRM state; + // the pool slot itself is freed below, after both sides are notified. + let dstport = self + .net_id_ports + .lock() + .await + .get(&net_id) + .map(|(_pair, port)| *port); + for (dest, remote, side, docker) in [ + (client, server, "c", client_docker), + (server, client, "s", server_docker), + ] { let outbound = self.clients.read().await.get(&dest).cloned(); if let Some(outbound) = outbound { println!("Sending network {net_id} teardown to client {dest}"); - let message = NET_TYPE.teardown(net_id, side, docker); + let message = NET_TYPE.teardown(net_id, side, docker, dest, remote, dstport); let _ = outbound.send(Ok(message)).await.handle_err(location!()); } } self.net_id_pool.lock().await.free(net_id); + if let Some((pair, port)) = self.net_id_ports.lock().await.remove(&net_id) + && let Some(pool) = self.udp_port_pools.lock().await.get_mut(&pair) + { + pool.free(port); + } } } +/// Normalize a host pair so both call orders (A, B) and (B, A) land on the +/// same per-pair port pool. +fn host_pair(a: IpAddr, b: IpAddr) -> HostPair { + if a <= b { (a, b) } else { (b, a) } +} + #[cfg(test)] impl Orchestrator { pub(crate) async fn net_ids_in_use(&self) -> u32 { @@ -486,3 +595,73 @@ impl Orchestrator { log } } + +#[cfg(test)] +mod udp_port_pool_tests { + use super::*; + + fn ip(a: u8, b: u8, c: u8, d: u8) -> IpAddr { + IpAddr::V4(Ipv4Addr::new(a, b, c, d)) + } + + #[tokio::test] + async fn allocations_within_one_pair_stay_distinct() { + let orch = Orchestrator::new(); + let (host_a, host_b) = (ip(10, 0, 0, 1), ip(10, 0, 0, 2)); + + let p1 = orch.allocate_vxlan_port(101, host_a, host_b).await.unwrap(); + let p2 = orch.allocate_vxlan_port(102, host_a, host_b).await.unwrap(); + + assert_ne!(p1, p2); + } + + #[tokio::test] + async fn different_pairs_can_reuse_the_same_port_number() { + let orch = Orchestrator::new(); + + // Two entirely separate host pairs, each allocating for the first + // time, should each get their own pool's first port - proving the + // pools are actually scoped per pair rather than drawn from one + // global pool (which would force the second call to skip ahead). + let p1 = orch + .allocate_vxlan_port(101, ip(10, 0, 0, 1), ip(10, 0, 0, 2)) + .await + .unwrap(); + let p2 = orch + .allocate_vxlan_port(102, ip(10, 0, 0, 3), ip(10, 0, 0, 4)) + .await + .unwrap(); + + assert_eq!(p1, p2); + } + + #[tokio::test] + async fn pair_lookup_is_order_independent() { + let orch = Orchestrator::new(); + let (host_a, host_b) = (ip(10, 0, 0, 1), ip(10, 0, 0, 2)); + + // Same two hosts, opposite argument order (as happens naturally: one + // edge's setup calls with (server, client), the other with + // (proxy, initiator) - either could be first) - must land on the + // same pool, not two independent ones. + let p1 = orch.allocate_vxlan_port(101, host_a, host_b).await.unwrap(); + let p2 = orch.allocate_vxlan_port(102, host_b, host_a).await.unwrap(); + + assert_ne!(p1, p2); + } + + #[tokio::test] + async fn teardown_frees_the_port_back_to_its_own_pair_pool() { + let orch = Orchestrator::new(); + let (host_a, host_b) = (ip(10, 0, 0, 1), ip(10, 0, 0, 2)); + + let port = orch.allocate_vxlan_port(101, host_a, host_b).await.unwrap(); + orch.send_net_teardown(host_a, None, host_b, None, 101) + .await; + + // The freed port is the lowest available again, so the next + // allocation for the same pair reuses it rather than advancing. + let reused = orch.allocate_vxlan_port(102, host_a, host_b).await.unwrap(); + assert_eq!(port, reused); + } +} diff --git a/members/nullnet-server/ui/src/App.tsx b/members/nullnet-server/ui/src/App.tsx index 6054465..d24ea9c 100644 --- a/members/nullnet-server/ui/src/App.tsx +++ b/members/nullnet-server/ui/src/App.tsx @@ -4,7 +4,6 @@ import Dashboard from './pages/Dashboard'; import Services from './pages/Services'; import Nodes from './pages/Nodes'; import Sessions from './pages/Sessions'; -import Pool from './pages/Pool'; import Config from './pages/Config'; import Events from './pages/Events'; import Certificates from './pages/Certificates'; @@ -19,7 +18,6 @@ export default function App() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/members/nullnet-server/ui/src/components/Layout.tsx b/members/nullnet-server/ui/src/components/Layout.tsx index 91369a9..57aba69 100644 --- a/members/nullnet-server/ui/src/components/Layout.tsx +++ b/members/nullnet-server/ui/src/components/Layout.tsx @@ -4,7 +4,7 @@ import { useApi } from '../hooks/useApi'; import type { SessionJson } from '../types'; import { useRef, useState, useEffect } from 'react'; -type Page = 'dashboard' | 'topology' | 'services' | 'nodes' | 'sessions' | 'pool' | 'config' | 'certificates' | 'events'; +type Page = 'dashboard' | 'topology' | 'services' | 'nodes' | 'sessions' | 'config' | 'certificates' | 'events'; interface Props { page: Page; @@ -26,7 +26,6 @@ const NAV = [ { id: 'services', icon: '◈', label: 'Services', to: '/services' }, { id: 'sessions', icon: '⌾', label: 'Sessions', to: '/sessions', live: true }, { id: 'nodes', icon: '◉', label: 'Nodes', to: '/nodes' }, - { id: 'pool', icon: '▦', label: 'Pool', to: '/pool' }, ], }, { diff --git a/members/nullnet-server/ui/src/index.css b/members/nullnet-server/ui/src/index.css index 387586a..7235096 100644 --- a/members/nullnet-server/ui/src/index.css +++ b/members/nullnet-server/ui/src/index.css @@ -189,7 +189,7 @@ body { .filter-chip.on { background: rgba(91,156,246,.1); border-color: rgba(91,156,246,.25); color: var(--blue); } /* ── Dashboard stats ── */ -.stats { display: grid; grid-template-columns: repeat(4,1fr); gap: 10px; margin-bottom: 24px; } +.stats { display: grid; grid-template-columns: repeat(3,1fr); gap: 10px; margin-bottom: 24px; } .stat { border-radius: 12px; padding: 20px 22px; position: relative; overflow: hidden; } .stat-label { font-size: 10px; color: var(--t2); letter-spacing: .06em; margin-bottom: 10px; font-weight: 500; } .stat-value { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 28px; letter-spacing: -.03em; line-height: 1; color: var(--t0); } @@ -267,16 +267,6 @@ body { .empty-dp { display: flex; align-items: center; justify-content: center; flex: 1; flex-direction: column; gap: 10px; color: var(--t2); } /* ── Pool ── */ -.pool-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 20px; } -.pool-card { background: var(--g1); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid var(--gb); box-shadow: inset 0 1px 0 rgba(255,255,255,.08), 0 8px 32px rgba(0,0,0,.3); border-radius: 14px; padding: 22px 24px; } -.pc-label { font-size: 10px; color: var(--t2); letter-spacing: .06em; font-weight: 600; text-transform: uppercase; margin-bottom: 16px; } -.pc-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; } -.pc-num { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 30px; letter-spacing: -.04em; } -.pc-total { font-size: 13px; color: var(--t2); } -.pc-pct { font-size: 12px; color: var(--t1); } -.track { height: 6px; background: rgba(255,255,255,.06); border-radius: 4px; overflow: hidden; margin-bottom: 10px; } -.fill { height: 100%; border-radius: 4px; transition: width .5s ease; } -.pc-foot { font-size: 11px; color: var(--t2); } /* ── Config ── */ .cfg-pre { font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.7; color: var(--t1); background: rgba(0,0,0,.3); border: 1px solid var(--gb); border-radius: 12px; padding: 20px 24px; overflow-x: auto; white-space: pre; } diff --git a/members/nullnet-server/ui/src/pages/Dashboard.tsx b/members/nullnet-server/ui/src/pages/Dashboard.tsx index 09f3abc..dd02c65 100644 --- a/members/nullnet-server/ui/src/pages/Dashboard.tsx +++ b/members/nullnet-server/ui/src/pages/Dashboard.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import Layout from '../components/Layout'; import { useApi } from '../hooks/useApi'; import { useStack } from '../StackContext'; -import type { NodeJson, PoolJson } from '../types'; +import type { NodeJson } from '../types'; import { TopologyProvider, useTopologyData, useTopologyUI } from '../components/topology/TopologyContext'; import TopologyGraph from '../components/topology/TopologyGraph'; import TopologyPanel from '../components/topology/TopologyPanel'; @@ -13,7 +13,6 @@ function DashboardView() { const { panel, dispatch } = useTopologyUI(); const { data: nodes } = useApi(`/api/nodes/${stack}`, 5000); - const { data: pool } = useApi('/api/pool', 5000); const chainByProxyNetId = useMemo(() => { const m = new Map(); @@ -35,7 +34,6 @@ function DashboardView() { const proxyCount = graph ? new Set(graph.edges.filter(e => e.via_proxy).map(e => e.via_proxy!)).size : 0; - const poolPct = pool ? ((pool.in_use / pool.total) * 100).toFixed(1) : null; // sorted: registered first, then alpha const sortedNodes = useMemo(() => { @@ -73,13 +71,6 @@ function DashboardView() {
{nodeCountG - registeredCount} unregistered
-
-
Pool
-
80 ? 'var(--amber)' : 'var(--t0)' }}> - {poolPct !== null ? `${poolPct}%` : '—'} -
-
{pool ? `${pool.in_use} / ${pool.total} in use` : 'loading…'}
-
Nodes
{nodeCount}
diff --git a/members/nullnet-server/ui/src/pages/Pool.tsx b/members/nullnet-server/ui/src/pages/Pool.tsx deleted file mode 100644 index 951538d..0000000 --- a/members/nullnet-server/ui/src/pages/Pool.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import Layout from '../components/Layout'; -import { useApi } from '../hooks/useApi'; -import type { PoolJson } from '../types'; - -export default function Pool() { - const { data: pool, loading } = useApi('/api/pool', 5000); - - const pct = pool ? (pool.in_use / pool.total) * 100 : 0; - const pctStr = pct.toFixed(1); - const warn = pct >= 80; - - return ( - live · 5s - } - > -
-
Network ID Pool
-
Network ID allocation status
- - {loading &&
Loading…
} - - {pool && ( -
-
-
ID Pool
-
-
- - {pool.in_use.toLocaleString()} - - / {pool.total.toLocaleString()} -
- {pctStr}% -
-
-
-
-
- {pool.free.toLocaleString()} free - {warn && ⚠ above 80% threshold} -
-
- -
-
Breakdown
- - - - - - - - - - - - - - - - - - - -
Total capacity - {pool.total.toLocaleString()} -
In use - {pool.in_use.toLocaleString()} -
Free - {pool.free.toLocaleString()} -
Utilization - {pctStr}% -
-
-
- )} -
- - ); -} diff --git a/members/nullnet-server/ui/src/types.ts b/members/nullnet-server/ui/src/types.ts index 0bf3e5a..16f8388 100644 --- a/members/nullnet-server/ui/src/types.ts +++ b/members/nullnet-server/ui/src/types.ts @@ -30,12 +30,6 @@ export interface NodeJson { hosted_services: HostedServiceJson[]; } -export interface PoolJson { - total: number; - in_use: number; - free: number; -} - export interface SessionJson { id: number; network_id: number;