diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8bd793e..51da1fb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,7 @@ jobs: cargo install cargo-zigbuild - name: Build Node - run: cargo zigbuild --release --target ${{ matrix.target }} --bin node --features xray,wireguard + run: cargo zigbuild --release --target ${{ matrix.target }} --bin node --features xray,wireguard,amnezia-wg - name: Upload Artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a5128ded..05d0c920 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -56,7 +56,7 @@ jobs: cargo zigbuild --release \ --bin node \ --target ${{ matrix.target }} \ - --features xray,wireguard + --features xray,wireguard,amnezia-wg - name: Verify static binary run: | diff --git a/Cargo.lock b/Cargo.lock index f7d31163..1ef8d469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ahash" version = "0.7.8" @@ -70,6 +80,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + [[package]] name = "anyhow" version = "1.0.93" @@ -85,6 +101,48 @@ dependencies = [ "object 0.37.3", ] +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn 2.0.101", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow 0.7.15", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -210,6 +268,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -234,6 +301,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -296,6 +372,38 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "cc" version = "1.2.57" @@ -330,6 +438,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.38" @@ -356,6 +488,56 @@ dependencies = [ "stacker", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "console-api" version = "0.8.1" @@ -492,6 +674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -541,13 +724,41 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "defguard_boringtun" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7c7f465dde186f958a0a0e4ae823af623451ba26817b8b366b9968286df7a1" +dependencies = [ + "aead", + "base64 0.22.1", + "blake2", + "chacha20poly1305", + "hex", + "hmac", + "ip_network", + "ip_network_table", + "libc", + "nix", + "parking_lot", + "ring", + "socket2 0.6.4", + "thiserror 2.0.12", + "tracing", + "uniffi", + "untrusted", + "x25519-dalek", +] + [[package]] name = "defguard_wireguard_rs" -version = "0.7.2" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32b0fc14c7fb1f2885fbe3e85ae587699abf7f7d06cb997523f8207203920ac" +checksum = "5657c83576553019b0a473b5417fe9faeba6128819025ad8969cb846be96b50d" dependencies = [ "base64 0.22.1", + "defguard_boringtun", + "ipnet", "libc", "log", "netlink-packet-core", @@ -557,8 +768,11 @@ dependencies = [ "netlink-packet-wireguard", "netlink-sys", "nix", + "regex", "serde", "thiserror 2.0.12", + "windows 0.62.2", + "wireguard-nt", "x25519-dalek", ] @@ -665,7 +879,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fcore" -version = "0.5.9-dev" +version = "0.5.10-dev" dependencies = [ "async-trait", "base32", @@ -679,6 +893,10 @@ dependencies = [ "hex", "hmac", "lettre", + "netlink-packet-amnezia-wireguard", + "netlink-packet-core", + "netlink-packet-generic", + "netlink-sys", "openssl", "parking_lot", "postgres-types", @@ -771,6 +989,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "funty" version = "2.0.0" @@ -909,6 +1136,23 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "h2" version = "0.3.26" @@ -1138,7 +1382,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -1226,7 +1470,7 @@ dependencies = [ "http-body 1.0.1", "hyper 1.4.1", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -1396,13 +1640,45 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown 0.15.0", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", ] +[[package]] +name = "ip_network" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" + +[[package]] +name = "ip_network_table" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" +dependencies = [ + "ip_network", + "ip_network_table-deps-treebitmap", +] + +[[package]] +name = "ip_network_table-deps-treebitmap" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" + [[package]] name = "ipnet" -version = "2.10.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "itertools" @@ -1476,7 +1752,7 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "socket2", + "socket2 0.5.7", "tokio", "tokio-native-tls", "url", @@ -1484,9 +1760,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] [[package]] name = "linux-raw-sys" @@ -1645,68 +1931,67 @@ dependencies = [ "tempfile", ] +[[package]] +name = "netlink-packet-amnezia-wireguard" +version = "0.1.0" +dependencies = [ + "libc", + "log", + "netlink-packet-core", + "netlink-packet-generic", +] + [[package]] name = "netlink-packet-core" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", + "paste", ] [[package]] name = "netlink-packet-generic" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b" +checksum = "2f891b2e0054cac5a684a06628f59568f841c93da4e551239da6e518f539e775" dependencies = [ - "anyhow", - "byteorder", "netlink-packet-core", - "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.22.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0e7987b28514adf555dc1f9a5c30dfc3e50750bbaffb1aec41ca7b23dcd8e4" +checksum = "be8919612f6028ab4eacbbfe1234a9a43e3722c6e0915e7ff519066991905092" dependencies = [ - "anyhow", "bitflags 2.6.0", - "byteorder", "libc", "log", "netlink-packet-core", - "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +checksum = "3176f18d11a1ae46053e59ec89d46ba318ae1343615bd3f8c908bfc84edae35c" dependencies = [ - "anyhow", "byteorder", - "paste", - "thiserror 1.0.64", + "pastey", + "thiserror 2.0.12", ] [[package]] name = "netlink-packet-wireguard" -version = "0.2.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b25b050ff1f6a1e23c6777b72db22790fe5b6b5ccfd3858672587a79876c8f" +checksum = "205d2bad950c9cbbbf08cc5432d6501edfe02d3a34ecad822a3e91c98e97dbf6" dependencies = [ - "anyhow", - "byteorder", "libc", "log", + "netlink-packet-core", "netlink-packet-generic", - "netlink-packet-utils", ] [[package]] @@ -1722,9 +2007,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1810,6 +2095,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.66" @@ -1899,6 +2190,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1930,7 +2227,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] @@ -1971,6 +2268,23 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "postgres-derive" version = "0.4.6" @@ -2165,7 +2479,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.4", "thiserror 2.0.12", "tokio", "tracing", @@ -2202,7 +2516,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.4", "tracing", "windows-sys 0.59.0", ] @@ -2324,13 +2638,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.14", "regex-syntax 0.8.5", ] @@ -2345,9 +2659,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2587,6 +2901,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "seahash" version = "4.1.0" @@ -2621,21 +2955,34 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "serde" -version = "1.0.210" +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 = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2746,6 +3093,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.9" @@ -2761,6 +3114,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.5.7" @@ -2771,6 +3130,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "spin" version = "0.9.8" @@ -2796,6 +3165,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.5" @@ -2807,6 +3182,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -2872,7 +3253,7 @@ dependencies = [ "memchr", "ntapi", "rayon", - "windows", + "windows 0.57.0", ] [[package]] @@ -2934,6 +3315,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -3053,7 +3443,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.7", "tokio-macros", "tracing", "windows-sys 0.52.0", @@ -3100,7 +3490,7 @@ dependencies = [ "postgres-protocol", "postgres-types", "rand 0.8.5", - "socket2", + "socket2 0.5.7", "tokio", "tokio-util", "whoami", @@ -3184,7 +3574,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] @@ -3208,7 +3598,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "socket2", + "socket2 0.5.7", "tokio", "tokio-stream", "tower 0.4.13", @@ -3417,6 +3807,149 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +[[package]] +name = "uniffi" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5f2297ee5b893405bed1a6929faec4713a061df158ecf5198089f23910d470" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "clap", + "uniffi_bindgen", + "uniffi_build", + "uniffi_core", + "uniffi_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc0c60a9607e7ab77a2ad47ec5530178015014839db25af7512447d2238016c" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck", + "indexmap 2.6.0", + "once_cell", + "serde", + "tempfile", + "textwrap", + "toml", + "uniffi_internal_macros", + "uniffi_meta", + "uniffi_pipeline", + "uniffi_udl", +] + +[[package]] +name = "uniffi_build" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c39413c43b955e4aa8a4e2b34bbd1b6b5ff6bd85532b52f9eb92fbe88c14458" +dependencies = [ + "anyhow", + "camino", + "uniffi_bindgen", +] + +[[package]] +name = "uniffi_core" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77baf5d539fe2e1ad6805e942dbc5dbdeb2b83eb5f2b3a6535d422ca4b02a12f" +dependencies = [ + "anyhow", + "bytes", + "once_cell", + "static_assertions", +] + +[[package]] +name = "uniffi_internal_macros" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b42137524f4be6400fcaca9d02c1d4ecb6ad917e4013c0b93235526d8396e5" +dependencies = [ + "anyhow", + "indexmap 2.6.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "uniffi_macros" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9273ec45330d8fe9a3701b7b983cea7a4e218503359831967cb95d26b873561" +dependencies = [ + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.101", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431d2f443e7828a6c29d188de98b6771a6491ee98bba2d4372643bf93f988a18" +dependencies = [ + "anyhow", + "siphasher 1.0.3", + "uniffi_internal_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_pipeline" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761ef74f6175e15603d0424cc5f98854c5baccfe7bf4ccb08e5816f9ab8af689" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.6.0", + "tempfile", + "uniffi_internal_macros", +] + +[[package]] +name = "uniffi_udl" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68773ec0e1c067b6505a73bbf6a5782f31a7f9209333a0df97b87565c46bf370" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "weedle2", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3670,6 +4203,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "whoami" version = "1.5.2" @@ -3681,6 +4223,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + [[package]] name = "winapi" version = "0.3.9" @@ -3722,6 +4270,27 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -3737,12 +4306,36 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.57.0", + "windows-interface 0.57.0", "windows-result 0.1.2", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.57.0" @@ -3754,6 +4347,17 @@ dependencies = [ "syn 2.0.101", ] +[[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 2.0.101", +] + [[package]] name = "windows-interface" version = "0.57.0" @@ -3765,6 +4369,17 @@ dependencies = [ "syn 2.0.101", ] +[[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 2.0.101", +] + [[package]] name = "windows-link" version = "0.1.1" @@ -3777,6 +4392,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-registry" version = "0.4.0" @@ -3784,7 +4409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result 0.3.2", - "windows-strings", + "windows-strings 0.3.1", "windows-targets 0.53.0", ] @@ -3806,6 +4431,15 @@ dependencies = [ "windows-link 0.1.1", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.3.1" @@ -3815,6 +4449,15 @@ dependencies = [ "windows-link 0.1.1", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3833,6 +4476,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3865,6 +4517,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3970,6 +4631,31 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "wireguard-nt" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22b4dbcc6c93786cf22e420ef96e8976bfb92a455070282302b74de5848191f4" +dependencies = [ + "bitflags 2.6.0", + "getrandom 0.2.15", + "ipnet", + "libloading", + "log", + "thiserror 1.0.64", + "widestring", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 6f4ef487..e9baf0e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fcore" -version = "0.5.9-dev" +version = "0.5.10-dev" edition = "2021" build = "build.rs" @@ -21,7 +21,7 @@ base32 = "0.5.1" chrono = { version = "0.4", features = ["serde", "rkyv"] } console-subscriber = {version = "0.4", optional = true} dashmap = "6.1.0" -defguard_wireguard_rs = {version = "0.7.2", features=["serde"], optional = true} +defguard_wireguard_rs = { version = "0.9.7", optional = true} futures = "0.3" hex = { version = "0.4"} hmac = "0.12" @@ -56,6 +56,13 @@ parking_lot = "0.12.5" sha2 = "0.10" data-encoding = "2.5" lettre = { version = "0.11", features = ["tokio1", "builder", "smtp-transport", "tokio1-native-tls"]} +netlink-packet-amnezia-wireguard = { git = "https://github.com/frkn-dev/netlink-packet-amnezia-wireguard.git", rev = "9c4fecd3c5de2f477ae7577bed2d8d5212af0207", optional = true } +netlink-packet-core = { version = "0.8", optional = true } +netlink-packet-generic = { version = "0.4", optional = true } +netlink-sys = { version = "0.8", optional = true } + + + [build-dependencies] tonic-build = {version = "0.12", optional = true} @@ -71,6 +78,7 @@ tokio-util = "0.7" default = [] debug = ["console-subscriber"] wireguard = ["defguard_wireguard_rs"] +amnezia-wg = ["netlink-packet-amnezia-wireguard", "netlink-packet-core", "netlink-packet-generic","netlink-sys"] xray = ["prost", "prost-derive", "tonic", "tonic-build"] [[bin]] diff --git a/dev/dev.sql b/dev/dev.sql index ebecee67..17d24aca 100644 --- a/dev/dev.sql +++ b/dev/dev.sql @@ -134,62 +134,12 @@ alter table inbounds drop column wg_pubkey; alter table inbounds drop column wg_network; - --- TIMESCALE METRICS (SINGLE SOURCE OF TRUTH) - -CREATE EXTENSION IF NOT EXISTS timescaledb; - -CREATE TABLE node_metrics ( - time TIMESTAMPTZ NOT NULL, - node_id UUID NOT NULL, - metric TEXT NOT NULL, - value DOUBLE PRECISION NOT NULL, - labels JSONB NOT NULL -); - -SELECT create_hypertable('node_metrics', 'time'); - --- индексы -CREATE INDEX idx_node_metrics_time ON node_metrics (time DESC); -CREATE INDEX idx_node_metrics_node ON node_metrics (node_id); -CREATE INDEX idx_node_metrics_metric ON node_metrics (metric); - --- GIN индекс для фильтрации по labels -CREATE INDEX idx_node_metrics_labels ON node_metrics USING GIN (labels); - --- compression (очень важно для прод) -ALTER TABLE node_metrics SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'node_id, metric' -); - -SELECT add_compression_policy('node_metrics', INTERVAL '7 days'); - --- retention (опционально) -SELECT add_retention_policy('node_metrics', INTERVAL '90 days'); - -ALTER TABLE node_metrics ADD PRIMARY KEY (time, node_id, metric); -CREATE INDEX ON node_metrics (node_id, time DESC); - -SELECT set_chunk_time_interval('node_metrics', INTERVAL '1 day'); - - -CREATE INDEX idx_node_metrics_grafana -ON node_metrics (metric, node_id, time DESC); - -ALTER TABLE node_metrics SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'node_id, metric', - timescaledb.compress_orderby = 'time DESC' -); - - -CREATE MATERIALIZED VIEW node_metrics_1m -WITH (timescaledb.continuous) AS -SELECT - time_bucket('1 minute', time) AS bucket, - node_id, - metric, - avg(value) AS value -FROM node_metrics -GROUP BY bucket, node_id, metric; \ No newline at end of file +ALTER TABLE inbounds + ADD COLUMN awg_privkey TEXT, + ADD COLUMN awg_interface TEXT, + ADD COLUMN awg_address TEXT, + ADD COLUMN awg_dns INET[], + ADD COLUMN awg_obfuscation JSONB; + +ALTER TYPE proto +ADD VALUE 'amnezia_wg'; \ No newline at end of file diff --git a/src/bin/api/config.rs b/src/bin/api/config.rs index 7570fb2b..63a945f4 100644 --- a/src/bin/api/config.rs +++ b/src/bin/api/config.rs @@ -47,6 +47,8 @@ pub struct ServiceConfig { pub cors_origins: Vec, #[serde(default = "default_wg_network")] pub wireguard_network: IpAddrMask, + #[serde(default = "default_wg_network")] + pub amnezia_wireguard_network: IpAddrMask, #[serde(default = "default_log_level")] pub log_level: String, pub updates_endpoint_zmq: String, diff --git a/src/bin/api/http/handlers/connection.rs b/src/bin/api/http/handlers/connection.rs index 49e3ee46..76c76b18 100644 --- a/src/bin/api/http/handlers/connection.rs +++ b/src/bin/api/http/handlers/connection.rs @@ -107,6 +107,7 @@ pub async fn create_connection_handler( conn_req: ConnCreateRequest, memory: MemSync, wg_network: IpAddrMask, + awg_network: IpAddrMask, ) -> Result where N: NodeStorageOperations + Sync + Send + Clone + 'static, @@ -186,6 +187,36 @@ where }, } } + Tag::AmneziaWg => { + let last_ip: Option = mem + .connections + .get_last_awg_addr() + .and_then(|mask| mask.as_ipv4()); + + let next = match last_ip { + Some(ip) => IpAddrMask::increment_ipv4(ip), + None => awg_network.first_peer_ip(), + }; + + let next = match next { + Some(ip) => ip, + None => return Ok(http::internal_error("Failed to allocate IP")), + }; + + if !awg_network.contains_ipv4(next) { + return Ok(http::internal_error("IP out of range")); + } + + Proto::AmneziaWg { + param: WgParam { + keys: WgKeys::default(), + address: IpAddrMask { + address: IpAddr::V4(next), + cidr: 32, + }, + }, + } + } Tag::Shadowsocks => { let password = utils::generate_random_password(15); Proto::Shadowsocks { password } @@ -442,6 +473,88 @@ where })))) } +pub async fn amnezia_wireguard_connections_handler( + req: ConnectionInfoRequest, + memory: MemSync, +) -> Result, warp::Rejection> +where + N: NodeStorageOperations + Sync + Send + Clone + 'static, + C: ConnectionApiOperations + + ConnectionBaseOperations + + Sync + + Send + + Clone + + 'static + + From + + PartialEq, + S: SubscriptionOperations + Send + Sync + Clone + 'static + PartialEq, + Connection: From, +{ + if let Err(e) = req.validate() { + return Ok(Box::new(http::bad_request(&format!("Bad Request: {}", e)))); + }; + + let mem = memory.memory.read().await; + + if let Some(sub) = mem.subscriptions.find_by_id(&req.id) { + if !sub.is_active() { + return Ok(Box::new(http::not_found(&format!( + "Subscription {} is expired", + req.id + )))); + } + } + + let conns = mem.connections.get_by_subscription_id(&req.id); + + if conns.is_none() { + return Ok(Box::new(http::not_found("No connections"))); + } + + let mut result = vec![]; + + if let Some(conns) = conns { + for (conn_id, conn) in conns { + if conn.get_deleted() || conn.get_env() != req.env { + continue; + } + + if conn.get_proto().proto() != Tag::AmneziaWg { + continue; + } + + if let Some(nodes) = mem.nodes.get_by_env(&conn.get_env()) { + for node in nodes { + if let Some(inbound) = node.inbounds.get(&Tag::AmneziaWg) { + let c: Connection = conn.clone().into(); + + if let Ok(link) = inbound.create_link( + &conn_id, + &c, + &node.hostname, + &node.address, + &node.label, + ) { + result.push(serde_json::json!({ + "conn_id": conn_id, + "label": node.label, + "env": node.env, + "config": link + })); + } + } + } + } + } + } + + drop(mem); + + Ok(Box::new(warp::reply::json(&serde_json::json!({ + "nodes": result + })))) +} + pub async fn mtproto_connections_handler( req: ConnectionInfoRequest, memory: MemSync, diff --git a/src/bin/api/http/handlers/subscription.rs b/src/bin/api/http/handlers/subscription.rs index 5854f855..583bb5ef 100644 --- a/src/bin/api/http/handlers/subscription.rs +++ b/src/bin/api/http/handlers/subscription.rs @@ -208,6 +208,7 @@ where let mut has_h2 = false; let mut has_mtproto = false; let mut has_wg = false; + let mut has_awg = false; let nodes = mem.nodes.get_by_env(&env); @@ -236,6 +237,12 @@ where .any(|n| n.inbounds.values().any(|i| i.tag == Tag::Wireguard)) }); + let awg_nodes = nodes.clone(); + let awg_node_exist = awg_nodes.is_some_and(|ns| { + ns.iter() + .any(|n| n.inbounds.values().any(|i| i.tag == Tag::AmneziaWg)) + }); + let h2_node_exists = nodes.is_some_and(|ns| { ns.iter() .any(|n| n.inbounds.values().any(|i| i.tag == Tag::Hysteria2)) @@ -255,6 +262,10 @@ where has_wg = true; } + if awg_node_exist && proto == Tag::AmneziaWg { + has_awg = true; + } + if mtproto_node_exist && proto == Tag::Mtproto { has_mtproto = true; } @@ -267,6 +278,7 @@ where has_h2, has_mtproto, has_wg, + has_awg, }); } } diff --git a/src/bin/api/http/handlers/trial.rs b/src/bin/api/http/handlers/trial.rs index 34a82028..ebcc7f6e 100644 --- a/src/bin/api/http/handlers/trial.rs +++ b/src/bin/api/http/handlers/trial.rs @@ -17,6 +17,7 @@ pub async fn post_trial_handler( memory: MemSync, store: EmailStore, wireguard_network: IpAddrMask, + amnezia_wireguard_network: IpAddrMask, system_refer_codes: Vec, envs: Vec, protos: Vec, @@ -135,6 +136,38 @@ where }, } } + + Tag::AmneziaWg => { + let mem = memory.memory.read().await; + + let last_ip: Option = mem + .connections + .get_last_awg_addr() + .and_then(|mask| mask.as_ipv4()); + + let next = match last_ip { + Some(ip) => IpAddrMask::increment_ipv4(ip), + None => amnezia_wireguard_network.first_peer_ip(), + }; + + let next = match next { + Some(ip) => ip, + None => return Ok(http::internal_error("Failed to allocate IP")), + }; + + if !amnezia_wireguard_network.contains_ipv4(next) { + return Ok(http::internal_error("IP out of range")); + } + Proto::AmneziaWg { + param: WgParam { + keys: WgKeys::default(), + address: IpAddrMask { + address: IpAddr::V4(next), + cidr: 32, + }, + }, + } + } Tag::Shadowsocks => { let password = utils::generate_random_password(15); Proto::Shadowsocks { password } diff --git a/src/bin/api/http/request.rs b/src/bin/api/http/request.rs index c73541bf..8162612b 100644 --- a/src/bin/api/http/request.rs +++ b/src/bin/api/http/request.rs @@ -25,6 +25,9 @@ pub enum TagReq { #[serde(alias = "wireguard", alias = "Wireguard")] Wireguard, + #[serde(alias = "amneziawg", alias = "Amneziawg")] + AmneziaWg, + #[serde(alias = "VlessTcpReality")] VlessTcpReality, @@ -72,6 +75,7 @@ impl TagReq { Tag::Hysteria2, ], TagReq::Wireguard => vec![Tag::Wireguard], + TagReq::AmneziaWg => vec![Tag::AmneziaWg], TagReq::Hysteria2 => vec![Tag::Hysteria2], TagReq::VlessTcpReality => vec![Tag::VlessTcpReality], TagReq::VlessGrpcReality => vec![Tag::VlessGrpcReality], @@ -198,6 +202,7 @@ impl SubscriptionInfoRequest { Xray => [Txt, Base64, Clash].into(), Proxy => [Txt, Base64].into(), Wireguard => [].into(), + AmneziaWg => [].into(), Hysteria2 => [Txt, Base64].into(), VlessTcpReality => [Txt, Base64, Clash].into(), VlessGrpcReality => [Txt, Base64, Clash].into(), diff --git a/src/bin/api/http/routes.rs b/src/bin/api/http/routes.rs index 7cb7dc74..1c422f9b 100644 --- a/src/bin/api/http/routes.rs +++ b/src/bin/api/http/routes.rs @@ -151,6 +151,12 @@ where .and(with_sync(self.sync.clone())) .and_then(wireguard_connections_handler); + let get_awg_connections_info_route = warp::path!("info" / "connections" / "amneziawg") + .and(warp::get()) + .and(warp::query::()) + .and(with_sync(self.sync.clone())) + .and_then(amnezia_wireguard_connections_handler); + let get_mtproto_connections_info_route = warp::path!("info" / "connections" / "mtproto") .and(warp::get()) .and(warp::query::()) @@ -172,6 +178,9 @@ where .and(warp::body::json()) .and(with_sync(self.sync.clone())) .and(with_param_ipaddrmask(params.wireguard_network.clone())) + .and(with_param_ipaddrmask( + params.amnezia_wireguard_network.clone(), + )) .and_then(create_connection_handler); let delete_connection_route = warp::delete() @@ -217,6 +226,9 @@ where .and(with_sync(self.sync.clone())) .and(with_email_store(self.email_store.clone())) .and(with_param_ipaddrmask(params.wireguard_network.clone())) + .and(with_param_ipaddrmask( + params.amnezia_wireguard_network.clone(), + )) .and(with_param_vec_string(params.system_refer_codes.clone())) .and(with_param_envs(params.enabled_envs.clone())) .and(with_param_tags(params.enabled_tags.clone())) @@ -254,6 +266,7 @@ where .or(delete_connection_route) .or(get_mtproto_connections_info_route) .or(get_wg_connections_info_route) + .or(get_awg_connections_info_route) .or(get_a_connection_route) // Key .or(get_key_validation_route) diff --git a/src/bin/api/postgres/connection.rs b/src/bin/api/postgres/connection.rs index 8d57768a..06893fdf 100644 --- a/src/bin/api/postgres/connection.rs +++ b/src/bin/api/postgres/connection.rs @@ -28,6 +28,7 @@ pub struct ConnRow { impl From<(uuid::Uuid, Connection)> for ConnRow { fn from((conn_id, conn): (uuid::Uuid, Connection)) -> Self { + let wg = conn.get_wireguard().or_else(|| conn.get_amneziawg()); ConnRow { conn_id, password: conn.get_password(), @@ -36,7 +37,7 @@ impl From<(uuid::Uuid, Connection)> for ConnRow { modified_at: conn.modified_at, expires_at: conn.expires_at, subscription_id: conn.subscription_id, - wg: conn.get_wireguard().cloned(), + wg: wg.cloned(), proto: conn.get_proto().proto(), token: conn.get_token(), is_deleted: conn.is_deleted, @@ -49,6 +50,14 @@ impl TryFrom for Connection { fn try_from(row: ConnRow) -> Result { let proto = match row.proto { + Tag::AmneziaWg => { + let wg = row + .wg + .ok_or_else(|| Error::Custom("Missing Wireguard param".into()))?; + + Proto::new_awg(&wg) + } + Tag::Wireguard => { let wg = row .wg diff --git a/src/bin/api/postgres/node.rs b/src/bin/api/postgres/node.rs index b7cf6a8c..991201ea 100644 --- a/src/bin/api/postgres/node.rs +++ b/src/bin/api/postgres/node.rs @@ -9,7 +9,8 @@ use tokio::sync::Mutex; use tracing::{debug, error, warn}; use fcore::{ - H2Settings, Inbound, IpAddrMask, Node, NodeStatus, NodeType, Result, WgKeys, WireguardSettings, + AmneziaWgSettings, AwgInterfaceConfig, AwgObfuscationParams, H2Settings, Inbound, IpAddrMask, + Node, NodeStatus, NodeType, Result, WgKeys, WireguardSettings, }; use super::pg::PgClientManager; @@ -77,12 +78,14 @@ impl PgNode { let inbound_query = " INSERT INTO inbounds ( id, node_id, tag, port, stream_settings, - wg_privkey, wg_interface, wg_address, dns, h2, mtproto_secret + wg_privkey, wg_interface, wg_address, dns, h2, mtproto_secret, + awg_privkey, awg_interface, awg_address, awg_dns, awg_obfuscation ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, - $9, $10, $11 + $9, $10, $11, + $12, $13, $14, $15, $16 ) ON CONFLICT (node_id, tag) DO UPDATE SET port = EXCLUDED.port, @@ -92,7 +95,12 @@ impl PgNode { wg_address = EXCLUDED.wg_address, dns = EXCLUDED.dns, h2 = EXCLUDED.h2, - mtproto_secret = EXCLUDED.mtproto_secret + mtproto_secret = EXCLUDED.mtproto_secret, + awg_privkey = EXCLUDED.awg_privkey, + awg_interface = EXCLUDED.awg_interface, + awg_address = EXCLUDED.awg_address, + awg_dns = EXCLUDED.awg_dns, + awg_obfuscation = EXCLUDED.awg_obfuscation "; for inbound in node.inbounds.values() { @@ -119,6 +127,27 @@ impl PgNode { }) .unwrap_or((None, None, None, None)); + let (awg_privkey, awg_interface, awg_address, awg_dns, awg_obfuscation) = inbound + .awg + .as_ref() + .map(|awg| { + ( + Some(&awg.interface.private_key.privkey), + Some(&awg.interface.interface), + Some(awg.interface.address.to_string()), + Some( + awg.interface + .dns + .iter() + .cloned() + .map(IpAddr::V4) + .collect::>(), + ), + serde_json::to_value(&awg.obfuscation).ok(), + ) + }) + .unwrap_or((None, None, None, None, None)); + tx.execute( inbound_query, &[ @@ -133,6 +162,11 @@ impl PgNode { &dns, &h2_settings, &inbound.mtproto_secret, + &awg_privkey, + &awg_interface, + &awg_address, + &awg_dns, + &awg_obfuscation, ], ) .await?; @@ -149,13 +183,41 @@ impl PgNode { let rows = client .query( "SELECT - n.id AS node_id, n.uuid, n.env, n.hostname, n.address, n.status, - n.created_at, n.modified_at, n.label, n.interface, - n.cores, n.max_bandwidth_bps, n.country, n.node_type, i.id - - AS inbound_id, i.tag, i.port, i.stream_settings, i.wg_privkey, i.wg_interface, i.wg_address, i.dns, i.h2, i.mtproto_secret - FROM nodes n - LEFT JOIN inbounds i ON n.id = i.node_id", + n.id AS node_id, + n.uuid, + n.env, + n.hostname, + n.address, + n.status, + n.created_at, + n.modified_at, + n.label, + n.interface, + n.cores, + n.max_bandwidth_bps, + n.country, + n.node_type, + + i.id AS inbound_id, + i.tag, + i.port, + i.stream_settings, + + i.wg_privkey, + i.wg_interface, + i.wg_address, + i.dns, + + i.awg_privkey, + i.awg_interface, + i.awg_address, + i.awg_dns, + i.awg_obfuscation, + + i.h2, + i.mtproto_secret + FROM nodes n + LEFT JOIN inbounds i ON n.id = i.node_id", &[], ) .await?; @@ -182,6 +244,24 @@ impl PgNode { .get::<_, Option>("wg_address") .and_then(|s| s.parse().ok()); + let awg_address: Option = row + .get::<_, Option>("awg_address") + .and_then(|s| s.parse().ok()); + + let awg_dns: Option> = + row.get::<_, Option>>("awg_dns").map(|ips| { + ips.into_iter() + .filter_map(|ip| match ip { + IpAddr::V4(v4) => Some(v4), + _ => None, + }) + .collect() + }); + + let awg_obfuscation: Option = row + .get::<_, Option>("awg_obfuscation") + .and_then(|v| serde_json::from_value(v).ok()); + let dns: Option> = row.get::<_, Option>>("dns").map(|ips| { ips.into_iter() .filter_map(|ip| match ip { @@ -233,6 +313,29 @@ impl PgNode { _ => None, }; + let awg = match ( + row.get::<_, Option>("awg_privkey"), + row.get::<_, Option>("awg_interface"), + awg_address, + awg_dns, + ) { + (Some(private_key), Some(interface), Some(address), Some(dns)) => { + Some(AmneziaWgSettings { + interface: AwgInterfaceConfig { + interface, + address, + listen_port: row.get::<_, i32>("port") as u16, + private_key: WgKeys { + privkey: private_key, + }, + dns, + }, + obfuscation: awg_obfuscation, + }) + } + _ => None, + }; + let mtproto_secret = row.get::<_, Option>("mtproto_secret"); let inbound = Inbound { @@ -243,6 +346,7 @@ impl PgNode { .and_then(|v| serde_json::from_value(v).ok()), wg, + awg, h2, mtproto_secret, }; diff --git a/src/bin/node/config.rs b/src/bin/node/config.rs index a78470ba..4e6f2cdb 100644 --- a/src/bin/node/config.rs +++ b/src/bin/node/config.rs @@ -20,6 +20,9 @@ pub struct ServiceSettings { #[cfg(feature = "wireguard")] #[serde(default)] pub wg: WgConfig, + #[cfg(feature = "amnezia-wg")] + #[serde(default)] + pub awg: AmneziaWgConfig, #[serde(default)] pub h2: H2Config, #[serde(default)] @@ -69,6 +72,14 @@ pub struct WgConfig { pub path: String, } +#[cfg(feature = "amnezia-wg")] +#[derive(Clone, Default, Debug, Deserialize)] +pub struct AmneziaWgConfig { + #[serde(default = "default_disabled")] + pub enabled: bool, + pub path: String, +} + #[derive(Clone, Default, Debug, Deserialize)] pub struct MtprotoConfig { #[serde(default = "default_disabled")] diff --git a/src/bin/node/node.rs b/src/bin/node/node.rs index dddc4d10..cc8e5d2d 100644 --- a/src/bin/node/node.rs +++ b/src/bin/node/node.rs @@ -17,6 +17,9 @@ use fcore::{XrayClient, XrayHandlerClient, XraySettings, XrayStatsClient}; #[cfg(feature = "wireguard")] use fcore::{WgApi, WireguardServerConfig, WireguardSettings}; +#[cfg(feature = "amnezia-wg")] +use fcore::{AmneziaWgServerConfig, AmneziaWgSettings, AwgInterface, Error}; + use fcore::{ utils::measure_time, BaseConnection as Connection, ConnectionBaseOperations, Connections, MetricBuffer, Node as MemNode, Publisher, Result, SnapshotManager, Subscriber, Tag, Topic, @@ -26,7 +29,7 @@ use fcore::{H2Settings, Hysteria2Settings, MtprotoSettings, NodeConfig, Settings use super::config::ServiceSettings; use super::http::ApiRequests; -#[cfg(any(feature = "xray", feature = "wireguard"))] +#[cfg(any(feature = "xray", feature = "wireguard", feature = "amnezia-wg"))] use super::snapshot::SnapshotRestore; use super::tasks::Tasks; @@ -44,6 +47,8 @@ where pub handler_client: Option>>, #[cfg(feature = "wireguard")] pub wg_client: Option, + #[cfg(feature = "amnezia-wg")] + pub awg_client: Option, } impl Node @@ -57,6 +62,7 @@ where #[cfg(feature = "xray")] stats_client: Option>>, #[cfg(feature = "xray")] handler_client: Option>>, #[cfg(feature = "wireguard")] wg_client: Option, + #[cfg(feature = "amnezia-wg")] awg_client: Option, ) -> Self { let memory = Arc::new(RwLock::new(Connections::default())); Self { @@ -70,6 +76,8 @@ where handler_client, #[cfg(feature = "wireguard")] wg_client, + #[cfg(feature = "amnezia-wg")] + awg_client, } } } @@ -128,6 +136,26 @@ pub async fn run(settings: ServiceSettings) -> Result<()> { (None, None) }; + #[cfg(feature = "amnezia-wg")] + let (awg_client, awg_config) = if settings.awg.enabled { + let raw_config = AmneziaWgServerConfig::from_file(&settings.awg.path)?; + let awg: AmneziaWgSettings = raw_config.try_into()?; + + debug!("{:?}", awg); + + let client = AwgInterface::connect(awg.interface.interface.clone()) + .map_err(|e| Error::Custom(format!("Cannot create AWG client: {}", e)))?; + + // optional: validate via real netlink call + let _device = client + .get_device() + .map_err(|e| Error::Custom(format!("Cannot validate AWG client: {}", e)))?; + + (Some(client), Some(awg)) + } else { + (None, None) + }; + // Init Hysteria2 let h2_config = if settings.h2.enabled { match Hysteria2Settings::from_file(&settings.h2.path) { @@ -169,6 +197,8 @@ pub async fn run(settings: ServiceSettings) -> Result<()> { xray_config, #[cfg(feature = "wireguard")] wg_config, + #[cfg(feature = "amnezia-wg")] + awg_config, h2_config, mtproto_config, ); @@ -197,6 +227,8 @@ pub async fn run(settings: ServiceSettings) -> Result<()> { handler_client.clone(), #[cfg(feature = "wireguard")] wg_client.clone(), + #[cfg(feature = "amnezia-wg")] + awg_client.clone(), )); let snapshot_path = settings.service.snapshot_path.clone(); diff --git a/src/bin/node/tasks.rs b/src/bin/node/tasks.rs index 12e1a496..0cedeeff 100644 --- a/src/bin/node/tasks.rs +++ b/src/bin/node/tasks.rs @@ -6,7 +6,7 @@ use tokio::time::Duration; use tonic::Status; use fcore::{Action, BaseConnection as Connection, Message, Metrics, Topic}; -#[cfg(any(feature = "xray", feature = "wireguard"))] +#[cfg(any(feature = "xray", feature = "wireguard", feature = "amnezia-wg"))] use fcore::{ConnectionStorageBaseOperations, Proto, Tag}; use fcore::{Error, Result}; #[cfg(feature = "xray")] @@ -18,6 +18,14 @@ use fcore::ConnectionBaseOperations; use super::metrics::BusinessMetrics; use super::node::Node; +#[cfg(feature = "amnezia-wg")] +use netlink_packet_amnezia_wireguard::{ + WireguardAddressFamily, WireguardAllowedIpAttr, WireguardPeer, WireguardPeerAttribute, +}; + +#[cfg(feature = "amnezia-wg")] +use fcore::AwgInterface; + #[async_trait] pub trait Tasks { async fn run_subscriber(&self) -> Result<()>; @@ -119,10 +127,78 @@ where async fn handle_message(&self, msg: Message) -> Result<()> { match msg.action { Action::Create | Action::Update => { - #[cfg(any(feature = "xray", feature = "wireguard"))] + #[cfg(any(feature = "xray", feature = "wireguard", feature = "amnezia-wg"))] let conn_id: uuid::Uuid = msg.conn_id; match msg.tag { + #[cfg(feature = "amnezia-wg")] + Tag::AmneziaWg => { + let awg = msg.wg.clone().ok_or_else(|| { + Error::Custom("Missing Amnezia WireGuard keys".into()) + })?; + + let proto = Proto::new_awg(&awg); + + let conn = Connection::new( + proto, + msg.expires_at.map(Into::into), + msg.subscription_id, + ); + + { + let mut mem = self.memory.write().await; + mem.add(&conn_id, conn.clone().into()).map_err(|err| { + Error::Custom(format!( + "Failed to add Amnezia WireGuard conn {}: {}", + conn_id, err + )) + })?; + } + + let awg_api = self.awg_client.as_ref().ok_or_else(|| { + Error::Custom("Amnezia WireGuard API is unavailable".into()) + })?; + + // pubkey + let pubkey = awg + .keys + .pubkey() + .map_err(|e| Error::Custom(format!("Invalid AWG pubkey: {}", e)))?; + + let pubkey = AwgInterface::decode_pubkey(&pubkey)?; + + // optional: check existence via netlink + let device = awg_api.get_device().map_err(|e| { + Error::Custom(format!("Failed to read AWG device: {}", e)) + })?; + + let exists = device.peers.iter().any(|p| { + p.0.iter().any(|attr| { + matches!(attr, WireguardPeerAttribute::PublicKey(k) if k == &pubkey) + }) + }); + + if exists { + return Err(Error::Custom("AWG user already exists".into())); + } + + let peer = WireguardPeer(vec![ + WireguardPeerAttribute::PublicKey(pubkey), + WireguardPeerAttribute::AllowedIps(vec![awg.address.into()]), + ]); + + tracing::error!("{:#?}", peer); + awg_api.add_peer(peer).map_err(|e| { + Error::Custom(format!( + "Failed to create AmneziaWG peer via netlink: {}", + e + )) + })?; + + tracing::debug!("AWG connection created {}", conn); + + return Ok(()); + } #[cfg(feature = "wireguard")] Tag::Wireguard => { let wg = msg @@ -250,9 +326,32 @@ where } Action::Delete => { let tag = msg.tag; - #[cfg(any(feature = "xray", feature = "wireguard"))] + #[cfg(any(feature = "xray", feature = "wireguard", feature = "amnezia-wg"))] let conn_id = msg.conn_id; match tag { + #[cfg(feature = "amnezia-wg")] + Tag::AmneziaWg => { + let awg_api = self + .awg_client + .as_ref() + .ok_or_else(|| Error::Custom("AWG API is unavailable".into()))?; + + let wg = msg + .wg + .clone() + .ok_or_else(|| Error::Custom("Missing AWG keys in message".into()))?; + + let pubkey = wg.keys.pubkey()?; + let pubkey = AwgInterface::decode_pubkey(&pubkey)?; + awg_api.remove_peer(pubkey).map_err(|e| { + Error::Custom(format!("Failed to remove AWG peer: {}", e)) + })?; + + let mut mem = self.memory.write().await; + let _ = mem.remove(&conn_id); + + return Ok(()); + } #[cfg(feature = "wireguard")] Tag::Wireguard => { let wg_api = self diff --git a/src/config/amnezia_wg.rs b/src/config/amnezia_wg.rs new file mode 100644 index 00000000..484c3f82 --- /dev/null +++ b/src/config/amnezia_wg.rs @@ -0,0 +1,195 @@ +use serde::{Deserialize, Serialize}; +use std::net::Ipv4Addr; + +use crate::{error::Error, WgKeys}; + +use crate::memory::connection::wireguard::IpAddrMask; + +// ===================== +// Obfuscation params +// ===================== + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct AwgObfuscationParams { + pub jc: u32, + pub jmin: u32, + pub jmax: u32, + + pub s1: u32, + pub s2: u32, + + pub h1: u32, + pub h2: u32, + pub h3: u32, + pub h4: u32, +} + +// ===================== +// Interface config +// ===================== + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct AwgInterfaceConfig { + pub interface: String, + pub address: IpAddrMask, + pub listen_port: u16, + pub private_key: WgKeys, + pub dns: Vec, +} + +// ===================== +// Full settings +// ===================== + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct AmneziaWgSettings { + pub interface: AwgInterfaceConfig, + pub obfuscation: Option, +} + +// ===================== +// Raw file config +// ===================== + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AmneziaWgServerConfig { + pub interface: String, + pub address: String, + pub port: u16, + pub private_key: String, + pub dns: Option>, + + pub obfuscation: Option, +} + +// ===================== +// Parse from file +// ===================== + +impl AmneziaWgServerConfig { + pub fn from_file(path: &str) -> Result { + let contents = std::fs::read_to_string(path)?; + + let interface = path + .split('/') + .next_back() + .and_then(|f| f.split('.').next()) + .ok_or_else(|| Error::Custom("no interface name".into()))? + .to_string(); + + let mut private_key = None; + let mut address = None; + let mut dns: Vec = vec![]; + let mut port = None; + + let mut jc: Option = None; + let mut jmin: Option = None; + let mut jmax: Option = None; + let mut s1: Option = None; + let mut s2: Option = None; + let mut h1: Option = None; + let mut h2: Option = None; + let mut h3: Option = None; + let mut h4: Option = None; + + for line in contents.lines() { + let line = line.trim(); + + let Some((key, value)) = line.split_once('=') else { + continue; + }; + + let value = value.trim(); + + match key.trim() { + "PrivateKey" => { + private_key = Some(value.to_string()); + } + + "Address" => { + address = Some(value.to_string()); + } + + "ListenPort" => { + port = value.parse::().ok(); + } + + "DNS" => { + dns = value + .split(',') + .filter_map(|v| v.trim().parse::().ok()) + .collect(); + } + + // ===== AWG obfuscation ===== + "Jc" => jc = value.parse().ok(), + "Jmin" => jmin = value.parse().ok(), + "Jmax" => jmax = value.parse().ok(), + "S1" => s1 = value.parse().ok(), + "S2" => s2 = value.parse().ok(), + "H1" => h1 = value.parse().ok(), + "H2" => h2 = value.parse().ok(), + "H3" => h3 = value.parse().ok(), + "H4" => h4 = value.parse().ok(), + + _ => {} + } + } + + let obfuscation = match jc { + Some(jc_val) => Some(AwgObfuscationParams { + jc: jc_val, + jmin: jmin.unwrap_or(0), + jmax: jmax.unwrap_or(0), + s1: s1.unwrap_or(0), + s2: s2.unwrap_or(0), + h1: h1.unwrap_or(0), + h2: h2.unwrap_or(0), + h3: h3.unwrap_or(0), + h4: h4.unwrap_or(0), + }), + None => None, + }; + + Ok(Self { + interface, + port: port.ok_or_else(|| Error::Custom("no ListenPort".into()))?, + private_key: private_key.ok_or_else(|| Error::Custom("no PrivateKey".into()))?, + address: address.ok_or_else(|| Error::Custom("no Address".into()))?, + dns: Some(dns), + obfuscation, + }) + } +} + +// ===================== +// Convert to runtime settings +// ===================== + +impl TryFrom for AmneziaWgSettings { + type Error = Error; + + fn try_from(cfg: AmneziaWgServerConfig) -> Result { + let address = cfg + .address + .parse::() + .map_err(|_| Error::Custom("Invalid AWG address".into()))?; + + let dns = cfg.dns.unwrap_or_default().into_iter().collect(); + + let keys = WgKeys { + privkey: cfg.private_key, + }; + + Ok(Self { + interface: AwgInterfaceConfig { + interface: cfg.interface, + address, + listen_port: cfg.port, + private_key: keys, + dns, + }, + obfuscation: cfg.obfuscation, + }) + } +} diff --git a/src/config/inbound.rs b/src/config/inbound.rs index 620f0df5..5d8aebec 100644 --- a/src/config/inbound.rs +++ b/src/config/inbound.rs @@ -10,6 +10,7 @@ use crate::memory::connection::operation::base::Operations; use crate::memory::tag::ProtoTag as Tag; use crate::utils::get_uuid_last_octet_simple; +use crate::config::amnezia_wg::AmneziaWgSettings; use crate::config::h2::H2Settings; use crate::config::wireguard::WireguardSettings; @@ -84,6 +85,7 @@ pub struct Inbound { #[serde(rename = "streamSettings")] pub stream_settings: Option, pub wg: Option, + pub awg: Option, pub h2: Option, pub mtproto_secret: Option, } @@ -186,6 +188,14 @@ pub trait InboundConnLink { address: &Ipv4Addr, label: &str, ) -> Result; + fn amneziawg( + &self, + conn_id: &uuid::Uuid, + conn: &Connection, + _hostname: &str, + address: &Ipv4Addr, + label: &str, + ) -> Result; } impl InboundConnLink for Inbound { @@ -203,12 +213,89 @@ impl InboundConnLink for Inbound { Tag::VlessXhttpReality => self.vless_xhttp(conn_id, hostname, address, label), Tag::Hysteria2 => self.h2(hostname, label, conn), Tag::Wireguard => self.wireguard(conn_id, conn, hostname, address, label), + Tag::AmneziaWg => self.amneziawg(conn_id, conn, hostname, address, label), Tag::Mtproto => self.mtproto(hostname, address, label), Tag::Vmess => self.vmess(conn_id, hostname, address, label), _ => Err(Error::Custom("Unsupported protocol tag".into())), } } + fn amneziawg( + &self, + conn_id: &uuid::Uuid, + conn: &Connection, + _hostname: &str, + address: &Ipv4Addr, + label: &str, + ) -> Result { + tracing::debug!("Trying to print AWG conn"); + + if let Some(awg_conn) = conn.get_amneziawg() { + let private_key = awg_conn.keys.privkey.clone(); + let client_ip = awg_conn.address.clone(); + + if let Some(awg) = &self.awg { + let server_pubkey = awg.interface.private_key.pubkey()?; + let host = address; + let port = awg.interface.listen_port; + + let dns = awg + .interface + .dns + .iter() + .map(|d| d.to_string()) + .collect::>() + .join(","); + + let mut config = format!( + r#"[Interface] +PrivateKey = {private_key} +Address = {client_ip} +"#, + ); + + if !dns.is_empty() { + config.push_str(&format!("DNS = {}\n", dns)); + } + + if let Some(obf) = &awg.obfuscation { + config.push_str(&format!( + r#" +Jc = {} +Jmin = {} +Jmax = {} +S1 = {} +S2 = {} +H1 = {} +H2 = {} +H3 = {} +H4 = {} +"#, + obf.jc, obf.jmin, obf.jmax, obf.s1, obf.s2, obf.h1, obf.h2, obf.h3, obf.h4, + )); + } + + config.push_str(&format!( + r#" +[Peer] +PublicKey = {server_pubkey} +Endpoint = {host}:{port} +AllowedIPs = 0.0.0.0/0, ::/0 +PersistentKeepalive = 25 +"# + )); + + config.push_str(&format!("\n# {} — conn_id: {}\n", label, conn_id)); + + Ok(config) + } else { + Err(Error::Custom("AWG Inbound is not configured".into())) + } + } else { + Err(Error::Custom("AWG Conn is not configured".into())) + } + } + fn wireguard( &self, conn_id: &uuid::Uuid, diff --git a/src/config/mod.rs b/src/config/mod.rs index c4d96f73..f5bbc549 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod amnezia_wg; pub(crate) mod clash; pub(crate) mod h2; pub(crate) mod inbound; diff --git a/src/http/response.rs b/src/http/response.rs index f59f6edf..8ddbf2ec 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -54,4 +54,5 @@ pub struct EnvInfo { pub has_h2: bool, pub has_mtproto: bool, pub has_wg: bool, + pub has_awg: bool, } diff --git a/src/lib.rs b/src/lib.rs index 28cdcbe9..6c8c319a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,9 +28,12 @@ pub const BANNER: &str = r#" // |_| |___/ "#; -pub const VERSION: &str = "0.5.9-dev"; +pub const VERSION: &str = "0.5.10-dev"; pub use config::{ + amnezia_wg::{ + AmneziaWgServerConfig, AmneziaWgSettings, AwgInterfaceConfig, AwgObfuscationParams, + }, clash::InboundClashConfig, h2::{H2Settings, Hysteria2Settings}, inbound::{Inbound, InboundConnLink, Settings as XraySettings}, @@ -73,6 +76,8 @@ pub use metrics::{ storage::{HasMetrics, MetricBuffer, MetricStorage}, MetricEnvelope, Metrics, }; +#[cfg(feature = "amnezia-wg")] +pub use proto::amnezia_wg::AwgInterface; #[cfg(feature = "wireguard")] pub use proto::wireguard::WgApi; #[cfg(feature = "xray")] diff --git a/src/memory/connection/operation/api.rs b/src/memory/connection/operation/api.rs index 02230d8b..ecabe1cd 100644 --- a/src/memory/connection/operation/api.rs +++ b/src/memory/connection/operation/api.rs @@ -38,6 +38,7 @@ impl Operations for Conn { let wg = match &self.proto { Proto::Wireguard { param, .. } => Some(param.clone()), + Proto::AmneziaWg { param, .. } => Some(param.clone()), _ => None, }; diff --git a/src/memory/connection/operation/base.rs b/src/memory/connection/operation/base.rs index ba6d1c79..9af19288 100644 --- a/src/memory/connection/operation/base.rs +++ b/src/memory/connection/operation/base.rs @@ -21,6 +21,7 @@ pub trait Operations { fn get_deleted(&self) -> bool; fn get_wireguard(&self) -> Option<&WgParam>; + fn get_amneziawg(&self) -> Option<&WgParam>; fn get_password(&self) -> Option; fn get_token(&self) -> Option; fn set_password(&mut self, password: Option) -> Result<()>; @@ -80,6 +81,13 @@ impl Operations for Base { } } + fn get_amneziawg(&self) -> Option<&WgParam> { + match &self.proto { + Proto::AmneziaWg { param, .. } => Some(param), + _ => None, + } + } + fn set_password(&mut self, password: Option) -> Result<()> { match (&mut self.proto, password) { (Proto::Shadowsocks { password: p }, Some(new_pw)) => { @@ -144,6 +152,13 @@ impl Operations for Conn { } } + fn get_amneziawg(&self) -> Option<&WgParam> { + match &self.proto { + Proto::AmneziaWg { param, .. } => Some(param), + _ => None, + } + } + fn set_password(&mut self, password: Option) -> Result<()> { match (&mut self.proto, password) { (Proto::Shadowsocks { password: p }, Some(new_pw)) => { diff --git a/src/memory/connection/proto.rs b/src/memory/connection/proto.rs index 80246cb8..003efa36 100644 --- a/src/memory/connection/proto.rs +++ b/src/memory/connection/proto.rs @@ -11,6 +11,7 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; )] pub enum Proto { Wireguard { param: WgParam }, + AmneziaWg { param: WgParam }, Shadowsocks { password: String }, Xray(Tag), Hysteria2 { token: uuid::Uuid }, @@ -21,6 +22,7 @@ impl Proto { pub fn proto(&self) -> Tag { match self { Proto::Wireguard { .. } => Tag::Wireguard, + Proto::AmneziaWg { .. } => Tag::AmneziaWg, Proto::Shadowsocks { .. } => Tag::Shadowsocks, Proto::Hysteria2 { .. } => Tag::Hysteria2, Proto::Xray(tag) => *tag, @@ -41,6 +43,12 @@ impl Proto { } } + pub fn new_awg(param: &WgParam) -> Self { + Proto::AmneziaWg { + param: param.clone(), + } + } + pub fn new_ss(password: &str) -> Self { Proto::Shadowsocks { password: password.to_string(), @@ -62,6 +70,10 @@ impl Proto { matches!(self, Proto::Wireguard { .. }) } + pub fn is_amneziawg(&self) -> bool { + matches!(self, Proto::AmneziaWg { .. }) + } + pub fn is_shadowsocks(&self) -> bool { matches!(self, Proto::Shadowsocks { .. }) } diff --git a/src/memory/connection/wireguard.rs b/src/memory/connection/wireguard.rs index 89111a54..d9b55f9e 100644 --- a/src/memory/connection/wireguard.rs +++ b/src/memory/connection/wireguard.rs @@ -6,6 +6,11 @@ use std::{ str::FromStr, }; +#[cfg(feature = "amnezia-wg")] +use netlink_packet_amnezia_wireguard::{ + WireguardAddressFamily, WireguardAllowedIp, WireguardAllowedIpAttr, +}; + use rand::rngs::OsRng; use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; use x25519_dalek::{PublicKey, StaticSecret}; @@ -313,3 +318,19 @@ impl Default for IpAddrMask { "10.0.0.0/8".parse().expect("valid default network") } } + +#[cfg(feature = "amnezia-wg")] +impl From for WireguardAllowedIp { + fn from(ip: IpAddrMask) -> Self { + let family = match ip.address { + std::net::IpAddr::V4(_) => WireguardAddressFamily::Ipv4, + std::net::IpAddr::V6(_) => WireguardAddressFamily::Ipv6, + }; + + WireguardAllowedIp(vec![ + WireguardAllowedIpAttr::Family(family), + WireguardAllowedIpAttr::IpAddr(ip.address), + WireguardAllowedIpAttr::Cidr(ip.cidr), + ]) + } +} diff --git a/src/memory/node.rs b/src/memory/node.rs index 35e9de0e..545c4869 100644 --- a/src/memory/node.rs +++ b/src/memory/node.rs @@ -12,14 +12,19 @@ use serde::{Deserialize, Serialize}; use super::env::Env; use super::tag::ProtoTag as Tag; + use crate::config::h2::H2Settings; #[cfg(feature = "xray")] use crate::config::inbound::Settings as XraySettings; +#[cfg(feature = "amnezia-wg")] +use crate::config::amnezia_wg::AmneziaWgSettings; + use crate::config::inbound::Inbound; use crate::config::mtproto::MtprotoSettings; use crate::config::settings::NodeConfig; + #[cfg(feature = "wireguard")] use crate::config::wireguard::WireguardSettings; @@ -153,6 +158,7 @@ impl Node { settings: NodeConfig, #[cfg(feature = "xray")] xray_config: Option, #[cfg(feature = "wireguard")] wg_config: Option, + #[cfg(feature = "amnezia-wg")] awg_config: Option, h2_config: Option, mtproto_config: Option, ) -> Self { @@ -179,6 +185,7 @@ impl Node { port: config.port, tag: Tag::Wireguard, stream_settings: None, + awg: None, wg: wg_config, h2: None, mtproto_secret: None, @@ -186,6 +193,22 @@ impl Node { ); } + #[cfg(feature = "amnezia-wg")] + if let Some(ref config) = awg_config { + inbounds.insert( + Tag::AmneziaWg, + Inbound { + port: config.interface.listen_port, + tag: Tag::AmneziaWg, + stream_settings: None, + awg: awg_config, + wg: None, + h2: None, + mtproto_secret: None, + }, + ); + } + if let Some(ref config) = mtproto_config { inbounds.insert( Tag::Mtproto, @@ -194,6 +217,7 @@ impl Node { tag: Tag::Mtproto, stream_settings: None, wg: None, + awg: None, h2: None, mtproto_secret: Some(config.secret[0].key.clone()), }, @@ -208,6 +232,7 @@ impl Node { tag: Tag::Hysteria2, stream_settings: None, wg: None, + awg: None, h2: h2_config, mtproto_secret: None, }, diff --git a/src/memory/storage/connection.rs b/src/memory/storage/connection.rs index dc3a4611..0919387b 100644 --- a/src/memory/storage/connection.rs +++ b/src/memory/storage/connection.rs @@ -19,6 +19,7 @@ where fn get_by_subscription_id(&self, subscription_id: &uuid::Uuid) -> Option>; fn get_by_proto(&self, proto: Tag) -> Option>; fn get_last_wg_addr(&self) -> Option; + fn get_last_awg_addr(&self) -> Option; fn apply_update(conn: &mut Connection, patch: ConnectionPatch) -> Option; } @@ -193,4 +194,18 @@ where }) .and_then(|(_, conn)| conn.get_wireguard().map(|wg| wg.address.clone())) } + + fn get_last_awg_addr(&self) -> Option { + self.iter() + .filter(|(_, conn)| conn.get_proto().proto() == Tag::AmneziaWg) + .max_by_key(|(_, conn)| { + conn.get_amneziawg() + .and_then(|wg| match wg.address.address { + std::net::IpAddr::V4(ip) => Some(u32::from(ip)), + _ => None, + }) + .unwrap_or(0) + }) + .and_then(|(_, conn)| conn.get_amneziawg().map(|wg| wg.address.clone())) + } } diff --git a/src/memory/tag.rs b/src/memory/tag.rs index 969c56fa..f9018bbf 100644 --- a/src/memory/tag.rs +++ b/src/memory/tag.rs @@ -36,6 +36,8 @@ pub enum ProtoTag { Shadowsocks, #[serde(rename = "Wireguard")] Wireguard, + #[serde(rename = "AmneziaWg")] + AmneziaWg, #[serde(rename = "Hysteria2")] Hysteria2, #[serde(rename = "Mtproto")] @@ -51,6 +53,7 @@ impl fmt::Display for ProtoTag { ProtoTag::Vmess => write!(f, "Vmess"), ProtoTag::Shadowsocks => write!(f, "Shadowsocks"), ProtoTag::Wireguard => write!(f, "Wireguard"), + ProtoTag::AmneziaWg => write!(f, "AmneziaWg"), ProtoTag::Hysteria2 => write!(f, "Hysteria2"), ProtoTag::Mtproto => write!(f, "Mtproto"), } @@ -62,6 +65,10 @@ impl ProtoTag { *self == ProtoTag::Wireguard } + pub fn is_amneziawg(&self) -> bool { + *self == ProtoTag::AmneziaWg + } + pub fn is_shadowsocks(&self) -> bool { *self == ProtoTag::Shadowsocks } @@ -84,6 +91,7 @@ impl std::str::FromStr for ProtoTag { "Vmess" => Ok(ProtoTag::Vmess), "Shadowsocks" => Ok(ProtoTag::Shadowsocks), "Wireguard" => Ok(ProtoTag::Wireguard), + "AmneziaWg" => Ok(ProtoTag::AmneziaWg), "Hysteria2" => Ok(ProtoTag::Hysteria2), "Mtproto" => Ok(ProtoTag::Mtproto), _ => Err(()), diff --git a/src/proto/amnezia_wg.rs b/src/proto/amnezia_wg.rs new file mode 100644 index 00000000..0bb43099 --- /dev/null +++ b/src/proto/amnezia_wg.rs @@ -0,0 +1,371 @@ +use crate::error::{Error, Result as MyResult}; +use base64::{engine::general_purpose, Engine as _}; +use netlink_packet_amnezia_wireguard::{ + AmneziaWg, AmneziaWgAttribute, WireguardCmd, WireguardMessage, WireguardPeer, + WireguardPeerAttribute, +}; +use netlink_packet_core::{ + NetlinkDeserializable, NetlinkMessage, NetlinkPayload, NetlinkSerializable, NLM_F_ACK, + NLM_F_DUMP, NLM_F_REQUEST, +}; +use netlink_packet_generic::{ + ctrl::{nlas::GenlCtrlAttrs, GenlCtrl, GenlCtrlCmd}, + GenlFamily, GenlMessage, +}; +use netlink_sys::{constants::NETLINK_GENERIC, Socket, SocketAddr}; +use std::collections::HashMap; +use std::{fmt::Debug, io::ErrorKind}; +use thiserror::Error as ThisError; +use tracing::{error, trace}; + +type NetlinkResult = Result; + +impl From for Error { + fn from(e: NetlinkError) -> Self { + Error::Custom(e.to_string()) + } +} + +#[derive(Clone)] +pub struct AwgInterface { + family_id: u16, + interface: String, +} +#[derive(Debug, Clone)] +pub struct PeerStats { + pub rx_bytes: u64, + pub tx_bytes: u64, + pub last_handshake: Option, +} + +#[derive(Debug, Clone)] +pub struct AwgDevice { + pub ifname: String, + pub listen_port: Option, + pub peers: Vec, +} + +const WGPEER_F_REMOVE_ME: u32 = 1; +const SOCKET_BUFFER_LENGTH: usize = 12288; + +#[derive(Debug, ThisError)] +pub(crate) enum NetlinkError { + #[error("Unexpected netlink payload")] + UnexpectedPayload, + #[error("Failed to send netlink request")] + SendFailure, + #[error("Attribute value not found")] + AttributeNotFound, + #[error("Socket error: {0}")] + SocketError(String), + #[error("Failed to read response")] + ResponseError(#[from] netlink_packet_core::DecodeError), + #[error("Netlink payload error: {0}")] + PayloadError(netlink_packet_core::ErrorMessage), + #[error("Failed to create WireGuard interface")] + CreateInterfaceError, + #[error("Failed to delete WireGuard interface")] + DeleteInterfaceError, + #[error("File already exists")] + FileAlreadyExists, + #[error("Add route error")] + AddRouteError, + #[error("No such file")] + NotFound, + #[error("Failed to add rule")] + AddRuleError, + #[error("Failed to delete rule")] + DeleteRuleError, +} + +macro_rules! get_nla_value { + ($nlas:expr, $e:ident, $v:ident) => { + $nlas.iter().find_map(|attr| match attr { + $e::$v(value) => Some(value), + _ => None, + }) + }; +} + +impl AwgInterface { + pub fn connect(interface: String) -> MyResult { + let family_id = resolve_family_id("amneziawg")?; + + Ok(Self { + family_id, + interface, + }) + } + + pub fn decode_pubkey(key: &str) -> Result<[u8; 32], Error> { + let bytes = general_purpose::STANDARD + .decode(key.trim()) + .map_err(|e| Error::Custom(format!("bad pubkey base64: {e}")))?; + + if bytes.len() != 32 { + return Err(Error::Custom("invalid pubkey length".into())); + } + + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + Ok(arr) + } + + pub fn get_device(&self) -> MyResult { + let ifname = &self.interface; + let msg = WireguardMessage:: { + cmd: WireguardCmd::GetDevice, + attributes: vec![AmneziaWgAttribute::IfName(ifname.into())], + }; + + let genlmsg = GenlMessage::from_payload(msg); + let responses = netlink_request_genl(genlmsg, NLM_F_REQUEST | NLM_F_DUMP)?; + + let mut device = AwgDevice { + ifname: ifname.to_string(), + listen_port: None, + peers: Vec::new(), + }; + + for response in responses { + let attrs = match &response.payload { + NetlinkPayload::InnerMessage(genl) => &genl.payload.attributes, + _ => continue, + }; + + for attr in attrs { + match attr { + AmneziaWgAttribute::ListenPort(port) => { + device.listen_port = Some(*port); + } + + AmneziaWgAttribute::Peers(peers) => { + device.peers.extend(peers.clone()); + } + + _ => {} + } + } + } + + Ok(device) + } + + pub fn add_peer(&self, peer: WireguardPeer) -> MyResult<()> { + let ifname = &self.interface; + let msg = WireguardMessage:: { + cmd: WireguardCmd::SetDevice, + attributes: vec![ + AmneziaWgAttribute::IfName(ifname.into()), + AmneziaWgAttribute::Peers(vec![peer]), + ], + }; + + let genlmsg = GenlMessage::from_payload(msg); + let responses = netlink_request_genl(genlmsg, NLM_F_REQUEST | NLM_F_ACK)?; + + Ok(()) + } + + pub fn remove_peer(&self, public_key: [u8; 32]) -> MyResult<()> { + let peer = WireguardPeer(vec![ + WireguardPeerAttribute::PublicKey(public_key), + WireguardPeerAttribute::Flags(WGPEER_F_REMOVE_ME), + ]); + + let ifname = &self.interface; + + let msg = WireguardMessage:: { + cmd: WireguardCmd::SetDevice, + attributes: vec![ + AmneziaWgAttribute::IfName(ifname.into()), + AmneziaWgAttribute::Peers(vec![peer]), + ], + }; + + let genlmsg = GenlMessage::from_payload(msg); + let responses = netlink_request_genl(genlmsg, NLM_F_REQUEST | NLM_F_ACK)?; + + Ok(()) + } + + pub fn peer_stats(&self) -> MyResult> { + let ifname = &self.interface; + let device = self.get_device()?; + + let mut stats = HashMap::new(); + + for peer in device.peers { + let mut pubkey = None; + let mut rx = 0; + let mut tx = 0; + let mut hs = None; + + for attr in peer.0 { + match attr { + WireguardPeerAttribute::PublicKey(key) => { + pubkey = Some(key); + } + + WireguardPeerAttribute::RxBytes(v) => { + rx = v; + } + + WireguardPeerAttribute::TxBytes(v) => { + tx = v; + } + + WireguardPeerAttribute::LastHandshake(time) => { + hs = Some((time.seconds * 1_000_000_000 + time.nano_seconds) as u64); + } + + _ => {} + } + } + + if let Some(key) = pubkey { + stats.insert( + key, + PeerStats { + rx_bytes: rx, + tx_bytes: tx, + last_handshake: hs, + }, + ); + } + } + + Ok(stats) + } +} + +fn resolve_family_id(name: &str) -> MyResult { + let genlmsg = GenlMessage::from_payload(GenlCtrl { + cmd: GenlCtrlCmd::GetFamily, + nlas: vec![GenlCtrlAttrs::FamilyName(name.to_string())], + }); + + let responses = netlink_request_genl(genlmsg, NLM_F_REQUEST | NLM_F_ACK)?; + + match responses.first() { + Some(NetlinkMessage { + payload: + NetlinkPayload::InnerMessage(GenlMessage { + payload: GenlCtrl { nlas, .. }, + .. + }), + .. + }) => { + let family_id = get_nla_value!(nlas, GenlCtrlAttrs, FamilyId) + .ok_or_else(|| Error::Custom("family id not found".to_string()))?; + + Ok(*family_id) + } + + _ => Err(Error::Custom("unexpected payload".to_string())), + } +} + +fn netlink_request_genl( + mut message: GenlMessage, + flags: u16, +) -> NetlinkResult>>> +where + F: GenlFamily + Clone + Debug + Eq, + GenlMessage: Clone + Debug + Eq + NetlinkSerializable + NetlinkDeserializable, +{ + if message.family_id() == 0 { + let genlmsg: GenlMessage = GenlMessage::from_payload(GenlCtrl { + cmd: GenlCtrlCmd::GetFamily, + nlas: vec![GenlCtrlAttrs::FamilyName(F::family_name().to_string())], + }); + let responses = netlink_request_genl::(genlmsg, NLM_F_REQUEST | NLM_F_ACK)?; + + match responses.first() { + Some(NetlinkMessage { + payload: + NetlinkPayload::InnerMessage(GenlMessage { + payload: GenlCtrl { nlas, .. }, + .. + }), + .. + }) => { + let family_id = get_nla_value!(nlas, GenlCtrlAttrs, FamilyId) + .ok_or_else(|| NetlinkError::AttributeNotFound)?; + message.set_resolved_family_id(*family_id); + } + _ => return Err(NetlinkError::UnexpectedPayload), + } + } + netlink_request(message, flags, NETLINK_GENERIC) +} + +fn netlink_request( + message: I, + flags: u16, + protocol: isize, +) -> NetlinkResult>> +where + NetlinkPayload: From, + I: Clone + Debug + Eq + NetlinkSerializable + NetlinkDeserializable, +{ + let mut req = NetlinkMessage::from(message); + + req.header.flags = flags; + req.finalize(); + let len = req.buffer_len(); + let mut buf = vec![0u8; len]; + req.serialize(&mut buf); + + let socket = Socket::new(protocol).map_err(|err| { + error!("Failed to open socket: {err}"); + NetlinkError::SocketError(err.to_string()) + })?; + let kernel_addr = SocketAddr::new(0, 0); + socket.connect(&kernel_addr).map_err(|err| { + error!("Failed to connect to socket: {err}"); + NetlinkError::SocketError(err.to_string()) + })?; + let n_sent = socket.send(&buf, 0).map_err(|err| { + error!("Failed to send to socket: {err}"); + NetlinkError::SocketError(err.to_string()) + })?; + if n_sent != len { + return Err(NetlinkError::SendFailure); + } + + let mut responses = Vec::new(); + loop { + let mut recv_buf = [0; SOCKET_BUFFER_LENGTH]; + let n_received = socket.recv(&mut &mut recv_buf[..], 0).map_err(|err| { + error!("Failed to receive from socket: {err}"); + NetlinkError::SocketError(err.to_string()) + })?; + let mut offset = 0; + loop { + let response = NetlinkMessage::::deserialize(&recv_buf[offset..])?; + trace!("Read netlink response from socket: {response:?}"); + match response.payload { + // We've parsed all parts of the response and can leave the loop. + NetlinkPayload::Error(msg) if msg.code.is_none() => return Ok(responses), + NetlinkPayload::Done(_) => return Ok(responses), + NetlinkPayload::Error(msg) => { + return match msg.to_io().kind() { + ErrorKind::AlreadyExists => Err(NetlinkError::FileAlreadyExists), + ErrorKind::NotFound => Err(NetlinkError::NotFound), + _ => Err(NetlinkError::PayloadError(msg)), + }; + } + _ => {} + } + let header_length = response.header.length as usize; + offset += header_length; + responses.push(response); + if offset == n_received || header_length == 0 { + // We've fully parsed the datagram, but there may be further datagrams + // with additional netlink response parts. + break; + } + } + } +} diff --git a/src/proto/mod.rs b/src/proto/mod.rs index a0833d16..55cc6a88 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -2,3 +2,6 @@ pub(crate) mod wireguard; #[cfg(feature = "xray")] pub mod xray; + +#[cfg(feature = "amnezia-wg")] +pub(crate) mod amnezia_wg; diff --git a/src/proto/wireguard.rs b/src/proto/wireguard.rs index dec28661..7ee58470 100644 --- a/src/proto/wireguard.rs +++ b/src/proto/wireguard.rs @@ -5,8 +5,8 @@ use std::net::IpAddr; use std::net::Ipv4Addr; use std::sync::Arc; -use defguard_wireguard_rs::host::Peer; use defguard_wireguard_rs::key::Key; +use defguard_wireguard_rs::peer::Peer; use defguard_wireguard_rs::WGApi; use defguard_wireguard_rs::WireguardInterfaceApi; @@ -66,7 +66,7 @@ impl WgApi { pub fn create(&self, pubkey: &str, ip: IpAddrMask) -> Result<()> { let ip = defguard_wireguard_rs::net::IpAddrMask { - ip: ip.address, + address: ip.address, cidr: ip.cidr, }; let key = Self::decode_pubkey(pubkey)?; @@ -85,7 +85,7 @@ impl WgApi { .flat_map(|peer| &peer.allowed_ips) .filter_map(|ip_mask| match ip_mask { defguard_wireguard_rs::net::IpAddrMask { - ip: IpAddr::V4(addr), + address: IpAddr::V4(addr), cidr: 32, } => Some(*addr), _ => None, @@ -128,7 +128,7 @@ impl WgApi { .flat_map(|peer| &peer.allowed_ips) .filter_map(|ip_mask| match ip_mask { defguard_wireguard_rs::net::IpAddrMask { - ip: IpAddr::V4(addr), + address: IpAddr::V4(addr), cidr: 32, } => Some(u32::from_be_bytes(addr.octets())), _ => None,