refactor!: use rtnetlink for client interfaces, add deregistration
This commit is contained in:
345
Cargo.lock
generated
345
Cargo.lock
generated
@@ -8,7 +8,7 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
@@ -29,8 +29,8 @@ dependencies = [
|
|||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
"bytestring",
|
"bytestring",
|
||||||
@@ -229,6 +229,12 @@ dependencies = [
|
|||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.101"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
@@ -287,12 +293,24 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
@@ -400,13 +418,17 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||||||
name = "client"
|
name = "client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
"console",
|
"console",
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"ipnetwork",
|
"ipnetwork",
|
||||||
|
"libc",
|
||||||
|
"netlink-packet-route 0.19.0",
|
||||||
"registry",
|
"registry",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rtnetlink",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"stunclient",
|
"stunclient",
|
||||||
@@ -418,6 +440,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
|
"wireguard-control",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -545,6 +568,32 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek"
|
||||||
|
version = "4.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"curve25519-dalek-derive",
|
||||||
|
"fiat-crypto",
|
||||||
|
"rustc_version",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek-derive"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
@@ -675,6 +724,12 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fiat-crypto"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -888,6 +943,12 @@ version = "0.16.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -997,7 +1058,7 @@ version = "0.1.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -1231,7 +1292,7 @@ version = "0.1.12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1300,6 +1361,24 @@ version = "2.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
@@ -1334,6 +1413,164 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577"
|
checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-packet-core"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"byteorder",
|
||||||
|
"netlink-packet-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-packet-generic"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"byteorder",
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-packet-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-packet-route"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"byteorder",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-packet-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-packet-route"
|
||||||
|
version = "0.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "483325d4bfef65699214858f097d504eb812c38ce7077d165f301ec406c3066e"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"byteorder",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-packet-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-packet-utils"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"byteorder",
|
||||||
|
"paste",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-packet-wireguard"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60b25b050ff1f6a1e23c6777b72db22790fe5b6b5ccfd3858672587a79876c8f"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"byteorder",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-generic",
|
||||||
|
"netlink-packet-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-proto"
|
||||||
|
version = "0.11.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-sys",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-request"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d83636acdf5aba609ff27a66f6a235f1bcfafe20ccdaad6a5bf9d7dfa64a811"
|
||||||
|
dependencies = [
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-packet-generic",
|
||||||
|
"netlink-packet-route 0.21.0",
|
||||||
|
"netlink-packet-utils",
|
||||||
|
"netlink-sys",
|
||||||
|
"nix 0.25.1",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-sys"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"memoffset 0.6.5",
|
||||||
|
"pin-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.30.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
"memoffset 0.9.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
@@ -1418,6 +1655,12 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@@ -1573,7 +1816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_core",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1583,7 +1826,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.9.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1628,7 +1880,7 @@ version = "0.5.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1683,7 +1935,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-ws",
|
"actix-ws",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"console",
|
"console",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -1698,6 +1950,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-actix-web",
|
"tracing-actix-web",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"wireguard-control",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1706,7 +1959,7 @@ version = "0.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
|
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -1754,6 +2007,24 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtnetlink"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-packet-route 0.19.0",
|
||||||
|
"netlink-packet-utils",
|
||||||
|
"netlink-proto",
|
||||||
|
"netlink-sys",
|
||||||
|
"nix 0.27.1",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@@ -1887,7 +2158,7 @@ version = "3.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -2141,7 +2412,7 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.9.4",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys",
|
||||||
]
|
]
|
||||||
@@ -2410,7 +2681,7 @@ version = "0.6.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.4.0",
|
"http 1.4.0",
|
||||||
@@ -3030,6 +3301,28 @@ version = "0.7.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wireguard-control"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a1ff6cde8cce93098564c1020e59da217c87e37b9f1bac3f539a6261a9af128"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"hex",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-packet-generic",
|
||||||
|
"netlink-packet-route 0.21.0",
|
||||||
|
"netlink-packet-utils",
|
||||||
|
"netlink-packet-wireguard",
|
||||||
|
"netlink-request",
|
||||||
|
"netlink-sys",
|
||||||
|
"nix 0.30.1",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"x25519-dalek",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
@@ -3042,6 +3335,18 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x25519-dalek"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
||||||
|
dependencies = [
|
||||||
|
"curve25519-dalek",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"serde",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xxhash-rust"
|
name = "xxhash-rust"
|
||||||
version = "0.8.15"
|
version = "0.8.15"
|
||||||
@@ -3117,6 +3422,20 @@ name = "zeroize"
|
|||||||
version = "1.8.2"
|
version = "1.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize_derive"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ serde = { version = "1.0.228", features = ["derive"] }
|
|||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
thiserror-ext = "0.3.0"
|
thiserror-ext = "0.3.0"
|
||||||
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.49.0", features = ["macros", "net", "rt-multi-thread", "signal"] }
|
||||||
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
|
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||||
@@ -22,3 +22,12 @@ url = "2.5.8"
|
|||||||
reqwest = { version = "0.13.2", features = ["json"] }
|
reqwest = { version = "0.13.2", features = ["json"] }
|
||||||
stunclient = "0.4.2"
|
stunclient = "0.4.2"
|
||||||
ipnetwork = { version = "0.21.1", features = ["serde"] }
|
ipnetwork = { version = "0.21.1", features = ["serde"] }
|
||||||
|
base64 = "0.22.1"
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
|
# WireGuard netlink control
|
||||||
|
wireguard-control = "1.1"
|
||||||
|
|
||||||
|
# Network interface management
|
||||||
|
rtnetlink = "0.14"
|
||||||
|
netlink-packet-route = "0.19"
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
use std::{ops::Deref, sync::Arc};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AppState {
|
|
||||||
pub reqwest_client: reqwest::Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Data<T>(Arc<T>);
|
|
||||||
|
|
||||||
impl<T> Deref for Data<T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Data<T> {
|
|
||||||
pub fn new(inner: T) -> Self {
|
|
||||||
Self(Arc::new(inner))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,19 +4,33 @@ use crate::error::{Error, Result};
|
|||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub const DEFAULT_KEEPALIVE: u16 = 25;
|
||||||
|
pub const INTERFACE_NAME: &str = "mesh0";
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub interface: InterfaceConfig,
|
pub interface: InterfaceConfig,
|
||||||
pub server: ServerConfig,
|
pub server: ServerConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct InterfaceConfig {
|
pub struct InterfaceConfig {
|
||||||
pub private_key: String,
|
pub private_key: String,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
|
#[serde(default = "default_listen_port")]
|
||||||
pub listen_port: u16,
|
pub listen_port: u16,
|
||||||
pub address: String,
|
#[serde(default = "default_keepalive")]
|
||||||
pub allowed_ips: Vec<IpNetwork>,
|
pub persistent_keepalive: u16,
|
||||||
|
pub allowed_ips: Option<Vec<IpNetwork>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_listen_port() -> u16 {
|
||||||
|
51820
|
||||||
|
}
|
||||||
|
fn default_keepalive() -> u16 {
|
||||||
|
DEFAULT_KEEPALIVE
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
pub ws_url: String,
|
pub ws_url: String,
|
||||||
@@ -36,7 +50,3 @@ pub fn base_path() -> PathBuf {
|
|||||||
.unwrap_or_else(|| PathBuf::from("."))
|
.unwrap_or_else(|| PathBuf::from("."))
|
||||||
.join("wg-mesh")
|
.join("wg-mesh")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wg_config_path() -> PathBuf {
|
|
||||||
PathBuf::from("/etc/wireguard/mesh0.conf")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,12 +29,6 @@ pub enum ErrorKind {
|
|||||||
Url(#[from] url::ParseError),
|
Url(#[from] url::ParseError),
|
||||||
#[error("invalid url scheme: {url}")]
|
#[error("invalid url scheme: {url}")]
|
||||||
UrlScheme { url: String },
|
UrlScheme { url: String },
|
||||||
#[error("error writing wireguard config to {path}")]
|
|
||||||
WriteConfig {
|
|
||||||
path: PathBuf,
|
|
||||||
#[source]
|
|
||||||
source: std::io::Error,
|
|
||||||
},
|
|
||||||
#[error("STUN discovery failed: {message}")]
|
#[error("STUN discovery failed: {message}")]
|
||||||
StunDiscovery { message: String },
|
StunDiscovery { message: String },
|
||||||
#[error("HTTP IP discovery failed")]
|
#[error("HTTP IP discovery failed")]
|
||||||
@@ -45,6 +39,53 @@ pub enum ErrorKind {
|
|||||||
DiscoveryFailed,
|
DiscoveryFailed,
|
||||||
#[error("error with request")]
|
#[error("error with request")]
|
||||||
Reqwest(#[from] reqwest::Error),
|
Reqwest(#[from] reqwest::Error),
|
||||||
|
#[error("invalid base64 key: {context}")]
|
||||||
|
InvalidKey { context: String },
|
||||||
|
#[error("error creating WireGuard interface {interface}")]
|
||||||
|
CreateInterface {
|
||||||
|
interface: String,
|
||||||
|
#[source]
|
||||||
|
source: std::io::Error,
|
||||||
|
},
|
||||||
|
#[error("error configuring WireGuard device {interface}")]
|
||||||
|
ConfigureDevice {
|
||||||
|
interface: String,
|
||||||
|
#[source]
|
||||||
|
source: std::io::Error,
|
||||||
|
},
|
||||||
|
#[error("error with netlink operation: {context}")]
|
||||||
|
Netlink { context: String },
|
||||||
|
#[error("error setting interface address")]
|
||||||
|
SetAddress {
|
||||||
|
#[source]
|
||||||
|
source: rtnetlink::Error,
|
||||||
|
},
|
||||||
|
#[error("error bringing interface up")]
|
||||||
|
SetLinkUp {
|
||||||
|
#[source]
|
||||||
|
source: rtnetlink::Error,
|
||||||
|
},
|
||||||
|
#[error("error getting interface index for {interface}")]
|
||||||
|
GetInterface { interface: String },
|
||||||
|
#[error("registration failed")]
|
||||||
|
Registration {
|
||||||
|
#[source]
|
||||||
|
source: reqwest::Error,
|
||||||
|
},
|
||||||
|
#[error("error deregistering device {public_key}")]
|
||||||
|
Deregister {
|
||||||
|
public_key: String,
|
||||||
|
#[source]
|
||||||
|
source: reqwest::Error,
|
||||||
|
},
|
||||||
|
#[error("registration failed with status {status}: {body}")]
|
||||||
|
RegistrationStatus { status: u16, body: String },
|
||||||
|
#[error("error deleting interface")]
|
||||||
|
DeleteInterface {
|
||||||
|
name: String,
|
||||||
|
#[source]
|
||||||
|
source: rtnetlink::Error,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
mod app_state;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod discovery;
|
mod discovery;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod netlink;
|
||||||
mod wireguard;
|
mod wireguard;
|
||||||
|
|
||||||
use std::{fs::OpenOptions, io::Write, net::IpAddr, os::unix::fs::OpenOptionsExt};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
|
||||||
use console::style;
|
use console::style;
|
||||||
use futures::StreamExt;
|
use futures::{StreamExt, TryFutureExt, stream::SplitStream};
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use registry::{Peer, PeerMessage};
|
use registry::{Peer, PeerMessage, RegisterResponse};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror_ext::AsReport;
|
use thiserror_ext::AsReport;
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio::{
|
||||||
|
net::TcpStream,
|
||||||
|
signal::{self, unix::SignalKind},
|
||||||
|
};
|
||||||
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream, tungstenite::Message};
|
||||||
use tracing::level_filters::LevelFilter;
|
use tracing::level_filters::LevelFilter;
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt,
|
EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt,
|
||||||
};
|
};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_state::{AppState, Data},
|
config::{Config, INTERFACE_NAME},
|
||||||
config::{Config, InterfaceConfig, wg_config_path},
|
|
||||||
discovery::{PublicEndpoint, discover_public_endpoint},
|
discovery::{PublicEndpoint, discover_public_endpoint},
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
};
|
};
|
||||||
@@ -34,105 +36,209 @@ pub struct RegisterRequest {
|
|||||||
pub allowed_ips: Vec<IpNetwork>,
|
pub allowed_ips: Vec<IpNetwork>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ws_url(input: &Url) -> Result<String> {
|
|
||||||
let url = input.join("/ws/peers")?;
|
|
||||||
if url.scheme() != "ws" && url.scheme() != "wss" {
|
|
||||||
return Err(Error::url_scheme(url.to_string()));
|
|
||||||
}
|
|
||||||
Ok(url.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_wg_config(interface: &InterfaceConfig, peers: &[Peer]) -> Result<()> {
|
|
||||||
let path = wg_config_path();
|
|
||||||
let config = wireguard::generate_config(interface, peers);
|
|
||||||
let mut file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.mode(0o600)
|
|
||||||
.open(&path)
|
|
||||||
.map_err(|e| Error::write_config(e, &path))?;
|
|
||||||
file.write_all(config.as_bytes())
|
|
||||||
.map_err(|e| Error::write_config(e, &path))?;
|
|
||||||
tracing::info!("wrote {} with {} peers", path.display(), peers.len());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn register_self(
|
async fn register_self(
|
||||||
app_state: Data<AppState>,
|
client: &reqwest::Client,
|
||||||
endpoint: &PublicEndpoint,
|
endpoint: &PublicEndpoint,
|
||||||
config: &InterfaceConfig,
|
config: &Config,
|
||||||
url: &str,
|
) -> Result<Ipv4Addr> {
|
||||||
) -> Result<()> {
|
let url = format!("{}/register", &config.server.url);
|
||||||
app_state
|
|
||||||
.reqwest_client
|
tracing::info!(
|
||||||
.post(url)
|
public_ip = %endpoint.ip,
|
||||||
|
port = endpoint.port,
|
||||||
|
"registering with registry"
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(&url)
|
||||||
.json(&RegisterRequest {
|
.json(&RegisterRequest {
|
||||||
public_key: config.public_key.clone(),
|
public_key: config.interface.public_key.clone(),
|
||||||
public_ip: endpoint.ip,
|
public_ip: endpoint.ip,
|
||||||
port: endpoint.port.to_string(),
|
port: endpoint.port.to_string(),
|
||||||
allowed_ips: config.allowed_ips.clone(),
|
allowed_ips: config.interface.allowed_ips.clone().unwrap_or(vec![]),
|
||||||
})
|
})
|
||||||
.send()
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(Error::registration)?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status().as_u16();
|
||||||
|
let body = response.text().await.unwrap_or_default();
|
||||||
|
return Err(Error::registration_status(status, body));
|
||||||
|
}
|
||||||
|
|
||||||
|
let register_response: RegisterResponse = response.json().await.map_err(Error::registration)?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
mesh_ip = %register_response.mesh_ip,
|
||||||
|
"registration successful, assigned mesh IP"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(register_response.mesh_ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn deregister_self(client: &reqwest::Client, config: &Config) -> Result<()> {
|
||||||
|
let url = format!(
|
||||||
|
"{}/deregister?public_key={}",
|
||||||
|
config.server.url, config.interface.public_key
|
||||||
|
);
|
||||||
|
client
|
||||||
|
.delete(&url)
|
||||||
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.error_for_status()?;
|
.error_for_status()
|
||||||
|
.map_err(|e| Error::deregister(e, &config.interface.public_key))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run() -> crate::error::Result<()> {
|
fn configure_peer(peer: &Peer, local_public_key: &str, keepalive: u16) -> Result<()> {
|
||||||
let config = Config::load()?;
|
if peer.public_key == local_public_key {
|
||||||
let ws_url = &config.server.ws_url;
|
tracing::debug!(peer_key = %peer.public_key, "skipping self");
|
||||||
let (ws_stream, response) = tokio_tungstenite::connect_async(ws_url)
|
return Ok(());
|
||||||
.await
|
}
|
||||||
.map_err(|e| Error::ws_connect(e, ws_url))?;
|
|
||||||
let (_, mut read) = ws_stream.split();
|
|
||||||
let app_state = Data::new(AppState {
|
|
||||||
reqwest_client: reqwest::Client::new(),
|
|
||||||
});
|
|
||||||
tracing::info!("connected, response: {:?}", response.status());
|
|
||||||
let endpoint = discover_public_endpoint(config.interface.listen_port).await?;
|
|
||||||
register_self(
|
|
||||||
app_state.clone(),
|
|
||||||
&endpoint,
|
|
||||||
&config.interface,
|
|
||||||
&format!("{}/register", &config.server.url),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut peers: Vec<Peer> = Vec::new();
|
let peer_key = wireguard::parse_key(&peer.public_key)?;
|
||||||
|
let endpoint: SocketAddr = format!("{}:{}", peer.public_ip, peer.port)
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::netlink(format!(
|
||||||
|
"invalid endpoint: {}:{}",
|
||||||
|
peer.public_ip, peer.port
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut allowed_ips: Vec<IpNetwork> =
|
||||||
|
vec![IpNetwork::new(std::net::IpAddr::V4(peer.mesh_ip), 32).expect("valid network")];
|
||||||
|
allowed_ips.extend(peer.allowed_ips.iter().cloned());
|
||||||
|
|
||||||
|
wireguard::configure_peer(&peer_key, Some(endpoint), &allowed_ips, Some(keepalive))?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
peer_key = %peer.public_key,
|
||||||
|
mesh_ip = %peer.mesh_ip,
|
||||||
|
endpoint = %endpoint,
|
||||||
|
"configured peer"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_all_peers(peers: &[Peer], local_public_key: &str, keepalive: u16) -> Result<()> {
|
||||||
|
for peer in peers {
|
||||||
|
if let Err(e) = configure_peer(peer, local_public_key, keepalive) {
|
||||||
|
tracing::warn!(peer_key = %peer.public_key, error = %e, "failed to configure peer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn events(
|
||||||
|
read: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<()> {
|
||||||
while let Some(msg) = read.next().await {
|
while let Some(msg) = read.next().await {
|
||||||
match msg.map_err(|e| Error::ws_read(e))? {
|
match msg.map_err(Error::ws_read)? {
|
||||||
Message::Text(text) => {
|
Message::Text(text) => {
|
||||||
let server_msg: PeerMessage =
|
let server_msg: PeerMessage =
|
||||||
serde_json::from_str(&text).map_err(|e| Error::deserialize_json(e))?;
|
serde_json::from_str(&text).map_err(Error::deserialize_json)?;
|
||||||
|
|
||||||
match server_msg {
|
match server_msg {
|
||||||
PeerMessage::HydratePeers { peers: new_peers } => {
|
PeerMessage::HydratePeers { peers } => {
|
||||||
tracing::info!("received {} peers", new_peers.len());
|
tracing::info!(count = peers.len(), "Received initial peer list");
|
||||||
peers = new_peers;
|
configure_all_peers(
|
||||||
write_wg_config(&config.interface, &peers)?;
|
&peers,
|
||||||
|
&config.interface.public_key,
|
||||||
|
config.interface.persistent_keepalive,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
PeerMessage::PeerUpdate { peer } => {
|
PeerMessage::PeerUpdate { peer } => {
|
||||||
tracing::info!("peer update: {}", peer.public_key);
|
tracing::info!(
|
||||||
if let Some(existing) =
|
peer_key = %peer.public_key,
|
||||||
peers.iter_mut().find(|p| p.public_key == peer.public_key)
|
mesh_ip = %peer.mesh_ip,
|
||||||
{
|
"Received peer update"
|
||||||
*existing = peer;
|
);
|
||||||
} else {
|
configure_peer(
|
||||||
peers.push(peer);
|
&peer,
|
||||||
}
|
&config.interface.public_key,
|
||||||
write_wg_config(&config.interface, &peers)?;
|
config.interface.persistent_keepalive,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::Ping(_) => {}
|
||||||
|
Message::Pong(_) => {}
|
||||||
|
Message::Close(_) => {
|
||||||
|
tracing::warn!("connection closed by server");
|
||||||
|
break;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn run() -> Result<()> {
|
||||||
|
let config = Config::load()?;
|
||||||
|
let private_key = wireguard::parse_key(&config.interface.private_key)?;
|
||||||
|
let endpoint = discover_public_endpoint(config.interface.listen_port).await?;
|
||||||
|
tracing::info!(
|
||||||
|
public_ip = %endpoint.ip,
|
||||||
|
public_port = endpoint.port,
|
||||||
|
"public endpoint"
|
||||||
|
);
|
||||||
|
let http_client = reqwest::Client::new();
|
||||||
|
let mesh_ip = register_self(&http_client, &endpoint, &config).await?;
|
||||||
|
wireguard::create_interface()?;
|
||||||
|
wireguard::configure_interface(&private_key, config.interface.listen_port)?;
|
||||||
|
|
||||||
|
let (nl_handle, _nl_task) = netlink::connect().await?;
|
||||||
|
netlink::add_address(&nl_handle, mesh_ip, 32).await?;
|
||||||
|
netlink::set_link_up(&nl_handle).await?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
interface = INTERFACE_NAME,
|
||||||
|
mesh_ip = %mesh_ip,
|
||||||
|
"interface configured and up"
|
||||||
|
);
|
||||||
|
|
||||||
|
let ws_url = &config.server.ws_url;
|
||||||
|
|
||||||
|
let (ws_stream, response) = tokio_tungstenite::connect_async(ws_url)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::ws_connect(e, ws_url))?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
status = ?response.status(),
|
||||||
|
"connected to registry WebSocket"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, mut read) = ws_stream.split();
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
receiver = events(&mut read, &config) => receiver?,
|
||||||
|
_ = signal::ctrl_c() => {
|
||||||
|
},
|
||||||
|
_ = on_shutdown() => {}
|
||||||
|
};
|
||||||
|
tracing::debug!("gracefully shutting down");
|
||||||
|
netlink::delete_interface(&nl_handle, INTERFACE_NAME).await?;
|
||||||
|
deregister_self(&http_client, &config).await?;
|
||||||
|
tracing::info!("connection closed");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_shutdown() {
|
||||||
|
use tokio::signal::unix::signal;
|
||||||
|
let mut sigterm = signal(SignalKind::terminate()).expect("failed to register SIGTERM");
|
||||||
|
let mut sigint = signal(SignalKind::interrupt()).expect("failed to register SIGINT");
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = sigterm.recv() => tracing::info!("received sigterm"),
|
||||||
|
_ = sigint.recv() => tracing::info!("received sigint")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let tracing_env_filter = EnvFilter::builder()
|
let tracing_env_filter = EnvFilter::builder()
|
||||||
|
|||||||
84
client/src/netlink.rs
Normal file
84
client/src/netlink.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
use rtnetlink::Handle;
|
||||||
|
|
||||||
|
use crate::config::INTERFACE_NAME;
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
|
async fn get_interface_index(handle: &Handle, name: &str) -> Result<u32> {
|
||||||
|
let mut links = handle.link().get().match_name(name.to_string()).execute();
|
||||||
|
|
||||||
|
if let Some(link) = links.try_next().await.map_err(Error::set_link_up)? {
|
||||||
|
Ok(link.header.index)
|
||||||
|
} else {
|
||||||
|
Err(Error::get_interface(name.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_address(handle: &Handle, addr: Ipv4Addr, prefix_len: u8) -> Result<()> {
|
||||||
|
let index = get_interface_index(handle, INTERFACE_NAME).await?;
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
interface = INTERFACE_NAME,
|
||||||
|
address = %addr,
|
||||||
|
prefix_len,
|
||||||
|
"adding address to interface"
|
||||||
|
);
|
||||||
|
|
||||||
|
match handle
|
||||||
|
.address()
|
||||||
|
.add(index, std::net::IpAddr::V4(addr), prefix_len)
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(rtnetlink::Error::NetlinkError(e)) if e.raw_code() == -libc::EEXIST => {
|
||||||
|
tracing::debug!(
|
||||||
|
interface = INTERFACE_NAME,
|
||||||
|
address = %addr,
|
||||||
|
"address already exists on interface"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => Err(Error::set_address(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_link_up(handle: &Handle) -> Result<()> {
|
||||||
|
let index = get_interface_index(handle, INTERFACE_NAME).await?;
|
||||||
|
|
||||||
|
tracing::info!(interface = INTERFACE_NAME, "set interface up");
|
||||||
|
|
||||||
|
handle
|
||||||
|
.link()
|
||||||
|
.set(index)
|
||||||
|
.up()
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.map_err(Error::set_link_up)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_interface(handle: &Handle, name: &str) -> Result<()> {
|
||||||
|
let index = get_interface_index(&handle, name).await?;
|
||||||
|
|
||||||
|
handle
|
||||||
|
.link()
|
||||||
|
.del(index)
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::delete_interface(e, name))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect() -> Result<(Handle, tokio::task::JoinHandle<()>)> {
|
||||||
|
let (connection, handle, _) = rtnetlink::new_connection()
|
||||||
|
.map_err(|e| Error::netlink(format!("failed to create netlink connection: {}", e)))?;
|
||||||
|
|
||||||
|
let join_handle = tokio::spawn(connection);
|
||||||
|
|
||||||
|
Ok((handle, join_handle))
|
||||||
|
}
|
||||||
@@ -1,24 +1,117 @@
|
|||||||
use registry::Peer;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use crate::config::InterfaceConfig;
|
use ipnetwork::IpNetwork;
|
||||||
|
use wireguard_control::{
|
||||||
|
AllowedIp, Backend, Device, DeviceUpdate, InterfaceName, InvalidKey, Key, PeerConfigBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn generate_config(interface: &InterfaceConfig, peers: &[Peer]) -> String {
|
use crate::config::INTERFACE_NAME;
|
||||||
let mut config = format!(
|
use crate::error::{Error, Result};
|
||||||
"[Interface]\nPrivateKey = {}\nListenPort = {}\nAddress = {}\n",
|
|
||||||
interface.private_key, interface.listen_port, interface.address,
|
pub fn parse_key(key_b64: &str) -> Result<Key> {
|
||||||
|
Key::from_base64(key_b64).map_err(|e: InvalidKey| Error::invalid_key(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interface_name() -> InterfaceName {
|
||||||
|
INTERFACE_NAME.parse().expect("valid interface name")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_interface() -> Result<()> {
|
||||||
|
let name = interface_name();
|
||||||
|
|
||||||
|
if Device::get(&name, Backend::Kernel).is_ok() {
|
||||||
|
tracing::debug!(interface = INTERFACE_NAME, "interface already exists");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(interface = INTERFACE_NAME, "creating WireGuard interface");
|
||||||
|
|
||||||
|
DeviceUpdate::new()
|
||||||
|
.apply(&name, Backend::Kernel)
|
||||||
|
.map_err(|e| Error::create_interface(e, INTERFACE_NAME.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_interface(private_key: &Key, listen_port: u16) -> Result<()> {
|
||||||
|
let name = interface_name();
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
interface = INTERFACE_NAME,
|
||||||
|
listen_port,
|
||||||
|
"configuring WireGuard interface"
|
||||||
);
|
);
|
||||||
for peer in peers {
|
|
||||||
config.push_str(&format!(
|
DeviceUpdate::new()
|
||||||
"\n[Peer]\nPublicKey = {}\nEndpoint = {}:{}\nAllowedIPs = {}\n",
|
.set_private_key(private_key.clone())
|
||||||
peer.public_key,
|
.set_listen_port(listen_port)
|
||||||
peer.public_ip,
|
.apply(&name, Backend::Kernel)
|
||||||
peer.port,
|
.map_err(|e| Error::configure_device(e, INTERFACE_NAME.to_string()))?;
|
||||||
peer.allowed_ips
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_peer(
|
||||||
|
public_key: &Key,
|
||||||
|
endpoint: Option<SocketAddr>,
|
||||||
|
allowed_ips: &[IpNetwork],
|
||||||
|
persistent_keepalive: Option<u16>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let name = interface_name();
|
||||||
|
|
||||||
|
let allowed: Vec<AllowedIp> = allowed_ips
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ip| ip.to_string())
|
.map(|ip| AllowedIp {
|
||||||
.collect::<Vec<_>>()
|
address: ip.ip(),
|
||||||
.join(", "),
|
cidr: ip.prefix(),
|
||||||
));
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut peer = PeerConfigBuilder::new(public_key)
|
||||||
|
.replace_allowed_ips()
|
||||||
|
.add_allowed_ips(&allowed);
|
||||||
|
if let Some(ep) = endpoint {
|
||||||
|
peer = peer.set_endpoint(ep);
|
||||||
}
|
}
|
||||||
config
|
if let Some(keepalive) = persistent_keepalive {
|
||||||
|
peer = peer.set_persistent_keepalive_interval(keepalive);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
peer_key = %public_key.to_base64(),
|
||||||
|
endpoint = ?endpoint,
|
||||||
|
allowed_ips = ?allowed_ips,
|
||||||
|
"configuring peer"
|
||||||
|
);
|
||||||
|
|
||||||
|
DeviceUpdate::new()
|
||||||
|
.add_peer(peer)
|
||||||
|
.apply(&name, Backend::Kernel)
|
||||||
|
.map_err(|e| Error::configure_device(e, INTERFACE_NAME.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn remove_peer(public_key: &Key) -> Result<()> {
|
||||||
|
let name = interface_name();
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
peer_key = %public_key.to_base64(),
|
||||||
|
"removing peer"
|
||||||
|
);
|
||||||
|
|
||||||
|
DeviceUpdate::new()
|
||||||
|
.remove_peer_by_key(public_key)
|
||||||
|
.apply(&name, Backend::Kernel)
|
||||||
|
.map_err(|e| Error::configure_device(e, INTERFACE_NAME.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn get_device() -> Result<Device> {
|
||||||
|
let name = interface_name();
|
||||||
|
Device::get(&name, Backend::Kernel)
|
||||||
|
.map_err(|e| Error::configure_device(e, INTERFACE_NAME.to_string()))
|
||||||
}
|
}
|
||||||
|
|||||||
5
justfile
5
justfile
@@ -1,2 +1,7 @@
|
|||||||
dev bin="registry":
|
dev bin="registry":
|
||||||
bacon dev -- --bin {{bin}}
|
bacon dev -- --bin {{bin}}
|
||||||
|
|
||||||
|
client target="debug":
|
||||||
|
cargo build -p client
|
||||||
|
sudo setcap cap_net_admin+ep ./target/{{target}}/client
|
||||||
|
./target/{{target}}/client
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ tracing-actix-web = "0.7.21"
|
|||||||
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread", "sync"] }
|
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread", "sync"] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
thiserror-ext = "0.3.0"
|
thiserror-ext = "0.3.0"
|
||||||
|
wireguard-control = "1.7.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "registry"
|
name = "registry"
|
||||||
|
|||||||
27
registry/src/endpoints/deregister.rs
Normal file
27
registry/src/endpoints/deregister.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use actix_web::{HttpResponse, web};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use wireguard_control::Key;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
error::{Error, Result},
|
||||||
|
storage::StorageImpl,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DeregisterRequest {
|
||||||
|
public_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn deregister(
|
||||||
|
app_state: web::Data<AppState>,
|
||||||
|
query: web::Query<DeregisterRequest>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
Key::from_base64(&query.public_key).map_err(|_| Error::invalid_key(&query.public_key))?;
|
||||||
|
|
||||||
|
app_state
|
||||||
|
.storage
|
||||||
|
.deregister_device(&query.public_key)
|
||||||
|
.await?;
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod deregister;
|
||||||
pub mod peers;
|
pub mod peers;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ use crate::{
|
|||||||
storage::{RegisterRequest, StorageImpl},
|
storage::{RegisterRequest, StorageImpl},
|
||||||
};
|
};
|
||||||
use actix_web::{HttpResponse, web};
|
use actix_web::{HttpResponse, web};
|
||||||
use registry::Peer;
|
use registry::{Peer, RegisterResponse};
|
||||||
|
|
||||||
pub async fn register_peer(
|
pub async fn register_peer(
|
||||||
app_state: web::Data<AppState>,
|
app_state: web::Data<AppState>,
|
||||||
request: web::Json<RegisterRequest>,
|
request: web::Json<RegisterRequest>,
|
||||||
) -> Result<HttpResponse> {
|
) -> Result<HttpResponse> {
|
||||||
app_state.storage.register_device(&request).await?;
|
let mesh_ip = app_state.storage.register_device(&request).await?;
|
||||||
|
|
||||||
app_state
|
app_state
|
||||||
.peer_updates
|
.peer_updates
|
||||||
.send(PeerUpdate {
|
.send(PeerUpdate {
|
||||||
@@ -18,10 +19,11 @@ pub async fn register_peer(
|
|||||||
public_key: request.public_key.as_str().to_string(),
|
public_key: request.public_key.as_str().to_string(),
|
||||||
public_ip: request.public_ip.to_string(),
|
public_ip: request.public_ip.to_string(),
|
||||||
port: request.port.clone(),
|
port: request.port.clone(),
|
||||||
|
mesh_ip,
|
||||||
allowed_ips: request.allowed_ips.clone(),
|
allowed_ips: request.allowed_ips.clone(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.unwrap();
|
.ok();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().json(RegisterResponse { mesh_ip }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use actix_web::{HttpResponse, ResponseError};
|
use actix_web::{HttpResponse, ResponseError};
|
||||||
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use thiserror_ext::{Box, Construct};
|
use thiserror_ext::{Box, Construct};
|
||||||
|
|
||||||
@@ -32,12 +33,30 @@ pub enum ErrorKind {
|
|||||||
},
|
},
|
||||||
#[error("error handling ws")]
|
#[error("error handling ws")]
|
||||||
Ws(#[source] actix_web::Error),
|
Ws(#[source] actix_web::Error),
|
||||||
|
#[error("IP pool exhausted: no available addresses in {pool}")]
|
||||||
|
IpPoolExhausted { pool: String },
|
||||||
|
#[error("error deregistering device {public_key}")]
|
||||||
|
DeregisterDevice {
|
||||||
|
public_key: String,
|
||||||
|
#[source]
|
||||||
|
source: redis::RedisError,
|
||||||
|
},
|
||||||
|
#[error("error invalid key")]
|
||||||
|
InvalidKey(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ErrorResponse {
|
||||||
|
error: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseError for Error {
|
impl ResponseError for Error {
|
||||||
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
|
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
|
||||||
match self.inner() {
|
match self.inner() {
|
||||||
ErrorKind::Ws(e) => e.error_response(),
|
ErrorKind::Ws(e) => e.error_response(),
|
||||||
|
ErrorKind::InvalidKey(key) => HttpResponse::BadRequest().json(ErrorResponse {
|
||||||
|
error: format!("error invalid key: {key}"),
|
||||||
|
}),
|
||||||
_ => HttpResponse::InternalServerError().finish(),
|
_ => HttpResponse::InternalServerError().finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
mod types;
|
mod types;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use types::peer_message::*;
|
pub use types::peer_message::{Peer, PeerMessage, RegisterResponse};
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ async fn run() -> crate::error::Result<()> {
|
|||||||
"/register",
|
"/register",
|
||||||
web::post().to(endpoints::register::register_peer),
|
web::post().to(endpoints::register::register_peer),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/deregister",
|
||||||
|
web::delete().to(endpoints::deregister::deregister),
|
||||||
|
)
|
||||||
.route("/peers", web::get().to(endpoints::peers::get_peers))
|
.route("/peers", web::get().to(endpoints::peers::get_peers))
|
||||||
.route("/ws/peers", web::get().to(endpoints::ws::peers::peers))
|
.route("/ws/peers", web::get().to(endpoints::ws::peers::peers))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
mod valkey;
|
mod valkey;
|
||||||
@@ -10,12 +12,13 @@ pub enum Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait StorageImpl {
|
pub trait StorageImpl {
|
||||||
async fn register_device(&self, request: &RegisterRequest) -> Result<()>;
|
async fn register_device(&self, request: &RegisterRequest) -> Result<Ipv4Addr>;
|
||||||
|
async fn deregister_device(&self, public_key: &str) -> Result<()>;
|
||||||
async fn get_peers(&self) -> Result<Vec<Peer>>;
|
async fn get_peers(&self) -> Result<Vec<Peer>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StorageImpl for Storage {
|
impl StorageImpl for Storage {
|
||||||
async fn register_device(&self, request: &RegisterRequest) -> Result<()> {
|
async fn register_device(&self, request: &RegisterRequest) -> Result<Ipv4Addr> {
|
||||||
match self {
|
match self {
|
||||||
Self::Valkey(storage) => storage.register_device(request).await,
|
Self::Valkey(storage) => storage.register_device(request).await,
|
||||||
}
|
}
|
||||||
@@ -26,6 +29,12 @@ impl StorageImpl for Storage {
|
|||||||
Self::Valkey(storage) => storage.get_peers().await,
|
Self::Valkey(storage) => storage.get_peers().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn deregister_device(&self, public_key: &str) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::Valkey(storage) => storage.deregister_device(public_key).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_storage_from_env() -> Result<Storage> {
|
pub fn get_storage_from_env() -> Result<Storage> {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::net::IpAddr;
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
|
use futures::TryFutureExt;
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use redis::AsyncTypedCommands;
|
use redis::AsyncTypedCommands;
|
||||||
use registry::Peer;
|
use registry::Peer;
|
||||||
@@ -10,6 +11,10 @@ use crate::error::Result;
|
|||||||
use crate::utils::WireguardPublicKey;
|
use crate::utils::WireguardPublicKey;
|
||||||
use crate::{error::Error, storage::StorageImpl};
|
use crate::{error::Error, storage::StorageImpl};
|
||||||
|
|
||||||
|
const MESH_NETWORK_BASE: [u8; 4] = [10, 100, 0, 0];
|
||||||
|
const MESH_POOL_START: u8 = 1;
|
||||||
|
const MESH_POOL_END: u8 = 254;
|
||||||
|
|
||||||
pub struct ValkeyStorage {
|
pub struct ValkeyStorage {
|
||||||
pub valkey_client: redis::Client,
|
pub valkey_client: redis::Client,
|
||||||
}
|
}
|
||||||
@@ -23,22 +28,38 @@ pub struct RegisterRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StorageImpl for ValkeyStorage {
|
impl StorageImpl for ValkeyStorage {
|
||||||
async fn register_device(&self, request: &RegisterRequest) -> Result<()> {
|
async fn register_device(&self, request: &RegisterRequest) -> Result<Ipv4Addr> {
|
||||||
let mut conn = self
|
let mut conn = self
|
||||||
.valkey_client
|
.valkey_client
|
||||||
.get_multiplexed_async_connection()
|
.get_multiplexed_async_connection()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::valkey_get_connection(e))?;
|
.map_err(|e| Error::valkey_get_connection(e))?;
|
||||||
|
|
||||||
|
let peer_key = format!("peer:{}", request.public_key.as_str());
|
||||||
|
|
||||||
|
let existing_mesh_ip: Option<String> = conn
|
||||||
|
.hget(&peer_key, "mesh_ip")
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::get_peer(e))?;
|
||||||
|
|
||||||
|
let mesh_ip = if let Some(ip_str) = existing_mesh_ip {
|
||||||
|
ip_str.parse::<Ipv4Addr>().unwrap()
|
||||||
|
} else {
|
||||||
|
let allocated_ip = self.allocate_mesh_ip(&mut conn).await?;
|
||||||
|
allocated_ip
|
||||||
|
};
|
||||||
|
|
||||||
conn.hset_multiple::<_, _, _>(
|
conn.hset_multiple::<_, _, _>(
|
||||||
format!("peer:{}", request.public_key.as_str()),
|
&peer_key,
|
||||||
&[
|
&[
|
||||||
("public_ip", &request.public_ip.to_string()),
|
("public_ip", request.public_ip.to_string()),
|
||||||
(
|
(
|
||||||
"allowed_ips",
|
"allowed_ips",
|
||||||
&serde_json::to_string(&request.allowed_ips)
|
serde_json::to_string(&request.allowed_ips)
|
||||||
.map_err(|e| Error::serialize_json(e, "serializing allowed_ips"))?,
|
.map_err(|e| Error::serialize_json(e, "serializing allowed_ips"))?,
|
||||||
),
|
),
|
||||||
("port", &request.port),
|
("port", request.port.clone()),
|
||||||
|
("mesh_ip", mesh_ip.to_string()),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -49,7 +70,8 @@ impl StorageImpl for ValkeyStorage {
|
|||||||
request.public_ip.to_string(),
|
request.public_ip.to_string(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
conn.sadd("peers", &request.public_key.as_str())
|
|
||||||
|
conn.sadd("peers", request.public_key.as_str())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
Error::add_peer(
|
Error::add_peer(
|
||||||
@@ -59,6 +81,25 @@ impl StorageImpl for ValkeyStorage {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
Ok(mesh_ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn deregister_device(&self, public_key: &str) -> Result<()> {
|
||||||
|
let mut conn = self
|
||||||
|
.valkey_client
|
||||||
|
.get_multiplexed_async_connection()
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::valkey_get_connection(e))?;
|
||||||
|
let hash_key = format!("peer:{public_key}");
|
||||||
|
conn.srem("peers", public_key)
|
||||||
|
.map_err(|e| Error::deregister_device(e, public_key))
|
||||||
|
.await?;
|
||||||
|
let response = conn
|
||||||
|
.del(hash_key)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::deregister_device(e, public_key))?;
|
||||||
|
tracing::debug!("deleted hash {keys} key(s) removed", keys = response);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,21 +130,72 @@ impl StorageImpl for ValkeyStorage {
|
|||||||
.map_err(|e| Error::get_peer(e))?
|
.map_err(|e| Error::get_peer(e))?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(keys.iter())
|
.zip(keys.iter())
|
||||||
.map(|(peer, key): (HashMap<String, String>, &String)| {
|
.filter_map(|(peer, key): (HashMap<String, String>, &String)| {
|
||||||
let allowed_ips: Vec<IpNetwork> = peer
|
let allowed_ips: Vec<IpNetwork> = peer
|
||||||
.get("allowed_ips")
|
.get("allowed_ips")
|
||||||
.map(|s| serde_json::from_str(s).unwrap_or_default())
|
.map(|s| serde_json::from_str(s).unwrap_or_default())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Peer {
|
let mesh_ip: Ipv4Addr = peer.get("mesh_ip")?.parse().ok()?;
|
||||||
|
Some(Peer {
|
||||||
public_key: key.clone(),
|
public_key: key.clone(),
|
||||||
public_ip: peer.get("public_ip").unwrap().to_string(),
|
public_ip: peer.get("public_ip")?.to_string(),
|
||||||
port: peer.get("port").unwrap().to_string(),
|
port: peer.get("port")?.to_string(),
|
||||||
|
mesh_ip,
|
||||||
allowed_ips,
|
allowed_ips,
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(peers)
|
Ok(peers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ValkeyStorage {
|
||||||
|
async fn allocate_mesh_ip(
|
||||||
|
&self,
|
||||||
|
conn: &mut redis::aio::MultiplexedConnection,
|
||||||
|
) -> Result<Ipv4Addr> {
|
||||||
|
let keys: HashSet<String> = conn
|
||||||
|
.smembers("peers")
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::get_peer(e))?;
|
||||||
|
|
||||||
|
let mut assigned_ips: HashSet<Ipv4Addr> = HashSet::new();
|
||||||
|
|
||||||
|
if !keys.is_empty() {
|
||||||
|
let mut pipe = redis::pipe();
|
||||||
|
for key in keys.iter() {
|
||||||
|
pipe.hget(format!("peer:{key}"), "mesh_ip");
|
||||||
|
}
|
||||||
|
|
||||||
|
let ips: Vec<Option<String>> = pipe
|
||||||
|
.query_async(conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::get_peer(e))?;
|
||||||
|
|
||||||
|
for ip_opt in ips {
|
||||||
|
if let Some(ip_str) = ip_opt {
|
||||||
|
if let Ok(ip) = ip_str.parse::<Ipv4Addr>() {
|
||||||
|
assigned_ips.insert(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for last_octet in MESH_POOL_START..=MESH_POOL_END {
|
||||||
|
let candidate = Ipv4Addr::new(
|
||||||
|
MESH_NETWORK_BASE[0],
|
||||||
|
MESH_NETWORK_BASE[1],
|
||||||
|
MESH_NETWORK_BASE[2],
|
||||||
|
last_octet,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !assigned_ips.contains(&candidate) {
|
||||||
|
return Ok(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::ip_pool_exhausted("10.100.0.0/24".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
@@ -7,9 +9,15 @@ pub struct Peer {
|
|||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
pub public_ip: String,
|
pub public_ip: String,
|
||||||
pub port: String,
|
pub port: String,
|
||||||
|
pub mesh_ip: Ipv4Addr,
|
||||||
pub allowed_ips: Vec<IpNetwork>,
|
pub allowed_ips: Vec<IpNetwork>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct RegisterResponse {
|
||||||
|
pub mesh_ip: Ipv4Addr,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum PeerMessage {
|
pub enum PeerMessage {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use serde::{Deserialize, de};
|
use serde::{de, Deserialize};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WireguardPublicKey(String);
|
pub struct WireguardPublicKey(String);
|
||||||
|
|||||||
Reference in New Issue
Block a user