diff --git a/core/Cargo.lock b/core/Cargo.lock index 4078946be..983caab2d 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -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", diff --git a/core/models/src/id/gateway.rs b/core/models/src/id/gateway.rs new file mode 100644 index 000000000..7721a7301 --- /dev/null +++ b/core/models/src/id/gateway.rs @@ -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 From for GatewayId +where + T: Into, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} +impl FromStr for GatewayId { + type Err = Infallible; + fn from_str(s: &str) -> Result { + Ok(GatewayId(InternedString::intern(s))) + } +} +impl AsRef 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 for GatewayId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl AsRef for GatewayId { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} +impl<'de> Deserialize<'de> for GatewayId { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + Ok(GatewayId(serde::Deserialize::deserialize(deserializer)?)) + } +} diff --git a/core/models/src/id/mod.rs b/core/models/src/id/mod.rs index 2039c7242..fe4503941 100644 --- a/core/models/src/id/mod.rs +++ b/core/models/src/id/mod.rs @@ -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; diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 35827a419..7f3e21f5c 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -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"] } diff --git a/core/startos/src/bins/start_init.rs b/core/startos/src/bins/start_init.rs index fdd0e075d..8bf20ce31 100644 --- a/core/startos/src/bins/start_init.rs +++ b/core/startos/src/bins/start_init.rs @@ -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, })); } diff --git a/core/startos/src/context/cli.rs b/core/startos/src/context/cli.rs index 533c627c9..f8eb1207d 100644 --- a/core/startos/src/context/cli.rs +++ b/core/startos/src/context/cli.rs @@ -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 { diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index cfbf4375f..ba164ddcd 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -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) }; diff --git a/core/startos/src/context/setup.rs b/core/startos/src/context/setup.rs index 2cd3b2f73..4902e7a7f 100644 --- a/core/startos/src/context/setup.rs +++ b/core/startos/src/context/setup.rs @@ -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>, pub result: OnceCell>, - pub shutdown: Sender<()>, + pub disk_guid: OnceCell>, + pub shutdown: Sender>, 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(), }))) diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index ef6566476..d00acbd81 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -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::")] + #[ts(as = "BTreeMap::")] #[serde(default)] - pub network_interfaces: BTreeMap, + pub network_interfaces: OrdMap, #[serde(default)] pub acme: BTreeMap, } @@ -199,9 +202,33 @@ pub struct NetworkInfo { #[ts(export)] pub struct NetworkInterfaceInfo { pub public: Option, + pub secure: Option, pub ip_info: Option, } 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, #[ts(type = "string[]")] - pub subnets: BTreeSet, + pub subnets: OrdSet, pub wan_ip: Option, #[ts(type = "string[]")] - pub ntp_servers: BTreeSet, + pub ntp_servers: OrdSet, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, TS)] diff --git a/core/startos/src/db/prelude.rs b/core/startos/src/db/prelude.rs index 30ce13b0f..bb779a3c0 100644 --- a/core/startos/src/db/prelude.rs +++ b/core/startos/src/db/prelude.rs @@ -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 Map for OrdMap +where + A: serde::Serialize + serde::de::DeserializeOwned + Clone + Ord + AsRef, + B: serde::Serialize + serde::de::DeserializeOwned + Clone, +{ + type Key = A; + type Value = B; + fn key_str(key: &Self::Key) -> Result, Error> { + Ok(key.as_ref()) + } +} + impl Model where T::Value: Serialize, diff --git a/core/startos/src/diagnostic.rs b/core/startos/src/diagnostic.rs index 2c6040e8d..49d82c16f 100644 --- a/core/startos/src/diagnostic.rs +++ b/core/startos/src/diagnostic.rs @@ -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() -> ParentHandler { ParentHandler::new() @@ -70,10 +68,7 @@ pub fn error(ctx: DiagnosticContext) -> Result, 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")) diff --git a/core/startos/src/hostname.rs b/core/startos/src/hostname.rs index 862411daf..f650529cc 100644 --- a/core/startos/src/hostname.rs +++ b/core/startos/src/hostname.rs @@ -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}" ))) diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index 6f1c7f011..7bb9a9ee3 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -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(); diff --git a/core/startos/src/net/dns.rs b/core/startos/src/net/dns.rs index beef7c382..e25aaf00c 100644 --- a/core/startos/src/net/dns.rs +++ b/core/startos/src/net/dns.rs @@ -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) -> Result { + pub async fn init( + bridge_activated: impl Future + Send + Sync + 'static, + ) -> Result { 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") diff --git a/core/startos/src/net/forward.rs b/core/startos/src/net/forward.rs index 0c3daca73..4b5fd39fc 100644 --- a/core/startos/src/net/forward.rs +++ b/core/startos/src/net/forward.rs @@ -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, - current: BTreeMap>, +struct ForwardEntry { + external: u16, + target: SocketAddr, + prev_filter: DynInterfaceFilter, + forwards: BTreeMap, + 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)>, + ip_info: &OrdMap, + filter: Option, ) -> 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::::new(); + for (iface, info) in ip_info .iter() - .filter(|(_, (public, _))| !*public) - .map(|(i, _)| i) - .collect::>(); - let all_interfaces = interfaces.keys().collect::>(); - self.requested.retain(|_, req| req.rc.strong_count() > 0); - for external in self - .requested - .keys() - .chain(self.current.keys()) - .copied() - .collect::>() + .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::>(); - let mut to_rm = actual - .difference(expected) - .copied() - .map(|i| (i.clone(), &interfaces[i].1)) - .collect::>(); - let mut to_add = expected - .difference(&actual) - .copied() - .map(|i| (i.clone(), &interfaces[i].1)) - .collect::>(); - 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::>(); - 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::>(); + 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, + ) -> 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, +} +impl ForwardState { + async fn handle_request( + &mut self, + request: ForwardRequest, + ip_info: &OrdMap, + ) -> 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, + ) -> 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) -> Error { ) } -pub struct LanPortForwardController { - req: mpsc::UnboundedSender<( - Option<(u16, ForwardRequest)>, - oneshot::Sender>, - )>, +pub struct PortForwardController { + req: mpsc::UnboundedSender<(Option, oneshot::Sender>)>, _thread: NonDetachingJoinHandle<()>, } -impl LanPortForwardController { - pub fn new(mut ip_info: Watch>) -> Self { - let (req_send, mut req_recv) = mpsc::unbounded_channel(); +impl PortForwardController { + pub fn new(mut ip_info: Watch>) -> Self { + let (req_send, mut req_recv) = mpsc::unbounded_channel::<( + Option, + oneshot::Sender>, + )>(); 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>> = 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, Error> { + pub async fn add( + &self, + external: u16, + filter: impl InterfaceFilter, + target: SocketAddr, + ) -> Result, 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)?; diff --git a/core/startos/src/net/host/address.rs b/core/startos/src/net/host/address.rs index 973f104bd..6939399d9 100644 --- a/core/startos/src/net/host/address.rs +++ b/core/startos/src/net/host/address.rs @@ -131,7 +131,7 @@ pub fn address_api( use prettytable::*; if let Some(format) = params.format { - display_serializable(format, res); + display_serializable(format, res)?; return Ok(()); } diff --git a/core/startos/src/net/host/binding.rs b/core/startos/src/net/host/binding.rs index 96423b9f9..934c9b8be 100644 --- a/core/startos/src/net/host/binding.rs +++ b/core/startos/src/net/host/binding.rs @@ -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::")] + pub private_disabled: OrdSet, + #[ts(as = "BTreeSet::")] + pub public_enabled: OrdSet, pub assigned_port: Option, pub assigned_ssl_port: Option, } @@ -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( } 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( .with_call_remote::(), ) .subcommand( - "set-public", - from_fn_async(set_public::) + "set-gateway-enabled", + from_fn_async(set_gateway_enabled::) .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::(), ) } @@ -215,29 +232,50 @@ pub async fn list_bindings( #[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, + enabled: Option, } -pub async fn set_public( +pub async fn set_gateway_enabled( 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(()) }) }) diff --git a/core/startos/src/net/net_controller.rs b/core/startos/src/net/net_controller.rs index 2c6fabda7..589d77246 100644 --- a/core/startos/src/net/net_controller.rs +++ b/core/startos/src/net/net_controller.rs @@ -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, pub(super) dns: DnsController, - pub(super) forward: LanPortForwardController, + pub(super) forward: PortForwardController, pub(super) server_hostnames: Vec>, pub(crate) callbacks: Arc, } @@ -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)>, + forwards: BTreeMap)>, vhosts: BTreeMap<(Option, u16), (TargetInfo, Arc<()>)>, tor: BTreeMap, Vec>)>, } @@ -217,7 +225,7 @@ impl NetServiceData { } async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> { - let mut forwards: BTreeMap = BTreeMap::new(); + let mut forwards: BTreeMap = BTreeMap::new(); let mut vhosts: BTreeMap<(Option, u16), TargetInfo> = BTreeMap::new(); let mut tor: BTreeMap)> = 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 = 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::>(); 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 { - 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 { diff --git a/core/startos/src/net/network_interface.rs b/core/startos/src/net/network_interface.rs index c3a72502a..f1c428070 100644 --- a/core/startos/src/net/network_interface.rs +++ b/core/startos/src/net/network_interface.rs @@ -1,5 +1,7 @@ +use std::any::Any; use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6}; +use std::future::Future; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; use std::sync::{Arc, Weak}; use std::task::Poll; use std::time::Duration; @@ -7,9 +9,11 @@ use std::time::Duration; use clap::Parser; use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; use helpers::NonDetachingJoinHandle; +use imbl::{OrdMap, OrdSet}; use imbl_value::InternedString; use ipnet::IpNet; use itertools::Itertools; +use models::GatewayId; use nix::net::if_::if_nametoindex; use patch_db::json_ptr::JsonPointer; use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; @@ -29,9 +33,10 @@ use crate::db::model::public::{IpInfo, NetworkInterfaceInfo, NetworkInterfaceTyp use crate::db::model::Database; use crate::net::forward::START9_BRIDGE_IFACE; use crate::net::network_interface::device::DeviceProxy; -use crate::net::utils::{ipv6_is_link_local, ipv6_is_local}; +use crate::net::utils::ipv6_is_link_local; use crate::net::web_server::Accept; use crate::prelude::*; +use crate::util::collections::OrdMapIterMut; use crate::util::future::Until; use crate::util::io::open_file; use crate::util::serde::{display_serializable, HandlerExtSerde}; @@ -117,15 +122,14 @@ pub fn network_interface_api() -> ParentHandler { async fn list_interfaces( ctx: RpcContext, -) -> Result, Error> { - Ok(ctx.net_controller.net_iface.ip_info.read()) +) -> Result, Error> { + Ok(ctx.net_controller.net_iface.watcher.ip_info()) } #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[ts(export)] struct NetworkInterfaceSetPublicParams { - #[ts(type = "string")] - interface: InternedString, + interface: GatewayId, public: Option, } @@ -142,8 +146,7 @@ async fn set_public( #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[ts(export)] struct UnsetInboundParams { - #[ts(type = "string")] - interface: InternedString, + interface: GatewayId, } async fn unset_public( @@ -159,8 +162,7 @@ async fn unset_public( #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[ts(export)] struct ForgetInterfaceParams { - #[ts(type = "string")] - interface: InternedString, + interface: GatewayId, } async fn forget_iface( @@ -173,8 +175,7 @@ async fn forget_iface( #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[ts(export)] struct RenameInterfaceParams { - #[ts(type = "string")] - interface: InternedString, + interface: GatewayId, name: String, } @@ -370,8 +371,8 @@ impl<'a> StubStream<'a> for SignalStream<'a> { #[instrument(skip_all)] async fn watcher( - write_to: Watch>, - lxcbr_status: Watch, + watch_ip_info: Watch>, + watch_activation: Watch>, ) { loop { let res: Result<(), Error> = async { @@ -417,11 +418,14 @@ async fn watcher( let iface = InternedString::intern(device_proxy.ip_interface().await?); if iface.is_empty() { continue; - } else if &*iface == START9_BRIDGE_IFACE { + } + let iface: GatewayId = iface.into(); + if watch_activation.peek(|a| a.contains_key(&iface)) { jobs.push(Either::Left(watch_activated( &connection, device_proxy.clone(), - &lxcbr_status, + iface.clone(), + &watch_activation, ))); } @@ -429,14 +433,14 @@ async fn watcher( &connection, device_proxy.clone(), iface.clone(), - &write_to, + &watch_ip_info, ))); ifaces.insert(iface); } - write_to.send_if_modified(|m| { + watch_ip_info.send_if_modified(|m| { let mut changed = false; - for (iface, info) in m { + for (iface, info) in OrdMapIterMut::from(m) { if !ifaces.contains(iface) { info.ip_info = None; changed = true; @@ -485,8 +489,8 @@ async fn get_wan_ipv4(iface: &str) -> Result, Error> { async fn watch_ip( connection: &Connection, device_proxy: device::DeviceProxy<'_>, - iface: InternedString, - write_to: &Watch>, + iface: GatewayId, + write_to: &Watch>, ) -> Result<(), Error> { let mut until = Until::new() .with_stream( @@ -591,7 +595,7 @@ async fn watch_ip( .into_iter() .chain(ip6_proxy.address_data().await?) .collect_vec(); - let mut ntp_servers = BTreeSet::new(); + let mut ntp_servers = OrdSet::new(); if let Some(dhcp4_proxy) = &dhcp4_proxy { let dhcp = dhcp4_proxy.options().await?; if let Some(ntp) = dhcp.ntp_servers { @@ -601,14 +605,14 @@ async fn watch_ip( ); } } - let scope_id = if_nametoindex(&*iface) + let scope_id = if_nametoindex(iface.as_str()) .with_kind(ErrorKind::Network)?; - let subnets: BTreeSet = addresses + let subnets: OrdSet = addresses .into_iter() - .map(TryInto::try_into) + .map(IpNet::try_from) .try_collect()?; let ip_info = if !subnets.is_empty() { - let wan_ip = match get_wan_ipv4(&*iface).await { + let wan_ip = match get_wan_ipv4(iface.as_str()).await { Ok(a) => a, Err(e) => { tracing::error!( @@ -631,16 +635,15 @@ async fn watch_ip( }; write_to.send_if_modified( - |m: &mut BTreeMap< - InternedString, - NetworkInterfaceInfo, - >| { - let public = - m.get(&iface).map_or(None, |i| i.public); + |m: &mut OrdMap| { + let (public, secure) = m + .get(&iface) + .map_or((None, None), |i| (i.public, i.secure)); m.insert( iface.clone(), NetworkInterfaceInfo { public, + secure, ip_info: ip_info.clone(), }, ) @@ -661,11 +664,12 @@ async fn watch_ip( } } -#[instrument(skip(_connection, device_proxy, write_to))] +#[instrument(skip(_connection, device_proxy, watch_activation))] async fn watch_activated( _connection: &Connection, device_proxy: device::DeviceProxy<'_>, - write_to: &Watch, + iface: GatewayId, + watch_activation: &Watch>, ) -> Result<(), Error> { let mut until = Until::new() .with_stream( @@ -685,36 +689,124 @@ async fn watch_activated( loop { until .run(async { - write_to.send(device_proxy._state().await? == 100); + let activated = device_proxy._state().await? == 100; + watch_activation.send_if_modified(|a| { + a.get_mut(&iface) + .map_or(false, |a| std::mem::replace(a, activated) != activated) + }); Ok(()) }) .await?; } } -pub struct NetworkInterfaceController { - db: TypedPatchDb, - lxcbr_status: Watch, - ip_info: Watch>, +pub struct NetworkInterfaceWatcher { + activated: Watch>, + ip_info: Watch>, _watcher: NonDetachingJoinHandle<()>, listeners: SyncMutex>>, } -impl NetworkInterfaceController { - pub fn lxcbr_status(&self) -> Watch { - self.lxcbr_status.clone_unseen() +impl NetworkInterfaceWatcher { + pub fn new( + seed: impl Future> + Send + Sync + 'static, + watch_activated: impl IntoIterator, + ) -> Self { + let ip_info = Watch::new(OrdMap::new()); + let activated = Watch::new(watch_activated.into_iter().map(|k| (k, false)).collect()); + Self { + activated: activated.clone(), + ip_info: ip_info.clone(), + _watcher: tokio::spawn(async move { + let seed = seed.await; + if !seed.is_empty() { + ip_info.send_replace(seed); + } + watcher(ip_info, activated).await + }) + .into(), + listeners: SyncMutex::new(BTreeMap::new()), + } } - pub fn subscribe(&self) -> Watch> { + pub fn activated(&self) -> Watch> { + self.activated.clone_unseen() + } + + pub fn wait_for_activated( + &self, + interface: GatewayId, + ) -> impl Future + Send + Sync + 'static { + let mut activated = self.activated(); + async move { + activated + .wait_for(|a| a.get(&interface).copied().unwrap_or(false)) + .await; + } + } + + pub fn subscribe(&self) -> Watch> { self.ip_info.clone_unseen() } - pub fn ip_info(&self) -> BTreeMap { + pub fn ip_info(&self) -> OrdMap { self.ip_info.read() } + pub fn bind(&self, port: u16) -> Result { + let arc = Arc::new(()); + self.listeners.mutate(|l| { + if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { + return Err(Error::new( + std::io::Error::from_raw_os_error(libc::EADDRINUSE), + ErrorKind::Network, + )); + } + l.insert(port, Arc::downgrade(&arc)); + Ok(()) + })?; + let ip_info = self.ip_info.clone_unseen(); + Ok(NetworkInterfaceListener { + _arc: arc, + ip_info, + listeners: ListenerMap::new(port), + }) + } + + pub fn upgrade_listener( + &self, + SelfContainedNetworkInterfaceListener { + mut listener, + .. + }: SelfContainedNetworkInterfaceListener, + ) -> Result { + let port = listener.listeners.port; + let arc = &listener._arc; + self.listeners.mutate(|l| { + if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { + return Err(Error::new( + std::io::Error::from_raw_os_error(libc::EADDRINUSE), + ErrorKind::Network, + )); + } + l.insert(port, Arc::downgrade(arc)); + Ok(()) + })?; + let ip_info = self.ip_info.clone_unseen(); + ip_info.mark_changed(); + listener.change_ip_info_source(ip_info); + Ok(listener) + } +} + +pub struct NetworkInterfaceController { + db: TypedPatchDb, + pub watcher: NetworkInterfaceWatcher, + _sync: NonDetachingJoinHandle<()>, +} +impl NetworkInterfaceController { async fn sync( db: &TypedPatchDb, - info: &BTreeMap, + info: &OrdMap, ) -> Result<(), Error> { tracing::debug!("syncronizing {info:?} to db"); @@ -779,111 +871,70 @@ impl NetworkInterfaceController { Ok(()) } pub fn new(db: TypedPatchDb) -> Self { - let mut ip_info = Watch::new(BTreeMap::new()); - let lxcbr_status = Watch::new(false); + let watcher = NetworkInterfaceWatcher::new( + { + let db = db.clone(); + async move { + match db + .peek() + .await + .as_public() + .as_server_info() + .as_network() + .as_network_interfaces() + .de() + { + Ok(mut info) => { + for (_, info) in OrdMapIterMut::from(&mut info) { + info.ip_info = None; + } + info + } + Err(e) => { + tracing::error!("Error loading network interface info: {e}"); + tracing::debug!("{e:?}"); + OrdMap::new() + } + } + } + }, + [START9_BRIDGE_IFACE.into()], + ); + let mut ip_info = watcher.subscribe(); Self { db: db.clone(), - lxcbr_status: lxcbr_status.clone(), - ip_info: ip_info.clone(), - _watcher: tokio::spawn(async move { - match db - .peek() - .await - .as_public() - .as_server_info() - .as_network() - .as_network_interfaces() - .de() - { - Ok(mut info) => { - for info in info.values_mut() { - info.ip_info = None; - } - ip_info.send_replace(info); - } - Err(e) => { - tracing::error!("Error loading network interface info: {e}"); - tracing::debug!("{e:?}"); - } - }; - tokio::join!(watcher(ip_info.clone(), lxcbr_status), async { - let res: Result<(), Error> = async { - loop { - if let Err(e) = async { - let ip_info = ip_info.read(); - Self::sync(&db, &ip_info).boxed().await?; + watcher, + _sync: tokio::spawn(async move { + let res: Result<(), Error> = async { + loop { + if let Err(e) = async { + let ip_info = ip_info.read(); + Self::sync(&db, &ip_info).boxed().await?; - Ok::<_, Error>(()) - } - .await - { - tracing::error!("Error syncing ip info to db: {e}"); - tracing::debug!("{e:?}"); - } - - let _ = ip_info.changed().await; + Ok::<_, Error>(()) } + .await + { + tracing::error!("Error syncing ip info to db: {e}"); + tracing::debug!("{e:?}"); + } + + let _ = ip_info.changed().await; } - .await; - if let Err(e) = res { - tracing::error!("Error syncing ip info to db: {e}"); - tracing::debug!("{e:?}"); - } - }); + } + .await; + if let Err(e) = res { + tracing::error!("Error syncing ip info to db: {e}"); + tracing::debug!("{e:?}"); + } }) .into(), - listeners: SyncMutex::new(BTreeMap::new()), } } - pub fn bind(&self, port: u16) -> Result { - let arc = Arc::new(()); - self.listeners.mutate(|l| { - if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { - return Err(Error::new( - std::io::Error::from_raw_os_error(libc::EADDRINUSE), - ErrorKind::Network, - )); - } - l.insert(port, Arc::downgrade(&arc)); - Ok(()) - })?; - let ip_info = self.ip_info.clone_unseen(); - Ok(NetworkInterfaceListener { - _arc: arc, - ip_info, - listeners: ListenerMap::new(port), - }) - } - - pub fn upgrade_listener( - &self, - SelfContainedNetworkInterfaceListener { - mut listener, - .. - }: SelfContainedNetworkInterfaceListener, - ) -> Result { - let port = listener.listeners.port; - let arc = &listener._arc; - self.listeners.mutate(|l| { - if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { - return Err(Error::new( - std::io::Error::from_raw_os_error(libc::EADDRINUSE), - ErrorKind::Network, - )); - } - l.insert(port, Arc::downgrade(arc)); - Ok(()) - })?; - let ip_info = self.ip_info.clone_unseen(); - ip_info.mark_changed(); - listener.change_ip_info_source(ip_info); - Ok(listener) - } - pub async fn set_public( &self, - interface: &InternedString, + interface: &GatewayId, public: Option, ) -> Result<(), Error> { let mut sub = self @@ -895,7 +946,7 @@ impl NetworkInterfaceController { ) .await; let mut err = None; - let changed = self.ip_info.send_if_modified(|ip_info| { + let changed = self.watcher.ip_info.send_if_modified(|ip_info| { let prev = std::mem::replace( &mut match ip_info.get_mut(interface).or_not_found(interface) { Ok(a) => a, @@ -918,7 +969,7 @@ impl NetworkInterfaceController { Ok(()) } - pub async fn forget(&self, interface: &InternedString) -> Result<(), Error> { + pub async fn forget(&self, interface: &GatewayId) -> Result<(), Error> { let mut sub = self .db .subscribe( @@ -928,7 +979,7 @@ impl NetworkInterfaceController { ) .await; let mut err = None; - let changed = self.ip_info.send_if_modified(|ip_info| { + let changed = self.watcher.ip_info.send_if_modified(|ip_info| { if ip_info .get(interface) .map_or(false, |i| i.ip_info.is_some()) @@ -950,8 +1001,9 @@ impl NetworkInterfaceController { Ok(()) } - pub async fn delete_iface(&self, interface: &InternedString) -> Result<(), Error> { + pub async fn delete_iface(&self, interface: &GatewayId) -> Result<(), Error> { let Some(has_ip_info) = self + .watcher .ip_info .peek(|ifaces| ifaces.get(interface).map(|i| i.ip_info.is_some())) else { @@ -959,15 +1011,19 @@ impl NetworkInterfaceController { }; if has_ip_info { - let mut ip_info = self.ip_info.clone_unseen(); + let mut ip_info = self.watcher.ip_info.clone_unseen(); let connection = Connection::system().await?; let netman_proxy = NetworkManagerProxy::new(&connection).await?; - let device = Some(netman_proxy.get_device_by_ip_iface(&**interface).await?) - .filter(|o| &**o != "/") - .or_not_found(lazy_format!("{interface} in NetworkManager"))?; + let device = Some( + netman_proxy + .get_device_by_ip_iface(interface.as_str()) + .await?, + ) + .filter(|o| &**o != "/") + .or_not_found(lazy_format!("{interface} in NetworkManager"))?; let device_proxy = DeviceProxy::new(&connection, device).await?; @@ -983,14 +1039,14 @@ impl NetworkInterfaceController { Ok(()) } - pub async fn set_name(&self, interface: &InternedString, name: &str) -> Result<(), Error> { + pub async fn set_name(&self, interface: &GatewayId, name: &str) -> Result<(), Error> { let (dump, mut sub) = self .db .dump_and_sub( "/public/serverInfo/network/networkInterfaces" .parse::>() .with_kind(ErrorKind::Database)? - .join_end(&**interface) + .join_end(interface.as_str()) .join_end("ipInfo") .join_end("name"), ) @@ -1005,9 +1061,13 @@ impl NetworkInterfaceController { let netman_proxy = NetworkManagerProxy::new(&connection).await?; - let device = Some(netman_proxy.get_device_by_ip_iface(&**interface).await?) - .filter(|o| &**o != "/") - .or_not_found(lazy_format!("{interface} in NetworkManager"))?; + let device = Some( + netman_proxy + .get_device_by_ip_iface(interface.as_str()) + .await?, + ) + .filter(|o| &**o != "/") + .or_not_found(lazy_format!("{interface} in NetworkManager"))?; let device_proxy = DeviceProxy::new(&connection, device).await?; @@ -1023,18 +1083,20 @@ impl NetworkInterfaceController { let settings_proxy = ConnectionSettingsProxy::new(&connection, settings).await?; - settings_proxy.update2( - [( - "connection".into(), - [("id".into(), zbus::zvariant::Value::Str(name.into()))] - .into_iter() - .collect(), - )] - .into_iter() - .collect(), - 0x1, - HashMap::new(), - ); + settings_proxy + .update2( + [( + "connection".into(), + [("id".into(), zbus::zvariant::Value::Str(name.into()))] + .into_iter() + .collect(), + )] + .into_iter() + .collect(), + 0x1, + HashMap::new(), + ) + .await?; sub.recv().await; Ok(()) @@ -1042,15 +1104,14 @@ impl NetworkInterfaceController { } struct ListenerMap { - prev_public: bool, + prev_filter: DynInterfaceFilter, port: u16, - listeners: BTreeMap)>, + listeners: BTreeMap)>, } impl ListenerMap { fn from_listener(listener: impl IntoIterator) -> Result { - let mut prev_public = false; let mut port = 0; - let mut listeners = BTreeMap::)>::new(); + let mut listeners = BTreeMap::)>::new(); for listener in listener { let mut local = listener.local_addr().with_kind(ErrorKind::Network)?; if let SocketAddr::V6(l) = &mut local { @@ -1064,17 +1125,8 @@ impl ListenerMap { ErrorKind::InvalidRequest, )); } - let public = match local.ip() { - IpAddr::V4(ip4) => { - !ip4.is_loopback() - && (!ip4.is_private() || ip4.octets().starts_with(&[10, 59])) // reserving 10.59 for public wireguard configurations - && !ip4.is_link_local() - } - IpAddr::V6(ip6) => !ipv6_is_local(ip6), - }; - prev_public |= public; port = local.port(); - listeners.insert(local, (listener, public, None)); + listeners.insert(local, (listener, None)); } if port == 0 { return Err(Error::new( @@ -1083,16 +1135,285 @@ impl ListenerMap { )); } Ok(Self { - prev_public, + prev_filter: false.into_dyn(), port, listeners, }) } } + +pub trait InterfaceFilter: Any + Clone + std::fmt::Debug + Eq + Ord + Send + Sync { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool; + fn simplify(&self) -> &dyn DynInterfaceFilterT { + self + } + fn eq(&self, other: &dyn Any) -> bool { + Some(self) == other.downcast_ref::() + } + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering { + match self.as_any().type_id().cmp(&other.type_id()) { + std::cmp::Ordering::Equal => std::cmp::Ord::cmp(&self, other.downcast_ref().unwrap()), + ord => ord, + } + } + fn as_any(&self) -> &dyn Any { + self + } + fn into_dyn(self) -> DynInterfaceFilter { + DynInterfaceFilter::new(self) + } +} + +impl InterfaceFilter for bool { + fn filter(&self, _: &GatewayId, _: &NetworkInterfaceInfo) -> bool { + *self + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct LoopbackFilter; +impl InterfaceFilter for LoopbackFilter { + fn filter(&self, _: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + info.ip_info.as_ref().map_or(false, |i| { + i.subnets + .iter() + .any(|i| i.contains(&IpAddr::V4(Ipv4Addr::LOCALHOST))) + }) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct PublicFilter { + pub public: bool, +} +impl InterfaceFilter for PublicFilter { + fn filter(&self, _: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.public || !info.public() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct SecureFilter { + pub secure: bool, +} +impl InterfaceFilter for SecureFilter { + fn filter(&self, _: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.secure || info.secure() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AndFilter(pub A, pub B); +impl InterfaceFilter for AndFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.filter(id, info) && self.1.filter(id, info) + } + fn simplify(&self) -> &dyn DynInterfaceFilterT { + if InterfaceFilter::eq(&self.0, &self.1) { + &self.0 + } else { + self + } + } + fn eq(&self, other: &dyn Any) -> bool { + if let Some(other) = other.downcast_ref::() { + (InterfaceFilter::eq(&self.0, other.0.as_any()) + && InterfaceFilter::eq(&self.1, other.1.as_any())) + || (InterfaceFilter::eq(&self.0, other.1.as_any()) + && InterfaceFilter::eq(&self.1, other.0.as_any())) + } else { + false + } + } + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering { + if let Some(other) = other.downcast_ref::() { + let mut lhs: [&dyn DynInterfaceFilterT; 2] = [&self.0, &self.1]; + lhs.sort_by(|a, b| a.cmp(b.as_any())); + let mut rhs: [&dyn DynInterfaceFilterT; 2] = [&other.0, &other.1]; + rhs.sort_by(|a, b| a.cmp(b.as_any())); + lhs.iter() + .zip_eq(rhs) + .fold_while(std::cmp::Ordering::Equal, |acc, (a, b)| { + match a.cmp(b.as_any()) { + std::cmp::Ordering::Equal => itertools::FoldWhile::Continue(acc), + ord => itertools::FoldWhile::Done(ord), + } + }) + .into_inner() + } else { + match InterfaceFilter::as_any(self) + .type_id() + .cmp(&other.type_id()) + { + std::cmp::Ordering::Equal => { + std::cmp::Ord::cmp(&self, other.downcast_ref().unwrap()) + } + ord => ord, + } + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct OrFilter(pub A, pub B); +impl InterfaceFilter for OrFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.filter(id, info) || self.1.filter(id, info) + } + fn simplify(&self) -> &dyn DynInterfaceFilterT { + if InterfaceFilter::eq(&self.0, &self.1) { + &self.0 + } else { + self + } + } + fn eq(&self, other: &dyn Any) -> bool { + if let Some(other) = other.downcast_ref::() { + (InterfaceFilter::eq(&self.0, other.0.as_any()) + && InterfaceFilter::eq(&self.1, other.1.as_any())) + || (InterfaceFilter::eq(&self.0, other.1.as_any()) + && InterfaceFilter::eq(&self.1, other.0.as_any())) + } else { + false + } + } + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering { + if let Some(other) = other.downcast_ref::() { + let mut lhs: [&dyn DynInterfaceFilterT; 2] = [&self.0, &self.1]; + lhs.sort_by(|a, b| a.cmp(b.as_any())); + let mut rhs: [&dyn DynInterfaceFilterT; 2] = [&other.0, &other.1]; + rhs.sort_by(|a, b| a.cmp(b.as_any())); + lhs.iter() + .zip_eq(rhs) + .fold_while(std::cmp::Ordering::Equal, |acc, (a, b)| { + match a.cmp(b.as_any()) { + std::cmp::Ordering::Equal => itertools::FoldWhile::Continue(acc), + ord => itertools::FoldWhile::Done(ord), + } + }) + .into_inner() + } else { + match InterfaceFilter::as_any(self) + .type_id() + .cmp(&other.type_id()) + { + std::cmp::Ordering::Equal => { + std::cmp::Ord::cmp(&self, other.downcast_ref().unwrap()) + } + ord => ord, + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct AnyFilter(pub BTreeSet); +impl InterfaceFilter for AnyFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.iter().any(|f| InterfaceFilter::filter(f, id, info)) + } + fn simplify(&self) -> &dyn DynInterfaceFilterT { + match self.0.len() { + 0 => &false, + 1 => self.0.first().unwrap(), + _ => self, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct AllFilter(pub BTreeSet); +impl InterfaceFilter for AllFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.iter().all(|f| InterfaceFilter::filter(f, id, info)) + } + fn simplify(&self) -> &dyn DynInterfaceFilterT { + match self.0.len() { + 0 => &true, + 1 => self.0.first().unwrap(), + _ => self, + } + } +} + +pub trait DynInterfaceFilterT: std::fmt::Debug + Send + Sync { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool; + fn eq(&self, other: &dyn Any) -> bool; + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering; + fn as_any(&self) -> &dyn Any; +} +impl DynInterfaceFilterT for T { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + InterfaceFilter::filter(self, id, info) + } + fn eq(&self, other: &dyn Any) -> bool { + let simplified = self.simplify(); + if std::ptr::eq(simplified, self) { + InterfaceFilter::eq(self, other) + } else { + simplified.eq(other) + } + } + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering { + let simplified = self.simplify(); + if std::ptr::eq(simplified, self) { + InterfaceFilter::cmp(self, other) + } else { + simplified.cmp(other) + } + } + fn as_any(&self) -> &dyn Any { + let simplified = self.simplify(); + if std::ptr::eq(simplified, self) { + InterfaceFilter::as_any(self) + } else { + simplified.as_any() + } + } +} + +#[derive(Clone, Debug)] +pub struct DynInterfaceFilter(Arc); +impl InterfaceFilter for DynInterfaceFilter { + fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + self.0.filter(id, info) + } + fn eq(&self, other: &dyn Any) -> bool { + self.0.eq(other) + } + fn cmp(&self, other: &dyn Any) -> std::cmp::Ordering { + self.0.cmp(other) + } + fn as_any(&self) -> &dyn Any { + self.0.as_any() + } +} +impl DynInterfaceFilter { + fn new(value: T) -> Self { + Self(Arc::new(value)) + } +} +impl PartialEq for DynInterfaceFilter { + fn eq(&self, other: &Self) -> bool { + DynInterfaceFilterT::eq(&*self.0, DynInterfaceFilterT::as_any(&*other.0)) + } +} +impl Eq for DynInterfaceFilter {} +impl PartialOrd for DynInterfaceFilter { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.cmp(other.0.as_any())) + } +} +impl Ord for DynInterfaceFilter { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(other.0.as_any()) + } +} + impl ListenerMap { fn new(port: u16) -> Self { Self { - prev_public: false, + prev_filter: false.into_dyn(), port, listeners: BTreeMap::new(), } @@ -1101,78 +1422,60 @@ impl ListenerMap { #[instrument(skip(self))] fn update( &mut self, - ip_info: &BTreeMap, - public: bool, + ip_info: &OrdMap, + filter: &impl InterfaceFilter, ) -> Result<(), Error> { let mut keep = BTreeSet::::new(); - for info in ip_info.values().chain([&NetworkInterfaceInfo { - public: Some(false), - 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(), - }), - }]) { - if public || !info.public() { - 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.port, - 0, - if ipv6_is_link_local(ip6) { - ip_info.scope_id - } else { - 0 - }, - ) - .into(), - ip => SocketAddr::new(ip, self.port), - }; - keep.insert(addr); - if let Some((_, is_public, wan_ip)) = self.listeners.get_mut(&addr) { - *is_public = info.public(); - *wan_ip = info.ip_info.as_ref().and_then(|i| i.wan_ip); - continue; - } - self.listeners.insert( - addr, - ( - TcpListener::from_std( - mio::net::TcpListener::bind(addr) - .with_ctx(|_| { - ( - ErrorKind::Network, - lazy_format!("binding to {addr:?}"), - ) - })? - .into(), - ) - .with_kind(ErrorKind::Network)?, - info.public(), - info.ip_info.as_ref().and_then(|i| i.wan_ip), - ), - ); + for (_, info) in ip_info + .iter() + .chain([NetworkInterfaceInfo::loopback()]) + .filter(|(id, info)| filter.filter(*id, *info)) + { + 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.port, + 0, + if ipv6_is_link_local(ip6) { + ip_info.scope_id + } else { + 0 + }, + ) + .into(), + ip => SocketAddr::new(ip, self.port), + }; + keep.insert(addr); + if let Some((_, wan_ip)) = self.listeners.get_mut(&addr) { + *wan_ip = info.ip_info.as_ref().and_then(|i| i.wan_ip); + continue; } + self.listeners.insert( + addr, + ( + TcpListener::from_std( + mio::net::TcpListener::bind(addr) + .with_ctx(|_| { + (ErrorKind::Network, lazy_format!("binding to {addr:?}")) + })? + .into(), + ) + .with_kind(ErrorKind::Network)?, + info.ip_info.as_ref().and_then(|i| i.wan_ip), + ), + ); } } } self.listeners.retain(|key, _| keep.contains(key)); - self.prev_public = public; + self.prev_filter = filter.clone().into_dyn(); Ok(()) } fn poll_accept(&self, cx: &mut std::task::Context<'_>) -> Poll> { - for (bind_addr, listener) in self.listeners.iter() { - if let Poll::Ready((stream, addr)) = listener.0.poll_accept(cx)? { + for (bind_addr, (listener, wan_ip)) in self.listeners.iter() { + if let Poll::Ready((stream, addr)) = listener.poll_accept(cx)? { if let Err(e) = socket2::SockRef::from(&stream).set_tcp_keepalive( &socket2::TcpKeepalive::new() .with_time(Duration::from_secs(900)) @@ -1185,8 +1488,7 @@ impl ListenerMap { return Poll::Ready(Ok(Accepted { stream, peer: addr, - is_public: listener.1, - wan_ip: listener.2, + wan_ip: *wan_ip, bind: *bind_addr, })); } @@ -1195,8 +1497,22 @@ impl ListenerMap { } } +pub fn lookup_info_by_addr( + ip_info: &OrdMap, + addr: SocketAddr, +) -> Option<(&GatewayId, &NetworkInterfaceInfo)> { + ip_info + .iter() + .chain([NetworkInterfaceInfo::loopback()]) + .find(|(_, i)| { + i.ip_info + .as_ref() + .map_or(false, |i| i.subnets.iter().any(|i| i.addr() == addr.ip())) + }) +} + pub struct NetworkInterfaceListener { - ip_info: Watch>, + pub ip_info: Watch>, listeners: ListenerMap, _arc: Arc<()>, } @@ -1208,17 +1524,19 @@ impl NetworkInterfaceListener { pub fn poll_accept( &mut self, cx: &mut std::task::Context<'_>, - public: bool, + filter: &impl InterfaceFilter, ) -> Poll> { - while self.ip_info.poll_changed(cx).is_ready() || public != self.listeners.prev_public { + while self.ip_info.poll_changed(cx).is_ready() + || !InterfaceFilter::eq(&self.listeners.prev_filter, filter) + { self.ip_info - .peek(|ip_info| self.listeners.update(ip_info, public))?; + .peek(|ip_info| self.listeners.update(ip_info, filter))?; } self.listeners.poll_accept(cx) } pub(super) fn new( - mut ip_info: Watch>, + mut ip_info: Watch>, port: u16, ) -> Self { ip_info.mark_unseen(); @@ -1231,21 +1549,31 @@ impl NetworkInterfaceListener { pub fn change_ip_info_source( &mut self, - mut ip_info: Watch>, + mut ip_info: Watch>, ) { ip_info.mark_unseen(); self.ip_info = ip_info; } - pub async fn accept(&mut self, public: bool) -> Result { - futures::future::poll_fn(|cx| self.poll_accept(cx, public)).await + pub async fn accept(&mut self, filter: &impl InterfaceFilter) -> Result { + futures::future::poll_fn(|cx| self.poll_accept(cx, filter)).await + } + + pub fn check_filter(&self) -> impl FnOnce(SocketAddr, &DynInterfaceFilter) -> bool + 'static { + let ip_info = self.ip_info.clone(); + move |addr, filter| { + ip_info.peek(|i| { + lookup_info_by_addr(i, addr).map_or(false, |(id, info)| { + InterfaceFilter::filter(filter, id, info) + }) + }) + } } } pub struct Accepted { pub stream: TcpStream, pub peer: SocketAddr, - pub is_public: bool, pub wan_ip: Option, pub bind: SocketAddr, } @@ -1256,8 +1584,9 @@ pub struct SelfContainedNetworkInterfaceListener { } impl SelfContainedNetworkInterfaceListener { pub fn bind(port: u16) -> Self { - let ip_info = Watch::new(BTreeMap::new()); - let _watch_thread = tokio::spawn(watcher(ip_info.clone(), Watch::new(false))).into(); + let ip_info = Watch::new(OrdMap::new()); + let _watch_thread = + tokio::spawn(watcher(ip_info.clone(), Watch::new(BTreeMap::new()))).into(); Self { _watch_thread, listener: NetworkInterfaceListener::new(ip_info, port), diff --git a/core/startos/src/net/service_interface.rs b/core/startos/src/net/service_interface.rs index c75b4601c..a56c5e8c2 100644 --- a/core/startos/src/net/service_interface.rs +++ b/core/startos/src/net/service_interface.rs @@ -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, }, diff --git a/core/startos/src/net/tunnel.rs b/core/startos/src/net/tunnel.rs index d4f45928b..e6b59c522 100644 --- a/core/startos/src/net/tunnel.rs +++ b/core/startos/src/net/tunnel.rs @@ -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 { +) -> Result { 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, diff --git a/core/startos/src/net/vhost.rs b/core/startos/src/net/vhost.rs index 11c4e5ce8..f080b7ac1 100644 --- a/core/startos/src/net/vhost.rs +++ b/core/startos/src/net/vhost.rs @@ -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() -> ParentHandler { 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() -> ParentHandler { 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, external: u16, - TargetInfo { - public, - acme, - addr, - connect_ssl, - }: TargetInfo, + target: TargetInfo, ) -> Result, 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, BTreeMap>, BTreeSet>> + ) -> BTreeMap, BTreeMap>, EqSet>> { + 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, 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, + pub acme: Option, + 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, + ) -> 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, BTreeMap>>> for AnyFilter { + fn from(value: &'a BTreeMap, BTreeMap>>) -> 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, db: TypedPatchDb, 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, acme_tls_alpn_cache: AcmeTlsAlpnCache, ) -> Result { - 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, diff --git a/core/startos/src/net/web_server.rs b/core/startos/src/net/web_server.rs index b9c5d0703..0b0fb160a 100644 --- a/core/startos/src/net/web_server.rs +++ b/core/startos/src/net/web_server.rs @@ -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 { } impl Accept for NetworkInterfaceListener { fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - 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, + } }) }) } diff --git a/core/startos/src/service/effects/net/bind.rs b/core/startos/src/service/effects/net/bind.rs index 2db3eb2ca..732249d74 100644 --- a/core/startos/src/service/effects/net/bind.rs +++ b/core/startos/src/service/effects/net/bind.rs @@ -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()) } diff --git a/core/startos/src/setup.rs b/core/startos/src/setup.rs index 466fea16f..48d7de673 100644 --- a/core/startos/src/setup.rs +++ b/core/startos/src/setup.rs @@ -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() -> ParentHandler { "logs", from_fn_async(crate::logs::cli_logs::).no_display(), ) + .subcommand("restart", from_fn_async(restart).no_cli()) } pub fn disk() -> ParentHandler { @@ -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 { } #[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 { diff --git a/core/startos/src/shutdown.rs b/core/startos/src/shutdown.rs index d71989da5..9fba71fca 100644 --- a/core/startos/src/shutdown.rs +++ b/core/startos/src/shutdown.rs @@ -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, PathBuf)>, + pub disk_guid: Option>, 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")) diff --git a/core/startos/src/tunnel/context.rs b/core/startos/src/tunnel/context.rs index bbcf9bf0d..c618d4ce0 100644 --- a/core/startos/src/tunnel/context.rs +++ b/core/startos/src/tunnel/context.rs @@ -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>, pub ephemeral_sessions: SyncMutex, + 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 for CliContext { .await } } - -impl CallRemote for RpcContext { - async fn call_remote( - &self, - mut method: &str, - params: Value, - TunnelAddrParams { tunnel }: TunnelAddrParams, - ) -> Result { - todo!() - } -} diff --git a/core/startos/src/tunnel/db.rs b/core/startos/src/tunnel/db.rs index 79a69055a..33e01d54b 100644 --- a/core/startos/src/tunnel/db.rs +++ b/core/startos/src/tunnel/db.rs @@ -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"] +pub struct TunnelDatabase { + pub sessions: Sessions, + pub password: String, + pub auth_pubkeys: HashSet, + pub clients: BTreeMap, + pub port_forwards: BTreeMap, +} + +#[derive(Debug, Default, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +pub struct ClientInfo { + pub server: bool, +} + pub fn db_api() -> ParentHandler { ParentHandler::new() .subcommand( diff --git a/core/startos/src/tunnel/init.rs b/core/startos/src/tunnel/init.rs new file mode 100644 index 000000000..e69de29bb diff --git a/core/startos/src/tunnel/mod.rs b/core/startos/src/tunnel/mod.rs index a414e2031..2a24a56ee 100644 --- a/core/startos/src/tunnel/mod.rs +++ b/core/startos/src/tunnel/mod.rs @@ -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"] -pub struct TunnelDatabase { - pub sessions: Sessions, - pub password: String, - pub auth_pubkeys: HashSet, -} - pub fn tunnel_api() -> ParentHandler { ParentHandler::new().subcommand( "db", diff --git a/core/startos/src/upload.rs b/core/startos/src/upload.rs index d7428bb44..4076c579d 100644 --- a/core/startos/src/upload.rs +++ b/core/startos/src/upload.rs @@ -359,7 +359,7 @@ impl UploadHandle { }); } } - async fn process_body>>( + async fn process_body>>( &mut self, mut body: impl Stream> + Unpin, ) { diff --git a/core/startos/src/util/collections/eq_map.rs b/core/startos/src/util/collections/eq_map.rs index 5078866a5..d03667e88 100644 --- a/core/startos/src/util/collections/eq_map.rs +++ b/core/startos/src/util/collections/eq_map.rs @@ -666,13 +666,27 @@ impl IntoIterator for EqMap { impl Extend<(K, V)> for EqMap { fn extend>(&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 FromIterator<(K, V)> for EqMap { fn from_iter>(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 From<[(K, V); N]> for EqMap { /// assert_eq!(map1, map2); /// ``` fn from(arr: [(K, V); N]) -> Self { - EqMap(Vec::from(arr)) + Self::from_iter(arr) } } diff --git a/core/startos/src/util/collections/eq_set.rs b/core/startos/src/util/collections/eq_set.rs new file mode 100644 index 000000000..84eaedf50 --- /dev/null +++ b/core/startos/src/util/collections/eq_set.rs @@ -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(Vec); +impl Default for EqSet { + fn default() -> Self { + Self(Default::default()) + } +} +impl EqSet { + 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(&self, value: &Q) -> Option<&T> + where + T: Borrow, + 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 { + 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(&self, value: &Q) -> bool + where + T: Borrow, + 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 { + 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(&mut self, value: &Q) -> Option + where + T: Borrow, + 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 = (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(&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 = (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::>(), [0, 2, 4, 6]); + // /// assert_eq!(odds.values().copied().collect::>(), [1, 3, 5, 7]); + // /// ``` + // pub fn extract_if(&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 fmt::Debug for EqSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.iter()).finish() + } +} + +impl IntoIterator for EqSet { + type IntoIter = std::vec::IntoIter; + type Item = T; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Extend for EqSet { + fn extend>(&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 FromIterator for EqSet { + fn from_iter>(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 From<[T; N]> for EqSet { + /// Converts a `[T; N]` into a `EqSet`. + /// + /// ``` + /// 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 PartialEq for EqSet { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().all(|v| other.get(v) == Some(v)) + } +} +impl Eq for EqSet {} + +impl<'de, T> Deserialize<'de> for EqSet +where + T: Deserialize<'de> + Eq, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor { + marker: PhantomData, + } + + impl<'de, T> serde::de::Visitor<'de> for Visitor + where + T: Deserialize<'de> + Eq, + { + type Value = EqSet; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + 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) + } +} diff --git a/core/startos/src/util/collections/mod.rs b/core/startos/src/util/collections/mod.rs index aa6e3ddb5..5aa52a3af 100644 --- a/core/startos/src/util/collections/mod.rs +++ b/core/startos/src/util/collections/mod.rs @@ -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, + prev: Option<&'a K>, + _marker: PhantomData<&'a mut (K, V)>, +} +impl<'a, K, V> From<&'a mut OrdMap> for OrdMapIterMut<'a, K, V> { + fn from(value: &'a mut OrdMap) -> 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 { + unsafe { + let map: &'a mut OrdMap = 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 + } + } +} diff --git a/core/startos/src/util/serde.rs b/core/startos/src/util/serde.rs index b7b08529f..ec0763bdb 100644 --- a/core/startos/src/util/serde.rs +++ b/core/startos/src/util/serde.rs @@ -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; diff --git a/core/startos/src/util/sync.rs b/core/startos/src/util/sync.rs index 9bf396629..0e65d8aec 100644 --- a/core/startos/src/util/sync.rs +++ b/core/startos/src/util/sync.rs @@ -140,6 +140,9 @@ impl Watch { 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 futures::Stream for Watch { type Item = T; diff --git a/patch-db b/patch-db index f43ee1958..de9a2a678 160000 --- a/patch-db +++ b/patch-db @@ -1 +1 @@ -Subproject commit f43ee195877364fb8aa34bb769515855ab995933 +Subproject commit de9a2a67872aee7caa751fa237c333e42aa72b1a diff --git a/sdk/base/lib/osBindings/BindingGatewaySetEnabledParams.ts b/sdk/base/lib/osBindings/BindingGatewaySetEnabledParams.ts new file mode 100644 index 000000000..f38b5d095 --- /dev/null +++ b/sdk/base/lib/osBindings/BindingGatewaySetEnabledParams.ts @@ -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 +} diff --git a/sdk/base/lib/osBindings/ForgetInterfaceParams.ts b/sdk/base/lib/osBindings/ForgetInterfaceParams.ts index b3532602c..b98229317 100644 --- a/sdk/base/lib/osBindings/ForgetInterfaceParams.ts +++ b/sdk/base/lib/osBindings/ForgetInterfaceParams.ts @@ -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 } diff --git a/sdk/base/lib/osBindings/BindingSetPublicParams.ts b/sdk/base/lib/osBindings/GatewayId.ts similarity index 55% rename from sdk/base/lib/osBindings/BindingSetPublicParams.ts rename to sdk/base/lib/osBindings/GatewayId.ts index 077cf8510..1b0cc9b38 100644 --- a/sdk/base/lib/osBindings/BindingSetPublicParams.ts +++ b/sdk/base/lib/osBindings/GatewayId.ts @@ -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 diff --git a/sdk/base/lib/osBindings/HostnameInfo.ts b/sdk/base/lib/osBindings/HostnameInfo.ts index ef8bafac0..7819eec54 100644 --- a/sdk/base/lib/osBindings/HostnameInfo.ts +++ b/sdk/base/lib/osBindings/HostnameInfo.ts @@ -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 } diff --git a/sdk/base/lib/osBindings/LoginParams.ts b/sdk/base/lib/osBindings/LoginParams.ts index a569e6c8e..2a907ad6f 100644 --- a/sdk/base/lib/osBindings/LoginParams.ts +++ b/sdk/base/lib/osBindings/LoginParams.ts @@ -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 } diff --git a/sdk/base/lib/osBindings/NetInfo.ts b/sdk/base/lib/osBindings/NetInfo.ts index e790cadaa..6ac8c4204 100644 --- a/sdk/base/lib/osBindings/NetInfo.ts +++ b/sdk/base/lib/osBindings/NetInfo.ts @@ -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 + publicEnabled: Array assignedPort: number | null assignedSslPort: number | null } diff --git a/sdk/base/lib/osBindings/NetworkInfo.ts b/sdk/base/lib/osBindings/NetworkInfo.ts index 1933332e4..28a8b5352 100644 --- a/sdk/base/lib/osBindings/NetworkInfo.ts +++ b/sdk/base/lib/osBindings/NetworkInfo.ts @@ -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 } } diff --git a/sdk/base/lib/osBindings/NetworkInterfaceInfo.ts b/sdk/base/lib/osBindings/NetworkInterfaceInfo.ts index 796046b93..b4cdde430 100644 --- a/sdk/base/lib/osBindings/NetworkInterfaceInfo.ts +++ b/sdk/base/lib/osBindings/NetworkInterfaceInfo.ts @@ -3,5 +3,6 @@ import type { IpInfo } from "./IpInfo" export type NetworkInterfaceInfo = { public: boolean | null + secure: boolean | null ipInfo: IpInfo | null } diff --git a/sdk/base/lib/osBindings/NetworkInterfaceSetPublicParams.ts b/sdk/base/lib/osBindings/NetworkInterfaceSetPublicParams.ts index 516bfc817..cde227c35 100644 --- a/sdk/base/lib/osBindings/NetworkInterfaceSetPublicParams.ts +++ b/sdk/base/lib/osBindings/NetworkInterfaceSetPublicParams.ts @@ -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 } diff --git a/sdk/base/lib/osBindings/RemoveTunnelParams.ts b/sdk/base/lib/osBindings/RemoveTunnelParams.ts index 2cc963410..67c7b08c5 100644 --- a/sdk/base/lib/osBindings/RemoveTunnelParams.ts +++ b/sdk/base/lib/osBindings/RemoveTunnelParams.ts @@ -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 } diff --git a/sdk/base/lib/osBindings/RenameInterfaceParams.ts b/sdk/base/lib/osBindings/RenameInterfaceParams.ts index e7bccac41..e33736080 100644 --- a/sdk/base/lib/osBindings/RenameInterfaceParams.ts +++ b/sdk/base/lib/osBindings/RenameInterfaceParams.ts @@ -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 } diff --git a/sdk/base/lib/osBindings/UnsetInboundParams.ts b/sdk/base/lib/osBindings/UnsetInboundParams.ts index df519e752..0afec0eae 100644 --- a/sdk/base/lib/osBindings/UnsetInboundParams.ts +++ b/sdk/base/lib/osBindings/UnsetInboundParams.ts @@ -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 } diff --git a/sdk/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index da3d5bd27..374ad1efe 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -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"