feat: bootstrap awg2 harness rust workspace and docs
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+682
@@ -0,0 +1,682 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "awg2-harness"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"rand",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "awg2-harness"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
chrono = { version = "0.4", features = ["clock"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
rand = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "2.0"
|
||||
toml = "0.8"
|
||||
@@ -0,0 +1,24 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "awg2-harness")]
|
||||
pub struct Cli {
|
||||
#[arg(long)]
|
||||
pub config: Option<PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Command {
|
||||
Daemon,
|
||||
ApplyLimits,
|
||||
ApplyFirewall,
|
||||
Status,
|
||||
EnsureSwap,
|
||||
ValidateConfig,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
use std::{fs, net::Ipv4Addr, path::Path};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Config {
|
||||
pub amnezia: AmneziaConfig,
|
||||
pub traffic: TrafficConfig,
|
||||
pub anti_torrent: AntiTorrentConfig,
|
||||
pub reboot: RebootConfig,
|
||||
pub swap: SwapConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AmneziaConfig {
|
||||
pub container_names: Vec<String>,
|
||||
pub interface_candidates: Vec<String>,
|
||||
pub interface_patterns: Vec<String>,
|
||||
pub peer_ipv4_cidr: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct TrafficConfig {
|
||||
pub enabled: bool,
|
||||
pub peer_limit: String,
|
||||
pub root_rate: String,
|
||||
pub reconcile_interval_secs: u64,
|
||||
pub container_ready_timeout_secs: u64,
|
||||
pub class_start_id: u16,
|
||||
pub ifb_name: String,
|
||||
pub full_rebuild_on_drift: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AntiTorrentConfig {
|
||||
pub enabled: bool,
|
||||
pub max_connections_per_peer: u32,
|
||||
pub peer_ipv4_cidr: String,
|
||||
pub apply_in_amnezia_netns: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RebootConfig {
|
||||
pub enabled: bool,
|
||||
pub window_start_utc: String,
|
||||
pub window_end_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SwapConfig {
|
||||
pub enabled: bool,
|
||||
pub path: String,
|
||||
pub size_mb: u64,
|
||||
pub priority: i32,
|
||||
pub swappiness: u8,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(path: &Path) -> anyhow::Result<Self> {
|
||||
let raw = fs::read_to_string(path)?;
|
||||
let cfg: Self = toml::from_str(&raw)?;
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
if self.amnezia.container_names.is_empty() {
|
||||
anyhow::bail!("amnezia.container_names must not be empty");
|
||||
}
|
||||
if self.traffic.class_start_id < 10 {
|
||||
anyhow::bail!("traffic.class_start_id must be >= 10");
|
||||
}
|
||||
if self.swap.swappiness > 100 {
|
||||
anyhow::bail!("swap.swappiness must be <= 100");
|
||||
}
|
||||
parse_cidr_v4(&self.amnezia.peer_ipv4_cidr)?;
|
||||
parse_cidr_v4(&self.anti_torrent.peer_ipv4_cidr)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_cidr_v4(cidr: &str) -> anyhow::Result<(Ipv4Addr, u8)> {
|
||||
let mut parts = cidr.split('/');
|
||||
let ip = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid cidr: {cidr}"))?
|
||||
.parse::<Ipv4Addr>()?;
|
||||
let prefix = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid cidr: {cidr}"))?
|
||||
.parse::<u8>()?;
|
||||
if prefix > 32 {
|
||||
anyhow::bail!("invalid prefix in cidr: {cidr}");
|
||||
}
|
||||
Ok((ip, prefix))
|
||||
}
|
||||
|
||||
pub fn ip_in_cidr(ip: Ipv4Addr, cidr: &str) -> anyhow::Result<bool> {
|
||||
let (base, prefix) = parse_cidr_v4(cidr)?;
|
||||
let mask = if prefix == 0 {
|
||||
0u32
|
||||
} else {
|
||||
u32::MAX << (32 - prefix)
|
||||
};
|
||||
let ip_u = u32::from(ip);
|
||||
let base_u = u32::from(base);
|
||||
Ok((ip_u & mask) == (base_u & mask))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
use crate::{config::Config, firewall, reboot, traffic};
|
||||
|
||||
pub fn run(cfg: Config) -> anyhow::Result<()> {
|
||||
println!("starting daemon");
|
||||
|
||||
let _ = traffic::reconcile_traffic(&cfg);
|
||||
let _ = firewall::apply_firewall(&cfg);
|
||||
let _ = reboot::maybe_schedule_reboot(&cfg);
|
||||
|
||||
let events_cfg = cfg.clone();
|
||||
thread::spawn(move || {
|
||||
let _ = docker_events_loop(events_cfg);
|
||||
});
|
||||
|
||||
loop {
|
||||
if let Err(e) = traffic::reconcile_traffic(&cfg) {
|
||||
eprintln!("traffic reconcile error: {e}");
|
||||
}
|
||||
if let Err(e) = firewall::apply_firewall(&cfg) {
|
||||
eprintln!("firewall reconcile error: {e}");
|
||||
}
|
||||
thread::sleep(Duration::from_secs(cfg.traffic.reconcile_interval_secs.max(5)));
|
||||
}
|
||||
}
|
||||
|
||||
fn docker_events_loop(cfg: Config) -> anyhow::Result<()> {
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let mut child = Command::new("docker")
|
||||
.args(["events", "--filter", "event=start", "--format", "{{.Actor.Attributes.name}}"])
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let stdout = child
|
||||
.stdout
|
||||
.take()
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to capture docker events stdout"))?;
|
||||
let reader = BufReader::new(stdout);
|
||||
|
||||
for line in reader.lines() {
|
||||
let name = line?.trim().to_string();
|
||||
if cfg.amnezia.container_names.iter().any(|n| n == &name) {
|
||||
let _ = traffic::reconcile_traffic(&cfg);
|
||||
let _ = firewall::apply_firewall(&cfg);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use std::process::Command;
|
||||
|
||||
use crate::{config::Config, error::AppError};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RuntimeTarget {
|
||||
pub container_name: String,
|
||||
pub container_id: String,
|
||||
pub container_pid: u32,
|
||||
pub netns_path: String,
|
||||
pub interface_name: String,
|
||||
}
|
||||
|
||||
pub fn discover_target(cfg: &Config) -> anyhow::Result<RuntimeTarget> {
|
||||
let container_name = find_running_container(&cfg.amnezia.container_names)?;
|
||||
let container_id = run_capture(&["docker", "inspect", "--format", "{{.Id}}", &container_name])?
|
||||
.trim()
|
||||
.to_string();
|
||||
let pid_str = run_capture(&[
|
||||
"docker",
|
||||
"inspect",
|
||||
"--format",
|
||||
"{{.State.Pid}}",
|
||||
&container_name,
|
||||
])?;
|
||||
let container_pid = pid_str.trim().parse::<u32>()?;
|
||||
let netns_path = format!("/proc/{container_pid}/ns/net");
|
||||
let interface_name = crate::iface::detect_interface(cfg, &container_name)?;
|
||||
|
||||
Ok(RuntimeTarget {
|
||||
container_name,
|
||||
container_id,
|
||||
container_pid,
|
||||
netns_path,
|
||||
interface_name,
|
||||
})
|
||||
}
|
||||
|
||||
fn find_running_container(names: &[String]) -> anyhow::Result<String> {
|
||||
let ps = run_capture(&["docker", "ps", "--format", "{{.Names}}"])?;
|
||||
for name in names {
|
||||
if ps.lines().any(|x| x.trim() == name) {
|
||||
return Ok(name.clone());
|
||||
}
|
||||
}
|
||||
Err(AppError::NotFound("amnezia container not running".to_string()).into())
|
||||
}
|
||||
|
||||
pub fn run_capture(args: &[&str]) -> anyhow::Result<String> {
|
||||
let (program, rest) = args
|
||||
.split_first()
|
||||
.ok_or_else(|| anyhow::anyhow!("empty command"))?;
|
||||
let out = Command::new(program).args(rest).output()?;
|
||||
if !out.status.success() {
|
||||
return Err(AppError::Command(format!(
|
||||
"{} {:?}: {}",
|
||||
program,
|
||||
rest,
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
))
|
||||
.into());
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&out.stdout).to_string())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AppError {
|
||||
#[error("command failed: {0}")]
|
||||
Command(String),
|
||||
#[error("not found: {0}")]
|
||||
NotFound(String),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::{config::Config, docker};
|
||||
|
||||
pub fn apply_firewall(cfg: &Config) -> anyhow::Result<()> {
|
||||
let target = docker::discover_target(cfg)?;
|
||||
|
||||
if !cfg.anti_torrent.enabled {
|
||||
let _ = docker::run_capture(&[
|
||||
"docker",
|
||||
"exec",
|
||||
&target.container_name,
|
||||
"sh",
|
||||
"-c",
|
||||
"nft delete table inet awg2_harness 2>/dev/null || true",
|
||||
])?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let rule = format!(
|
||||
"nft 'add table inet awg2_harness' 2>/dev/null || true; \
|
||||
nft 'add chain inet awg2_harness forward_antitorrent {{ type filter hook forward priority filter; policy accept; }}' 2>/dev/null || true; \
|
||||
nft 'flush chain inet awg2_harness forward_antitorrent'; \
|
||||
nft 'add rule inet awg2_harness forward_antitorrent ip saddr {cidr} ct state new meter awg2_peer_connlimit_v4 {{ ip saddr ct count over {max} }} counter name antitorrent_dropped_v4 drop'",
|
||||
cidr = cfg.anti_torrent.peer_ipv4_cidr,
|
||||
max = cfg.anti_torrent.max_connections_per_peer
|
||||
);
|
||||
let _ = docker::run_capture(&["docker", "exec", &target.container_name, "sh", "-c", &rule])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use crate::{config::Config, docker::run_capture};
|
||||
|
||||
pub fn detect_interface(cfg: &Config, container: &str) -> anyhow::Result<String> {
|
||||
for ifname in &cfg.amnezia.interface_candidates {
|
||||
let ok = std::process::Command::new("docker")
|
||||
.args(["exec", container, "ip", "link", "show", ifname])
|
||||
.output()?;
|
||||
if ok.status.success() {
|
||||
return Ok(ifname.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let links = run_capture(&[
|
||||
"docker",
|
||||
"exec",
|
||||
container,
|
||||
"sh",
|
||||
"-c",
|
||||
"ip -o link show | awk -F': ' '{print $2}'",
|
||||
])?;
|
||||
|
||||
for raw in links.lines() {
|
||||
let name = raw.trim();
|
||||
for p in &cfg.amnezia.interface_patterns {
|
||||
if glob_like_match(p, name) {
|
||||
return Ok(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::bail!("unable to detect amnezia interface")
|
||||
}
|
||||
|
||||
fn glob_like_match(pattern: &str, value: &str) -> bool {
|
||||
if let Some(prefix) = pattern.strip_suffix('*') {
|
||||
value.starts_with(prefix)
|
||||
} else {
|
||||
pattern == value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
pub struct FileLock {
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl FileLock {
|
||||
pub fn acquire(path: &str) -> anyhow::Result<Self> {
|
||||
let p = Path::new(path);
|
||||
if let Some(parent) = p.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
if p.exists() {
|
||||
anyhow::bail!("lock already exists: {path}");
|
||||
}
|
||||
fs::write(p, std::process::id().to_string())?;
|
||||
Ok(Self {
|
||||
path: path.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileLock {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_file(&self.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
mod cli;
|
||||
mod config;
|
||||
mod daemon;
|
||||
mod docker;
|
||||
mod error;
|
||||
mod firewall;
|
||||
mod iface;
|
||||
mod lock;
|
||||
mod netns;
|
||||
mod reboot;
|
||||
mod status;
|
||||
mod swap;
|
||||
mod traffic;
|
||||
mod wg;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::cli::{Cli, Command};
|
||||
use crate::config::Config;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let config_path = cli
|
||||
.config
|
||||
.unwrap_or_else(|| PathBuf::from("/etc/awg2-harness/config.toml"));
|
||||
|
||||
match cli.command {
|
||||
Command::ValidateConfig => {
|
||||
let cfg = Config::load(&config_path)?;
|
||||
cfg.validate()?;
|
||||
println!("config valid: {}", config_path.display());
|
||||
}
|
||||
Command::ApplyLimits => {
|
||||
let cfg = Config::load(&config_path)?;
|
||||
cfg.validate()?;
|
||||
traffic::reconcile_traffic(&cfg)?;
|
||||
println!("traffic limits reconciled");
|
||||
}
|
||||
Command::ApplyFirewall => {
|
||||
let cfg = Config::load(&config_path)?;
|
||||
cfg.validate()?;
|
||||
firewall::apply_firewall(&cfg)?;
|
||||
println!("firewall reconciled");
|
||||
}
|
||||
Command::Status => {
|
||||
let cfg = Config::load(&config_path)?;
|
||||
cfg.validate()?;
|
||||
status::print_status(&cfg)?;
|
||||
}
|
||||
Command::EnsureSwap => {
|
||||
let cfg = Config::load(&config_path)?;
|
||||
cfg.validate()?;
|
||||
swap::ensure_swap(&cfg)?;
|
||||
println!("swap ensured");
|
||||
}
|
||||
Command::Daemon => {
|
||||
let cfg = Config::load(&config_path)?;
|
||||
cfg.validate()?;
|
||||
daemon::run(cfg)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use std::process::Command;
|
||||
|
||||
pub fn nsenter_run_capture(pid: u32, args: &[&str]) -> anyhow::Result<String> {
|
||||
let mut cmd = Command::new("nsenter");
|
||||
cmd.args(["-t", &pid.to_string(), "-n"]);
|
||||
cmd.args(args);
|
||||
let out = cmd.output()?;
|
||||
if !out.status.success() {
|
||||
anyhow::bail!(
|
||||
"nsenter failed: {}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&out.stdout).to_string())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use chrono::{Duration, Timelike, Utc};
|
||||
use rand::Rng;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub fn maybe_schedule_reboot(cfg: &Config) -> anyhow::Result<()> {
|
||||
if !cfg.reboot.enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let start = parse_hhmm(&cfg.reboot.window_start_utc)?;
|
||||
let end = parse_hhmm(&cfg.reboot.window_end_utc)?;
|
||||
let now = Utc::now();
|
||||
let date = now.date_naive();
|
||||
|
||||
let start_dt = date
|
||||
.and_hms_opt(start.0, start.1, 0)
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid reboot start time"))?;
|
||||
let end_dt = date
|
||||
.and_hms_opt(end.0, end.1, 0)
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid reboot end time"))?;
|
||||
|
||||
let span = if end_dt > start_dt {
|
||||
end_dt - start_dt
|
||||
} else {
|
||||
Duration::hours(1)
|
||||
};
|
||||
|
||||
let secs = rand::thread_rng().gen_range(0..span.num_seconds().max(1));
|
||||
let planned = start_dt + Duration::seconds(secs);
|
||||
println!("reboot planned at UTC {}", planned);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_hhmm(v: &str) -> anyhow::Result<(u32, u32)> {
|
||||
let mut s = v.split(':');
|
||||
let h = s.next().ok_or_else(|| anyhow::anyhow!("bad time"))?.parse()?;
|
||||
let m = s.next().ok_or_else(|| anyhow::anyhow!("bad time"))?.parse()?;
|
||||
if h > 23 || m > 59 {
|
||||
anyhow::bail!("bad time")
|
||||
}
|
||||
Ok((h, m))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::{config::Config, docker, wg};
|
||||
|
||||
pub fn print_status(cfg: &Config) -> anyhow::Result<()> {
|
||||
println!("System:");
|
||||
let target = match docker::discover_target(cfg) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
println!(" container: not available ({e})");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
println!(" container: {}", target.container_name);
|
||||
println!(" container_id: {}", target.container_id);
|
||||
println!(" pid: {}", target.container_pid);
|
||||
println!(" netns: {}", target.netns_path);
|
||||
println!(" interface: {}", target.interface_name);
|
||||
|
||||
let peers = wg::discover_peers(&target.container_name, &cfg.amnezia.peer_ipv4_cidr)?;
|
||||
println!("Peers:");
|
||||
println!(" total: {}", peers.len());
|
||||
for p in peers {
|
||||
println!(" - {}", p.ipv4);
|
||||
}
|
||||
|
||||
println!("Traffic desired:");
|
||||
println!(" peer_limit: {}", cfg.traffic.peer_limit);
|
||||
println!(" root_rate: {}", cfg.traffic.root_rate);
|
||||
println!(" ifb_name: {}", cfg.traffic.ifb_name);
|
||||
println!(" class_start_id: {}", cfg.traffic.class_start_id);
|
||||
|
||||
println!("Anti-torrent:");
|
||||
println!(" enabled: {}", cfg.anti_torrent.enabled);
|
||||
println!(
|
||||
" max_connections_per_peer: {}",
|
||||
cfg.anti_torrent.max_connections_per_peer
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
use std::{fs, process::Command};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub fn ensure_swap(cfg: &Config) -> anyhow::Result<()> {
|
||||
if !cfg.swap.enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = &cfg.swap.path;
|
||||
if !std::path::Path::new(path).exists() {
|
||||
run("fallocate", &["-l", &format!("{}M", cfg.swap.size_mb), path])?;
|
||||
run("chmod", &["600", path])?;
|
||||
run("mkswap", &[path])?;
|
||||
}
|
||||
|
||||
let swapon = Command::new("swapon").args(["--show"]).output()?;
|
||||
let active = String::from_utf8_lossy(&swapon.stdout);
|
||||
if !active.contains(path) {
|
||||
run("swapon", &["-p", &cfg.swap.priority.to_string(), path])?;
|
||||
}
|
||||
|
||||
ensure_fstab(path, cfg.swap.priority)?;
|
||||
run("sysctl", &["-w", &format!("vm.swappiness={}", cfg.swap.swappiness)])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_fstab(path: &str, priority: i32) -> anyhow::Result<()> {
|
||||
let file = "/etc/fstab";
|
||||
let content = fs::read_to_string(file).unwrap_or_default();
|
||||
if content.lines().any(|l| l.contains(path) && l.contains("swap")) {
|
||||
return Ok(());
|
||||
}
|
||||
let line = format!("{path} none swap sw,pri={priority} 0 0\n");
|
||||
fs::write(file, format!("{}{}", content, line))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(program: &str, args: &[&str]) -> anyhow::Result<()> {
|
||||
let out = Command::new(program).args(args).output()?;
|
||||
if !out.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} {:?} failed: {}",
|
||||
program,
|
||||
args,
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::{config::Config, docker, lock::FileLock, wg};
|
||||
|
||||
pub fn reconcile_traffic(cfg: &Config) -> anyhow::Result<()> {
|
||||
if !cfg.traffic.enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _lock = FileLock::acquire("/run/awg2-harness/traffic.lock")?;
|
||||
let target = docker::discover_target(cfg)?;
|
||||
let peers = wg::discover_peers(&target.container_name, &cfg.amnezia.peer_ipv4_cidr)?;
|
||||
|
||||
if peers.is_empty() {
|
||||
println!("no peers found, skip tc apply");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if cfg.traffic.full_rebuild_on_drift {
|
||||
teardown_owned_topology(&target.container_name, &target.interface_name, &cfg.traffic.ifb_name)?;
|
||||
apply_full_topology(cfg, &target.container_name, &target.interface_name, &cfg.traffic.ifb_name, &peers)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn teardown_owned_topology(container: &str, iface: &str, ifb: &str) -> anyhow::Result<()> {
|
||||
let cmd = format!(
|
||||
"tc qdisc del dev {iface} root 2>/dev/null || true; \
|
||||
tc qdisc del dev {iface} ingress 2>/dev/null || true; \
|
||||
ip link del {ifb} 2>/dev/null || true"
|
||||
);
|
||||
let _ = docker::run_capture(&["docker", "exec", container, "sh", "-c", &cmd])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_full_topology(
|
||||
cfg: &Config,
|
||||
container: &str,
|
||||
iface: &str,
|
||||
ifb: &str,
|
||||
peers: &[wg::Peer],
|
||||
) -> anyhow::Result<()> {
|
||||
let setup_cmd = format!(
|
||||
"tc qdisc add dev {iface} root handle 1: htb; \
|
||||
tc class add dev {iface} parent 1: classid 1:1 htb rate {root} ceil {root}; \
|
||||
modprobe ifb 2>/dev/null || true; \
|
||||
ip link add name {ifb} type ifb 2>/dev/null || true; \
|
||||
ip link set dev {ifb} up; \
|
||||
tc qdisc add dev {iface} handle ffff: ingress; \
|
||||
tc filter add dev {iface} parent ffff: protocol all u32 match u32 0 0 action mirred egress redirect dev {ifb}; \
|
||||
tc qdisc add dev {ifb} root handle 2: htb; \
|
||||
tc class add dev {ifb} parent 2: classid 2:1 htb rate {root} ceil {root}",
|
||||
root = cfg.traffic.root_rate
|
||||
);
|
||||
let _ = docker::run_capture(&["docker", "exec", container, "sh", "-c", &setup_cmd])?;
|
||||
|
||||
for (idx, p) in peers.iter().enumerate() {
|
||||
let class_id = cfg.traffic.class_start_id + idx as u16;
|
||||
let ip = p.ipv4;
|
||||
let per_peer = format!(
|
||||
"tc class add dev {iface} parent 1:1 classid 1:{class_id} htb rate {limit} ceil {limit}; \
|
||||
tc qdisc add dev {iface} parent 1:{class_id} fq_codel; \
|
||||
tc filter add dev {iface} parent 1: protocol ip u32 match ip dst {ip} flowid 1:{class_id}; \
|
||||
tc class add dev {ifb} parent 2:1 classid 2:{class_id} htb rate {limit} ceil {limit}; \
|
||||
tc qdisc add dev {ifb} parent 2:{class_id} fq_codel; \
|
||||
tc filter add dev {ifb} parent 2: protocol ip u32 match ip src {ip} flowid 2:{class_id}",
|
||||
limit = cfg.traffic.peer_limit
|
||||
);
|
||||
let _ = docker::run_capture(&["docker", "exec", container, "sh", "-c", &per_peer])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::{config, docker::run_capture};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Peer {
|
||||
pub ipv4: Ipv4Addr,
|
||||
}
|
||||
|
||||
pub fn discover_peers(container: &str, peer_cidr: &str) -> anyhow::Result<Vec<Peer>> {
|
||||
let primary = run_capture(&[
|
||||
"docker",
|
||||
"exec",
|
||||
container,
|
||||
"sh",
|
||||
"-c",
|
||||
"wg show all allowed-ips 2>/dev/null",
|
||||
])
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut peers = parse_allowed_ips(&primary, peer_cidr)?;
|
||||
if peers.is_empty() {
|
||||
let fallback = run_capture(&["docker", "exec", container, "sh", "-c", "wg show 2>/dev/null"])
|
||||
.unwrap_or_default();
|
||||
peers = parse_fallback_wg_show(&fallback, peer_cidr)?;
|
||||
}
|
||||
|
||||
peers.sort_by_key(|p| p.ipv4.to_string());
|
||||
peers.dedup_by_key(|p| p.ipv4);
|
||||
Ok(peers)
|
||||
}
|
||||
|
||||
fn parse_allowed_ips(raw: &str, peer_cidr: &str) -> anyhow::Result<Vec<Peer>> {
|
||||
let mut out = Vec::new();
|
||||
for line in raw.lines() {
|
||||
let cols: Vec<&str> = line.split_whitespace().collect();
|
||||
if cols.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
for tok in cols[2].split(',') {
|
||||
let tok = tok.trim();
|
||||
if let Some((ip, pfx)) = tok.split_once('/') {
|
||||
if pfx != "32" {
|
||||
continue;
|
||||
}
|
||||
if let Ok(v4) = ip.parse::<Ipv4Addr>() {
|
||||
if config::ip_in_cidr(v4, peer_cidr)? {
|
||||
out.push(Peer { ipv4: v4 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn parse_fallback_wg_show(raw: &str, peer_cidr: &str) -> anyhow::Result<Vec<Peer>> {
|
||||
let mut out = Vec::new();
|
||||
for line in raw.lines() {
|
||||
let t = line.trim();
|
||||
if !t.starts_with("allowed ips:") {
|
||||
continue;
|
||||
}
|
||||
let rest = t.trim_start_matches("allowed ips:").trim();
|
||||
for tok in rest.split(',') {
|
||||
let tok = tok.trim();
|
||||
if let Some((ip, pfx)) = tok.split_once('/') {
|
||||
if pfx != "32" {
|
||||
continue;
|
||||
}
|
||||
if let Ok(v4) = ip.parse::<Ipv4Addr>() {
|
||||
if config::ip_in_cidr(v4, peer_cidr)? {
|
||||
out.push(Peer { ipv4: v4 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_primary() {
|
||||
let raw = "wg0 key1 10.8.1.10/32\nwg0 key2 10.8.1.2/32";
|
||||
let peers = parse_allowed_ips(raw, "10.8.1.0/24").expect("parse");
|
||||
assert_eq!(peers.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user