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

View File

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

View File

@@ -1,4 +1,3 @@
use std::path::Path;
use std::sync::Arc;
use tokio::process::Command;
@@ -48,7 +47,7 @@ async fn setup_or_init(
update_phase.complete();
reboot_phase.start();
return Ok(Err(Shutdown {
export_args: None,
disk_guid: None,
restart: true,
}));
}
@@ -103,7 +102,7 @@ async fn setup_or_init(
.expect("context dropped");
return Ok(Err(Shutdown {
export_args: None,
disk_guid: None,
restart: true,
}));
}
@@ -117,7 +116,9 @@ async fn setup_or_init(
server.serve_setup(ctx.clone());
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;
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));
reboot_phase.start();
return Ok(Err(Shutdown {
export_args: Some((disk_guid, Path::new(DATA_DIR).to_owned())),
disk_guid: Some(disk_guid),
restart: true,
}));
}

View File

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

View File

@@ -174,7 +174,7 @@ impl RpcContext {
)
.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?;
(net_ctrl, os_net_service)
};

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ use std::marker::PhantomData;
use std::str::FromStr;
use chrono::{DateTime, Utc};
use imbl::OrdMap;
pub use imbl_value::Value;
use patch_db::value::InternedString;
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>
where
T::Value: Serialize,

View File

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

View File

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

View File

@@ -216,7 +216,7 @@ pub async fn init(
)
.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?;
start_net.complete();

View File

@@ -1,5 +1,6 @@
use std::borrow::Borrow;
use std::collections::BTreeMap;
use std::future::Future;
use std::net::Ipv4Addr;
use std::sync::{Arc, Weak};
use std::time::Duration;
@@ -18,7 +19,6 @@ use trust_dns_server::server::{Request, RequestHandler, ResponseHandler, Respons
use trust_dns_server::ServerFuture;
use crate::net::forward::START9_BRIDGE_IFACE;
use crate::util::sync::Watch;
use crate::util::Invoke;
use crate::{Error, ErrorKind, ResultExt};
@@ -140,7 +140,9 @@ impl RequestHandler for Resolver {
impl DnsController {
#[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 mut server = ServerFuture::new(Resolver {
@@ -160,7 +162,7 @@ impl DnsController {
.with_kind(ErrorKind::Network)?,
);
lxcbr_status.wait_for(|a| *a).await;
bridge_activated.await;
Command::new("resolvectl")
.arg("dns")

View File

@@ -1,16 +1,19 @@
use std::collections::{BTreeMap, BTreeSet};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::net::{IpAddr, SocketAddr, SocketAddrV6};
use std::sync::{Arc, Weak};
use futures::channel::oneshot;
use helpers::NonDetachingJoinHandle;
use id_pool::IdPool;
use imbl_value::InternedString;
use imbl::OrdMap;
use models::GatewayId;
use serde::{Deserialize, Serialize};
use tokio::process::Command;
use tokio::sync::mpsc;
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::util::sync::Watch;
use crate::util::Invoke;
@@ -39,106 +42,162 @@ impl AvailablePorts {
}
}
#[derive(Debug)]
struct ForwardRequest {
public: bool,
external: u16,
target: SocketAddr,
filter: DynInterfaceFilter,
rc: Weak<()>,
}
#[derive(Debug, Default)]
struct ForwardState {
requested: BTreeMap<u16, ForwardRequest>,
current: BTreeMap<u16, BTreeMap<InternedString, SocketAddr>>,
struct ForwardEntry {
external: u16,
target: SocketAddr,
prev_filter: DynInterfaceFilter,
forwards: BTreeMap<SocketAddr, GatewayId>,
rc: Weak<()>,
}
impl ForwardState {
async fn sync(
impl ForwardEntry {
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,
interfaces: &BTreeMap<InternedString, (bool, Vec<Ipv4Addr>)>,
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
filter: Option<DynInterfaceFilter>,
) -> 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()
.filter(|(_, (public, _))| !*public)
.map(|(i, _)| i)
.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<_>>()
.chain([NetworkInterfaceInfo::loopback()])
.filter(|(id, info)| filter_ref.filter(*id, *info))
{
match (
self.requested.get(&external),
self.current.get_mut(&external),
) {
(Some(req), Some(cur)) => {
let expected = if req.public {
&all_interfaces
} else {
&private_interfaces
if let Some(ip_info) = &info.ip_info {
for ipnet in &ip_info.subnets {
let addr = match ipnet.addr() {
IpAddr::V6(ip6) => SocketAddrV6::new(
ip6,
self.external,
0,
if ipv6_is_link_local(ip6) {
ip_info.scope_id
} else {
0
},
)
.into(),
ip => SocketAddr::new(ip, self.external),
};
let actual = cur.keys().collect::<BTreeSet<_>>();
let mut to_rm = actual
.difference(expected)
.copied()
.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?;
}
keep.insert(addr);
if !self.forwards.contains_key(&addr) {
forward(iface.as_str(), addr, self.target).await?;
self.forwards.insert(addr, iface.clone());
}
}
(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(())
}
}
@@ -150,87 +209,37 @@ fn err_has_exited<T>(_: T) -> Error {
)
}
pub struct LanPortForwardController {
req: mpsc::UnboundedSender<(
Option<(u16, ForwardRequest)>,
oneshot::Sender<Result<(), Error>>,
)>,
pub struct PortForwardController {
req: mpsc::UnboundedSender<(Option<ForwardRequest>, oneshot::Sender<Result<(), Error>>)>,
_thread: NonDetachingJoinHandle<()>,
}
impl LanPortForwardController {
pub fn new(mut ip_info: Watch<BTreeMap<InternedString, NetworkInterfaceInfo>>) -> Self {
let (req_send, mut req_recv) = mpsc::unbounded_channel();
impl PortForwardController {
pub fn new(mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>) -> Self {
let (req_send, mut req_recv) = mpsc::unbounded_channel::<(
Option<ForwardRequest>,
oneshot::Sender<Result<(), Error>>,
)>();
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
let mut state = ForwardState::default();
let mut interfaces = ip_info.peek_and_mark_seen(|ip_info| {
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;
let mut interfaces = ip_info.read_and_mark_seen();
loop {
tokio::select! {
msg = req_recv.recv() => {
if let Some((msg, re)) = msg {
if let Some((external, req)) = msg {
state.requested.insert(external, req);
if let Some(req) = msg {
re.send(state.handle_request(req, &interfaces).await).ok();
} else {
re.send(state.sync(&interfaces).await).ok();
}
reply = Some(re);
} else {
break;
}
}
_ = ip_info.changed() => {
interfaces = ip_info.peek(|ip_info| {
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()
});
interfaces = ip_info.read();
state.sync(&interfaces).await.log_err();
}
}
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 {
@@ -238,19 +247,22 @@ impl LanPortForwardController {
_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 (send, recv) = oneshot::channel();
self.req
.send((
Some((
port,
ForwardRequest {
public,
target,
rc: Arc::downgrade(&rc),
},
)),
Some(ForwardRequest {
external,
target,
filter: filter.into_dyn(),
rc: Arc::downgrade(&rc),
}),
send,
))
.map_err(err_has_exited)?;

View File

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

View File

@@ -1,16 +1,19 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::str::FromStr;
use clap::builder::ValueParserFactory;
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 serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::context::{CliContext, RpcContext};
use crate::db::model::public::NetworkInterfaceInfo;
use crate::net::forward::AvailablePorts;
use crate::net::host::HostApiKind;
use crate::net::network_interface::InterfaceFilter;
use crate::net::vhost::AlpnInfo;
use crate::prelude::*;
use crate::util::serde::{display_serializable, HandlerExtSerde};
@@ -50,11 +53,14 @@ pub struct BindInfo {
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")]
#[ts(export)]
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_ssl_port: Option<u16>,
}
@@ -65,16 +71,19 @@ impl BindInfo {
if options.add_ssl.is_some() {
assigned_ssl_port = Some(available_ports.alloc()?);
}
if let Some(secure) = options.secure {
if !secure.ssl || !options.add_ssl.is_some() {
assigned_port = Some(available_ports.alloc()?);
}
if options
.secure
.map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
{
assigned_port = Some(available_ports.alloc()?);
}
Ok(Self {
enabled: true,
options,
net: NetInfo {
public: false,
private_disabled: OrdSet::new(),
public_enabled: OrdSet::new(),
assigned_port,
assigned_ssl_port,
},
@@ -88,7 +97,7 @@ impl BindInfo {
let Self { net: mut lan, .. } = self;
if options
.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
{
lan.assigned_port = if let Some(port) = lan.assigned_port.take() {
@@ -122,6 +131,15 @@ impl BindInfo {
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)]
#[ts(export)]
@@ -165,12 +183,11 @@ pub fn binding<C: Context, Kind: HostApiKind>(
}
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 {
table.add_row(row![
internal,
info.enabled,
info.net.public,
if let Some(port) = info.net.assigned_port {
port.to_string()
} else {
@@ -192,12 +209,12 @@ pub fn binding<C: Context, Kind: HostApiKind>(
.with_call_remote::<CliContext>(),
)
.subcommand(
"set-public",
from_fn_async(set_public::<Kind>)
"set-gateway-enabled",
from_fn_async(set_gateway_enabled::<Kind>)
.with_metadata("sync_db", Value::Bool(true))
.with_inherited(Kind::inheritance)
.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>(),
)
}
@@ -215,29 +232,50 @@ pub async fn list_bindings<Kind: HostApiKind>(
#[derive(Deserialize, Serialize, Parser, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct BindingSetPublicParams {
pub struct BindingGatewaySetEnabledParams {
internal_port: u16,
gateway: GatewayId,
#[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,
BindingSetPublicParams {
BindingGatewaySetEnabledParams {
internal_port,
public,
}: BindingSetPublicParams,
gateway,
enabled,
}: BindingGatewaySetEnabledParams,
inheritance: Kind::Inheritance,
) -> 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
.mutate(|db| {
Kind::host_for(&inheritance, db)?
.as_bindings_mut()
.mutate(|b| {
b.get_mut(&internal_port)
.or_not_found(internal_port)?
.net
.public = public.unwrap_or(true);
let net = &mut b.get_mut(&internal_port).or_not_found(internal_port)?.net;
if gateway_public {
if enabled {
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(())
})
})

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
use std::future::Future;
use std::net::SocketAddr;
use std::ops::Deref;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock};
use std::sync::Arc;
use std::task::Poll;
use std::time::Duration;
@@ -16,7 +15,7 @@ use tokio::sync::oneshot;
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
use crate::net::network_interface::{
NetworkInterfaceListener, SelfContainedNetworkInterfaceListener,
lookup_info_by_addr, NetworkInterfaceListener, SelfContainedNetworkInterfaceListener,
};
use crate::net::static_server::{
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 {
fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
NetworkInterfaceListener::poll_accept(self, cx, true).map(|res| {
res.map(|a| Accepted {
https_redirect: a.is_public,
stream: a.stream,
NetworkInterfaceListener::poll_accept(self, cx, &true).map(|res| {
res.map(|a| {
let public = self
.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()?
.get(&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::progress::{FullProgress, PhaseProgressTrackerHandle, ProgressUnits};
use crate::rpc_continuations::Guid;
use crate::shutdown::Shutdown;
use crate::system::sync_kiosk;
use crate::util::crypto::EncryptedWire;
use crate::util::io::{create_file, dir_copy, dir_size, Counter};
@@ -67,6 +68,7 @@ pub fn setup<C: Context>() -> ParentHandler<C> {
"logs",
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> {
@@ -172,6 +174,7 @@ pub async fn attach(
if disk_guid.ends_with("_UNENC") { None } else { Some(DEFAULT_PASSWORD) },
)
.await?;
let _ = setup_ctx.disk_guid.set(disk_guid.clone());
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
tokio::fs::remove_file(REPAIR_DISK_PATH)
.await
@@ -390,9 +393,19 @@ pub async fn complete(ctx: SetupContext) -> Result<SetupResult, Error> {
}
#[instrument(skip_all)]
// #[command(rpc_only)]
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(())
}
@@ -435,6 +448,7 @@ pub async fn execute_inner(
);
let _ = crate::disk::main::import(&*guid, DATA_DIR, RepairStrategy::Preen, encryption_password)
.await?;
let _ = ctx.disk_guid.set(guid.clone());
disk_phase.complete();
let progress = SetupExecuteProgress {

View File

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

View File

@@ -1,10 +1,11 @@
use std::collections::BTreeSet;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use clap::Parser;
use imbl::OrdMap;
use imbl_value::InternedString;
use patch_db::PatchDb;
use rpc_toolkit::yajrc::RpcError;
@@ -15,13 +16,15 @@ use tracing::instrument;
use crate::auth::{check_password, Sessions};
use crate::context::config::ContextConfig;
use crate::context::{CliContext, RpcContext};
use crate::context::CliContext;
use crate::middleware::auth::AuthContext;
use crate::middleware::signature::SignatureAuthContext;
use crate::net::forward::PortForwardController;
use crate::net::network_interface::NetworkInterfaceWatcher;
use crate::prelude::*;
use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations};
use crate::tunnel::{TunnelDatabase, TUNNEL_DEFAULT_PORT};
use crate::util::iter::TransposeResultIterExt;
use crate::tunnel::db::TunnelDatabase;
use crate::tunnel::TUNNEL_DEFAULT_PORT;
use crate::util::sync::SyncMutex;
#[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)]
@@ -62,6 +65,8 @@ pub struct TunnelContextSeed {
pub rpc_continuations: RpcContinuations,
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
pub ephemeral_sessions: SyncMutex<Sessions>,
pub net_iface: NetworkInterfaceWatcher,
pub forward: PortForwardController,
pub shutdown: Sender<()>,
}
@@ -89,6 +94,8 @@ impl TunnelContext {
Ipv6Addr::UNSPECIFIED.into(),
TUNNEL_DEFAULT_PORT,
));
let net_iface = NetworkInterfaceWatcher::new(async { OrdMap::new() }, []);
let forward = PortForwardController::new(net_iface.subscribe());
Ok(Self(Arc::new(TunnelContextSeed {
listen,
addrs: crate::net::utils::all_socket_addrs_for(listen.port())
@@ -101,6 +108,8 @@ impl TunnelContext {
rpc_continuations: RpcContinuations::new(),
open_authed_continuations: OpenAuthedContinuations::new(),
ephemeral_sessions: SyncMutex::new(Sessions::new()),
net_iface,
forward,
shutdown,
})))
}
@@ -213,14 +222,3 @@ impl CallRemote<TunnelContext> for CliContext {
.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 clap::Parser;
@@ -10,12 +12,31 @@ use serde::{Deserialize, Serialize};
use tracing::instrument;
use ts_rs::TS;
use crate::auth::Sessions;
use crate::context::CliContext;
use crate::prelude::*;
use crate::sign::AnyVerifyingKey;
use crate::tunnel::context::TunnelContext;
use crate::tunnel::TunnelDatabase;
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> {
ParentHandler::new()
.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 futures::future::ready;
@@ -17,18 +18,10 @@ use crate::tunnel::context::TunnelContext;
pub mod context;
pub mod db;
pub mod init;
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> {
ParentHandler::new().subcommand(
"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 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> {
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> {
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);
/// ```
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_set;
use std::marker::PhantomData;
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::{ArgMatches, CommandFactory, FromArgMatches};
use color_eyre::eyre::eyre;
use imbl::OrdMap;
use imbl_value::imbl::OrdMap;
use models::FromStrParser;
use openssl::pkey::{PKey, Private};
use openssl::x509::X509;

View File

@@ -140,6 +140,9 @@ impl<T: Clone> Watch<T> {
pub fn read(&self) -> T {
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> {
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.
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.
export type BindingSetPublicParams = {
internalPort: number
public: boolean | null
}
export type GatewayId = string

View File

@@ -3,10 +3,5 @@ import type { IpHostname } from "./IpHostname"
import type { OnionHostname } from "./OnionHostname"
export type HostnameInfo =
| {
kind: "ip"
networkInterfaceId: string
public: boolean
hostname: IpHostname
}
| { kind: "ip"; gatewayId: string; public: boolean; hostname: IpHostname }
| { 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.
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.
import type { GatewayId } from "./GatewayId"
export type NetInfo = {
public: boolean
privateDisabled: Array<GatewayId>
publicEnabled: Array<GatewayId>
assignedPort: 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.
import type { AcmeProvider } from "./AcmeProvider"
import type { AcmeSettings } from "./AcmeSettings"
import type { GatewayId } from "./GatewayId"
import type { Host } from "./Host"
import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
import type { WifiInfo } from "./WifiInfo"
@@ -8,6 +9,6 @@ import type { WifiInfo } from "./WifiInfo"
export type NetworkInfo = {
wifi: WifiInfo
host: Host
networkInterfaces: { [key: string]: NetworkInterfaceInfo }
networkInterfaces: { [key: GatewayId]: NetworkInterfaceInfo }
acme: { [key: AcmeProvider]: AcmeSettings }
}

View File

@@ -3,5 +3,6 @@ import type { IpInfo } from "./IpInfo"
export type NetworkInterfaceInfo = {
public: boolean | null
secure: boolean | 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.
import type { GatewayId } from "./GatewayId"
export type NetworkInterfaceSetPublicParams = {
interface: string
interface: GatewayId
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.
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.
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.
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 { BindId } from "./BindId"
export { BindInfo } from "./BindInfo"
export { BindingSetPublicParams } from "./BindingSetPublicParams"
export { BindingGatewaySetEnabledParams } from "./BindingGatewaySetEnabledParams"
export { BindOptions } from "./BindOptions"
export { BindParams } from "./BindParams"
export { Blake3Commitment } from "./Blake3Commitment"
@@ -78,6 +78,7 @@ export { FileType } from "./FileType"
export { ForgetInterfaceParams } from "./ForgetInterfaceParams"
export { FullIndex } from "./FullIndex"
export { FullProgress } from "./FullProgress"
export { GatewayId } from "./GatewayId"
export { GetActionInputParams } from "./GetActionInputParams"
export { GetContainerIpParams } from "./GetContainerIpParams"
export { GetHostInfoParams } from "./GetHostInfoParams"