refactor public/private gateways

This commit is contained in:
Aiden McClelland
2025-08-05 17:07:25 -06:00
parent e6b7390a61
commit 10af26116d
50 changed files with 1878 additions and 777 deletions

251
core/Cargo.lock generated
View File

@@ -116,6 +116,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "ansi-width"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219e3ce6f2611d83b51ec2098a12702112c29e57203a6b0a0929b2cddb486608"
dependencies = [
"unicode-width 0.1.14",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.19" version = "0.6.19"
@@ -181,6 +190,12 @@ dependencies = [
"derive_arbitrary", "derive_arbitrary",
] ]
[[package]]
name = "archery"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae2ed21cd55021f05707a807a5fc85695dafb98832921f6cfa06db67ca5b869"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.9" version = "0.3.9"
@@ -306,9 +321,9 @@ dependencies = [
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.25" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
dependencies = [ dependencies = [
"brotli", "brotli",
"flate2", "flate2",
@@ -334,9 +349,9 @@ dependencies = [
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "2.4.1" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
dependencies = [ dependencies = [
"async-lock", "async-lock",
"cfg-if", "cfg-if",
@@ -345,10 +360,9 @@ dependencies = [
"futures-lite", "futures-lite",
"parking", "parking",
"polling", "polling",
"rustix 1.0.7", "rustix 1.0.8",
"slab", "slab",
"tracing", "windows-sys 0.60.2",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -364,9 +378,9 @@ dependencies = [
[[package]] [[package]]
name = "async-process" name = "async-process"
version = "2.3.1" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00"
dependencies = [ dependencies = [
"async-channel 2.5.0", "async-channel 2.5.0",
"async-io", "async-io",
@@ -377,8 +391,7 @@ dependencies = [
"cfg-if", "cfg-if",
"event-listener 5.4.0", "event-listener 5.4.0",
"futures-lite", "futures-lite",
"rustix 1.0.7", "rustix 1.0.8",
"tracing",
] ]
[[package]] [[package]]
@@ -394,9 +407,9 @@ dependencies = [
[[package]] [[package]]
name = "async-signal" name = "async-signal"
version = "0.2.11" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1"
dependencies = [ dependencies = [
"async-io", "async-io",
"async-lock", "async-lock",
@@ -404,10 +417,10 @@ dependencies = [
"cfg-if", "cfg-if",
"futures-core", "futures-core",
"futures-io", "futures-io",
"rustix 1.0.7", "rustix 1.0.8",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -472,9 +485,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "aws-lc-rs" name = "aws-lc-rs"
version = "1.13.2" version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08b5d4e069cbc868041a64bd68dc8cb39a0d79585cd6c5a24caa8c2d622121be" checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba"
dependencies = [ dependencies = [
"aws-lc-sys", "aws-lc-sys",
"zeroize", "zeroize",
@@ -950,9 +963,9 @@ checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.29" version = "1.2.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@@ -1340,9 +1353,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@@ -1403,7 +1416,7 @@ dependencies = [
"futures-core", "futures-core",
"mio", "mio",
"parking_lot", "parking_lot",
"rustix 1.0.7", "rustix 1.0.8",
"signal-hook", "signal-hook",
"signal-hook-mio", "signal-hook-mio",
"winapi", "winapi",
@@ -1491,9 +1504,9 @@ dependencies = [
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "4.2.0" version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373b7c5dbd637569a2cca66e8d66b8c446a1e7bf064ea321d265d7b3dfe7c97e" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@@ -1829,7 +1842,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [ dependencies = [
"curve25519-dalek 4.2.0", "curve25519-dalek 4.1.3",
"ed25519 2.2.3", "ed25519 2.2.3",
"rand_core 0.6.4", "rand_core 0.6.4",
"serde", "serde",
@@ -2073,9 +2086,9 @@ dependencies = [
[[package]] [[package]]
name = "fiat-crypto" name = "fiat-crypto"
version = "0.3.0" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]] [[package]]
name = "filetime" name = "filetime"
@@ -2602,9 +2615,9 @@ dependencies = [
[[package]] [[package]]
name = "hifijson" name = "hifijson"
version = "0.2.2" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9958ab3ce3170c061a27679916bd9b969eceeb5e8b120438e6751d0987655c42" checksum = "0a7763b98ba8a24f59e698bf9ab197e7676c640d6455d1580b4ce7dc560f0f0d"
[[package]] [[package]]
name = "hkdf" name = "hkdf"
@@ -2753,9 +2766,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.15" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -2769,7 +2782,7 @@ dependencies = [
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.6.0",
"system-configuration", "system-configuration",
"tokio", "tokio",
"tower-service", "tower-service",
@@ -2946,13 +2959,28 @@ dependencies = [
[[package]] [[package]]
name = "imbl" name = "imbl"
version = "4.0.1" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ae128b3bc67ed43ec0a7bb1c337a9f026717628b3c4033f07ded1da3e854951" checksum = "33afdc5d333c1a43f1f640bfc6ad3788729e5b2f18472e5d33a9187315257f8e"
dependencies = [ dependencies = [
"archery",
"bitmaps", "bitmaps",
"imbl-sized-chunks", "imbl-sized-chunks",
"rand_core 0.6.4", "rand_core 0.9.3",
"rand_xoshiro",
"serde",
"version_check",
]
[[package]]
name = "imbl"
version = "6.0.0"
source = "git+https://github.com/dr-bonez/imbl.git?branch=bugfix%2Fordmap-lifetimes#897cf45b8c32bbee760d1348872724d4d9987ffe"
dependencies = [
"archery",
"bitmaps",
"imbl-sized-chunks",
"rand_core 0.9.3",
"rand_xoshiro", "rand_xoshiro",
"serde", "serde",
"version_check", "version_check",
@@ -2969,10 +2997,11 @@ dependencies = [
[[package]] [[package]]
name = "imbl-value" name = "imbl-value"
version = "0.3.0" version = "0.4.0"
source = "git+https://github.com/Start9Labs/imbl-value.git#229506ca83ae26bd78968719e5a78631deb39250" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80b000e99a562d9598ae56029f675dbf1104e8aed5a9b074824a752de0a61f39"
dependencies = [ dependencies = [
"imbl", "imbl 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde", "serde",
"serde_json", "serde_json",
"yasi", "yasi",
@@ -2980,11 +3009,10 @@ dependencies = [
[[package]] [[package]]
name = "imbl-value" name = "imbl-value"
version = "0.3.2" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/Start9Labs/imbl-value.git#82b91f973d67139ce5b3f662407f436bc64d2fe9"
checksum = "c04359f6198e92e1986d221cfa7de62cd4c9c27880dffb2f98b6eaaec40cde4f"
dependencies = [ dependencies = [
"imbl", "imbl 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde", "serde",
"serde_json", "serde_json",
"yasi", "yasi",
@@ -3072,9 +3100,9 @@ dependencies = [
[[package]] [[package]]
name = "io-uring" name = "io-uring"
version = "0.7.8" version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"cfg-if", "cfg-if",
@@ -3087,7 +3115,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [ dependencies = [
"socket2", "socket2 0.5.10",
"widestring", "widestring",
"windows-sys 0.48.0", "windows-sys 0.48.0",
"winreg", "winreg",
@@ -3288,7 +3316,7 @@ dependencies = [
name = "json-patch" name = "json-patch"
version = "0.2.7-alpha.0" version = "0.2.7-alpha.0"
dependencies = [ dependencies = [
"imbl-value 0.3.2", "imbl-value 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"json-ptr", "json-ptr",
"serde", "serde",
] ]
@@ -3297,8 +3325,8 @@ dependencies = [
name = "json-ptr" name = "json-ptr"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"imbl", "imbl 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"imbl-value 0.3.2", "imbl-value 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde", "serde",
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
@@ -3308,7 +3336,7 @@ name = "jsonpath_lib"
version = "0.3.0" version = "0.3.0"
source = "git+https://github.com/Start9Labs/jsonpath.git#1cacbd64afa2e1941a21fef06bad14317ba92f30" source = "git+https://github.com/Start9Labs/jsonpath.git#1cacbd64afa2e1941a21fef06bad14317ba92f30"
dependencies = [ dependencies = [
"imbl-value 0.3.0", "imbl-value 0.4.0 (git+https://github.com/Start9Labs/imbl-value.git)",
"log", "log",
"serde", "serde",
"serde_json", "serde_json",
@@ -3422,13 +3450,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.4" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"libc", "libc",
"redox_syscall 0.5.13", "redox_syscall 0.5.15",
] ]
[[package]] [[package]]
@@ -3486,9 +3514,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]] [[package]]
name = "litrs" name = "litrs"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -3658,9 +3686,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.9.5" version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -3732,7 +3760,7 @@ dependencies = [
"num_enum", "num_enum",
"openssl", "openssl",
"patch-db", "patch-db",
"rand 0.9.1", "rand 0.9.2",
"regex", "regex",
"reqwest", "reqwest",
"rpc-toolkit", "rpc-toolkit",
@@ -4187,7 +4215,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall 0.5.13", "redox_syscall 0.5.15",
"smallvec", "smallvec",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
@@ -4199,8 +4227,8 @@ dependencies = [
"async-trait", "async-trait",
"fd-lock-rs", "fd-lock-rs",
"futures", "futures",
"imbl", "imbl 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"imbl-value 0.3.2", "imbl-value 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"json-patch", "json-patch",
"json-ptr", "json-ptr",
"lazy_static", "lazy_static",
@@ -4409,17 +4437,16 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "polling" name = "polling"
version = "3.8.0" version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"concurrent-queue 2.5.0", "concurrent-queue 2.5.0",
"hermit-abi", "hermit-abi",
"pin-project-lite", "pin-project-lite",
"rustix 1.0.7", "rustix 1.0.8",
"tracing", "windows-sys 0.60.2",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -4460,9 +4487,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.35" version = "0.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 2.0.104", "syn 2.0.104",
@@ -4545,7 +4572,7 @@ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"lazy_static", "lazy_static",
"num-traits", "num-traits",
"rand 0.9.1", "rand 0.9.2",
"rand_chacha 0.9.0", "rand_chacha 0.9.0",
"rand_xorshift", "rand_xorshift",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
@@ -4605,11 +4632,11 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
[[package]] [[package]]
name = "pty-process" name = "pty-process"
version = "0.5.2" version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a480f2bcfed0fee5dd30e529d985c2ff4457dc7468b3c0dcb92b9fd2b14c6b9" checksum = "71cec9e2670207c5ebb9e477763c74436af3b9091dd550b9fb3c1bec7f3ea266"
dependencies = [ dependencies = [
"rustix 1.0.7", "rustix 1.0.8",
] ]
[[package]] [[package]]
@@ -4699,9 +4726,9 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.1" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [ dependencies = [
"rand_chacha 0.9.0", "rand_chacha 0.9.0",
"rand_core 0.9.3", "rand_core 0.9.3",
@@ -4784,11 +4811,11 @@ dependencies = [
[[package]] [[package]]
name = "rand_xoshiro" name = "rand_xoshiro"
version = "0.6.0" 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 = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41"
dependencies = [ dependencies = [
"rand_core 0.6.4", "rand_core 0.9.3",
] ]
[[package]] [[package]]
@@ -4830,9 +4857,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.13" version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
] ]
@@ -4965,9 +4992,9 @@ dependencies = [
[[package]] [[package]]
name = "reqwest_cookie_store" name = "reqwest_cookie_store"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0b36498c7452f11b1833900f31fbb01fc46be20992a50269c88cf59d79f54e9" checksum = "2314c325724fea278d44c13a525ebf60074e33c05f13b4345c076eb65b2446b3"
dependencies = [ dependencies = [
"bytes", "bytes",
"cookie_store", "cookie_store",
@@ -5019,7 +5046,7 @@ dependencies = [
[[package]] [[package]]
name = "rpc-toolkit" name = "rpc-toolkit"
version = "0.3.1" version = "0.3.1"
source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master#f83d9d9934036c1bc929073cad537774ecdbb5f2" source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master#b73d3375b5fec21df6221e644ae0a7c623e81a62"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
@@ -5028,7 +5055,7 @@ dependencies = [
"futures", "futures",
"http", "http",
"http-body-util", "http-body-util",
"imbl-value 0.3.2", "imbl-value 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.14.0", "itertools 0.14.0",
"lazy_format", "lazy_format",
"lazy_static", "lazy_static",
@@ -5131,15 +5158,15 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"errno 0.3.13", "errno 0.3.13",
"libc", "libc",
"linux-raw-sys 0.9.4", "linux-raw-sys 0.9.4",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -5264,17 +5291,17 @@ dependencies = [
[[package]] [[package]]
name = "rustyline-async" name = "rustyline-async"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "055f3061e6c11d3c981f55b9c60da44d0413344ceeaf2fb479450f18dc9c2675" checksum = "6e07ddce8399c61495b405dc94d4f30d01fc1c5e1238f10b9c09940678bc81ab"
dependencies = [ dependencies = [
"ansi-width",
"crossterm", "crossterm",
"futures-util", "futures-util",
"pin-project", "pin-project",
"thingbuf", "thingbuf",
"thiserror 2.0.12", "thiserror 2.0.12",
"unicode-segmentation", "unicode-segmentation",
"unicode-width 0.2.1",
] ]
[[package]] [[package]]
@@ -5426,9 +5453,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
dependencies = [ dependencies = [
"indexmap 2.10.0", "indexmap 2.10.0",
"itoa", "itoa",
@@ -5727,6 +5754,16 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "solana-nohash-hasher" name = "solana-nohash-hasher"
version = "0.2.1" version = "0.2.1"
@@ -6067,8 +6104,8 @@ dependencies = [
"hyper", "hyper",
"hyper-util", "hyper-util",
"id-pool", "id-pool",
"imbl", "imbl 6.0.0 (git+https://github.com/dr-bonez/imbl.git?branch=bugfix%2Fordmap-lifetimes)",
"imbl-value 0.3.2", "imbl-value 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"include_dir", "include_dir",
"indexmap 2.10.0", "indexmap 2.10.0",
"indicatif", "indicatif",
@@ -6110,7 +6147,7 @@ dependencies = [
"proptest-derive", "proptest-derive",
"pty-process", "pty-process",
"qrcode", "qrcode",
"rand 0.9.1", "rand 0.9.2",
"regex", "regex",
"reqwest", "reqwest",
"reqwest_cookie_store", "reqwest_cookie_store",
@@ -6131,7 +6168,7 @@ dependencies = [
"shell-words", "shell-words",
"signal-hook", "signal-hook",
"simple-logging", "simple-logging",
"socket2", "socket2 0.5.10",
"sqlx", "sqlx",
"sscanf", "sscanf",
"ssh-key", "ssh-key",
@@ -6301,7 +6338,7 @@ dependencies = [
"fastrand", "fastrand",
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"rustix 1.0.7", "rustix 1.0.8",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -6498,7 +6535,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"socket2", "socket2 0.5.10",
"tokio-macros", "tokio-macros",
"tracing", "tracing",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@@ -6708,7 +6745,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"pin-project", "pin-project",
"prost", "prost",
"socket2", "socket2 0.5.10",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tower 0.4.13", "tower 0.4.13",
@@ -6981,7 +7018,7 @@ dependencies = [
"httparse", "httparse",
"log", "log",
"native-tls", "native-tls",
"rand 0.9.1", "rand 0.9.2",
"sha1", "sha1",
"thiserror 2.0.12", "thiserror 2.0.12",
"url", "url",
@@ -7358,14 +7395,14 @@ version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [ dependencies = [
"webpki-roots 1.0.1", "webpki-roots 1.0.2",
] ]
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
dependencies = [ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
@@ -7388,7 +7425,7 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
dependencies = [ dependencies = [
"redox_syscall 0.5.13", "redox_syscall 0.5.15",
"wasite", "wasite",
] ]
@@ -7811,7 +7848,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909"
dependencies = [ dependencies = [
"libc", "libc",
"rustix 1.0.7", "rustix 1.0.8",
] ]
[[package]] [[package]]
@@ -7888,9 +7925,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "5.8.0" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-executor", "async-executor",
@@ -7921,9 +7958,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus_macros" name = "zbus_macros"
version = "5.8.0" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c8e4e14dcdd9d97a98b189cd1220f30e8394ad271e8c987da84f73693862c2" checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659"
dependencies = [ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",

View File

@@ -0,0 +1,58 @@
use std::convert::Infallible;
use std::path::Path;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use yasi::InternedString;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
#[ts(type = "string")]
pub struct GatewayId(InternedString);
impl GatewayId {
pub fn as_str(&self) -> &str {
&*self.0
}
}
impl<T> From<T> for GatewayId
where
T: Into<InternedString>,
{
fn from(value: T) -> Self {
Self(value.into())
}
}
impl FromStr for GatewayId {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(GatewayId(InternedString::intern(s)))
}
}
impl AsRef<GatewayId> for GatewayId {
fn as_ref(&self) -> &GatewayId {
self
}
}
impl std::fmt::Display for GatewayId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
impl AsRef<str> for GatewayId {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl AsRef<Path> for GatewayId {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl<'de> Deserialize<'de> for GatewayId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
Ok(GatewayId(serde::Deserialize::deserialize(deserializer)?))
}
}

View File

@@ -6,6 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use yasi::InternedString; use yasi::InternedString;
mod action; mod action;
mod gateway;
mod health_check; mod health_check;
mod host; mod host;
mod image; mod image;
@@ -16,6 +17,7 @@ mod service_interface;
mod volume; mod volume;
pub use action::ActionId; pub use action::ActionId;
pub use gateway::GatewayId;
pub use health_check::HealthCheckId; pub use health_check::HealthCheckId;
pub use host::HostId; pub use host::HostId;
pub use image::ImageId; pub use image::ImageId;

View File

@@ -124,8 +124,11 @@ id-pool = { version = "0.2.2", default-features = false, features = [
"serde", "serde",
"u16", "u16",
] } ] }
imbl = "4.0.1" imbl = { version = "6", git = "https://github.com/dr-bonez/imbl.git", branch = "bugfix/ordmap-lifetimes", features = [
imbl-value = "0.3.2" "serde",
"small-chunks",
] }
imbl-value = "0.4.0"
include_dir = { version = "0.7.3", features = ["metadata"] } include_dir = { version = "0.7.3", features = ["metadata"] }
indexmap = { version = "2.0.2", features = ["serde"] } indexmap = { version = "2.0.2", features = ["serde"] }
indicatif = { version = "0.17.7", features = ["tokio"] } indicatif = { version = "0.17.7", features = ["tokio"] }

View File

@@ -1,4 +1,3 @@
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use tokio::process::Command; use tokio::process::Command;
@@ -48,7 +47,7 @@ async fn setup_or_init(
update_phase.complete(); update_phase.complete();
reboot_phase.start(); reboot_phase.start();
return Ok(Err(Shutdown { return Ok(Err(Shutdown {
export_args: None, disk_guid: None,
restart: true, restart: true,
})); }));
} }
@@ -103,7 +102,7 @@ async fn setup_or_init(
.expect("context dropped"); .expect("context dropped");
return Ok(Err(Shutdown { return Ok(Err(Shutdown {
export_args: None, disk_guid: None,
restart: true, restart: true,
})); }));
} }
@@ -117,7 +116,9 @@ async fn setup_or_init(
server.serve_setup(ctx.clone()); server.serve_setup(ctx.clone());
let mut shutdown = ctx.shutdown.subscribe(); let mut shutdown = ctx.shutdown.subscribe();
shutdown.recv().await.expect("context dropped"); if let Some(shutdown) = shutdown.recv().await.expect("context dropped") {
return Ok(Err(shutdown));
}
tokio::task::yield_now().await; tokio::task::yield_now().await;
if let Err(e) = Command::new("killall") if let Err(e) = Command::new("killall")
@@ -183,7 +184,7 @@ async fn setup_or_init(
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1)); let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
reboot_phase.start(); reboot_phase.start();
return Ok(Err(Shutdown { return Ok(Err(Shutdown {
export_args: Some((disk_guid, Path::new(DATA_DIR).to_owned())), disk_guid: Some(disk_guid),
restart: true, restart: true,
})); }));
} }

View File

@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use cookie::{Cookie, Expiration, SameSite}; use cookie::{Cookie, Expiration, SameSite};
use cookie_store::{CookieStore, RawCookie}; use cookie_store::CookieStore;
use imbl_value::InternedString; use imbl_value::InternedString;
use josekit::jwk::Jwk; use josekit::jwk::Jwk;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@@ -13,7 +13,7 @@ use reqwest::Proxy;
use reqwest_cookie_store::CookieStoreMutex; use reqwest_cookie_store::CookieStoreMutex;
use rpc_toolkit::reqwest::{Client, Url}; use rpc_toolkit::reqwest::{Client, Url};
use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::yajrc::RpcError;
use rpc_toolkit::{call_remote_http, CallRemote, Context, Empty}; use rpc_toolkit::{CallRemote, Context, Empty};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
@@ -27,7 +27,6 @@ use crate::middleware::auth::AuthContext;
use crate::prelude::*; use crate::prelude::*;
use crate::rpc_continuations::Guid; use crate::rpc_continuations::Guid;
use crate::tunnel::context::TunnelContext; use crate::tunnel::context::TunnelContext;
use crate::tunnel::TUNNEL_DEFAULT_PORT;
#[derive(Debug)] #[derive(Debug)]
pub struct CliContextSeed { pub struct CliContextSeed {

View File

@@ -174,7 +174,7 @@ impl RpcContext {
) )
.await?, .await?,
); );
webserver.try_upgrade(|a| net_ctrl.net_iface.upgrade_listener(a))?; webserver.try_upgrade(|a| net_ctrl.net_iface.watcher.upgrade_listener(a))?;
let os_net_service = net_ctrl.os_bindings().await?; let os_net_service = net_ctrl.os_bindings().await?;
(net_ctrl, os_net_service) (net_ctrl, os_net_service)
}; };

View File

@@ -25,6 +25,7 @@ use crate::prelude::*;
use crate::progress::FullProgressTracker; use crate::progress::FullProgressTracker;
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations}; use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
use crate::setup::SetupProgress; use crate::setup::SetupProgress;
use crate::shutdown::Shutdown;
use crate::util::net::WebSocketExt; use crate::util::net::WebSocketExt;
use crate::MAIN_DATA; use crate::MAIN_DATA;
@@ -71,7 +72,8 @@ pub struct SetupContextSeed {
pub progress: FullProgressTracker, pub progress: FullProgressTracker,
pub task: OnceCell<NonDetachingJoinHandle<()>>, pub task: OnceCell<NonDetachingJoinHandle<()>>,
pub result: OnceCell<Result<(SetupResult, RpcContext), Error>>, pub result: OnceCell<Result<(SetupResult, RpcContext), Error>>,
pub shutdown: Sender<()>, pub disk_guid: OnceCell<Arc<String>>,
pub shutdown: Sender<Option<Shutdown>>,
pub rpc_continuations: RpcContinuations, pub rpc_continuations: RpcContinuations,
} }
@@ -97,6 +99,7 @@ impl SetupContext {
progress: FullProgressTracker::new(), progress: FullProgressTracker::new(),
task: OnceCell::new(), task: OnceCell::new(),
result: OnceCell::new(), result: OnceCell::new(),
disk_guid: OnceCell::new(),
shutdown, shutdown,
rpc_continuations: RpcContinuations::new(), rpc_continuations: RpcContinuations::new(),
}))) })))

View File

@@ -1,13 +1,15 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use exver::{Version, VersionRange}; use exver::{Version, VersionRange};
use imbl::{OrdMap, OrdSet};
use imbl_value::InternedString; use imbl_value::InternedString;
use ipnet::IpNet; use ipnet::IpNet;
use isocountry::CountryCode; use isocountry::CountryCode;
use itertools::Itertools; use itertools::Itertools;
use models::PackageId; use lazy_static::lazy_static;
use models::{GatewayId, PackageId};
use openssl::hash::MessageDigest; use openssl::hash::MessageDigest;
use patch_db::{HasModel, Value}; use patch_db::{HasModel, Value};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -71,7 +73,8 @@ impl Public {
net: NetInfo { net: NetInfo {
assigned_port: None, assigned_port: None,
assigned_ssl_port: Some(443), assigned_ssl_port: Some(443),
public: false, private_disabled: OrdSet::new(),
public_enabled: OrdSet::new(),
}, },
}, },
)] )]
@@ -89,7 +92,7 @@ impl Public {
enabled: true, enabled: true,
..Default::default() ..Default::default()
}, },
network_interfaces: BTreeMap::new(), network_interfaces: OrdMap::new(),
acme: BTreeMap::new(), acme: BTreeMap::new(),
}, },
status_info: ServerStatus { status_info: ServerStatus {
@@ -186,9 +189,9 @@ pub struct ServerInfo {
pub struct NetworkInfo { pub struct NetworkInfo {
pub wifi: WifiInfo, pub wifi: WifiInfo,
pub host: Host, pub host: Host,
#[ts(as = "BTreeMap::<String, NetworkInterfaceInfo>")] #[ts(as = "BTreeMap::<GatewayId, NetworkInterfaceInfo>")]
#[serde(default)] #[serde(default)]
pub network_interfaces: BTreeMap<InternedString, NetworkInterfaceInfo>, pub network_interfaces: OrdMap<GatewayId, NetworkInterfaceInfo>,
#[serde(default)] #[serde(default)]
pub acme: BTreeMap<AcmeProvider, AcmeSettings>, pub acme: BTreeMap<AcmeProvider, AcmeSettings>,
} }
@@ -199,9 +202,33 @@ pub struct NetworkInfo {
#[ts(export)] #[ts(export)]
pub struct NetworkInterfaceInfo { pub struct NetworkInterfaceInfo {
pub public: Option<bool>, pub public: Option<bool>,
pub secure: Option<bool>,
pub ip_info: Option<IpInfo>, pub ip_info: Option<IpInfo>,
} }
impl NetworkInterfaceInfo { impl NetworkInterfaceInfo {
pub fn loopback() -> (&'static GatewayId, &'static Self) {
lazy_static! {
static ref LO: GatewayId = GatewayId::from("lo");
static ref LOOPBACK: NetworkInterfaceInfo = NetworkInterfaceInfo {
public: Some(false),
secure: Some(true),
ip_info: Some(IpInfo {
name: "lo".into(),
scope_id: 1,
device_type: None,
subnets: [
IpNet::new(Ipv4Addr::LOCALHOST.into(), 8).unwrap(),
IpNet::new(Ipv6Addr::LOCALHOST.into(), 128).unwrap(),
]
.into_iter()
.collect(),
wan_ip: None,
ntp_servers: Default::default(),
}),
};
}
(&*LO, &*LOOPBACK)
}
pub fn public(&self) -> bool { pub fn public(&self) -> bool {
self.public.unwrap_or_else(|| { self.public.unwrap_or_else(|| {
!self.ip_info.as_ref().map_or(true, |ip_info| { !self.ip_info.as_ref().map_or(true, |ip_info| {
@@ -233,6 +260,14 @@ impl NetworkInterfaceInfo {
}) })
}) })
} }
pub fn secure(&self) -> bool {
self.secure.unwrap_or_else(|| {
self.ip_info.as_ref().map_or(false, |ip_info| {
ip_info.device_type == Some(NetworkInterfaceType::Wireguard)
})
})
}
} }
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, TS, HasModel)] #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, TS, HasModel)]
@@ -245,10 +280,10 @@ pub struct IpInfo {
pub scope_id: u32, pub scope_id: u32,
pub device_type: Option<NetworkInterfaceType>, pub device_type: Option<NetworkInterfaceType>,
#[ts(type = "string[]")] #[ts(type = "string[]")]
pub subnets: BTreeSet<IpNet>, pub subnets: OrdSet<IpNet>,
pub wan_ip: Option<Ipv4Addr>, pub wan_ip: Option<Ipv4Addr>,
#[ts(type = "string[]")] #[ts(type = "string[]")]
pub ntp_servers: BTreeSet<InternedString>, pub ntp_servers: OrdSet<InternedString>,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, TS)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, TS)]

View File

@@ -3,6 +3,7 @@ use std::marker::PhantomData;
use std::str::FromStr; use std::str::FromStr;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use imbl::OrdMap;
pub use imbl_value::Value; pub use imbl_value::Value;
use patch_db::value::InternedString; use patch_db::value::InternedString;
pub use patch_db::{HasModel, MutateResult, PatchDb}; pub use patch_db::{HasModel, MutateResult, PatchDb};
@@ -199,6 +200,18 @@ where
} }
} }
impl<A, B> Map for OrdMap<A, B>
where
A: serde::Serialize + serde::de::DeserializeOwned + Clone + Ord + AsRef<str>,
B: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
type Key = A;
type Value = B;
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
Ok(key.as_ref())
}
}
impl<T: Map> Model<T> impl<T: Map> Model<T>
where where
T::Value: Serialize, T::Value: Serialize,

View File

@@ -1,4 +1,3 @@
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::yajrc::RpcError;
@@ -12,7 +11,6 @@ use crate::init::SYSTEM_REBUILD_PATH;
use crate::prelude::*; use crate::prelude::*;
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
use crate::util::io::delete_file; use crate::util::io::delete_file;
use crate::DATA_DIR;
pub fn diagnostic<C: Context>() -> ParentHandler<C> { pub fn diagnostic<C: Context>() -> ParentHandler<C> {
ParentHandler::new() ParentHandler::new()
@@ -70,10 +68,7 @@ pub fn error(ctx: DiagnosticContext) -> Result<Arc<RpcError>, Error> {
pub fn restart(ctx: DiagnosticContext) -> Result<(), Error> { pub fn restart(ctx: DiagnosticContext) -> Result<(), Error> {
ctx.shutdown ctx.shutdown
.send(Shutdown { .send(Shutdown {
export_args: ctx disk_guid: ctx.disk_guid.clone(),
.disk_guid
.clone()
.map(|guid| (guid, Path::new(DATA_DIR).to_owned())),
restart: true, restart: true,
}) })
.map_err(|_| eyre!("receiver dropped")) .map_err(|_| eyre!("receiver dropped"))

View File

@@ -35,8 +35,8 @@ impl Hostname {
pub fn generate_hostname() -> Hostname { pub fn generate_hostname() -> Hostname {
let mut rng = rng(); let mut rng = rng();
let adjective = &ADJECTIVES[rng.gen_range(0..ADJECTIVES.len())]; let adjective = &ADJECTIVES[rng.random_range(0..ADJECTIVES.len())];
let noun = &NOUNS[rng.gen_range(0..NOUNS.len())]; let noun = &NOUNS[rng.random_range(0..NOUNS.len())];
Hostname(InternedString::from_display(&lazy_format!( Hostname(InternedString::from_display(&lazy_format!(
"{adjective}-{noun}" "{adjective}-{noun}"
))) )))

View File

@@ -216,7 +216,7 @@ pub async fn init(
) )
.await?, .await?,
); );
webserver.try_upgrade(|a| net_ctrl.net_iface.upgrade_listener(a))?; webserver.try_upgrade(|a| net_ctrl.net_iface.watcher.upgrade_listener(a))?;
let os_net_service = net_ctrl.os_bindings().await?; let os_net_service = net_ctrl.os_bindings().await?;
start_net.complete(); start_net.complete();

View File

@@ -1,5 +1,6 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::future::Future;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::time::Duration; use std::time::Duration;
@@ -18,7 +19,6 @@ use trust_dns_server::server::{Request, RequestHandler, ResponseHandler, Respons
use trust_dns_server::ServerFuture; use trust_dns_server::ServerFuture;
use crate::net::forward::START9_BRIDGE_IFACE; use crate::net::forward::START9_BRIDGE_IFACE;
use crate::util::sync::Watch;
use crate::util::Invoke; use crate::util::Invoke;
use crate::{Error, ErrorKind, ResultExt}; use crate::{Error, ErrorKind, ResultExt};
@@ -140,7 +140,9 @@ impl RequestHandler for Resolver {
impl DnsController { impl DnsController {
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn init(mut lxcbr_status: Watch<bool>) -> Result<Self, Error> { pub async fn init(
bridge_activated: impl Future<Output = ()> + Send + Sync + 'static,
) -> Result<Self, Error> {
let services = Arc::new(RwLock::new(BTreeMap::new())); let services = Arc::new(RwLock::new(BTreeMap::new()));
let mut server = ServerFuture::new(Resolver { let mut server = ServerFuture::new(Resolver {
@@ -160,7 +162,7 @@ impl DnsController {
.with_kind(ErrorKind::Network)?, .with_kind(ErrorKind::Network)?,
); );
lxcbr_status.wait_for(|a| *a).await; bridge_activated.await;
Command::new("resolvectl") Command::new("resolvectl")
.arg("dns") .arg("dns")

View File

@@ -1,16 +1,19 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::net::{IpAddr, SocketAddr, SocketAddrV6};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use futures::channel::oneshot; use futures::channel::oneshot;
use helpers::NonDetachingJoinHandle; use helpers::NonDetachingJoinHandle;
use id_pool::IdPool; use id_pool::IdPool;
use imbl_value::InternedString; use imbl::OrdMap;
use models::GatewayId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::process::Command; use tokio::process::Command;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::db::model::public::NetworkInterfaceInfo; use crate::db::model::public::NetworkInterfaceInfo;
use crate::net::network_interface::{DynInterfaceFilter, InterfaceFilter};
use crate::net::utils::ipv6_is_link_local;
use crate::prelude::*; use crate::prelude::*;
use crate::util::sync::Watch; use crate::util::sync::Watch;
use crate::util::Invoke; use crate::util::Invoke;
@@ -39,106 +42,162 @@ impl AvailablePorts {
} }
} }
#[derive(Debug)]
struct ForwardRequest { struct ForwardRequest {
public: bool, external: u16,
target: SocketAddr, target: SocketAddr,
filter: DynInterfaceFilter,
rc: Weak<()>, rc: Weak<()>,
} }
#[derive(Debug, Default)] struct ForwardEntry {
struct ForwardState { external: u16,
requested: BTreeMap<u16, ForwardRequest>, target: SocketAddr,
current: BTreeMap<u16, BTreeMap<InternedString, SocketAddr>>, prev_filter: DynInterfaceFilter,
forwards: BTreeMap<SocketAddr, GatewayId>,
rc: Weak<()>,
} }
impl ForwardState { impl ForwardEntry {
async fn sync( fn new(external: u16, target: SocketAddr, rc: Weak<()>) -> Self {
Self {
external,
target,
prev_filter: false.into_dyn(),
forwards: BTreeMap::new(),
rc,
}
}
fn take(&mut self) -> Self {
Self {
external: self.external,
target: self.target,
prev_filter: std::mem::replace(&mut self.prev_filter, false.into_dyn()),
forwards: std::mem::take(&mut self.forwards),
rc: self.rc.clone(),
}
}
async fn destroy(mut self) -> Result<(), Error> {
while let Some((source, interface)) = self.forwards.pop_first() {
unforward(interface.as_str(), source, self.target).await?;
}
Ok(())
}
async fn update(
&mut self, &mut self,
interfaces: &BTreeMap<InternedString, (bool, Vec<Ipv4Addr>)>, ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
filter: Option<DynInterfaceFilter>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let private_interfaces = interfaces if self.rc.strong_count() == 0 {
return self.take().destroy().await;
}
let filter_ref = filter.as_ref().unwrap_or(&self.prev_filter);
let mut keep = BTreeSet::<SocketAddr>::new();
for (iface, info) in ip_info
.iter() .iter()
.filter(|(_, (public, _))| !*public) .chain([NetworkInterfaceInfo::loopback()])
.map(|(i, _)| i) .filter(|(id, info)| filter_ref.filter(*id, *info))
.collect::<BTreeSet<_>>();
let all_interfaces = interfaces.keys().collect::<BTreeSet<_>>();
self.requested.retain(|_, req| req.rc.strong_count() > 0);
for external in self
.requested
.keys()
.chain(self.current.keys())
.copied()
.collect::<BTreeSet<_>>()
{ {
match ( if let Some(ip_info) = &info.ip_info {
self.requested.get(&external), for ipnet in &ip_info.subnets {
self.current.get_mut(&external), let addr = match ipnet.addr() {
) { IpAddr::V6(ip6) => SocketAddrV6::new(
(Some(req), Some(cur)) => { ip6,
let expected = if req.public { self.external,
&all_interfaces 0,
} else { if ipv6_is_link_local(ip6) {
&private_interfaces ip_info.scope_id
} else {
0
},
)
.into(),
ip => SocketAddr::new(ip, self.external),
}; };
let actual = cur.keys().collect::<BTreeSet<_>>(); keep.insert(addr);
let mut to_rm = actual if !self.forwards.contains_key(&addr) {
.difference(expected) forward(iface.as_str(), addr, self.target).await?;
.copied() self.forwards.insert(addr, iface.clone());
.map(|i| (i.clone(), &interfaces[i].1))
.collect::<BTreeMap<_, _>>();
let mut to_add = expected
.difference(&actual)
.copied()
.map(|i| (i.clone(), &interfaces[i].1))
.collect::<BTreeMap<_, _>>();
for interface in actual.intersection(expected).copied() {
if cur[interface] != req.target {
to_rm.insert(interface.clone(), &interfaces[interface].1);
to_add.insert(interface.clone(), &interfaces[interface].1);
}
}
for (interface, ips) in to_rm {
for ip in ips {
unforward(&*interface, (*ip, external).into(), cur[&interface]).await?;
}
cur.remove(&interface);
}
for (interface, ips) in to_add {
cur.insert(interface.clone(), req.target);
for ip in ips {
forward(&*interface, (*ip, external).into(), cur[&interface]).await?;
}
} }
} }
(Some(req), None) => {
let cur = self.current.entry(external).or_default();
for interface in if req.public {
&all_interfaces
} else {
&private_interfaces
}
.into_iter()
.copied()
{
cur.insert(interface.clone(), req.target);
for ip in &interfaces[interface].1 {
forward(&**interface, (*ip, external).into(), req.target).await?;
}
}
}
(None, Some(cur)) => {
let to_rm = cur.keys().cloned().collect::<BTreeSet<_>>();
for interface in to_rm {
for ip in &interfaces[&interface].1 {
unforward(&*interface, (*ip, external).into(), cur[&interface]).await?;
}
cur.remove(&interface);
}
self.current.remove(&external);
}
_ => (),
} }
} }
let rm = self
.forwards
.keys()
.copied()
.filter(|a| !keep.contains(a))
.collect::<Vec<_>>();
for rm in rm {
if let Some((source, interface)) = self.forwards.remove_entry(&rm) {
unforward(interface.as_str(), source, self.target).await?;
}
}
if let Some(filter) = filter {
self.prev_filter = filter;
}
Ok(())
}
async fn update_request(
&mut self,
ForwardRequest {
external,
target,
filter,
rc,
}: ForwardRequest,
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
) -> Result<(), Error> {
if external != self.external || target != self.target {
self.take().destroy().await?;
*self = Self::new(external, target, rc);
self.update(ip_info, Some(filter)).await?;
} else {
if self.prev_filter != filter {
self.update(ip_info, Some(filter)).await?;
}
self.rc = rc;
}
Ok(())
}
}
impl Drop for ForwardEntry {
fn drop(&mut self) {
if !self.forwards.is_empty() {
let take = self.take();
tokio::spawn(async move {
take.destroy().await.log_err();
});
}
}
}
#[derive(Default)]
struct ForwardState {
state: BTreeMap<u16, ForwardEntry>,
}
impl ForwardState {
async fn handle_request(
&mut self,
request: ForwardRequest,
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
) -> Result<(), Error> {
self.state
.entry(request.external)
.or_insert_with(|| ForwardEntry::new(request.external, request.target, Weak::new()))
.update_request(request, ip_info)
.await
}
async fn sync(
&mut self,
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
) -> Result<(), Error> {
for entry in self.state.values_mut() {
entry.update(ip_info, None).await?;
}
self.state.retain(|_, fwd| !fwd.forwards.is_empty());
Ok(()) Ok(())
} }
} }
@@ -150,87 +209,37 @@ fn err_has_exited<T>(_: T) -> Error {
) )
} }
pub struct LanPortForwardController { pub struct PortForwardController {
req: mpsc::UnboundedSender<( req: mpsc::UnboundedSender<(Option<ForwardRequest>, oneshot::Sender<Result<(), Error>>)>,
Option<(u16, ForwardRequest)>,
oneshot::Sender<Result<(), Error>>,
)>,
_thread: NonDetachingJoinHandle<()>, _thread: NonDetachingJoinHandle<()>,
} }
impl LanPortForwardController { impl PortForwardController {
pub fn new(mut ip_info: Watch<BTreeMap<InternedString, NetworkInterfaceInfo>>) -> Self { pub fn new(mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>) -> Self {
let (req_send, mut req_recv) = mpsc::unbounded_channel(); let (req_send, mut req_recv) = mpsc::unbounded_channel::<(
Option<ForwardRequest>,
oneshot::Sender<Result<(), Error>>,
)>();
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move { let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
let mut state = ForwardState::default(); let mut state = ForwardState::default();
let mut interfaces = ip_info.peek_and_mark_seen(|ip_info| { let mut interfaces = ip_info.read_and_mark_seen();
ip_info
.iter()
.map(|(iface, info)| {
(
iface.clone(),
(
info.public(),
info.ip_info.as_ref().map_or(Vec::new(), |i| {
i.subnets
.iter()
.filter_map(|s| {
if let IpAddr::V4(ip) = s.addr() {
Some(ip)
} else {
None
}
})
.collect()
}),
),
)
})
.collect()
});
let mut reply: Option<oneshot::Sender<Result<(), Error>>> = None;
loop { loop {
tokio::select! { tokio::select! {
msg = req_recv.recv() => { msg = req_recv.recv() => {
if let Some((msg, re)) = msg { if let Some((msg, re)) = msg {
if let Some((external, req)) = msg { if let Some(req) = msg {
state.requested.insert(external, req); re.send(state.handle_request(req, &interfaces).await).ok();
} else {
re.send(state.sync(&interfaces).await).ok();
} }
reply = Some(re);
} else { } else {
break; break;
} }
} }
_ = ip_info.changed() => { _ = ip_info.changed() => {
interfaces = ip_info.peek(|ip_info| { interfaces = ip_info.read();
ip_info state.sync(&interfaces).await.log_err();
.iter()
.map(|(iface, info)| (iface.clone(), (
info.public(),
info.ip_info.as_ref().map_or(Vec::new(), |i| {
i.subnets
.iter()
.filter_map(|s| {
if let IpAddr::V4(ip) = s.addr() {
Some(ip)
} else {
None
}
})
.collect()
}),
)))
.collect()
});
} }
} }
let res = state.sync(&interfaces).await;
if let Err(e) = &res {
tracing::error!("Error in PortForwardController: {e}");
tracing::debug!("{e:?}");
}
if let Some(re) = reply.take() {
let _ = re.send(res);
}
} }
})); }));
Self { Self {
@@ -238,19 +247,22 @@ impl LanPortForwardController {
_thread: thread, _thread: thread,
} }
} }
pub async fn add(&self, port: u16, public: bool, target: SocketAddr) -> Result<Arc<()>, Error> { pub async fn add(
&self,
external: u16,
filter: impl InterfaceFilter,
target: SocketAddr,
) -> Result<Arc<()>, Error> {
let rc = Arc::new(()); let rc = Arc::new(());
let (send, recv) = oneshot::channel(); let (send, recv) = oneshot::channel();
self.req self.req
.send(( .send((
Some(( Some(ForwardRequest {
port, external,
ForwardRequest { target,
public, filter: filter.into_dyn(),
target, rc: Arc::downgrade(&rc),
rc: Arc::downgrade(&rc), }),
},
)),
send, send,
)) ))
.map_err(err_has_exited)?; .map_err(err_has_exited)?;

View File

@@ -131,7 +131,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>(
use prettytable::*; use prettytable::*;
if let Some(format) = params.format { if let Some(format) = params.format {
display_serializable(format, res); display_serializable(format, res)?;
return Ok(()); return Ok(());
} }

View File

@@ -1,16 +1,19 @@
use std::collections::BTreeMap; use std::collections::{BTreeMap, BTreeSet};
use std::str::FromStr; use std::str::FromStr;
use clap::builder::ValueParserFactory; use clap::builder::ValueParserFactory;
use clap::Parser; use clap::Parser;
use models::{FromStrParser, HostId}; use imbl::OrdSet;
use models::{FromStrParser, GatewayId, HostId};
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
use crate::context::{CliContext, RpcContext}; use crate::context::{CliContext, RpcContext};
use crate::db::model::public::NetworkInterfaceInfo;
use crate::net::forward::AvailablePorts; use crate::net::forward::AvailablePorts;
use crate::net::host::HostApiKind; use crate::net::host::HostApiKind;
use crate::net::network_interface::InterfaceFilter;
use crate::net::vhost::AlpnInfo; use crate::net::vhost::AlpnInfo;
use crate::prelude::*; use crate::prelude::*;
use crate::util::serde::{display_serializable, HandlerExtSerde}; use crate::util::serde::{display_serializable, HandlerExtSerde};
@@ -50,11 +53,14 @@ pub struct BindInfo {
pub net: NetInfo, pub net: NetInfo,
} }
#[derive(Clone, Copy, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export)] #[ts(export)]
pub struct NetInfo { pub struct NetInfo {
pub public: bool, #[ts(as = "BTreeSet::<GatewayId>")]
pub private_disabled: OrdSet<GatewayId>,
#[ts(as = "BTreeSet::<GatewayId>")]
pub public_enabled: OrdSet<GatewayId>,
pub assigned_port: Option<u16>, pub assigned_port: Option<u16>,
pub assigned_ssl_port: Option<u16>, pub assigned_ssl_port: Option<u16>,
} }
@@ -65,16 +71,19 @@ impl BindInfo {
if options.add_ssl.is_some() { if options.add_ssl.is_some() {
assigned_ssl_port = Some(available_ports.alloc()?); assigned_ssl_port = Some(available_ports.alloc()?);
} }
if let Some(secure) = options.secure { if options
if !secure.ssl || !options.add_ssl.is_some() { .secure
assigned_port = Some(available_ports.alloc()?); .map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
} {
assigned_port = Some(available_ports.alloc()?);
} }
Ok(Self { Ok(Self {
enabled: true, enabled: true,
options, options,
net: NetInfo { net: NetInfo {
public: false, private_disabled: OrdSet::new(),
public_enabled: OrdSet::new(),
assigned_port, assigned_port,
assigned_ssl_port, assigned_ssl_port,
}, },
@@ -88,7 +97,7 @@ impl BindInfo {
let Self { net: mut lan, .. } = self; let Self { net: mut lan, .. } = self;
if options if options
.secure .secure
.map_or(false, |s| !(s.ssl && options.add_ssl.is_some())) .map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
// doesn't make sense to have 2 listening ports, both with ssl // doesn't make sense to have 2 listening ports, both with ssl
{ {
lan.assigned_port = if let Some(port) = lan.assigned_port.take() { lan.assigned_port = if let Some(port) = lan.assigned_port.take() {
@@ -122,6 +131,15 @@ impl BindInfo {
self.enabled = false; self.enabled = false;
} }
} }
impl InterfaceFilter for NetInfo {
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
if info.public() {
self.public_enabled.contains(id)
} else {
!self.private_disabled.contains(id)
}
}
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS)] #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)] #[ts(export)]
@@ -165,12 +183,11 @@ pub fn binding<C: Context, Kind: HostApiKind>(
} }
let mut table = Table::new(); let mut table = Table::new();
table.add_row(row![bc => "INTERNAL PORT", "ENABLED", "PUBLIC", "EXTERNAL PORT", "EXTERNAL SSL PORT"]); table.add_row(row![bc => "INTERNAL PORT", "ENABLED", "EXTERNAL PORT", "EXTERNAL SSL PORT"]);
for (internal, info) in res { for (internal, info) in res {
table.add_row(row![ table.add_row(row![
internal, internal,
info.enabled, info.enabled,
info.net.public,
if let Some(port) = info.net.assigned_port { if let Some(port) = info.net.assigned_port {
port.to_string() port.to_string()
} else { } else {
@@ -192,12 +209,12 @@ pub fn binding<C: Context, Kind: HostApiKind>(
.with_call_remote::<CliContext>(), .with_call_remote::<CliContext>(),
) )
.subcommand( .subcommand(
"set-public", "set-gateway-enabled",
from_fn_async(set_public::<Kind>) from_fn_async(set_gateway_enabled::<Kind>)
.with_metadata("sync_db", Value::Bool(true)) .with_metadata("sync_db", Value::Bool(true))
.with_inherited(Kind::inheritance) .with_inherited(Kind::inheritance)
.no_display() .no_display()
.with_about("Add an binding to this host") .with_about("Set whether this gateway should be enabled for this binding")
.with_call_remote::<CliContext>(), .with_call_remote::<CliContext>(),
) )
} }
@@ -215,29 +232,50 @@ pub async fn list_bindings<Kind: HostApiKind>(
#[derive(Deserialize, Serialize, Parser, TS)] #[derive(Deserialize, Serialize, Parser, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export)] #[ts(export)]
pub struct BindingSetPublicParams { pub struct BindingGatewaySetEnabledParams {
internal_port: u16, internal_port: u16,
gateway: GatewayId,
#[arg(long)] #[arg(long)]
public: Option<bool>, enabled: Option<bool>,
} }
pub async fn set_public<Kind: HostApiKind>( pub async fn set_gateway_enabled<Kind: HostApiKind>(
ctx: RpcContext, ctx: RpcContext,
BindingSetPublicParams { BindingGatewaySetEnabledParams {
internal_port, internal_port,
public, gateway,
}: BindingSetPublicParams, enabled,
}: BindingGatewaySetEnabledParams,
inheritance: Kind::Inheritance, inheritance: Kind::Inheritance,
) -> Result<(), Error> { ) -> Result<(), Error> {
let enabled = enabled.unwrap_or(true);
let gateway_public = ctx
.net_controller
.net_iface
.watcher
.ip_info()
.get(&gateway)
.or_not_found(&gateway)?
.public();
ctx.db ctx.db
.mutate(|db| { .mutate(|db| {
Kind::host_for(&inheritance, db)? Kind::host_for(&inheritance, db)?
.as_bindings_mut() .as_bindings_mut()
.mutate(|b| { .mutate(|b| {
b.get_mut(&internal_port) let net = &mut b.get_mut(&internal_port).or_not_found(internal_port)?.net;
.or_not_found(internal_port)? if gateway_public {
.net if enabled {
.public = public.unwrap_or(true); net.public_enabled.insert(gateway);
} else {
net.public_enabled.remove(&gateway);
}
} else {
if enabled {
net.private_disabled.remove(&gateway);
} else {
net.private_disabled.insert(gateway);
}
}
Ok(()) Ok(())
}) })
}) })

View File

@@ -16,11 +16,14 @@ use crate::db::model::Database;
use crate::error::ErrorCollection; use crate::error::ErrorCollection;
use crate::hostname::Hostname; use crate::hostname::Hostname;
use crate::net::dns::DnsController; use crate::net::dns::DnsController;
use crate::net::forward::LanPortForwardController; use crate::net::forward::{PortForwardController, START9_BRIDGE_IFACE};
use crate::net::host::address::HostAddress; use crate::net::host::address::HostAddress;
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions}; use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
use crate::net::host::{host_for, Host, Hosts}; use crate::net::host::{host_for, Host, Hosts};
use crate::net::network_interface::NetworkInterfaceController; use crate::net::network_interface::{
AndFilter, DynInterfaceFilter, InterfaceFilter, LoopbackFilter, NetworkInterfaceController,
SecureFilter,
};
use crate::net::service_interface::{HostnameInfo, IpHostname, OnionHostname}; use crate::net::service_interface::{HostnameInfo, IpHostname, OnionHostname};
use crate::net::tor::TorController; use crate::net::tor::TorController;
use crate::net::utils::ipv6_is_local; use crate::net::utils::ipv6_is_local;
@@ -36,7 +39,7 @@ pub struct NetController {
pub(super) vhost: VHostController, pub(super) vhost: VHostController,
pub(crate) net_iface: Arc<NetworkInterfaceController>, pub(crate) net_iface: Arc<NetworkInterfaceController>,
pub(super) dns: DnsController, pub(super) dns: DnsController,
pub(super) forward: LanPortForwardController, pub(super) forward: PortForwardController,
pub(super) server_hostnames: Vec<Option<InternedString>>, pub(super) server_hostnames: Vec<Option<InternedString>>,
pub(crate) callbacks: Arc<ServiceCallbacks>, pub(crate) callbacks: Arc<ServiceCallbacks>,
} }
@@ -53,8 +56,13 @@ impl NetController {
db: db.clone(), db: db.clone(),
tor: TorController::new(tor_control, tor_socks), tor: TorController::new(tor_control, tor_socks),
vhost: VHostController::new(db, net_iface.clone()), vhost: VHostController::new(db, net_iface.clone()),
dns: DnsController::init(net_iface.lxcbr_status()).await?, dns: DnsController::init(
forward: LanPortForwardController::new(net_iface.subscribe()), net_iface
.watcher
.wait_for_activated(START9_BRIDGE_IFACE.into()),
)
.await?,
forward: PortForwardController::new(net_iface.watcher.subscribe()),
net_iface, net_iface,
server_hostnames: vec![ server_hostnames: vec![
// LAN IP // LAN IP
@@ -126,7 +134,7 @@ impl NetController {
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct HostBinds { struct HostBinds {
forwards: BTreeMap<u16, (SocketAddr, bool, Arc<()>)>, forwards: BTreeMap<u16, (SocketAddr, DynInterfaceFilter, Arc<()>)>,
vhosts: BTreeMap<(Option<InternedString>, u16), (TargetInfo, Arc<()>)>, vhosts: BTreeMap<(Option<InternedString>, u16), (TargetInfo, Arc<()>)>,
tor: BTreeMap<OnionAddressV3, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>, tor: BTreeMap<OnionAddressV3, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
} }
@@ -217,7 +225,7 @@ impl NetServiceData {
} }
async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> { async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> {
let mut forwards: BTreeMap<u16, (SocketAddr, bool)> = BTreeMap::new(); let mut forwards: BTreeMap<u16, (SocketAddr, DynInterfaceFilter)> = BTreeMap::new();
let mut vhosts: BTreeMap<(Option<InternedString>, u16), TargetInfo> = BTreeMap::new(); let mut vhosts: BTreeMap<(Option<InternedString>, u16), TargetInfo> = BTreeMap::new();
let mut tor: BTreeMap<OnionAddressV3, (TorSecretKeyV3, OrdMap<u16, SocketAddr>)> = let mut tor: BTreeMap<OnionAddressV3, (TorSecretKeyV3, OrdMap<u16, SocketAddr>)> =
BTreeMap::new(); BTreeMap::new();
@@ -228,7 +236,7 @@ impl NetServiceData {
// LAN // LAN
let server_info = peek.as_public().as_server_info(); let server_info = peek.as_public().as_server_info();
let net_ifaces = ctrl.net_iface.ip_info(); let net_ifaces = ctrl.net_iface.watcher.ip_info();
let hostname = server_info.as_hostname().de()?; let hostname = server_info.as_hostname().de()?;
for (port, bind) in &host.bindings { for (port, bind) in &host.bindings {
if !bind.enabled { if !bind.enabled {
@@ -255,7 +263,7 @@ impl NetServiceData {
vhosts.insert( vhosts.insert(
(hostname, external), (hostname, external),
TargetInfo { TargetInfo {
public: bind.net.public, filter: bind.net.clone().into_dyn(),
acme: None, acme: None,
addr, addr,
connect_ssl: connect_ssl.clone(), connect_ssl: connect_ssl.clone(),
@@ -270,7 +278,7 @@ impl NetServiceData {
vhosts.insert( vhosts.insert(
(Some(hostname), external), (Some(hostname), external),
TargetInfo { TargetInfo {
public: false, filter: LoopbackFilter.into_dyn(),
acme: None, acme: None,
addr, addr,
connect_ssl: connect_ssl.clone(), connect_ssl: connect_ssl.clone(),
@@ -286,11 +294,11 @@ impl NetServiceData {
if hostnames.insert(address.clone()) { if hostnames.insert(address.clone()) {
let address = Some(address.clone()); let address = Some(address.clone());
if ssl.preferred_external_port == 443 { if ssl.preferred_external_port == 443 {
if public && bind.net.public { if public {
vhosts.insert( vhosts.insert(
(address.clone(), 5443), (address.clone(), 5443),
TargetInfo { TargetInfo {
public: false, filter: bind.net.clone().into_dyn(),
acme: acme.clone(), acme: acme.clone(),
addr, addr,
connect_ssl: connect_ssl.clone(), connect_ssl: connect_ssl.clone(),
@@ -300,7 +308,7 @@ impl NetServiceData {
vhosts.insert( vhosts.insert(
(address.clone(), 443), (address.clone(), 443),
TargetInfo { TargetInfo {
public: public && bind.net.public, filter: bind.net.clone().into_dyn(),
acme, acme,
addr, addr,
connect_ssl: connect_ssl.clone(), connect_ssl: connect_ssl.clone(),
@@ -310,7 +318,7 @@ impl NetServiceData {
vhosts.insert( vhosts.insert(
(address.clone(), external), (address.clone(), external),
TargetInfo { TargetInfo {
public: public && bind.net.public, filter: bind.net.clone().into_dyn(),
acme, acme,
addr, addr,
connect_ssl: connect_ssl.clone(), connect_ssl: connect_ssl.clone(),
@@ -322,28 +330,35 @@ impl NetServiceData {
} }
} }
} }
if let Some(security) = bind.options.secure { if bind
if bind.options.add_ssl.is_some() && security.ssl { .options
// doesn't make sense to have 2 listening ports, both with ssl .secure
} else { .map_or(true, |s| !(s.ssl && bind.options.add_ssl.is_some()))
let external = bind.net.assigned_port.or_not_found("assigned lan port")?; {
forwards.insert(external, ((self.ip, *port).into(), bind.net.public)); let external = bind.net.assigned_port.or_not_found("assigned lan port")?;
} forwards.insert(
external,
(
(self.ip, *port).into(),
AndFilter(
SecureFilter {
secure: bind.options.secure.is_some(),
},
bind.net.clone(),
)
.into_dyn(),
),
);
} }
let mut bind_hostname_info: Vec<HostnameInfo> = let mut bind_hostname_info: Vec<HostnameInfo> =
hostname_info.remove(port).unwrap_or_default(); hostname_info.remove(port).unwrap_or_default();
for (interface, public, ip_info) in for (interface, info) in net_ifaces
net_ifaces.iter().filter_map(|(interface, info)| { .iter()
if let Some(ip_info) = &info.ip_info { .filter(|(id, info)| bind.net.filter(id, info))
Some((interface, info.public(), ip_info))
} else {
None
}
})
{ {
if !public { if !info.public() {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo::Ip {
network_interface_id: interface.clone(), gateway_id: interface.clone(),
public: false, public: false,
hostname: IpHostname::Local { hostname: IpHostname::Local {
value: InternedString::from_display(&{ value: InternedString::from_display(&{
@@ -357,47 +372,44 @@ impl NetServiceData {
} }
for address in host.addresses() { for address in host.addresses() {
if let HostAddress::Domain { if let HostAddress::Domain {
address, address, public, ..
public: domain_public,
..
} = address } = address
{ {
if !public || (domain_public && bind.net.public) { if bind
if bind .options
.options .add_ssl
.add_ssl .as_ref()
.as_ref() .map_or(false, |ssl| ssl.preferred_external_port == 443)
.map_or(false, |ssl| ssl.preferred_external_port == 443) {
{ bind_hostname_info.push(HostnameInfo::Ip {
bind_hostname_info.push(HostnameInfo::Ip { gateway_id: interface.clone(),
network_interface_id: interface.clone(), public, // TODO: check if port forward is active
public: public && domain_public && bind.net.public, // TODO: check if port forward is active hostname: IpHostname::Domain {
hostname: IpHostname::Domain { domain: address.clone(),
domain: address.clone(), subdomain: None,
subdomain: None, port: None,
port: None, ssl_port: Some(443),
ssl_port: Some(443), },
}, });
}); } else {
} else { bind_hostname_info.push(HostnameInfo::Ip {
bind_hostname_info.push(HostnameInfo::Ip { gateway_id: interface.clone(),
network_interface_id: interface.clone(), public,
public, hostname: IpHostname::Domain {
hostname: IpHostname::Domain { domain: address.clone(),
domain: address.clone(), subdomain: None,
subdomain: None, port: bind.net.assigned_port,
port: bind.net.assigned_port, ssl_port: bind.net.assigned_ssl_port,
ssl_port: bind.net.assigned_ssl_port, },
}, });
});
}
} }
} }
} }
if !public || bind.net.public { if let Some(ip_info) = &info.ip_info {
let public = info.public();
if let Some(wan_ip) = ip_info.wan_ip.filter(|_| public) { if let Some(wan_ip) = ip_info.wan_ip.filter(|_| public) {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo::Ip {
network_interface_id: interface.clone(), gateway_id: interface.clone(),
public, public,
hostname: IpHostname::Ipv4 { hostname: IpHostname::Ipv4 {
value: wan_ip, value: wan_ip,
@@ -411,7 +423,7 @@ impl NetServiceData {
IpNet::V4(net) => { IpNet::V4(net) => {
if !public { if !public {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo::Ip {
network_interface_id: interface.clone(), gateway_id: interface.clone(),
public, public,
hostname: IpHostname::Ipv4 { hostname: IpHostname::Ipv4 {
value: net.addr(), value: net.addr(),
@@ -423,7 +435,7 @@ impl NetServiceData {
} }
IpNet::V6(net) => { IpNet::V6(net) => {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo::Ip {
network_interface_id: interface.clone(), gateway_id: interface.clone(),
public: public && !ipv6_is_local(net.addr()), public: public && !ipv6_is_local(net.addr()),
hostname: IpHostname::Ipv6 { hostname: IpHostname::Ipv6 {
value: net.addr(), value: net.addr(),
@@ -509,8 +521,8 @@ impl NetServiceData {
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();
for external in all { for external in all {
let mut prev = binds.forwards.remove(&external); let mut prev = binds.forwards.remove(&external);
if let Some((internal, public)) = forwards.remove(&external) { if let Some((internal, filter)) = forwards.remove(&external) {
prev = prev.filter(|(i, p, _)| i == &internal && *p == public); prev = prev.filter(|(i, f, _)| i == &internal && *f == filter);
binds.forwards.insert( binds.forwards.insert(
external, external,
if let Some(prev) = prev { if let Some(prev) = prev {
@@ -518,8 +530,8 @@ impl NetServiceData {
} else { } else {
( (
internal, internal,
public, filter.clone(),
ctrl.forward.add(external, public, internal).await?, ctrl.forward.add(external, filter, internal).await?,
) )
}, },
); );
@@ -662,7 +674,7 @@ impl NetService {
} }
fn new(data: NetServiceData) -> Result<Self, Error> { fn new(data: NetServiceData) -> Result<Self, Error> {
let mut ip_info = data.net_controller()?.net_iface.subscribe(); let mut ip_info = data.net_controller()?.net_iface.watcher.subscribe();
let data = Arc::new(Mutex::new(data)); let data = Arc::new(Mutex::new(data));
let thread_data = data.clone(); let thread_data = data.clone();
let sync_task = tokio::spawn(async move { let sync_task = tokio::spawn(async move {

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ use std::net::{Ipv4Addr, Ipv6Addr};
use imbl_value::InternedString; use imbl_value::InternedString;
use lazy_format::lazy_format; use lazy_format::lazy_format;
use models::{HostId, ServiceInterfaceId}; use models::{GatewayId, HostId, ServiceInterfaceId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
@@ -14,7 +14,7 @@ use ts_rs::TS;
pub enum HostnameInfo { pub enum HostnameInfo {
Ip { Ip {
#[ts(type = "string")] #[ts(type = "string")]
network_interface_id: InternedString, gateway_id: GatewayId,
public: bool, public: bool,
hostname: IpHostname, hostname: IpHostname,
}, },

View File

@@ -1,5 +1,6 @@
use clap::Parser; use clap::Parser;
use imbl_value::InternedString; use imbl_value::InternedString;
use models::GatewayId;
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::process::Command; use tokio::process::Command;
@@ -44,7 +45,7 @@ pub async fn add_tunnel(
config, config,
public, public,
}: AddTunnelParams, }: AddTunnelParams,
) -> Result<InternedString, Error> { ) -> Result<GatewayId, Error> {
let existing = ctx let existing = ctx
.db .db
.peek() .peek()
@@ -54,17 +55,17 @@ pub async fn add_tunnel(
.into_network() .into_network()
.into_network_interfaces() .into_network_interfaces()
.keys()?; .keys()?;
let mut iface = InternedString::intern("wg0"); let mut iface = GatewayId::from("wg0");
for id in 1.. { for id in 1.. {
if !existing.contains(&iface) { if !existing.contains(&iface) {
break; break;
} }
iface = InternedString::from_display(&lazy_format!("wg{id}")); iface = InternedString::from_display(&lazy_format!("wg{id}")).into();
} }
let tmpdir = TmpDir::new().await?; let tmpdir = TmpDir::new().await?;
let conf = tmpdir.join(&*iface).with_extension("conf"); let conf = tmpdir.join(&iface).with_extension("conf");
write_file_atomic(&conf, &config).await?; write_file_atomic(&conf, &config).await?;
let mut ifaces = ctx.net_controller.net_iface.subscribe(); let mut ifaces = ctx.net_controller.net_iface.watcher.subscribe();
Command::new("nmcli") Command::new("nmcli")
.arg("connection") .arg("connection")
.arg("import") .arg("import")
@@ -91,8 +92,7 @@ pub async fn add_tunnel(
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)] #[ts(export)]
pub struct RemoveTunnelParams { pub struct RemoveTunnelParams {
#[ts(type = "string")] id: GatewayId,
id: InternedString,
} }
pub async fn remove_tunnel( pub async fn remove_tunnel(
ctx: RpcContext, ctx: RpcContext,

View File

@@ -10,8 +10,10 @@ use color_eyre::eyre::eyre;
use futures::FutureExt; use futures::FutureExt;
use helpers::NonDetachingJoinHandle; use helpers::NonDetachingJoinHandle;
use http::Uri; use http::Uri;
use imbl::OrdMap;
use imbl_value::InternedString; use imbl_value::InternedString;
use models::ResultExt; use itertools::Itertools;
use models::{GatewayId, ResultExt};
use rpc_toolkit::{from_fn, Context, HandlerArgs, HandlerExt, ParentHandler}; use rpc_toolkit::{from_fn, Context, HandlerArgs, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
@@ -31,13 +33,16 @@ use tracing::instrument;
use ts_rs::TS; use ts_rs::TS;
use crate::context::{CliContext, RpcContext}; use crate::context::{CliContext, RpcContext};
use crate::db::model::public::NetworkInterfaceInfo;
use crate::db::model::Database; use crate::db::model::Database;
use crate::net::acme::{AcmeCertCache, AcmeProvider}; use crate::net::acme::{AcmeCertCache, AcmeProvider};
use crate::net::network_interface::{ use crate::net::network_interface::{
Accepted, NetworkInterfaceController, NetworkInterfaceListener, Accepted, AnyFilter, DynInterfaceFilter, InterfaceFilter, NetworkInterfaceController,
NetworkInterfaceListener,
}; };
use crate::net::static_server::server_error; use crate::net::static_server::server_error;
use crate::prelude::*; use crate::prelude::*;
use crate::util::collections::EqSet;
use crate::util::io::BackTrackingIO; use crate::util::io::BackTrackingIO;
use crate::util::serde::{display_serializable, HandlerExtSerde, MaybeUtf8String}; use crate::util::serde::{display_serializable, HandlerExtSerde, MaybeUtf8String};
use crate::util::sync::SyncMutex; use crate::util::sync::SyncMutex;
@@ -51,12 +56,13 @@ pub fn vhost_api<C: Context>() -> ParentHandler<C> {
use prettytable::*; use prettytable::*;
if let Some(format) = params.format { if let Some(format) = params.format {
display_serializable(format, res); display_serializable(format, res)?;
return Ok::<_, Error>(()); return Ok::<_, Error>(());
} }
let mut table = Table::new(); let mut table = Table::new();
table.add_row(row![bc => "FROM", "TO", "PUBLIC", "ACME", "CONNECT SSL", "ACTIVE"]); table
.add_row(row![bc => "FROM", "TO", "GATEWAYS", "ACME", "CONNECT SSL", "ACTIVE"]);
for (external, targets) in res { for (external, targets) in res {
for (host, targets) in targets { for (host, targets) in targets {
@@ -68,7 +74,7 @@ pub fn vhost_api<C: Context>() -> ParentHandler<C> {
external.0 external.0
), ),
target.addr, target.addr,
target.public, target.gateways.iter().join(", "),
target.acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE"), target.acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE"),
target.connect_ssl.is_ok(), target.connect_ssl.is_ok(),
idx == 0 idx == 0
@@ -117,12 +123,7 @@ impl VHostController {
&self, &self,
hostname: Option<InternedString>, hostname: Option<InternedString>,
external: u16, external: u16,
TargetInfo { target: TargetInfo,
public,
acme,
addr,
connect_ssl,
}: TargetInfo,
) -> Result<Arc<()>, Error> { ) -> Result<Arc<()>, Error> {
self.servers.mutate(|writable| { self.servers.mutate(|writable| {
let server = if let Some(server) = writable.remove(&external) { let server = if let Some(server) = writable.remove(&external) {
@@ -136,15 +137,7 @@ impl VHostController {
self.acme_tls_alpn_cache.clone(), self.acme_tls_alpn_cache.clone(),
)? )?
}; };
let rc = server.add( let rc = server.add(hostname, target);
hostname,
TargetInfo {
public,
acme,
addr,
connect_ssl,
},
);
writable.insert(external, server); writable.insert(external, server);
Ok(rc?) Ok(rc?)
}) })
@@ -152,8 +145,9 @@ impl VHostController {
pub fn dump_table( pub fn dump_table(
&self, &self,
) -> BTreeMap<JsonKey<u16>, BTreeMap<JsonKey<Option<InternedString>>, BTreeSet<TargetInfo>>> ) -> BTreeMap<JsonKey<u16>, BTreeMap<JsonKey<Option<InternedString>>, EqSet<ShowTargetInfo>>>
{ {
let ip_info = self.interfaces.watcher.ip_info();
self.servers.peek(|s| { self.servers.peek(|s| {
s.iter() s.iter()
.map(|(k, v)| { .map(|(k, v)| {
@@ -167,8 +161,7 @@ impl VHostController {
JsonKey::new(k.clone()), JsonKey::new(k.clone()),
v.iter() v.iter()
.filter(|(_, v)| v.strong_count() > 0) .filter(|(_, v)| v.strong_count() > 0)
.map(|(k, _)| k) .map(|(k, _)| ShowTargetInfo::new(k.clone(), &ip_info))
.cloned()
.collect(), .collect(),
) )
}) })
@@ -192,14 +185,45 @@ impl VHostController {
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct TargetInfo { pub struct TargetInfo {
pub public: bool, pub filter: DynInterfaceFilter,
pub acme: Option<AcmeProvider>, pub acme: Option<AcmeProvider>,
pub addr: SocketAddr, pub addr: SocketAddr,
pub connect_ssl: Result<(), AlpnInfo>, // Ok: yes, connect using ssl, pass through alpn; Err: connect tcp, use provided strategy for alpn pub connect_ssl: Result<(), AlpnInfo>, // Ok: yes, connect using ssl, pass through alpn; Err: connect tcp, use provided strategy for alpn
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct ShowTargetInfo {
pub gateways: BTreeSet<GatewayId>,
pub acme: Option<AcmeProvider>,
pub addr: SocketAddr,
pub connect_ssl: Result<(), AlpnInfo>, // Ok: yes, connect using ssl, pass through alpn; Err: connect tcp, use provided strategy for alpn
}
impl ShowTargetInfo {
pub fn new(
TargetInfo {
filter,
acme,
addr,
connect_ssl,
}: TargetInfo,
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
) -> Self {
ShowTargetInfo {
gateways: ip_info
.iter()
.filter(|(id, info)| filter.filter(*id, *info))
.map(|(k, _)| k)
.cloned()
.collect(),
acme,
addr,
connect_ssl,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export)] #[ts(export)]
@@ -222,6 +246,21 @@ struct VHostServer {
_thread: NonDetachingJoinHandle<()>, _thread: NonDetachingJoinHandle<()>,
} }
impl<'a> From<&'a BTreeMap<Option<InternedString>, BTreeMap<TargetInfo, Weak<()>>>> for AnyFilter {
fn from(value: &'a BTreeMap<Option<InternedString>, BTreeMap<TargetInfo, Weak<()>>>) -> Self {
Self(
value
.iter()
.flat_map(|(_, v)| {
v.iter()
.filter(|(_, r)| r.strong_count() > 0)
.map(|(t, _)| t.filter.clone())
})
.collect(),
)
}
}
impl VHostServer { impl VHostServer {
async fn accept( async fn accept(
listener: &mut NetworkInterfaceListener, listener: &mut NetworkInterfaceListener,
@@ -233,35 +272,35 @@ impl VHostServer {
let accepted; let accepted;
loop { loop {
let any_public = mapping let any_filter = AnyFilter::from(&*mapping.borrow());
.borrow()
.iter()
.any(|(_, targets)| targets.iter().any(|(target, _)| target.public));
let changed_public = mapping let changed_filter = mapping
.wait_for(|m| { .wait_for(|m| any_filter != AnyFilter::from(m))
m.iter()
.any(|(_, targets)| targets.iter().any(|(target, _)| target.public))
!= any_public
})
.boxed(); .boxed();
tokio::select! { tokio::select! {
a = listener.accept(any_public) => { a = listener.accept(&any_filter) => {
accepted = a?; accepted = a?;
break; break;
} }
_ = changed_public => { _ = changed_filter => {
tracing::debug!("port {} {} public bindings", listener.port(), if any_public { "no longer has" } else { "now has" }); tracing::debug!("port {} filter changed", listener.port());
} }
} }
} }
let check = listener.check_filter();
tokio::spawn(async move { tokio::spawn(async move {
let bind = accepted.bind; let bind = accepted.bind;
if let Err(e) = if let Err(e) = Self::handle_stream(
Self::handle_stream(accepted, mapping, db, acme_tls_alpn_cache, crypto_provider) accepted,
.await check,
mapping,
db,
acme_tls_alpn_cache,
crypto_provider,
)
.await
{ {
tracing::error!("Error in VHostController on {bind}: {e}"); tracing::error!("Error in VHostController on {bind}: {e}");
tracing::debug!("{e:?}") tracing::debug!("{e:?}")
@@ -273,11 +312,11 @@ impl VHostServer {
async fn handle_stream( async fn handle_stream(
Accepted { Accepted {
stream, stream,
is_public,
wan_ip, wan_ip,
bind, bind,
.. ..
}: Accepted, }: Accepted,
check_filter: impl FnOnce(SocketAddr, &DynInterfaceFilter) -> bool,
mapping: watch::Receiver<Mapping>, mapping: watch::Receiver<Mapping>,
db: TypedPatchDb<Database>, db: TypedPatchDb<Database>,
acme_tls_alpn_cache: AcmeTlsAlpnCache, acme_tls_alpn_cache: AcmeTlsAlpnCache,
@@ -431,10 +470,8 @@ impl VHostServer {
.map(|(target, _)| target.clone()) .map(|(target, _)| target.clone())
}; };
if let Some(target) = target { if let Some(target) = target {
if is_public && !target.public { if !check_filter(bind, &target.filter) {
log::warn!( log::warn!("Connection from {bind} to {target:?} rejected by filter");
"Rejecting connection from public interface to private bind: {bind} -> {target:?}"
);
return Ok(()); return Ok(());
} }
let peek = db.peek().await; let peek = db.peek().await;
@@ -660,7 +697,10 @@ impl VHostServer {
crypto_provider: Arc<CryptoProvider>, crypto_provider: Arc<CryptoProvider>,
acme_tls_alpn_cache: AcmeTlsAlpnCache, acme_tls_alpn_cache: AcmeTlsAlpnCache,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut listener = iface_ctrl.bind(port).with_kind(crate::ErrorKind::Network)?; let mut listener = iface_ctrl
.watcher
.bind(port)
.with_kind(crate::ErrorKind::Network)?;
let (map_send, map_recv) = watch::channel(BTreeMap::new()); let (map_send, map_recv) = watch::channel(BTreeMap::new());
Ok(Self { Ok(Self {
mapping: map_send, mapping: map_send,

View File

@@ -1,8 +1,7 @@
use std::future::Future; use std::future::Future;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::AtomicBool; use std::sync::Arc;
use std::sync::{Arc, RwLock};
use std::task::Poll; use std::task::Poll;
use std::time::Duration; use std::time::Duration;
@@ -16,7 +15,7 @@ use tokio::sync::oneshot;
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
use crate::net::network_interface::{ use crate::net::network_interface::{
NetworkInterfaceListener, SelfContainedNetworkInterfaceListener, lookup_info_by_addr, NetworkInterfaceListener, SelfContainedNetworkInterfaceListener,
}; };
use crate::net::static_server::{ use crate::net::static_server::{
diagnostic_ui_router, init_ui_router, install_ui_router, main_ui_router, redirecter, refresher, diagnostic_ui_router, init_ui_router, install_ui_router, main_ui_router, redirecter, refresher,
@@ -50,10 +49,15 @@ impl Accept for Vec<TcpListener> {
} }
impl Accept for NetworkInterfaceListener { impl Accept for NetworkInterfaceListener {
fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> { fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
NetworkInterfaceListener::poll_accept(self, cx, true).map(|res| { NetworkInterfaceListener::poll_accept(self, cx, &true).map(|res| {
res.map(|a| Accepted { res.map(|a| {
https_redirect: a.is_public, let public = self
stream: a.stream, .ip_info
.peek(|i| lookup_info_by_addr(i, a.bind).map_or(true, |(_, i)| i.public()));
Accepted {
https_redirect: public,
stream: a.stream,
}
}) })
}) })
} }

View File

@@ -89,5 +89,6 @@ pub async fn get_service_port_forward(
.de()? .de()?
.get(&internal_port) .get(&internal_port)
.or_not_found(lazy_format!("binding for port {internal_port}"))? .or_not_found(lazy_format!("binding for port {internal_port}"))?
.net) .net
.clone())
} }

View File

@@ -36,6 +36,7 @@ use crate::net::ssl::root_ca_start_time;
use crate::prelude::*; use crate::prelude::*;
use crate::progress::{FullProgress, PhaseProgressTrackerHandle, ProgressUnits}; use crate::progress::{FullProgress, PhaseProgressTrackerHandle, ProgressUnits};
use crate::rpc_continuations::Guid; use crate::rpc_continuations::Guid;
use crate::shutdown::Shutdown;
use crate::system::sync_kiosk; use crate::system::sync_kiosk;
use crate::util::crypto::EncryptedWire; use crate::util::crypto::EncryptedWire;
use crate::util::io::{create_file, dir_copy, dir_size, Counter}; use crate::util::io::{create_file, dir_copy, dir_size, Counter};
@@ -67,6 +68,7 @@ pub fn setup<C: Context>() -> ParentHandler<C> {
"logs", "logs",
from_fn_async(crate::logs::cli_logs::<SetupContext, Empty>).no_display(), from_fn_async(crate::logs::cli_logs::<SetupContext, Empty>).no_display(),
) )
.subcommand("restart", from_fn_async(restart).no_cli())
} }
pub fn disk<C: Context>() -> ParentHandler<C> { pub fn disk<C: Context>() -> ParentHandler<C> {
@@ -172,6 +174,7 @@ pub async fn attach(
if disk_guid.ends_with("_UNENC") { None } else { Some(DEFAULT_PASSWORD) }, if disk_guid.ends_with("_UNENC") { None } else { Some(DEFAULT_PASSWORD) },
) )
.await?; .await?;
let _ = setup_ctx.disk_guid.set(disk_guid.clone());
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() { if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
tokio::fs::remove_file(REPAIR_DISK_PATH) tokio::fs::remove_file(REPAIR_DISK_PATH)
.await .await
@@ -390,9 +393,19 @@ pub async fn complete(ctx: SetupContext) -> Result<SetupResult, Error> {
} }
#[instrument(skip_all)] #[instrument(skip_all)]
// #[command(rpc_only)]
pub async fn exit(ctx: SetupContext) -> Result<(), Error> { pub async fn exit(ctx: SetupContext) -> Result<(), Error> {
ctx.shutdown.send(()).expect("failed to shutdown"); ctx.shutdown.send(None).expect("failed to shutdown");
Ok(())
}
#[instrument(skip_all)]
pub async fn restart(ctx: SetupContext) -> Result<(), Error> {
ctx.shutdown
.send(Some(Shutdown {
disk_guid: ctx.disk_guid.get().cloned(),
restart: true,
}))
.expect("failed to shutdown");
Ok(()) Ok(())
} }
@@ -435,6 +448,7 @@ pub async fn execute_inner(
); );
let _ = crate::disk::main::import(&*guid, DATA_DIR, RepairStrategy::Preen, encryption_password) let _ = crate::disk::main::import(&*guid, DATA_DIR, RepairStrategy::Preen, encryption_password)
.await?; .await?;
let _ = ctx.disk_guid.set(guid.clone());
disk_phase.complete(); disk_phase.complete();
let progress = SetupExecuteProgress { let progress = SetupExecuteProgress {

View File

@@ -1,4 +1,3 @@
use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use crate::context::RpcContext; use crate::context::RpcContext;
@@ -7,11 +6,11 @@ use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
use crate::prelude::*; use crate::prelude::*;
use crate::sound::SHUTDOWN; use crate::sound::SHUTDOWN;
use crate::util::Invoke; use crate::util::Invoke;
use crate::{DATA_DIR, PLATFORM}; use crate::PLATFORM;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Shutdown { pub struct Shutdown {
pub export_args: Option<(Arc<String>, PathBuf)>, pub disk_guid: Option<Arc<String>>,
pub restart: bool, pub restart: bool,
} }
impl Shutdown { impl Shutdown {
@@ -41,8 +40,8 @@ impl Shutdown {
tracing::error!("Error Stopping Journald: {}", e); tracing::error!("Error Stopping Journald: {}", e);
tracing::debug!("{:?}", e); tracing::debug!("{:?}", e);
} }
if let Some((guid, datadir)) = &self.export_args { if let Some(guid) = &self.disk_guid {
if let Err(e) = export(guid, datadir).await { if let Err(e) = export(guid, crate::DATA_DIR).await {
tracing::error!("Error Exporting Volume Group: {}", e); tracing::error!("Error Exporting Volume Group: {}", e);
tracing::debug!("{:?}", e); tracing::debug!("{:?}", e);
} }
@@ -87,7 +86,7 @@ pub async fn shutdown(ctx: RpcContext) -> Result<(), Error> {
.result?; .result?;
ctx.shutdown ctx.shutdown
.send(Some(Shutdown { .send(Some(Shutdown {
export_args: Some((ctx.disk_guid.clone(), Path::new(DATA_DIR).to_owned())), disk_guid: Some(ctx.disk_guid.clone()),
restart: false, restart: false,
})) }))
.map_err(|_| eyre!("receiver dropped")) .map_err(|_| eyre!("receiver dropped"))
@@ -108,7 +107,7 @@ pub async fn restart(ctx: RpcContext) -> Result<(), Error> {
.result?; .result?;
ctx.shutdown ctx.shutdown
.send(Some(Shutdown { .send(Some(Shutdown {
export_args: Some((ctx.disk_guid.clone(), Path::new(DATA_DIR).to_owned())), disk_guid: Some(ctx.disk_guid.clone()),
restart: true, restart: true,
})) }))
.map_err(|_| eyre!("receiver dropped")) .map_err(|_| eyre!("receiver dropped"))

View File

@@ -1,10 +1,11 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::ops::Deref; use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use clap::Parser; use clap::Parser;
use imbl::OrdMap;
use imbl_value::InternedString; use imbl_value::InternedString;
use patch_db::PatchDb; use patch_db::PatchDb;
use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::yajrc::RpcError;
@@ -15,13 +16,15 @@ use tracing::instrument;
use crate::auth::{check_password, Sessions}; use crate::auth::{check_password, Sessions};
use crate::context::config::ContextConfig; use crate::context::config::ContextConfig;
use crate::context::{CliContext, RpcContext}; use crate::context::CliContext;
use crate::middleware::auth::AuthContext; use crate::middleware::auth::AuthContext;
use crate::middleware::signature::SignatureAuthContext; use crate::middleware::signature::SignatureAuthContext;
use crate::net::forward::PortForwardController;
use crate::net::network_interface::NetworkInterfaceWatcher;
use crate::prelude::*; use crate::prelude::*;
use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations}; use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations};
use crate::tunnel::{TunnelDatabase, TUNNEL_DEFAULT_PORT}; use crate::tunnel::db::TunnelDatabase;
use crate::util::iter::TransposeResultIterExt; use crate::tunnel::TUNNEL_DEFAULT_PORT;
use crate::util::sync::SyncMutex; use crate::util::sync::SyncMutex;
#[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)] #[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)]
@@ -62,6 +65,8 @@ pub struct TunnelContextSeed {
pub rpc_continuations: RpcContinuations, pub rpc_continuations: RpcContinuations,
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>, pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
pub ephemeral_sessions: SyncMutex<Sessions>, pub ephemeral_sessions: SyncMutex<Sessions>,
pub net_iface: NetworkInterfaceWatcher,
pub forward: PortForwardController,
pub shutdown: Sender<()>, pub shutdown: Sender<()>,
} }
@@ -89,6 +94,8 @@ impl TunnelContext {
Ipv6Addr::UNSPECIFIED.into(), Ipv6Addr::UNSPECIFIED.into(),
TUNNEL_DEFAULT_PORT, TUNNEL_DEFAULT_PORT,
)); ));
let net_iface = NetworkInterfaceWatcher::new(async { OrdMap::new() }, []);
let forward = PortForwardController::new(net_iface.subscribe());
Ok(Self(Arc::new(TunnelContextSeed { Ok(Self(Arc::new(TunnelContextSeed {
listen, listen,
addrs: crate::net::utils::all_socket_addrs_for(listen.port()) addrs: crate::net::utils::all_socket_addrs_for(listen.port())
@@ -101,6 +108,8 @@ impl TunnelContext {
rpc_continuations: RpcContinuations::new(), rpc_continuations: RpcContinuations::new(),
open_authed_continuations: OpenAuthedContinuations::new(), open_authed_continuations: OpenAuthedContinuations::new(),
ephemeral_sessions: SyncMutex::new(Sessions::new()), ephemeral_sessions: SyncMutex::new(Sessions::new()),
net_iface,
forward,
shutdown, shutdown,
}))) })))
} }
@@ -213,14 +222,3 @@ impl CallRemote<TunnelContext> for CliContext {
.await .await
} }
} }
impl CallRemote<TunnelContext, TunnelAddrParams> for RpcContext {
async fn call_remote(
&self,
mut method: &str,
params: Value,
TunnelAddrParams { tunnel }: TunnelAddrParams,
) -> Result<Value, RpcError> {
todo!()
}
}

View File

@@ -1,3 +1,5 @@
use std::collections::{BTreeMap, HashSet};
use std::net::{Ipv4Addr, SocketAddr};
use std::path::PathBuf; use std::path::PathBuf;
use clap::Parser; use clap::Parser;
@@ -10,12 +12,31 @@ use serde::{Deserialize, Serialize};
use tracing::instrument; use tracing::instrument;
use ts_rs::TS; use ts_rs::TS;
use crate::auth::Sessions;
use crate::context::CliContext; use crate::context::CliContext;
use crate::prelude::*; use crate::prelude::*;
use crate::sign::AnyVerifyingKey;
use crate::tunnel::context::TunnelContext; use crate::tunnel::context::TunnelContext;
use crate::tunnel::TunnelDatabase;
use crate::util::serde::{apply_expr, HandlerExtSerde}; use crate::util::serde::{apply_expr, HandlerExtSerde};
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct TunnelDatabase {
pub sessions: Sessions,
pub password: String,
pub auth_pubkeys: HashSet<AnyVerifyingKey>,
pub clients: BTreeMap<Ipv4Addr, ClientInfo>,
pub port_forwards: BTreeMap<SocketAddr, SocketAddr>,
}
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct ClientInfo {
pub server: bool,
}
pub fn db_api<C: Context>() -> ParentHandler<C> { pub fn db_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new() ParentHandler::new()
.subcommand( .subcommand(

View File

View File

@@ -1,4 +1,5 @@
use std::collections::HashSet; use std::collections::{BTreeMap, HashSet};
use std::net::{Ipv4Addr, SocketAddr};
use axum::Router; use axum::Router;
use futures::future::ready; use futures::future::ready;
@@ -17,18 +18,10 @@ use crate::tunnel::context::TunnelContext;
pub mod context; pub mod context;
pub mod db; pub mod db;
pub mod init;
pub const TUNNEL_DEFAULT_PORT: u16 = 5960; pub const TUNNEL_DEFAULT_PORT: u16 = 5960;
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct TunnelDatabase {
pub sessions: Sessions,
pub password: String,
pub auth_pubkeys: HashSet<AnyVerifyingKey>,
}
pub fn tunnel_api<C: Context>() -> ParentHandler<C> { pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new().subcommand( ParentHandler::new().subcommand(
"db", "db",

View File

@@ -359,7 +359,7 @@ impl UploadHandle {
}); });
} }
} }
async fn process_body<E: Into<Box<(dyn std::error::Error + Send + Sync + 'static)>>>( async fn process_body<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>>(
&mut self, &mut self,
mut body: impl Stream<Item = Result<Bytes, E>> + Unpin, mut body: impl Stream<Item = Result<Bytes, E>> + Unpin,
) { ) {

View File

@@ -666,13 +666,27 @@ impl<K: Eq, V> IntoIterator for EqMap<K, V> {
impl<K: Eq, V> Extend<(K, V)> for EqMap<K, V> { impl<K: Eq, V> Extend<(K, V)> for EqMap<K, V> {
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
self.0.extend(iter) let iter = iter.into_iter();
if let (_, Some(len)) = iter.size_hint() {
self.0.reserve(len)
}
for (k, v) in iter {
self.insert(k, v);
}
} }
} }
impl<K: Eq, V> FromIterator<(K, V)> for EqMap<K, V> { impl<K: Eq, V> FromIterator<(K, V)> for EqMap<K, V> {
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
Self(Vec::from_iter(iter)) let mut res = Self(Vec::new());
let iter = iter.into_iter();
if let (_, Some(len)) = iter.size_hint() {
res.0.reserve(len)
}
for (k, v) in iter {
res.insert(k, v);
}
res
} }
} }
@@ -687,7 +701,7 @@ impl<K: Eq, V, const N: usize> From<[(K, V); N]> for EqMap<K, V> {
/// assert_eq!(map1, map2); /// assert_eq!(map1, map2);
/// ``` /// ```
fn from(arr: [(K, V); N]) -> Self { fn from(arr: [(K, V); N]) -> Self {
EqMap(Vec::from(arr)) Self::from_iter(arr)
} }
} }

View File

@@ -0,0 +1,425 @@
use std::borrow::Borrow;
use std::fmt;
use std::marker::PhantomData;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize)]
pub struct EqSet<T: Eq>(Vec<T>);
impl<T: Eq> Default for EqSet<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T: Eq> EqSet<T> {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.0.clear()
}
/// Returns a reference to the element in the set, if any, that is equal to
/// the value.
///
/// The value may be any borrowed form of the set's element type,
/// but the ordering on the borrowed form *must* match the
/// ordering on the element type.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let set = EqSet::from([1, 2, 3]);
/// assert_eq!(set.get(&2), Some(&2));
/// assert_eq!(set.get(&4), None);
/// ```
pub fn get<Q: ?Sized>(&self, value: &Q) -> Option<&T>
where
T: Borrow<Q>,
Q: Eq,
{
self.0.iter().find(|k| (*k).borrow() == value)
}
/// Removes and returns an element in the set.
/// There is no guarantee about which element this might be
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut set = EqSet::new();
/// set.insert("a");
/// set.insert("b");
/// while let Some(_val) = set.pop() { }
/// assert!(set.is_empty());
/// ```
pub fn pop(&mut self) -> Option<T> {
self.0.pop()
}
/// Returns `true` if the set contains a value for the specified value.
///
/// The value may be any borrowed form of the set's value type, but the equality
/// on the borrowed form *must* match the equality on the value type.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut set = EqSet::new();
/// set.insert("a");
/// assert_eq!(set.contains("a"), true);
/// assert_eq!(set.contains("b"), false);
/// ```
pub fn contains<Q: ?Sized>(&self, value: &Q) -> bool
where
T: Borrow<Q>,
Q: Eq,
{
self.get(value).is_some()
}
/// Inserts a value into the set.
///
/// If the set did not have this value present, `None` is returned.
///
/// If the set did have this value present, the value is updated, and the old
/// value is returned.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut set = EqSet::new();
/// assert_eq!(set.insert("a"), None);
/// assert_eq!(set.is_empty(), false);
///
/// set.insert("b");
/// assert_eq!(set.insert("b"), Some("b"));
/// assert!(set.contains("a"));
/// ```
pub fn insert(&mut self, value: T) -> Option<T> {
if let Some(entry) = self.0.iter_mut().find(|a| *a == &value) {
Some(std::mem::replace(entry, value))
} else {
self.0.push(value);
None
}
}
/// Tries to insert a value into the set.
///
/// If the set already had this value present, nothing is updated.
///
/// Returns whether the value was inserted.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut set = EqSet::new();
/// assert!(set.try_insert("a"));
/// assert!(!set.try_insert("a"));
/// ```
pub fn try_insert(&mut self, value: T) -> bool {
if self.0.iter().find(|a| *a == &value).is_some() {
false
} else {
self.0.push(value);
true
}
}
/// Removes a value from the set, returning the value if it
/// was previously in the set.
///
/// The value may be any borrowed form of the set's value type, but the equality
/// on the borrowed form *must* match the equality on the value type.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut set = EqSet::new();
/// set.insert("a");
/// assert_eq!(set.remove("a"), Some("a"));
/// assert_eq!(set.remove("a"), None);
/// ```
pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> Option<T>
where
T: Borrow<Q>,
Q: Eq,
{
if let Some((idx, _)) = self
.0
.iter()
.enumerate()
.find(|(_, v)| (*v).borrow() == value)
{
Some(self.0.swap_remove(idx))
} else {
None
}
}
/// Retains only the elements specified by the predicate.
///
/// In other words, remove all pairs `(k, v)` for which `f(&k, &mut v)` returns `false`.
/// The elements are visited in ascending value order.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut set: EqSet<i32, i32> = (0..8).set(|x| (x, x*10)).collect();
/// // Keep only the elements with even-numbered values.
/// set.retain(|&k, _| k % 2 == 0);
/// assert!(set.into_iter().eq(vec![(0, 0), (2, 20), (4, 40), (6, 60)]));
/// ```
#[inline]
pub fn retain<F>(&mut self, f: F)
where
F: FnMut(&T) -> bool,
{
self.0.retain(f)
}
/// Moves all elements from `other` into `self`, leaving `other` empty.
///
/// If a value from `other` is already present in `self`, the respective
/// value from `self` will be overwritten with the respective value from `other`.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut a = EqSet::new();
/// a.insert("a");
/// a.insert("b");
/// a.insert("c"); // Note: "c" also present in b.
///
/// let mut b = EqSet::new();
/// b.insert(3, "c"); // Note: "c" also present in a.
/// b.insert(4, "d");
/// b.insert(5, "e");
///
/// a.append(&mut b);
///
/// assert_eq!(a.len(), 5);
/// assert_eq!(b.len(), 0);
/// ```
pub fn append(&mut self, other: &mut Self) {
other.retain(|v| !self.contains(v));
self.0.append(&mut other.0)
}
// /// Creates an iterator that visits all elements (values) and
// /// uses a closure to determine if an element should be removed. If the
// /// closure returns `true`, the element is removed from the set and yielded.
// /// If the closure returns `false`, or panics, the element remains in the set
// /// and will not be yielded.
// ///
// /// The iterator also lets you mutate the value of each element in the
// /// closure, regardless of whether you choose to keep or remove it.
// ///
// /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating
// /// or the iteration short-circuits, then the remaining elements will be retained.
// /// Use [`retain`] with a negated predicate if you do not need the returned iterator.
// ///
// /// [`retain`]: EqSet::retain
// ///
// /// # Examples
// ///
// /// Splitting a set into even and odd values, reusing the original set:
// ///
// /// ```
// /// use startos::util::collections::EqSet;
// ///
// /// let mut set: EqSet<i32, i32> = (0..8).set(|x| (x, x)).collect();
// /// let evens: EqSet<_, _> = set.extract_if(|k, _v| k % 2 == 0).collect();
// /// let odds = set;
// /// assert_eq!(evens.values().copied().collect::<Vec<_>>(), [0, 2, 4, 6]);
// /// assert_eq!(odds.values().copied().collect::<Vec<_>>(), [1, 3, 5, 7]);
// /// ```
// pub fn extract_if<F>(&mut self, pred: F) -> ExtractIf<'_, T, F>
// where
// K: Eq,
// F: FnMut(&K, &mut V) -> bool,
// {
// let (inner, alloc) = self.extract_if_inner();
// ExtractIf { pred, inner, alloc }
// }
/// Gets an iterator over the entries of the set, in no particular order.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut set = EqSet::new();
/// set.insert("c");
/// set.insert("b");
/// set.insert("a");
///
/// for value in set.iter() {
/// println!("{value}");
/// }
///
/// let first_value = set.iter().next().unwrap();
/// assert_eq!(*first_value, "c");
/// ```
pub fn iter(&self) -> std::slice::Iter<'_, T> {
self.0.iter()
}
/// Returns the number of elements in the set.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut a = EqSet::new();
/// assert_eq!(a.len(), 0);
/// a.insert("a");
/// assert_eq!(a.len(), 1);
/// ```
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
/// Returns `true` if the set contains no elements.
///
/// # Examples
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let mut a = EqSet::new();
/// assert!(a.is_empty());
/// a.insert("a");
/// assert!(!a.is_empty());
/// ```
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<T: fmt::Debug + Eq> fmt::Debug for EqSet<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
impl<T: Eq> IntoIterator for EqSet<T> {
type IntoIter = std::vec::IntoIter<T>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<T: Eq> Extend<T> for EqSet<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
let iter = iter.into_iter();
if let (_, Some(len)) = iter.size_hint() {
self.0.reserve(len)
}
for v in iter {
self.insert(v);
}
}
}
impl<T: Eq> FromIterator<T> for EqSet<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut res = Self(Vec::new());
let iter = iter.into_iter();
if let (_, Some(len)) = iter.size_hint() {
res.0.reserve(len)
}
for v in iter {
res.insert(v);
}
res
}
}
impl<T: Eq, const N: usize> From<[T; N]> for EqSet<T> {
/// Converts a `[T; N]` into a `EqSet<T>`.
///
/// ```
/// use startos::util::collections::EqSet;
///
/// let set1 = EqSet::from([(1, 2), (3, 4)]);
/// let set2: EqSet<_, _> = [(1, 2), (3, 4)].into();
/// assert_eq!(set1, set2);
/// ```
fn from(arr: [T; N]) -> Self {
EqSet::from_iter(arr)
}
}
impl<T: Eq> PartialEq for EqSet<T> {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().all(|v| other.get(v) == Some(v))
}
}
impl<T: Eq> Eq for EqSet<T> {}
impl<'de, T> Deserialize<'de> for EqSet<T>
where
T: Deserialize<'de> + Eq,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor<T> {
marker: PhantomData<T>,
}
impl<'de, T> serde::de::Visitor<'de> for Visitor<T>
where
T: Deserialize<'de> + Eq,
{
type Value = EqSet<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut values = EqSet(Vec::new());
while let Some(value) = seq.next_element()? {
values.insert(value);
}
Ok(values)
}
}
let visitor = Visitor {
marker: PhantomData,
};
deserializer.deserialize_seq(visitor)
}
}

View File

@@ -1,3 +1,44 @@
pub mod eq_map; pub mod eq_map;
pub mod eq_set;
use std::marker::PhantomData;
pub use eq_map::EqMap; pub use eq_map::EqMap;
pub use eq_set::EqSet;
use imbl::OrdMap;
pub struct OrdMapIterMut<'a, K: 'a, V: 'a> {
map: *mut OrdMap<K, V>,
prev: Option<&'a K>,
_marker: PhantomData<&'a mut (K, V)>,
}
impl<'a, K, V> From<&'a mut OrdMap<K, V>> for OrdMapIterMut<'a, K, V> {
fn from(value: &'a mut OrdMap<K, V>) -> Self {
Self {
map: value,
prev: None,
_marker: PhantomData,
}
}
}
impl<'a, K: Ord + Clone, V: Clone> Iterator for OrdMapIterMut<'a, K, V> {
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let map: &'a mut OrdMap<K, V> = self.map.as_mut().unwrap();
let res = if let Some(k) = self.prev.take() {
map.get_next_mut(k)
} else {
let Some((k, _)) = map.get_min() else {
return None;
};
let k = k.clone(); // hate that I have to do this but whatev
map.get_key_value_mut(&k)
};
if let Some((k, _)) = &res {
self.prev = Some(*k);
}
res
}
}
}

View File

@@ -7,7 +7,7 @@ use base64::Engine;
use clap::builder::ValueParserFactory; use clap::builder::ValueParserFactory;
use clap::{ArgMatches, CommandFactory, FromArgMatches}; use clap::{ArgMatches, CommandFactory, FromArgMatches};
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use imbl::OrdMap; use imbl_value::imbl::OrdMap;
use models::FromStrParser; use models::FromStrParser;
use openssl::pkey::{PKey, Private}; use openssl::pkey::{PKey, Private};
use openssl::x509::X509; use openssl::x509::X509;

View File

@@ -140,6 +140,9 @@ impl<T: Clone> Watch<T> {
pub fn read(&self) -> T { pub fn read(&self) -> T {
self.peek(|a| a.clone()) self.peek(|a| a.clone())
} }
pub fn read_and_mark_seen(&mut self) -> T {
self.peek_and_mark_seen(|a| a.clone())
}
} }
impl<T: Clone> futures::Stream for Watch<T> { impl<T: Clone> futures::Stream for Watch<T> {
type Item = T; type Item = T;

View File

@@ -0,0 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type BindingGatewaySetEnabledParams = {
internalPort: number
gateway: GatewayId
enabled: boolean | null
}

View File

@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type ForgetInterfaceParams = { interface: string } export type ForgetInterfaceParams = { interface: GatewayId }

View File

@@ -1,6 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type BindingSetPublicParams = { export type GatewayId = string
internalPort: number
public: boolean | null
}

View File

@@ -3,10 +3,5 @@ import type { IpHostname } from "./IpHostname"
import type { OnionHostname } from "./OnionHostname" import type { OnionHostname } from "./OnionHostname"
export type HostnameInfo = export type HostnameInfo =
| { | { kind: "ip"; gatewayId: string; public: boolean; hostname: IpHostname }
kind: "ip"
networkInterfaceId: string
public: boolean
hostname: IpHostname
}
| { kind: "onion"; hostname: OnionHostname } | { kind: "onion"; hostname: OnionHostname }

View File

@@ -1,4 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PasswordType } from "./PasswordType"
export type LoginParams = { password: PasswordType | null; ephemeral: boolean } export type LoginParams = { password: string; ephemeral: boolean }

View File

@@ -1,7 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type NetInfo = { export type NetInfo = {
public: boolean privateDisabled: Array<GatewayId>
publicEnabled: Array<GatewayId>
assignedPort: number | null assignedPort: number | null
assignedSslPort: number | null assignedSslPort: number | null
} }

View File

@@ -1,6 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AcmeProvider } from "./AcmeProvider" import type { AcmeProvider } from "./AcmeProvider"
import type { AcmeSettings } from "./AcmeSettings" import type { AcmeSettings } from "./AcmeSettings"
import type { GatewayId } from "./GatewayId"
import type { Host } from "./Host" import type { Host } from "./Host"
import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo" import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
import type { WifiInfo } from "./WifiInfo" import type { WifiInfo } from "./WifiInfo"
@@ -8,6 +9,6 @@ import type { WifiInfo } from "./WifiInfo"
export type NetworkInfo = { export type NetworkInfo = {
wifi: WifiInfo wifi: WifiInfo
host: Host host: Host
networkInterfaces: { [key: string]: NetworkInterfaceInfo } networkInterfaces: { [key: GatewayId]: NetworkInterfaceInfo }
acme: { [key: AcmeProvider]: AcmeSettings } acme: { [key: AcmeProvider]: AcmeSettings }
} }

View File

@@ -3,5 +3,6 @@ import type { IpInfo } from "./IpInfo"
export type NetworkInterfaceInfo = { export type NetworkInterfaceInfo = {
public: boolean | null public: boolean | null
secure: boolean | null
ipInfo: IpInfo | null ipInfo: IpInfo | null
} }

View File

@@ -1,6 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type NetworkInterfaceSetPublicParams = { export type NetworkInterfaceSetPublicParams = {
interface: string interface: GatewayId
public: boolean | null public: boolean | null
} }

View File

@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type RemoveTunnelParams = { id: string } export type RemoveTunnelParams = { id: GatewayId }

View File

@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type RenameInterfaceParams = { interface: string; name: string } export type RenameInterfaceParams = { interface: GatewayId; name: string }

View File

@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type UnsetInboundParams = { interface: string } export type UnsetInboundParams = { interface: GatewayId }

View File

@@ -34,7 +34,7 @@ export { BackupTargetFS } from "./BackupTargetFS"
export { Base64 } from "./Base64" export { Base64 } from "./Base64"
export { BindId } from "./BindId" export { BindId } from "./BindId"
export { BindInfo } from "./BindInfo" export { BindInfo } from "./BindInfo"
export { BindingSetPublicParams } from "./BindingSetPublicParams" export { BindingGatewaySetEnabledParams } from "./BindingGatewaySetEnabledParams"
export { BindOptions } from "./BindOptions" export { BindOptions } from "./BindOptions"
export { BindParams } from "./BindParams" export { BindParams } from "./BindParams"
export { Blake3Commitment } from "./Blake3Commitment" export { Blake3Commitment } from "./Blake3Commitment"
@@ -78,6 +78,7 @@ export { FileType } from "./FileType"
export { ForgetInterfaceParams } from "./ForgetInterfaceParams" export { ForgetInterfaceParams } from "./ForgetInterfaceParams"
export { FullIndex } from "./FullIndex" export { FullIndex } from "./FullIndex"
export { FullProgress } from "./FullProgress" export { FullProgress } from "./FullProgress"
export { GatewayId } from "./GatewayId"
export { GetActionInputParams } from "./GetActionInputParams" export { GetActionInputParams } from "./GetActionInputParams"
export { GetContainerIpParams } from "./GetContainerIpParams" export { GetContainerIpParams } from "./GetContainerIpParams"
export { GetHostInfoParams } from "./GetHostInfoParams" export { GetHostInfoParams } from "./GetHostInfoParams"