diff --git a/core/Cargo.lock b/core/Cargo.lock index 1990555ff..6f1100238 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -105,7 +105,7 @@ dependencies = [ "amplify_num", "ascii", "getrandom 0.2.16", - "getrandom 0.3.3", + "getrandom 0.3.4", "wasm-bindgen", ] @@ -156,15 +156,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" -[[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.21" @@ -358,7 +349,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "synstructure", ] @@ -370,7 +361,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "synstructure", ] @@ -382,7 +373,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -394,7 +385,7 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-acme" version = "0.6.0" -source = "git+https://github.com/dr-bonez/async-acme.git#0ddf25152237b5fc1726d977a7931e44513ce309" +source = "git+https://github.com/dr-bonez/async-acme.git#d22a20f9ac0a5dafb8fb383958b12bf6f4151833" dependencies = [ "async-trait", "base64 0.22.1", @@ -404,7 +395,7 @@ dependencies = [ "pem", "rcgen", "ring", - "rustls 0.23.32", + "rustls 0.23.34", "rustls-pemfile", "serde", "serde_json", @@ -558,7 +549,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -625,7 +616,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -642,7 +633,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -722,16 +713,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libloading", ] [[package]] @@ -961,7 +951,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -972,7 +962,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1013,9 +1003,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitmaps" @@ -1090,7 +1080,7 @@ checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1224,9 +1214,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.41" +version = "1.2.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "81bbf3b3619004ad9bd139f62a9ab5cfe467f307455a0d307b0cf58bf070feaa" dependencies = [ "find-msvc-tools", "jobserver", @@ -1251,9 +1241,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -1309,7 +1299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half 2.7.0", + "half 2.7.1", ] [[package]] @@ -1345,9 +1335,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" dependencies = [ "clap_builder", "clap_derive", @@ -1355,9 +1345,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" dependencies = [ "anstream", "anstyle", @@ -1367,21 +1357,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cmake" @@ -1787,18 +1777,16 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" -version = "0.29.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "crossterm_winapi", - "derive_more 2.0.1", - "document-features", "futures-core", - "mio", + "libc", + "mio 0.8.11", "parking_lot", - "rustix 1.1.2", "signal-hook", "signal-hook-mio", "winapi", @@ -1853,21 +1841,21 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -1927,7 +1915,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1975,7 +1963,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1997,7 +1985,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2054,7 +2042,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2094,14 +2082,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357422a457ccb850dc8f1c1680e0670079560feaad6c2e247e3f345c4fab8a3f" dependencies = [ "heck 0.5.0", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", "quote", "sha3 0.10.8", "strum", - "syn 2.0.106", + "syn 2.0.108", "void", ] @@ -2112,14 +2100,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea41269bd490d251b9eca50ccb43117e641cc68b129849757c15ece88fe0574" dependencies = [ "heck 0.5.0", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", "quote", "sha3 0.10.8", "strum", - "syn 2.0.106", + "syn 2.0.108", "void", ] @@ -2131,7 +2119,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2175,7 +2163,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2196,7 +2184,7 @@ dependencies = [ "convert_case 0.7.1", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "unicode-xid", ] @@ -2280,7 +2268,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2297,15 +2285,15 @@ checksum = "cf5597a4b7fe5275fc9dcf88ce26326bc8e4cb87d0130f33752d4c5f717793cf" dependencies = [ "cfg-if", "libc", - "socket2 0.6.0", + "socket2 0.6.1", "windows-sys 0.60.2", ] [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -2340,13 +2328,13 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7d4c414c94bc830797115b8e5f434d58e7e80cb42ba88508c14bc6ea270625" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "byteorder", "lazy_static", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2540,7 +2528,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2553,7 +2541,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2574,7 +2562,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2780,9 +2768,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -2987,7 +2975,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -3008,7 +2996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.32", + "rustls 0.23.34", "rustls-pki-types", ] @@ -3044,9 +3032,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -3098,15 +3086,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -3146,7 +3134,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3696fafb1ecdcc2ae3ce337de73e9202806068594b77d22fdf2f3573c5ec2219" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "crc", "simple-bytes", "uuid", @@ -3187,7 +3175,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.4", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -3202,9 +3190,9 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "half" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", @@ -3437,11 +3425,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3555,7 +3543,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.32", + "rustls 0.23.34", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", @@ -3609,7 +3597,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2 0.6.1", "system-configuration", "tokio", "tower-service", @@ -3874,9 +3862,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -3904,7 +3892,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "futures-core", "inotify-sys", "libc", @@ -3948,17 +3936,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -3980,20 +3957,20 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "isocountry" @@ -4122,7 +4099,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] @@ -4245,7 +4222,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.7", + "regex-syntax 0.8.8", "string_cache", "term", "tiny-keccak", @@ -4308,9 +4285,9 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.32", + "rustls 0.23.34", "rustls-platform-verifier", - "socket2 0.6.0", + "socket2 0.6.1", "tokio", "tokio-rustls 0.26.4", "url", @@ -4337,12 +4314,12 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.5", + "windows-link 0.2.1", ] [[package]] @@ -4377,7 +4354,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", "redox_syscall 0.5.18", ] @@ -4423,9 +4400,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" @@ -4497,9 +4474,9 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -4558,14 +4535,26 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", ] [[package]] @@ -4590,7 +4579,7 @@ dependencies = [ "regex", "reqwest", "rpc-toolkit", - "rustls 0.23.32", + "rustls 0.23.34", "serde", "serde_json", "ssh-key", @@ -4687,7 +4676,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -4732,12 +4721,12 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "inotify", "kqueue", "libc", "log", - "mio", + "mio 1.1.0", "notify-types", "walkdir", "windows-sys 0.60.2", @@ -4876,9 +4865,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -4886,14 +4875,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -4914,7 +4903,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -4957,9 +4946,9 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oneshot-fused-workaround" @@ -4996,11 +4985,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "foreign-types", "libc", @@ -5017,7 +5006,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5028,18 +5017,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.3+3.5.4" +version = "300.5.4+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" dependencies = [ "cc", "libc", @@ -5267,7 +5256,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5287,7 +5276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.11.4", + "indexmap 2.12.0", ] [[package]] @@ -5321,7 +5310,7 @@ dependencies = [ "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5365,7 +5354,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5528,7 +5517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5561,7 +5550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" dependencies = [ "equivalent", - "indexmap 2.11.4", + "indexmap 2.12.0", "serde", ] @@ -5593,14 +5582,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -5611,7 +5600,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "chrono", "flate2", "hex", @@ -5625,7 +5614,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "chrono", "hex", ] @@ -5638,13 +5627,13 @@ checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" dependencies = [ "bit-set 0.8.0", "bit-vec 0.8.0", - "bitflags 2.9.4", + "bitflags 2.10.0", "lazy_static", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", - "regex-syntax 0.8.7", + "regex-syntax 0.8.8", "rusty-fork", "tempfile", "unarray", @@ -5658,7 +5647,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5681,7 +5670,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5894,7 +5883,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -5997,7 +5986,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -6045,30 +6034,30 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "regex" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.7", + "regex-syntax 0.8.8", ] [[package]] name = "regex-automata" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.7", + "regex-syntax 0.8.8", ] [[package]] @@ -6079,15 +6068,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", @@ -6245,7 +6234,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -6301,7 +6290,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno 0.3.14", "libc", "linux-raw-sys 0.4.15", @@ -6314,7 +6303,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno 0.3.14", "libc", "linux-raw-sys 0.11.0", @@ -6337,9 +6326,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "aws-lc-rs", "log", @@ -6353,9 +6342,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -6383,23 +6372,23 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", "once_cell", - "rustls 0.23.32", + "rustls 0.23.34", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.7", "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6451,17 +6440,18 @@ dependencies = [ [[package]] name = "rustyline-async" -version = "0.4.7" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e07ddce8399c61495b405dc94d4f30d01fc1c5e1238f10b9c09940678bc81ab" +checksum = "8b6eb06391513b2184f0a5405c11a4a0a5302e8be442f4c5c35267187c2b37d5" dependencies = [ - "ansi-width", "crossterm", + "futures-channel", "futures-util", "pin-project", "thingbuf", - "thiserror 2.0.17", + "thiserror 1.0.69", "unicode-segmentation", + "unicode-width 0.1.14", ] [[package]] @@ -6559,7 +6549,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -6572,7 +6562,7 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -6663,7 +6653,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -6682,7 +6672,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "itoa", "memchr", "ryu", @@ -6720,7 +6710,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -6755,15 +6745,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.4", + "indexmap 2.12.0", "schemars 0.9.0", "schemars 1.0.4", "serde_core", @@ -6774,14 +6764,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -6790,7 +6780,7 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "itoa", "libyml", "memchr", @@ -6917,7 +6907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 0.8.11", "signal-hook", ] @@ -7030,12 +7020,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -7096,12 +7086,12 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap 2.11.4", + "indexmap 2.12.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.32", + "rustls 0.23.34", "serde", "serde_json", "sha2 0.10.9", @@ -7124,7 +7114,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -7145,7 +7135,7 @@ dependencies = [ "sha2 0.10.9", "sqlx-core", "sqlx-postgres", - "syn 2.0.106", + "syn 2.0.108", "tokio", "url", ] @@ -7158,7 +7148,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.4", + "bitflags 2.10.0", "byteorder", "crc", "dotenvy", @@ -7210,7 +7200,7 @@ dependencies = [ "quote", "regex-syntax 0.6.29", "strsim 0.10.0", - "syn 2.0.106", + "syn 2.0.108", "unicode-width 0.1.14", ] @@ -7330,7 +7320,7 @@ dependencies = [ "imbl", "imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "include_dir", - "indexmap 2.11.4", + "indexmap 2.12.0", "indicatif", "inotify", "integer-encoding", @@ -7348,7 +7338,7 @@ dependencies = [ "libc", "log", "mbrman", - "mio", + "mio 1.1.0", "models", "new_mime_guess", "nix 0.30.1", @@ -7390,7 +7380,7 @@ dependencies = [ "shell-words", "signal-hook", "simple-logging", - "socket2 0.6.0", + "socket2 0.6.1", "socks5-impl", "sqlx", "sscanf", @@ -7491,7 +7481,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -7513,9 +7503,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -7539,7 +7529,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -7562,7 +7552,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -7601,7 +7591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.2", "windows-sys 0.61.2", @@ -7686,7 +7676,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -7697,7 +7687,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -7797,34 +7787,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", - "mio", + "mio 1.1.0", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2 0.6.1", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -7854,7 +7841,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.32", + "rustls 0.23.34", "tokio", ] @@ -7942,7 +7929,7 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "serde_core", "serde_spanned 1.0.3", "toml_datetime 0.7.3", @@ -7975,7 +7962,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -7989,7 +7976,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "toml_datetime 0.7.3", "toml_parser", "winnow", @@ -8088,7 +8075,7 @@ dependencies = [ "derive-deftly 1.2.0", "digest 0.10.7", "educe", - "getrandom 0.3.3", + "getrandom 0.3.4", "safelog", "thiserror 2.0.17", "tor-error", @@ -8102,7 +8089,7 @@ version = "0.33.0" source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ "amplify", - "bitflags 2.9.4", + "bitflags 2.10.0", "bytes", "caret", "derive-deftly 1.2.0", @@ -8672,7 +8659,7 @@ dependencies = [ "digest 0.10.7", "ed25519-dalek 2.2.0", "educe", - "getrandom 0.3.3", + "getrandom 0.3.4", "hex", "rand 0.9.2", "rand_chacha 0.9.0", @@ -8744,7 +8731,7 @@ version = "0.33.0" source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ "async-trait", - "bitflags 2.9.4", + "bitflags 2.10.0", "derive_more 2.0.1", "digest 0.10.7", "futures", @@ -8777,7 +8764,7 @@ source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit dependencies = [ "amplify", "base64ct", - "bitflags 2.9.4", + "bitflags 2.10.0", "cipher 0.4.4", "derive-deftly 1.2.0", "derive_builder_fork_arti", @@ -9083,7 +9070,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytes", "futures-util", "http", @@ -9127,7 +9114,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -9218,7 +9205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -9245,7 +9232,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "termcolor", ] @@ -9302,7 +9289,7 @@ checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -9373,9 +9360,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "unicode-linebreak" @@ -9486,8 +9473,9 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", + "serde", "wasm-bindgen", ] @@ -9529,14 +9517,14 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "visit-rs" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb1924bd417090100e20559c09bea160cd6189f3d8867c832e9985d43756fa" +checksum = "ccaee8d068e8594cffeb617e934bd19607a71c0b0eaf40bdc548578d59abaae9" dependencies = [ "async-stream", "futures", @@ -9546,13 +9534,13 @@ dependencies = [ [[package]] name = "visit-rs-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2071973e6712c1caa51d425c2caa56fc0694028ba686311d2264e129aa19d14d" +checksum = "de41688745bbd6ed24e2f4923026911b523f0c057e10f86f44652a20e65555ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -9601,15 +9589,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -9657,7 +9636,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "wasm-bindgen-shared", ] @@ -9692,7 +9671,7 @@ checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -9880,7 +9859,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -9891,7 +9870,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -10414,15 +10393,15 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "synstructure", ] [[package]] name = "zbus" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" +checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" dependencies = [ "async-broadcast", "async-executor", @@ -10444,7 +10423,8 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "windows-sys 0.60.2", + "uuid", + "windows-sys 0.61.2", "winnow", "zbus_macros", "zbus_names", @@ -10453,14 +10433,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" +checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "zbus_names", "zvariant", "zvariant_utils", @@ -10495,7 +10475,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -10515,7 +10495,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "synstructure", ] @@ -10536,7 +10516,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -10569,7 +10549,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -10602,9 +10582,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.7.0" +version = "5.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" +checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" dependencies = [ "endi", "enumflags2", @@ -10616,14 +10596,14 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.7.0" +version = "5.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" +checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "zvariant_utils", ] @@ -10636,6 +10616,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.106", + "syn 2.0.108", "winnow", ] diff --git a/core/startos/src/bins/tunnel.rs b/core/startos/src/bins/tunnel.rs index d1deede3f..5e4eb0853 100644 --- a/core/startos/src/bins/tunnel.rs +++ b/core/startos/src/bins/tunnel.rs @@ -1,13 +1,15 @@ use std::ffi::OsString; +use std::time::Duration; use clap::Parser; use futures::FutureExt; +use helpers::NonDetachingJoinHandle; use rpc_toolkit::CliApp; use tokio::signal::unix::signal; use tracing::instrument; -use crate::context::CliContext; use crate::context::config::ClientConfig; +use crate::context::CliContext; use crate::net::web_server::{Acceptor, WebServer}; use crate::prelude::*; use crate::tunnel::context::{TunnelConfig, TunnelContext}; @@ -17,13 +19,51 @@ use crate::util::logger::LOGGER; async fn inner_main(config: &TunnelConfig) -> Result<(), Error> { let server = async { let ctx = TunnelContext::init(config).await?; - let mut server = WebServer::new(Acceptor::bind([ctx.listen]).await?); + let listen = ctx.listen; + let mut server = WebServer::new(Acceptor::bind([listen]).await?); + let https_thread: NonDetachingJoinHandle<()> = tokio::spawn(async move { + let mut sub = setter_db.subscribe("/webserver".parse().unwrap()).await; + while sub.recv().await.is_some() { + while let Err(e) = async { + let external = setter_db.peek().await.into_webserver().de()?; + + let mut bind_err = None; + + setter.send_modify(|a| { + a.retain(|a, _| *a == listen || Some(*a) == external); + if let Some(external) = external { + if !a.contains_key(&external) { + match mio::net::TcpListener::bind(external) { + Ok(l) => { + a.insert(external, TcpListener::from_std(l.into())); + } + Err(e) => bind_err = Some(e), + } + } + } + }); + + if let Some(e) = bind_err { + return Err(e); + } + + Ok::<_, Error>(()) + } + .await + { + tracing::error!("error updating webserver bind: {e}"); + tracing::debug!("{e:?}"); + tokio::time::sleep(Duration::from_secs(5)).await; + } + } + }) + .into(); server.serve_tunnel(ctx.clone()); let mut shutdown_recv = ctx.shutdown.subscribe(); let sig_handler_ctx = ctx; - let sig_handler = tokio::spawn(async move { + let sig_handler: NonDetachingJoinHandle<()> = tokio::spawn(async move { use tokio::signal::unix::SignalKind; futures::future::select_all( [ @@ -48,14 +88,16 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> { .send(()) .map_err(|_| ()) .expect("send shutdown signal"); - }); + }) + .into(); shutdown_recv .recv() .await .with_kind(crate::ErrorKind::Unknown)?; - sig_handler.abort(); + sig_handler.wait_for_abort().await; + setter_thread.wait_for_abort().await; Ok::<_, Error>(server) } diff --git a/core/startos/src/db/mod.rs b/core/startos/src/db/mod.rs index 3c48b12d8..c0d2415fb 100644 --- a/core/startos/src/db/mod.rs +++ b/core/startos/src/db/mod.rs @@ -1,6 +1,7 @@ pub mod model; pub mod prelude; +use std::panic::UnwindSafe; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -12,7 +13,7 @@ use itertools::Itertools; use patch_db::json_ptr::{JsonPointer, ROOT}; use patch_db::{DiffPatch, Dump, Revision}; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::{self, UnboundedReceiver}; use tokio::sync::watch; @@ -23,12 +24,22 @@ use crate::context::{CliContext, RpcContext}; use crate::prelude::*; use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::util::net::WebSocketExt; -use crate::util::serde::{HandlerExtSerde, apply_expr}; +use crate::util::serde::{apply_expr, HandlerExtSerde}; lazy_static::lazy_static! { static ref PUBLIC: JsonPointer = "/public".parse().unwrap(); } +pub trait DbAccess: Sized { + type Key<'a>; + fn access<'a>(db: &'a Model, key: Self::Key<'_>) -> &'a Model; +} + +pub trait DbAccessMut: Sized { + type Key<'a>; + fn access_mut<'a>(db: &'a mut Model, key: Self::Key<'_>) -> &'a mut Model; +} + pub fn db() -> ParentHandler { ParentHandler::new() .subcommand( diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index 3895c761c..780541211 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -1,5 +1,6 @@ use std::collections::{BTreeMap, BTreeSet, VecDeque}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::Arc; use chrono::{DateTime, Utc}; use exver::{Version, VersionRange}; @@ -8,7 +9,6 @@ use imbl_value::InternedString; use ipnet::IpNet; use isocountry::CountryCode; use itertools::Itertools; -use lazy_static::lazy_static; use models::{GatewayId, PackageId}; use openssl::hash::MessageDigest; use patch_db::{HasModel, Value}; @@ -18,7 +18,6 @@ use ts_rs::TS; use crate::account::AccountInfo; use crate::db::model::package::AllPackageData; use crate::net::acme::AcmeProvider; -use crate::net::forward::START9_BRIDGE_IFACE; use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, NetInfo}; use crate::net::host::Host; use crate::net::utils::ipv6_is_local; @@ -30,7 +29,7 @@ use crate::util::cpupower::Governor; use crate::util::lshw::LshwDevice; use crate::util::serde::MaybeUtf8String; use crate::version::{Current, VersionT}; -use crate::{ARCH, HOST_IP, PLATFORM}; +use crate::{ARCH, PLATFORM}; #[derive(Debug, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] @@ -216,40 +215,9 @@ pub struct NetworkInterfaceInfo { pub name: Option, pub public: Option, pub secure: Option, - pub ip_info: Option, + pub ip_info: Option>, } impl NetworkInterfaceInfo { - pub fn loopback() -> (&'static GatewayId, &'static Self) { - lazy_static! { - static ref LO: GatewayId = GatewayId::from(InternedString::intern("lo")); - static ref LOOPBACK: NetworkInterfaceInfo = NetworkInterfaceInfo { - name: Some(InternedString::from_static("Loopback")), - 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(), - lan_ip: [ - IpAddr::from(Ipv4Addr::LOCALHOST), - IpAddr::from(Ipv6Addr::LOCALHOST) - ] - .into_iter() - .collect(), - wan_ip: None, - ntp_servers: Default::default(), - dns_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| { @@ -309,7 +277,7 @@ pub struct IpInfo { pub dns_servers: OrdSet, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, TS)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)] #[ts(export)] #[serde(rename_all = "kebab-case")] pub enum NetworkInterfaceType { diff --git a/core/startos/src/db/prelude.rs b/core/startos/src/db/prelude.rs index 6237ca050..6004c8776 100644 --- a/core/startos/src/db/prelude.rs +++ b/core/startos/src/db/prelude.rs @@ -193,7 +193,7 @@ where A: serde::Serialize + serde::de::DeserializeOwned + Ord, B: serde::Serialize + serde::de::DeserializeOwned, { - type Key = A; + type Key = JsonKey; type Value = B; fn key_str(key: &Self::Key) -> Result, Error> { serde_json::to_string(key).with_kind(ErrorKind::Serialization) @@ -433,6 +433,12 @@ impl std::ops::DerefMut for JsonKey { &mut self.0 } } +impl FromStr for JsonKey { + type Err = Error; + fn from_str(s: &str) -> Result { + serde_json::from_str(s).with_kind(ErrorKind::Deserialization) + } +} impl Serialize for JsonKey { fn serialize(&self, serializer: S) -> Result where @@ -445,7 +451,7 @@ impl Serialize for JsonKey { } } // { "foo": "bar" } -> "{ \"foo\": \"bar\" }" -impl<'de, T: Serialize + DeserializeOwned> Deserialize<'de> for JsonKey { +impl<'de, T: DeserializeOwned> Deserialize<'de> for JsonKey { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index ecba6fde9..0f9382515 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -22,10 +22,11 @@ use crate::db::model::Database; use crate::developer::OS_DEVELOPER_KEY_PATH; use crate::hostname::Hostname; use crate::middleware::auth::AuthContext; +use crate::net::gateway::UpgradableListener; use crate::net::net_controller::{NetController, NetService}; use crate::net::socks::DEFAULT_SOCKS_LISTEN; use crate::net::utils::find_wifi_iface; -use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter}; +use crate::net::web_server::WebServerAcceptorSetter; use crate::prelude::*; use crate::progress::{ FullProgress, FullProgressTracker, PhaseProgressTrackerHandle, PhasedProgressBar, ProgressUnits, diff --git a/core/startos/src/net/acme.rs b/core/startos/src/net/acme.rs index f1648f3eb..fc1698043 100644 --- a/core/startos/src/net/acme.rs +++ b/core/startos/src/net/acme.rs @@ -1,24 +1,229 @@ use std::collections::{BTreeMap, BTreeSet}; +use std::net::IpAddr; use std::str::FromStr; +use std::sync::Arc; -use async_acme::acme::Identifier; -use clap::Parser; +use async_acme::acme::{Identifier, ACME_TLS_ALPN_NAME}; use clap::builder::ValueParserFactory; +use clap::Parser; +use futures::StreamExt; use imbl_value::InternedString; use itertools::Itertools; use models::{ErrorData, FromStrParser}; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; -use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; +use tokio_rustls::rustls::crypto::CryptoProvider; +use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; +use tokio_rustls::rustls::sign::CertifiedKey; +use tokio_rustls::rustls::ServerConfig; use ts_rs::TS; use url::Url; use crate::context::{CliContext, RpcContext}; -use crate::db::model::Database; use crate::db::model::public::AcmeSettings; +use crate::db::model::Database; +use crate::db::{DbAccess, DbAccessMut}; +use crate::net::tls::{SingleCertResolver, TlsHandler}; +use crate::net::web_server::Accept; use crate::prelude::*; use crate::util::serde::{Pem, Pkcs8Doc}; +use crate::util::sync::{SyncMutex, Watch}; + +pub type AcmeTlsAlpnCache = + Arc>>>>>; + +pub struct AcmeTlsHandler<'a, M: HasModel, S: 'a> { + pub db: &'a TypedPatchDb, + pub acme_cache: &'a AcmeTlsAlpnCache, + pub crypto_provider: &'a Arc, + pub get_provider: S, + pub in_progress: Watch>>, +} +impl<'b, M, S> AcmeTlsHandler<'b, M, S> +where + for<'a> M: DbAccess = ()> + + DbAccess = &'a AcmeProvider> + + DbAccessMut = ()> + + HasModel> + + Send + + Sync, + S: GetAcmeProvider<'b> + Clone + 'b, +{ + pub async fn get_cert(&self, san_info: &BTreeSet) -> Option { + let provider = self.get_provider.clone().get_provider(san_info).await?; + loop { + let peek = self.db.peek().await; + let store = >::access(&peek, ()); + if let Some(cert) = store + .as_certs() + .as_idx(&provider.0) + .and_then(|p| p.as_idx(JsonKey::new_ref(san_info))) + { + let cert = cert.de().log_err()?; + return Some( + CertifiedKey::from_der( + cert.fullchain + .into_iter() + .map(|c| Ok(CertificateDer::from(c.to_der()?))) + .collect::>() + .log_err()?, + PrivateKeyDer::from(PrivatePkcs8KeyDer::from( + cert.key.0.private_key_to_pkcs8().log_err()?, + )), + &*self.crypto_provider, + ) + .log_err()?, + ); + } + + if !self.in_progress.send_if_modified(|x| { + if !x.contains(san_info) { + x.insert(san_info.clone()); + true + } else { + false + } + }) { + self.in_progress + .clone() + .wait_for(|x| !x.contains(san_info)) + .await; + continue; + } + + let contact = >::access(&peek, provider) + .as_contact() + .de() + .log_err()?; + + let identifiers: Vec<_> = san_info + .iter() + .map(|d| match d.parse::() { + Ok(a) => Identifier::Ip(a), + _ => Identifier::Dns((&**d).into()), + }) + .collect::>(); + + let cache_entries = san_info + .iter() + .cloned() + .map(|d| (d, Watch::new(None))) + .collect::>(); + self.acme_cache.mutate(|c| { + c.extend(cache_entries.iter().map(|(k, v)| (k.clone(), v.clone()))); + }); + + let cert = async_acme::rustls_helper::order( + |identifier, cert| { + let domain = InternedString::from_display(&identifier); + if let Some(entry) = cache_entries.get(&domain) { + entry.send(Some(Arc::new(cert))); + } + Ok(()) + }, + provider.0.as_str(), + &identifiers, + Some(&AcmeCertCache(&self.db)), + &contact, + ) + .await + .log_err()?; + + self.acme_cache + .mutate(|c| c.retain(|c, _| !cache_entries.contains_key(c))); + + self.in_progress.send_modify(|i| i.remove(san_info)); + + return Some(cert); + } + } +} + +pub trait GetAcmeProvider<'a> { + fn get_provider<'b>( + self, + san_info: &'b BTreeSet, + ) -> impl Future> + Send + 'b + where + Self: 'b; +} + +impl<'b, A, M, S> TlsHandler for &'b AcmeTlsHandler<'b, M, S> +where + A: Accept, + ::Metadata: Send + Sync, + for<'a> M: DbAccess = ()> + + DbAccess = &'a AcmeProvider> + + DbAccessMut = ()> + + HasModel> + + Send + + Sync, + S: GetAcmeProvider<'b> + Clone + Send + Sync + 'b, +{ + async fn get_config<'a>( + self, + hello: &'a tokio_rustls::rustls::server::ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> Option + where + Self: 'a, + A: 'a, + ::Metadata: 'a, + { + let domain = hello.server_name()?; + if hello + .alpn() + .into_iter() + .flatten() + .any(|a| a == ACME_TLS_ALPN_NAME) + { + let cert = self + .acme_cache + .peek(|c| c.get(domain).cloned()) + .ok_or_else(|| { + Error::new( + eyre!("No challenge recv available for {domain}"), + ErrorKind::OpenSsl, + ) + }) + .log_err()?; + tracing::info!("Waiting for verification cert for {domain}"); + let cert = cert + .filter(|c| futures::future::ready(c.is_some())) + .next() + .await + .flatten()?; + tracing::info!("Verification cert received for {domain}"); + let mut cfg = ServerConfig::builder_with_provider(self.crypto_provider.clone()) + .with_safe_default_protocol_versions() + .log_err()? + .with_no_client_auth() + .with_cert_resolver(Arc::new(SingleCertResolver(cert))); + + cfg.alpn_protocols = vec![ACME_TLS_ALPN_NAME.to_vec()]; + tracing::info!("performing ACME auth challenge"); + + return Some(cfg); + } + + let domains: BTreeSet = [domain.into()].into_iter().collect(); + + let crypto_provider = self.crypto_provider.clone(); + if let Some(cert) = self.get_cert(&domains).await { + return Some( + ServerConfig::builder_with_provider(crypto_provider) + .with_safe_default_protocol_versions() + .log_err()? + .with_no_client_auth() + .with_cert_resolver(Arc::new(SingleCertResolver(Arc::new(cert)))), + ); + } + + None + } +} #[derive(Debug, Default, Deserialize, Serialize, HasModel)] #[model = "Model"] @@ -32,29 +237,41 @@ impl AcmeCertStore { } } +impl DbAccess for Database { + type Key<'a> = (); + fn access<'a>(db: &'a Model, _: Self::Key<'_>) -> &'a Model { + db.as_private().as_key_store().as_acme() + } +} +impl DbAccessMut for Database { + type Key<'a> = (); + fn access_mut<'a>(db: &'a mut Model, _: Self::Key<'_>) -> &'a mut Model { + db.as_private_mut().as_key_store_mut().as_acme_mut() + } +} + #[derive(Debug, Deserialize, Serialize)] pub struct AcmeCert { pub key: Pem>, pub fullchain: Vec>, } -pub struct AcmeCertCache<'a>(pub &'a TypedPatchDb); +pub struct AcmeCertCache<'a, M: HasModel>(pub &'a TypedPatchDb); #[async_trait::async_trait] -impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> { +impl<'a, M> async_acme::cache::AcmeCache for AcmeCertCache<'a, M> +where + for<'b> M: HasModel> + + DbAccess = ()> + + DbAccessMut = ()> + + Send + + Sync, +{ type Error = ErrorData; async fn read_account(&self, contacts: &[&str]) -> Result>, Self::Error> { let contacts = JsonKey::new(contacts.into_iter().map(|s| (*s).to_owned()).collect_vec()); - let Some(account) = self - .0 - .peek() - .await - .into_private() - .into_key_store() - .into_acme() - .into_accounts() - .into_idx(&contacts) - else { + let peek = self.0.peek().await; + let Some(account) = M::access(&peek, ()).as_accounts().as_idx(&contacts) else { return Ok(None); }; Ok(Some(account.de()?.0.document.into_vec())) @@ -68,9 +285,7 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> { }; self.0 .mutate(|db| { - db.as_private_mut() - .as_key_store_mut() - .as_acme_mut() + M::access_mut(db, ()) .as_accounts_mut() .insert(&contacts, &Pem::new(key)) }) @@ -96,16 +311,11 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> { let directory_url = directory_url .parse::() .with_kind(ErrorKind::ParseUrl)?; - let Some(cert) = self - .0 - .peek() - .await - .into_private() - .into_key_store() - .into_acme() - .into_certs() - .into_idx(&directory_url) - .and_then(|a| a.into_idx(&identifiers)) + let peek = self.0.peek().await; + let Some(cert) = M::access(&peek, ()) + .as_certs() + .as_idx(&directory_url) + .and_then(|a| a.as_idx(&identifiers)) else { return Ok(None); }; @@ -160,9 +370,7 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> { }; self.0 .mutate(|db| { - db.as_private_mut() - .as_key_store_mut() - .as_acme_mut() + M::access_mut(db, ()) .as_certs_mut() .upsert(&directory_url, || Ok(BTreeMap::new()))? .insert(&identifiers, &cert) diff --git a/core/startos/src/net/dns.rs b/core/startos/src/net/dns.rs index c14c7a2c1..cfb150505 100644 --- a/core/startos/src/net/dns.rs +++ b/core/startos/src/net/dns.rs @@ -415,7 +415,6 @@ impl Resolver { { if let Some(res) = self.net_iface.peek(|i| { i.values() - .chain([NetworkInterfaceInfo::loopback().1]) .filter_map(|i| i.ip_info.as_ref()) .find(|i| i.subnets.iter().any(|s| s.contains(&src))) .map(|ip_info| { diff --git a/core/startos/src/net/gateway.rs b/core/startos/src/net/gateway.rs index 3cf14db80..56bb151f9 100644 --- a/core/startos/src/net/gateway.rs +++ b/core/startos/src/net/gateway.rs @@ -3,10 +3,11 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; use std::sync::{Arc, Weak}; -use std::task::Poll; +use std::task::{ready, Poll}; use std::time::Duration; use clap::Parser; +use futures::future::Either; use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; use helpers::NonDetachingJoinHandle; use imbl::{OrdMap, OrdSet}; @@ -19,10 +20,11 @@ use patch_db::json_ptr::JsonPointer; use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::net::{TcpListener, TcpStream}; +use tokio::net::TcpListener; use tokio::process::Command; use tokio::sync::oneshot; use ts_rs::TS; +use visit_rs::{Visit, VisitFields}; use zbus::proxy::{PropertyChanged, PropertyStream, SignalStream}; use zbus::zvariant::{ DeserializeDict, Dict, OwnedObjectPath, OwnedValue, Type as ZType, Value as ZValue, @@ -35,7 +37,7 @@ use crate::db::model::Database; use crate::net::forward::START9_BRIDGE_IFACE; use crate::net::gateway::device::DeviceProxy; use crate::net::utils::ipv6_is_link_local; -use crate::net::web_server::Accept; +use crate::net::web_server::{Accept, AcceptStream, Acceptor, MetadataVisitor}; use crate::prelude::*; use crate::util::collections::OrdMapIterMut; use crate::util::future::Until; @@ -714,6 +716,7 @@ async fn watch_ip( ) }); ip_info.wan_ip = ip_info.wan_ip.or(prev_wan_ip); + let ip_info = Arc::new(ip_info); m.insert( iface.clone(), NetworkInterfaceInfo { @@ -828,7 +831,7 @@ impl NetworkInterfaceWatcher { self.ip_info.read() } - pub fn bind(&self, port: u16) -> Result { + pub fn bind(&self, bind: B, port: u16) -> Result, Error> { let arc = Arc::new(()); self.listeners.mutate(|l| { if l.get(&port).filter(|w| w.strong_count() > 0).is_some() { @@ -841,22 +844,20 @@ impl NetworkInterfaceWatcher { Ok(()) })?; let ip_info = self.ip_info.clone_unseen(); - let activated = self.activated.clone_unseen(); Ok(NetworkInterfaceListener { _arc: arc, ip_info, - activated, - listeners: ListenerMap::new(port), + listeners: ListenerMap::new(bind, port), }) } - pub fn upgrade_listener( + pub fn upgrade_listener( &self, SelfContainedNetworkInterfaceListener { mut listener, .. - }: SelfContainedNetworkInterfaceListener, - ) -> Result { + }: SelfContainedNetworkInterfaceListener, + ) -> Result, Error> { let port = listener.listeners.port; let arc = &listener._arc; self.listeners.mutate(|l| { @@ -1169,45 +1170,6 @@ impl NetworkInterfaceController { } } -struct ListenerMap { - prev_filter: DynInterfaceFilter, - port: u16, - listeners: BTreeMap)>, -} -impl ListenerMap { - fn from_listener(listener: impl IntoIterator) -> Result { - let mut port = 0; - 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 { - if ipv6_is_link_local(*l.ip()) && l.scope_id() == 0 { - continue; // TODO determine scope id - } - } - if port != 0 && port != local.port() { - return Err(Error::new( - eyre!("Provided listeners are bound to different ports"), - ErrorKind::InvalidRequest, - )); - } - port = local.port(); - listeners.insert(local, (listener, None)); - } - if port == 0 { - return Err(Error::new( - eyre!("Listener array cannot be empty"), - ErrorKind::InvalidRequest, - )); - } - Ok(Self { - 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 eq(&self, other: &dyn Any) -> bool { @@ -1235,6 +1197,14 @@ impl InterfaceFilter for bool { } } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct TypeFilter(pub NetworkInterfaceType); +impl InterfaceFilter for TypeFilter { + fn filter(&self, _: &GatewayId, info: &NetworkInterfaceInfo) -> bool { + info.ip_info.as_ref().and_then(|i| i.device_type) == Some(self.0) + } +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct IdFilter(pub GatewayId); impl InterfaceFilter for IdFilter { @@ -1366,10 +1336,17 @@ impl Ord for DynInterfaceFilter { } } -impl ListenerMap { - fn new(port: u16) -> Self { +struct ListenerMap { + prev_filter: DynInterfaceFilter, + bind: B, + port: u16, + listeners: BTreeMap, +} +impl ListenerMap { + fn new(bind: B, port: u16) -> Self { Self { prev_filter: false.into_dyn(), + bind, port, listeners: BTreeMap::new(), } @@ -1384,7 +1361,6 @@ impl ListenerMap { let mut keep = BTreeSet::::new(); for (_, info) in ip_info .iter() - .chain([NetworkInterfaceInfo::loopback()]) .filter(|(id, info)| filter.filter(*id, *info)) { if let Some(ip_info) = &info.ip_info { @@ -1404,24 +1380,9 @@ impl ListenerMap { 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; + if !self.listeners.contains_key(&addr) { + self.listeners.insert(addr, self.bind.bind(addr)?); } - 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), - ), - ); } } } @@ -1429,24 +1390,13 @@ impl ListenerMap { self.prev_filter = filter.clone().into_dyn(); Ok(()) } - fn poll_accept(&self, cx: &mut std::task::Context<'_>) -> Poll> { - 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)) - .with_interval(Duration::from_secs(60)) - .with_retries(5), - ) { - tracing::error!("Failed to set tcp keepalive: {e}"); - tracing::debug!("{e:?}"); - } - return Poll::Ready(Ok(Accepted { - stream, - peer: addr, - wan_ip: *wan_ip, - bind: *bind_addr, - })); + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll::Metadata, AcceptStream), Error>> { + for (addr, listener) in self.listeners.iter_mut() { + if let Poll::Ready((metadata, stream)) = listener.poll_accept(cx)? { + return Poll::Ready(Ok((*addr, metadata, stream))); } } Poll::Pending @@ -1457,54 +1407,100 @@ 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())) - }) + ip_info.iter().find(|(_, i)| { + i.ip_info + .as_ref() + .map_or(false, |i| i.subnets.iter().any(|i| i.addr() == addr.ip())) + }) } -pub struct NetworkInterfaceListener { +pub trait Bind { + type Accept: Accept; + fn bind(&mut self, addr: SocketAddr) -> Result; +} + +#[derive(Clone, Copy, Default)] +pub struct BindTcp; +impl Bind for BindTcp { + type Accept = TcpListener; + fn bind(&mut self, addr: SocketAddr) -> Result { + TcpListener::from_std( + mio::net::TcpListener::bind(addr) + .with_kind(ErrorKind::Network)? + .into(), + ) + .with_kind(ErrorKind::Network) + } +} + +pub trait FromGatewayInfo { + fn from_gateway_info(id: &GatewayId, info: &NetworkInterfaceInfo) -> Self; +} +#[derive(Clone, Debug)] +pub struct GatewayInfo { + pub id: GatewayId, + pub info: NetworkInterfaceInfo, +} +impl Visit for GatewayInfo { + fn visit(&self, visitor: &mut V) -> ::Result { + visitor.visit(self) + } +} +impl FromGatewayInfo for GatewayInfo { + fn from_gateway_info(id: &GatewayId, info: &NetworkInterfaceInfo) -> Self { + Self { + id: id.clone(), + info: info.clone(), + } + } +} + +pub struct NetworkInterfaceListener { pub ip_info: Watch>, - activated: Watch>, - listeners: ListenerMap, + listeners: ListenerMap, _arc: Arc<()>, } -impl NetworkInterfaceListener { +impl NetworkInterfaceListener { + pub(super) fn new( + mut ip_info: Watch>, + bind: B, + port: u16, + ) -> Self { + ip_info.mark_unseen(); + Self { + ip_info, + listeners: ListenerMap::new(bind, port), + _arc: Arc::new(()), + } + } + pub fn port(&self) -> u16 { self.listeners.port } #[cfg_attr(feature = "unstable", inline(never))] - pub fn poll_accept( + pub fn poll_accept( &mut self, cx: &mut std::task::Context<'_>, filter: &impl InterfaceFilter, - ) -> Poll> { + ) -> Poll::Metadata, AcceptStream), Error>> { while self.ip_info.poll_changed(cx).is_ready() || !DynInterfaceFilterT::eq(&self.listeners.prev_filter, filter.as_any()) { self.ip_info .peek_and_mark_seen(|ip_info| self.listeners.update(ip_info, filter))?; } - self.listeners.poll_accept(cx) - } - - pub(super) fn new( - mut ip_info: Watch>, - activated: Watch>, - port: u16, - ) -> Self { - ip_info.mark_unseen(); - Self { - ip_info, - activated, - listeners: ListenerMap::new(port), - _arc: Arc::new(()), - } + let (addr, inner, stream) = ready!(self.listeners.poll_accept(cx)?); + Poll::Ready(Ok(( + self.ip_info + .peek(|ip_info| { + lookup_info_by_addr(ip_info, addr) + .map(|(id, info)| M::from_gateway_info(id, info)) + }) + .or_not_found(lazy_format!("gateway for {addr}"))?, + inner, + stream, + ))) } pub fn change_ip_info_source( @@ -1515,7 +1511,10 @@ impl NetworkInterfaceListener { self.ip_info = ip_info; } - pub async fn accept(&mut self, filter: &impl InterfaceFilter) -> Result { + pub async fn accept( + &mut self, + filter: &impl InterfaceFilter, + ) -> Result<(M, ::Metadata, AcceptStream), Error> { futures::future::poll_fn(|cx| self.poll_accept(cx, filter)).await } @@ -1531,40 +1530,73 @@ impl NetworkInterfaceListener { } } -pub struct Accepted { - pub stream: TcpStream, - pub peer: SocketAddr, - pub wan_ip: Option, - pub bind: SocketAddr, +#[derive(VisitFields)] +pub struct NetworkInterfaceListenerAcceptMetadata { + pub inner: ::Metadata, + pub info: GatewayInfo, } - -pub struct SelfContainedNetworkInterfaceListener { - _watch_thread: NonDetachingJoinHandle<()>, - listener: NetworkInterfaceListener, -} -impl SelfContainedNetworkInterfaceListener { - pub fn bind(port: u16) -> Self { - let ip_info = Watch::new(OrdMap::new()); - let activated = Watch::new( - [( - GatewayId::from(InternedString::from(START9_BRIDGE_IFACE)), - false, - )] - .into_iter() - .collect(), - ); - let _watch_thread = tokio::spawn(watcher(ip_info.clone(), activated.clone())).into(); - Self { - _watch_thread, - listener: NetworkInterfaceListener::new(ip_info, activated, port), - } +impl Visit for NetworkInterfaceListenerAcceptMetadata +where + B: Bind, + ::Metadata: Visit + Clone + Send + Sync + 'static, + V: MetadataVisitor, +{ + fn visit(&self, visitor: &mut V) -> V::Result { + self.visit_fields(visitor).collect() } } -impl Accept for SelfContainedNetworkInterfaceListener { + +impl Accept for NetworkInterfaceListener { + type Metadata = NetworkInterfaceListenerAcceptMetadata; fn poll_accept( &mut self, cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { + ) -> Poll> { + NetworkInterfaceListener::poll_accept(self, cx, &true).map(|res| { + res.map(|(info, inner, stream)| { + ( + NetworkInterfaceListenerAcceptMetadata { inner, info }, + stream, + ) + }) + }) + } +} + +pub struct SelfContainedNetworkInterfaceListener { + _watch_thread: NonDetachingJoinHandle<()>, + listener: NetworkInterfaceListener, +} +impl SelfContainedNetworkInterfaceListener { + pub fn bind(bind: B, port: u16) -> Self { + 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, bind, port), + } + } +} +impl Accept for SelfContainedNetworkInterfaceListener { + type Metadata = as Accept>::Metadata; + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { Accept::poll_accept(&mut self.listener, cx) } } + +pub type UpgradableListener = + Option, NetworkInterfaceListener>>; + +impl Acceptor> +where + B: Bind + Send + Sync + 'static, + B::Accept: Send + Sync, +{ + pub fn bind_upgradable(listener: SelfContainedNetworkInterfaceListener) -> Self { + Self::new(Some(Either::Left(listener))) + } +} diff --git a/core/startos/src/net/mod.rs b/core/startos/src/net/mod.rs index a3878e6dc..739eb861d 100644 --- a/core/startos/src/net/mod.rs +++ b/core/startos/src/net/mod.rs @@ -12,6 +12,7 @@ pub mod service_interface; pub mod socks; pub mod ssl; pub mod static_server; +pub mod tls; pub mod tor; pub mod tunnel; pub mod utils; diff --git a/core/startos/src/net/net_controller.rs b/core/startos/src/net/net_controller.rs index d3b623ad3..bc2cf81b6 100644 --- a/core/startos/src/net/net_controller.rs +++ b/core/startos/src/net/net_controller.rs @@ -19,7 +19,7 @@ use crate::net::dns::DnsController; use crate::net::forward::{PortForwardController, START9_BRIDGE_IFACE}; use crate::net::gateway::{ AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter, - PublicFilter, SecureFilter, + PublicFilter, SecureFilter, TypeFilter, }; use crate::net::host::address::HostAddress; use crate::net::host::binding::{AddSslOptions, BindId, BindOptions}; @@ -28,7 +28,7 @@ use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname, Onion use crate::net::socks::SocksController; use crate::net::tor::{OnionAddress, TorController, TorSecretKey}; use crate::net::utils::ipv6_is_local; -use crate::net::vhost::{AlpnInfo, TargetInfo, VHostController}; +use crate::net::vhost::{AlpnInfo, ProxyTarget, VHostController}; use crate::prelude::*; use crate::service::effects::callbacks::ServiceCallbacks; use crate::util::serde::MaybeUtf8String; @@ -134,7 +134,7 @@ impl NetController { #[derive(Default, Debug)] struct HostBinds { forwards: BTreeMap)>, - vhosts: BTreeMap<(Option, u16), (TargetInfo, Arc<()>)>, + vhosts: BTreeMap<(Option, u16), (ProxyTarget, Arc<()>)>, private_dns: BTreeMap>, tor: BTreeMap, Vec>)>, } @@ -226,7 +226,7 @@ impl NetServiceData { async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> { let mut forwards: BTreeMap = BTreeMap::new(); - let mut vhosts: BTreeMap<(Option, u16), TargetInfo> = BTreeMap::new(); + let mut vhosts: BTreeMap<(Option, u16), ProxyTarget> = BTreeMap::new(); let mut private_dns: BTreeSet = BTreeSet::new(); let mut tor: BTreeMap)> = BTreeMap::new(); @@ -263,7 +263,7 @@ impl NetServiceData { for hostname in ctrl.server_hostnames.iter().cloned() { vhosts.insert( (hostname, external), - TargetInfo { + ProxyTarget { filter: bind.net.clone().into_dyn(), acme: None, addr, @@ -278,11 +278,9 @@ impl NetServiceData { if hostnames.insert(hostname.clone()) { vhosts.insert( (Some(hostname), external), - TargetInfo { + ProxyTarget { filter: OrFilter( - IdFilter( - NetworkInterfaceInfo::loopback().0.clone(), - ), + TypeFilter(NetworkInterfaceType::Loopback), IdFilter(GatewayId::from(InternedString::from( START9_BRIDGE_IFACE, ))), @@ -306,7 +304,7 @@ impl NetServiceData { if let Some(public) = &public { vhosts.insert( (address.clone(), 5443), - TargetInfo { + ProxyTarget { filter: AndFilter( bind.net.clone(), AndFilter( @@ -322,7 +320,7 @@ impl NetServiceData { ); vhosts.insert( (address.clone(), 443), - TargetInfo { + ProxyTarget { filter: AndFilter( bind.net.clone(), if private { @@ -348,7 +346,7 @@ impl NetServiceData { } else { vhosts.insert( (address.clone(), 443), - TargetInfo { + ProxyTarget { filter: AndFilter( bind.net.clone(), PublicFilter { public: false }, @@ -364,7 +362,7 @@ impl NetServiceData { if let Some(public) = public { vhosts.insert( (address.clone(), external), - TargetInfo { + ProxyTarget { filter: AndFilter( bind.net.clone(), if private { @@ -387,7 +385,7 @@ impl NetServiceData { } else { vhosts.insert( (address.clone(), external), - TargetInfo { + ProxyTarget { filter: AndFilter( bind.net.clone(), PublicFilter { public: false }, diff --git a/core/startos/src/net/static_server.rs b/core/startos/src/net/static_server.rs index aad3aca20..f328ad22d 100644 --- a/core/startos/src/net/static_server.rs +++ b/core/startos/src/net/static_server.rs @@ -8,7 +8,7 @@ use std::time::UNIX_EPOCH; use async_compression::tokio::bufread::GzipEncoder; use axum::body::Body; use axum::extract::{self as x, Request}; -use axum::response::{Redirect, Response}; +use axum::response::{IntoResponse, Redirect, Response}; use axum::routing::{any, get}; use axum::Router; use base64::display::Base64Display; @@ -37,6 +37,8 @@ use crate::main_api; use crate::middleware::auth::{Auth, HasValidSession}; use crate::middleware::cors::Cors; use crate::middleware::db::SyncDb; +use crate::net::gateway::GatewayInfo; +use crate::net::tls::TlsHandshakeInfo; use crate::prelude::*; use crate::rpc_continuations::{Guid, RpcContinuations}; use crate::s9pk::merkle_archive::source::http::HttpSource; @@ -80,6 +82,30 @@ impl UiContext for RpcContext { .middleware(SyncDb::new()) } fn extend_router(self, router: Router) -> Router { + async fn https_redirect_if_public_http( + req: Request, + next: axum::middleware::Next, + ) -> Response { + if req + .extensions() + .get::() + .map_or(false, |p| p.info.public()) + && req.extensions().get::().is_none() + { + Redirect::temporary(&format!( + "https://{}{}", + req.headers() + .get(HOST) + .and_then(|s| s.to_str().ok()) + .unwrap_or("localhost"), + req.uri() + )) + .into_response() + } else { + next.run(req).await + } + } + router .route("/proxy/{url}", { let ctx = self.clone(); @@ -103,6 +129,7 @@ impl UiContext for RpcContext { } }), ) + .layer(axum::middleware::from_fn(https_redirect_if_public_http)) } } @@ -229,20 +256,6 @@ pub fn refresher() -> Router { })) } -pub fn redirecter() -> Router { - Router::new().fallback(get(|request: Request| async move { - Redirect::temporary(&format!( - "https://{}{}", - request - .headers() - .get(HOST) - .and_then(|s| s.to_str().ok()) - .unwrap_or("localhost"), - request.uri() - )) - })) -} - async fn proxy_request(ctx: RpcContext, request: Request, url: String) -> Result { if_authorized(&ctx, request, |mut request| async { for header in PROXY_STRIP_HEADERS { diff --git a/core/startos/src/net/tls.rs b/core/startos/src/net/tls.rs new file mode 100644 index 000000000..45284e400 --- /dev/null +++ b/core/startos/src/net/tls.rs @@ -0,0 +1,282 @@ +use std::sync::Arc; +use std::task::Poll; + +use futures::future::BoxFuture; +use futures::FutureExt; +use imbl_value::InternedString; +use tokio::io::AsyncWriteExt; +use tokio_rustls::rustls::server::{Acceptor, ClientHello, ResolvesServerCert}; +use tokio_rustls::rustls::sign::CertifiedKey; +use tokio_rustls::rustls::ServerConfig; +use tokio_rustls::LazyConfigAcceptor; +use visit_rs::{Visit, VisitFields}; + +use crate::net::web_server::{Accept, AcceptStream, MetadataVisitor}; +use crate::prelude::*; +use crate::util::io::{BackTrackingIO, ReadWriter}; +use crate::util::serde::MaybeUtf8String; + +#[derive(Debug, Clone, VisitFields)] +pub struct TlsMetadata { + pub inner: M, + pub tls_info: TlsHandshakeInfo, +} +impl, M: Visit> Visit for TlsMetadata { + fn visit(&self, visitor: &mut V) -> ::Result { + self.visit_fields(visitor).collect() + } +} + +#[derive(Debug, Clone)] +pub struct TlsHandshakeInfo { + pub sni: Option, + pub alpn: Vec, +} +impl Visit for TlsHandshakeInfo { + fn visit(&self, visitor: &mut V) -> ::Result { + visitor.visit(self) + } +} + +pub trait TlsHandler { + fn get_config<'a>( + self, + hello: &'a ClientHello<'a>, + metadata: &'a A::Metadata, + ) -> impl Future> + Send + 'a + where + Self: 'a, + A: 'a, + A::Metadata: 'a; +} + +#[derive(Clone)] +pub struct ChainedHandler(pub H0, pub H1); +impl TlsHandler for ChainedHandler +where + A: Accept, + ::Metadata: Send + Sync, + H0: TlsHandler + Send, + H1: TlsHandler + Send, +{ + async fn get_config<'a>( + self, + hello: &'a ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> Option + where + Self: 'a, + { + if let Some(config) = self.0.get_config(hello, metadata).await { + return Some(config); + } + self.1.get_config(hello, metadata).await + } +} + +pub struct TlsHandlerWrapper { + pub inner: I, + pub wrapper: W, +} + +pub trait WrapTlsHandler { + fn wrap<'a>( + self, + prev: ServerConfig, + hello: &'a ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> impl Future> + Send + 'a + where + Self: 'a; +} + +impl TlsHandler for TlsHandlerWrapper +where + A: Accept, + ::Metadata: Send + Sync, + I: TlsHandler + Send, + W: WrapTlsHandler + Send, +{ + async fn get_config<'a>( + self, + hello: &'a ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> Option + where + Self: 'a, + A: 'a, + ::Metadata: 'a, + { + let prev = self.inner.get_config(hello, metadata).await?; + self.wrapper.wrap(prev, hello, metadata).await + } +} + +#[derive(Debug)] +pub struct SingleCertResolver(pub Arc); +impl ResolvesServerCert for SingleCertResolver { + fn resolve(&self, _: ClientHello) -> Option> { + Some(self.0.clone()) + } +} + +pub struct TlsListener> { + pub accept: A, + pub tls_handler: H, + in_progress: + Vec, AcceptStream)>, Error>>>, +} +impl> TlsListener { + pub fn new(accept: A, cert_handler: H) -> Self { + Self { + accept, + tls_handler: cert_handler, + in_progress: Vec::new(), + } + } +} +impl Accept for TlsListener +where + A: Accept, + A::Metadata: Send + 'static, + H: TlsHandler + Clone + Send + 'static, +{ + type Metadata = TlsMetadata; + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + if let Some((idx, res)) = self + .in_progress + .iter_mut() + .enumerate() + .find_map(|(idx, fut)| match fut.poll_unpin(cx) { + Poll::Ready(a) => Some((idx, a)), + Poll::Pending => None, + }) + { + self.in_progress.swap_remove(idx); + if let Some(res) = res.transpose() { + return Poll::Ready(res); + } + } + + if let Poll::Ready((metadata, stream)) = self.accept.poll_accept(cx)? { + let tls_handler = self.tls_handler.clone(); + self.in_progress.push( + async move { + let mut acceptor = + LazyConfigAcceptor::new(Acceptor::default(), BackTrackingIO::new(stream)); + let mut mid: tokio_rustls::StartHandshake> = + match (&mut acceptor).await { + Ok(a) => a, + Err(e) => { + let mut stream = acceptor.take_io().or_not_found("acceptor io")?; + let (_, buf) = stream.rewind(); + if std::str::from_utf8(buf) + .ok() + .and_then(|buf| { + buf.lines() + .map(|l| l.trim()) + .filter(|l| !l.is_empty()) + .next() + }) + .map_or(false, |buf| { + regex::Regex::new("[A-Z]+ (.+) HTTP/1") + .unwrap() + .is_match(buf) + }) + { + handle_http_on_https(stream).await.log_err(); + + return Ok(None); + } else { + return Err(e).with_kind(ErrorKind::Network); + } + } + }; + let hello = mid.client_hello(); + if let Some(cfg) = tls_handler.get_config(&hello, &metadata).await { + let metadata = TlsMetadata { + inner: metadata, + tls_info: TlsHandshakeInfo { + sni: hello.server_name().map(InternedString::intern), + alpn: hello + .alpn() + .into_iter() + .flatten() + .map(|a| MaybeUtf8String(a.to_vec())) + .collect(), + }, + }; + let buffered = mid.io.stop_buffering(); + mid.io + .write_all(&buffered) + .await + .with_kind(ErrorKind::Network)?; + return Ok(Some(( + metadata, + Box::pin(mid.into_stream(Arc::new(cfg)).await?) as AcceptStream, + ))); + } + + Ok(None) + } + .boxed(), + ); + } + + Poll::Pending + } +} + +async fn handle_http_on_https(stream: impl ReadWriter + Unpin + 'static) -> Result<(), Error> { + use axum::body::Body; + use axum::extract::Request; + use axum::response::Response; + use http::Uri; + + use crate::net::static_server::server_error; + + hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new()) + .serve_connection( + hyper_util::rt::TokioIo::new(stream), + hyper_util::service::TowerToHyperService::new(axum::Router::new().fallback( + axum::routing::method_routing::any(move |req: Request| async move { + match async move { + let host = req + .headers() + .get(http::header::HOST) + .and_then(|host| host.to_str().ok()); + if let Some(host) = host { + let uri = Uri::from_parts({ + let mut parts = req.uri().to_owned().into_parts(); + parts.scheme = Some("https".parse()?); + parts.authority = Some(host.parse()?); + parts + })?; + Response::builder() + .status(http::StatusCode::TEMPORARY_REDIRECT) + .header(http::header::LOCATION, uri.to_string()) + .body(Body::default()) + } else { + Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body(Body::from("Host header required")) + } + } + .await + { + Ok(a) => a, + Err(e) => { + tracing::warn!("Error redirecting http request on ssl port: {e}"); + tracing::error!("{e:?}"); + server_error(Error::new(e, ErrorKind::Network)) + } + } + }), + )), + ) + .await + .map_err(|e| Error::new(color_eyre::eyre::Report::msg(e), ErrorKind::Network)) +} diff --git a/core/startos/src/net/utils.rs b/core/startos/src/net/utils.rs index 98407c400..da4295eea 100644 --- a/core/startos/src/net/utils.rs +++ b/core/startos/src/net/utils.rs @@ -5,15 +5,62 @@ use async_stream::try_stream; use color_eyre::eyre::eyre; use futures::stream::BoxStream; use futures::{StreamExt, TryStreamExt}; +use imbl::OrdMap; use imbl_value::InternedString; use ipnet::{IpNet, Ipv4Net, Ipv6Net}; +use models::GatewayId; use nix::net::if_::if_nametoindex; use tokio::net::{TcpListener, TcpStream}; use tokio::process::Command; +use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType}; use crate::prelude::*; +use crate::util::collections::OrdMapIterMut; use crate::util::Invoke; +pub async fn load_network_interface_info() -> Result, Error> +{ + let output = String::from_utf8( + Command::new("ip") + .arg("-o") + .arg("addr") + .arg("show") + .invoke(crate::ErrorKind::Network) + .await?, + )?; + + let err_fn = || { + Error::new( + eyre!("malformed output from `ip`"), + crate::ErrorKind::Network, + ) + }; + + let mut res = OrdMap::::new(); + + for line in output.lines() { + let split = line.split_ascii_whitespace().collect::>(); + let iface = GatewayId::from(InternedString::from(*split.get(1).ok_or_else(&err_fn)?)); + let subnet: IpNet = split.get(3).ok_or_else(&err_fn)?.parse()?; + let info = res.entry(iface).or_default(); + let ip_info = info.ip_info.get_or_insert_default(); + ip_info.scope_id = split + .get(0) + .ok_or_else(&err_fn)? + .strip_suffix(":") + .ok_or_else(&err_fn)? + .parse()?; + ip_info.subnets.insert(subnet); + } + + for (id, info) in OrdMapIterMut::from(&mut res) { + let ip_info = info.ip_info.get_or_insert_default(); + ip_info.device_type = probe_iface_type(id.as_str()).await; + } + + Ok(res) +} + pub fn ipv6_is_link_local(addr: Ipv6Addr) -> bool { (addr.segments()[0] & 0xffc0) == 0xfe80 } @@ -75,6 +122,22 @@ pub async fn get_iface_ipv6_addr(iface: &str) -> Result Option { + match tokio::fs::read_to_string(Path::new("/sys/class/net").join(iface).join("uevent")) + .await + .ok()? + .lines() + .find_map(|l| l.strip_prefix("DEVTYPE=")) + { + Some("wlan") => Some(NetworkInterfaceType::Wireless), + Some("bridge") => Some(NetworkInterfaceType::Bridge), + Some("wireguard") => Some(NetworkInterfaceType::Wireguard), + None if iface_is_physical(iface).await => Some(NetworkInterfaceType::Ethernet), + None if iface_is_loopback(iface).await => Some(NetworkInterfaceType::Loopback), + _ => None, + } +} + pub async fn iface_is_physical(iface: &str) -> bool { tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("device")) .await @@ -87,6 +150,19 @@ pub async fn iface_is_wireless(iface: &str) -> bool { .is_ok() } +pub async fn iface_is_bridge(iface: &str) -> bool { + tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("bridge")) + .await + .is_ok() +} + +pub async fn iface_is_loopback(iface: &str) -> bool { + tokio::fs::read_to_string(Path::new("/sys/class/net").join(iface).join("type")) + .await + .ok() + .map_or(false, |x| x.trim() == "772") +} + pub fn list_interfaces() -> BoxStream<'static, Result> { try_stream! { let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?; diff --git a/core/startos/src/net/vhost.rs b/core/startos/src/net/vhost.rs index 40fb1441c..f5994eb87 100644 --- a/core/startos/src/net/vhost.rs +++ b/core/startos/src/net/vhost.rs @@ -1,20 +1,22 @@ +use std::any::Any; use std::collections::{BTreeMap, BTreeSet}; use std::net::{IpAddr, SocketAddr}; use std::sync::{Arc, Weak}; -use async_acme::acme::{ACME_TLS_ALPN_NAME, Identifier}; +use async_acme::acme::{Identifier, ACME_TLS_ALPN_NAME}; use axum::body::Body; use axum::extract::Request; use axum::response::Response; use color_eyre::eyre::eyre; +use futures::future::BoxFuture; use futures::FutureExt; use helpers::NonDetachingJoinHandle; -use http::Uri; +use http::{Extensions, Uri}; use imbl::OrdMap; -use imbl_value::InternedString; +use imbl_value::{InOMap, InternedString}; use itertools::Itertools; use models::{GatewayId, ResultExt}; -use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn}; +use rpc_toolkit::{from_fn, Context, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tokio::net::TcpStream; @@ -23,29 +25,29 @@ use tokio_rustls::rustls::crypto::CryptoProvider; use tokio_rustls::rustls::pki_types::{ CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, }; -use tokio_rustls::rustls::server::{Acceptor, ResolvesServerCert}; -use tokio_rustls::rustls::sign::CertifiedKey; -use tokio_rustls::rustls::{RootCertStore, ServerConfig}; +use tokio_rustls::rustls::server::{Acceptor, ClientHello}; +use tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig}; use tokio_rustls::{LazyConfigAcceptor, TlsConnector}; -use tokio_stream::StreamExt; use tokio_stream::wrappers::WatchStream; +use tokio_stream::StreamExt; use tracing::instrument; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::db::model::Database; use crate::db::model::public::NetworkInterfaceInfo; -use crate::net::acme::{AcmeCertCache, AcmeProvider}; +use crate::db::model::Database; +use crate::net::acme::{AcmeCertCache, AcmeTlsAlpnCache, AcmeTlsHandler}; use crate::net::gateway::{ - Accepted, AnyFilter, DynInterfaceFilter, InterfaceFilter, NetworkInterfaceController, - NetworkInterfaceListener, + AnyFilter, BindTcp, DynInterfaceFilter, GatewayInfo, InterfaceFilter, NetworkInterfaceController, NetworkInterfaceListener }; use crate::net::static_server::server_error; +use crate::net::tls::{ChainedHandler, TlsHandler, TlsListener, WrapTlsHandler}; +use crate::net::web_server::{Accept, AcceptStream, extract}; use crate::prelude::*; use crate::util::collections::EqSet; use crate::util::io::BackTrackingIO; -use crate::util::serde::{HandlerExtSerde, MaybeUtf8String, display_serializable}; -use crate::util::sync::SyncMutex; +use crate::util::serde::{display_serializable, HandlerExtSerde, MaybeUtf8String}; +use crate::util::sync::{SyncMutex, Watch}; pub fn vhost_api() -> ParentHandler { ParentHandler::new().subcommand( @@ -61,8 +63,7 @@ pub fn vhost_api() -> ParentHandler { } let mut table = Table::new(); - table - .add_row(row![bc => "FROM", "TO", "GATEWAYS", "ACME", "CONNECT SSL", "ACTIVE"]); + table.add_row(row![bc => "FROM", "TO", "GATEWAYS", "CONNECT SSL", "ACTIVE"]); for (external, targets) in res { for (host, targets) in targets { @@ -75,7 +76,6 @@ pub fn vhost_api() -> ParentHandler { ), target.addr, target.gateways.iter().join(", "), - target.acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE"), target.connect_ssl.is_ok(), idx == 0 ]); @@ -91,22 +91,14 @@ pub fn vhost_api() -> ParentHandler { ) } -#[derive(Debug)] -struct SingleCertResolver(Arc); -impl ResolvesServerCert for SingleCertResolver { - fn resolve(&self, _: tokio_rustls::rustls::server::ClientHello) -> Option> { - Some(self.0.clone()) - } -} - // not allowed: <=1024, >=32768, 5355, 5432, 9050, 6010, 9051, 5353 pub struct VHostController { db: TypedPatchDb, interfaces: Arc, crypto_provider: Arc, - acme_tls_alpn_cache: AcmeTlsAlpnCache, - servers: SyncMutex>, + acme_cache: AcmeTlsAlpnCache, + servers: SyncMutex>>, } impl VHostController { pub fn new(db: TypedPatchDb, interfaces: Arc) -> Self { @@ -114,7 +106,7 @@ impl VHostController { db, interfaces, crypto_provider: Arc::new(tokio_rustls::rustls::crypto::ring::default_provider()), - acme_tls_alpn_cache: Arc::new(SyncMutex::new(BTreeMap::new())), + acme_cache: Arc::new(SyncMutex::new(BTreeMap::new())), servers: SyncMutex::new(BTreeMap::new()), } } @@ -123,19 +115,18 @@ impl VHostController { &self, hostname: Option, external: u16, - target: TargetInfo, + target: impl VHostTarget, ) -> Result, Error> { self.servers.mutate(|writable| { let server = if let Some(server) = writable.remove(&external) { server } else { VHostServer::new( - external, + self.interfaces.watcher.bind(BindTcp, external)?, self.db.clone(), - self.interfaces.clone(), self.crypto_provider.clone(), - self.acme_tls_alpn_cache.clone(), - )? + self.acme_cache.clone(), + ) }; let rc = server.add(hostname, target); writable.insert(external, server); @@ -185,43 +176,158 @@ impl VHostController { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct TargetInfo { - 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 +pub trait VHostTarget: std::fmt::Debug + Eq { + type PreprocessRes: Send + 'static; + #[allow(unused_variables)] + fn skip(&self, metadata: &::Metadata) -> bool { + false + } + fn preprocess<'a>( + &'a self, + prev: ServerConfig, + hello: &'a ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> impl Future> + Send + 'a; + fn handle_stream(&self, stream: AcceptStream, prev: Self::PreprocessRes); } -#[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 +pub trait DynVHostTargetT: std::fmt::Debug + Any { + fn skip(&self, metadata: &::Metadata) -> bool; + fn preprocess<'a>( + &'a self, + prev: ServerConfig, + hello: &'a ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> BoxFuture<'a, Option<(ServerConfig, Box)>>; + fn handle_stream(&self, stream: AcceptStream, prev: Box); + fn eq(&self, other: &dyn DynVHostTargetT) -> bool; } -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, +impl + 'static> DynVHostTargetT for T { + fn skip(&self, metadata: &::Metadata) -> bool { + VHostTarget::skip(self, metadata) + } + fn preprocess<'a>( + &'a self, + prev: ServerConfig, + hello: &'a ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> BoxFuture<'a, Option<(ServerConfig, Box)>> { + VHostTarget::preprocess(self, prev, hello, metadata) + .map(|o| o.map(|(cfg, res)| (cfg, Box::new(res) as Box))) + .boxed() + } + fn handle_stream(&self, stream: AcceptStream, prev: Box) { + if let Ok(prev) = prev.downcast() { + VHostTarget::handle_stream(self, stream, *prev); } } + fn eq(&self, other: &dyn DynVHostTargetT) -> bool { + Some(self) == (other as &dyn Any).downcast_ref() + } +} + +struct DynVHostTarget(Arc + Send + Sync>); +impl Clone for DynVHostTarget { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} +impl PartialEq for DynVHostTarget { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&*other.0) + } +} +impl Eq for DynVHostTarget {} +struct Preprocessed(DynVHostTarget, Box); +impl DynVHostTarget { + async fn into_preprocessed( + self, + prev: ServerConfig, + hello: &ClientHello<'_>, + metadata: &::Metadata, + ) -> Option<(ServerConfig, Preprocessed)> { + let (cfg, res) = self.0.preprocess(prev, hello, metadata).await?; + Some((cfg, Preprocessed(self, res))) + } +} +impl Preprocessed { + fn finish(self, stream: AcceptStream) { + (self.0).0.handle_stream(stream, self.1); + } +} + +#[derive(Debug, Clone)] +pub struct ProxyTarget { + pub filter: DynInterfaceFilter, + 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 PartialEq for ProxyTarget { + fn eq(&self, other: &Self) -> bool { + self.filter == other.filter + && self.addr == other.addr + && self.connect_ssl.as_ref().err() == other.connect_ssl.as_ref().err() + } +} +impl Eq for ProxyTarget {} + +impl VHostTarget for ProxyTarget +where + A: Accept + 'static, + ::Metadata: Send + Sync, +{ + type PreprocessRes = AcceptStream; + fn skip(&self, metadata: &::Metadata) -> bool { + let info = extract::(metadata) + } + async fn preprocess<'a>( + &'a self, + mut prev: ServerConfig, + hello: &'a ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> Option<(ServerConfig, Self::PreprocessRes)> { + let tcp_stream = TcpStream::connect(self.addr) + .await + .with_ctx(|_| (ErrorKind::Network, self.addr)) + .log_err()?; + match &self.connect_ssl { + Ok(client_cfg) => { + let mut client_cfg = (&**client_cfg).clone(); + client_cfg.alpn_protocols = hello + .alpn() + .into_iter() + .flatten() + .map(|x| x.to_vec()) + .collect(); + let target_stream = TlsConnector::from(Arc::new(client_cfg)) + .connect_with( + ServerName::IpAddress(self.addr.ip().into()), + tcp_stream, + |conn| { + prev.alpn_protocols + .extend(conn.alpn_protocol().into_iter().map(|p| p.to_vec())) + }, + ) + .await + .log_err()?; + return Some((prev, Box::pin(target_stream))); + } + Err(AlpnInfo::Reflect) => { + for alpn in hello.alpn().into_iter().flatten() { + prev.alpn_protocols.push(alpn.to_vec()); + } + } + Err(AlpnInfo::Specified(a)) => { + for alpn in a { + prev.alpn_protocols.push(alpn.0.clone()); + } + } + } + Some((prev, Box::pin(tcp_stream))) + } + fn handle_stream(&self, mut stream: AcceptStream, mut prev: Self::PreprocessRes) { + tokio::spawn(async move { tokio::io::copy_bidirectional(&mut stream, &mut prev).await }); + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)] @@ -237,17 +343,57 @@ impl Default for AlpnInfo { } } -type AcmeTlsAlpnCache = - Arc>>>>>; -type Mapping = BTreeMap, BTreeMap>>; +type Mapping = BTreeMap, InOMap, Weak<()>>>; -struct VHostServer { - mapping: watch::Sender, +pub struct VHostConnector<'a, A: Accept + 'static>(&'a Watch>, Option>); + +impl<'b, A> WrapTlsHandler for &'b mut VHostConnector<'b, A> +where + A: Accept + 'static, + ::Metadata: Send + Sync, +{ + async fn wrap<'a>( + self, + prev: ServerConfig, + hello: &'a ClientHello<'a>, + metadata: &'a ::Metadata, + ) -> Option + where + Self: 'a, + { + if hello + .alpn() + .into_iter() + .flatten() + .any(|a| a == ACME_TLS_ALPN_NAME) + { + return Some(prev); + } + + let target = self.0.peek(|m| { + m.get(&hello.server_name().map(InternedString::from)) + .into_iter() + .flatten() + .filter(|(_, rc)| rc.strong_count() > 0) + .find(|(t, _)| !t.skip(metadata)) + .map(|(e, _)| e.clone()) + })?; + + let (prev, store) = target.into_preprocessed(prev, hello, metadata).await?; + + self.1 = Some(store); + + Some(prev) + } +} + +struct VHostServer { + mapping: Watch>, _thread: NonDetachingJoinHandle<()>, } -impl<'a> From<&'a BTreeMap, BTreeMap>>> for AnyFilter { - fn from(value: &'a BTreeMap, BTreeMap>>) -> Self { +impl<'a> From<&'a BTreeMap, BTreeMap>>> for AnyFilter { + fn from(value: &'a BTreeMap, BTreeMap>>) -> Self { Self( value .iter() @@ -690,34 +836,35 @@ impl VHostServer { } #[instrument(skip_all)] - fn new( - port: u16, + fn new( + listener: A, db: TypedPatchDb, - iface_ctrl: Arc, crypto_provider: Arc, - acme_tls_alpn_cache: AcmeTlsAlpnCache, + acme_cache: AcmeTlsAlpnCache, ) -> Result { - let mut listener = iface_ctrl - .watcher - .bind(port) - .with_kind(crate::ErrorKind::Network)?; - let (map_send, map_recv) = watch::channel(BTreeMap::new()); + let mapping = Watch::new(BTreeMap::new()); Ok(Self { - mapping: map_send, + mapping: mapping.clone(), _thread: tokio::spawn(async move { + let listener = TlsListener::new( + listener, + VHostTlsHandler { + cert_handler: ChainedHandler( + &AcmeTlsHandler { + db: &db, + acme_cache: &acme_cache, + crypto_provider: &crypto_provider, + get_provider: todo!(), + in_progress: Watch::new(BTreeSet::new()), + }, + todo!(), + ), + alpn_handler: todo!(), + }, + ); loop { - if let Err(e) = Self::accept( - &mut listener, - map_recv.clone(), - db.clone(), - acme_tls_alpn_cache.clone(), - crypto_provider.clone(), - ) - .await - { - tracing::error!( - "VHostController: failed to accept connection on {port}: {e}" - ); + if let Err(e) = Self::accept(&mut listener, &mapping).await { + tracing::error!("VHostController: failed to accept connection: {e}"); tracing::debug!("{e:?}"); } } @@ -725,7 +872,7 @@ impl VHostServer { .into(), }) } - fn add(&self, hostname: Option, target: TargetInfo) -> Result, Error> { + fn add(&self, hostname: Option, target: ProxyTarget) -> Result, Error> { let mut res = Ok(Arc::new(())); self.mapping.send_if_modified(|writable| { let mut changed = false; diff --git a/core/startos/src/net/web_server.rs b/core/startos/src/net/web_server.rs index 0a6f584bb..5a5ef465c 100644 --- a/core/startos/src/net/web_server.rs +++ b/core/startos/src/net/web_server.rs @@ -1,73 +1,136 @@ +use std::any::Any; use std::future::Future; use std::net::SocketAddr; use std::ops::Deref; +use std::pin::Pin; use std::sync::Arc; use std::task::Poll; use std::time::Duration; -use axum::extract::ConnectInfo; use axum::Router; use futures::future::Either; use futures::FutureExt; use helpers::NonDetachingJoinHandle; +use http::Extensions; use hyper_util::rt::{TokioIo, TokioTimer}; -use tokio::net::{TcpListener, TcpStream}; +use tokio::net::TcpListener; use tokio::sync::oneshot; +use visit_rs::{Visit, Visitor}; -use crate::net::gateway::{ - lookup_info_by_addr, NetworkInterfaceListener, SelfContainedNetworkInterfaceListener, -}; -use crate::net::static_server::{redirecter, refresher, ui_router, UiContext}; +use crate::net::static_server::{ui_router, UiContext}; use crate::prelude::*; use crate::util::actor::background::BackgroundJobQueue; +use crate::util::io::ReadWriter; use crate::util::sync::{SyncRwLock, Watch}; -pub struct Accepted { +pub type AcceptStream = Pin>; + +pub trait MetadataVisitor: Visitor { + fn visit(&mut self, metadata: &M) -> Self::Result; +} + +pub struct ExtensionVisitor<'a>(&'a mut Extensions); +impl<'a> Visitor for ExtensionVisitor<'a> { + type Result = (); +} +impl<'a> MetadataVisitor for ExtensionVisitor<'a> { + fn visit(&mut self, metadata: &M) -> Self::Result { + self.0.insert(metadata.clone()); + } +} + +pub struct ExtractVisitor(Option); +impl Visitor for ExtractVisitor { + type Result = (); +} +impl MetadataVisitor for ExtractVisitor { + fn visit(&mut self, metadata: &M) -> Self::Result { + if let Some(matching) = (metadata as &dyn Any).downcast_ref::() { + self.0 = Some(matching.clone()); + } + } +} +pub fn extract>>(metadata: &M) -> Option { + let mut visitor = ExtractVisitor(None); + visitor.visit(metadata); + visitor.0 +} + +#[derive(Clone, Copy, Debug)] +pub struct TcpMetadata { pub peer_addr: SocketAddr, pub local_addr: SocketAddr, - pub https_redirect: bool, - pub stream: TcpStream, +} +impl Visit for TcpMetadata { + fn visit(&self, visitor: &mut V) -> ::Result { + visitor.visit(self) + } } pub trait Accept { - fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll>; + type Metadata; + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll>; } -impl Accept for Vec { - fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - for listener in &*self { - if let Poll::Ready((stream, peer_addr)) = listener.poll_accept(cx)? { - return Poll::Ready(Ok(Accepted { - local_addr: listener.local_addr()?, +impl Accept for TcpListener { + type Metadata = TcpMetadata; + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + if let Poll::Ready((stream, peer_addr)) = TcpListener::poll_accept(self, cx)? { + if let Err(e) = socket2::SockRef::from(&stream).set_tcp_keepalive( + &socket2::TcpKeepalive::new() + .with_time(Duration::from_secs(900)) + .with_interval(Duration::from_secs(60)) + .with_retries(5), + ) { + tracing::error!("Failed to set tcp keepalive: {e}"); + tracing::debug!("{e:?}"); + } + return Poll::Ready(Ok(( + TcpMetadata { + local_addr: self.local_addr()?, peer_addr, - https_redirect: false, - stream, - })); + }, + Box::pin(stream), + ))); + } + Poll::Pending + } +} + +impl Accept for Vec +where + A: Accept, +{ + type Metadata = A::Metadata; + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + for listener in self { + if let Poll::Ready(accepted) = listener.poll_accept(cx)? { + return Poll::Ready(Ok(accepted)); } } Poll::Pending } } -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| { - let public = self - .ip_info - .peek(|i| lookup_info_by_addr(i, a.bind).map_or(true, |(_, i)| i.public())); - Accepted { - peer_addr: a.peer, - local_addr: a.bind, - https_redirect: public, - stream: a.stream, - } - }) - }) - } -} -impl Accept for Either { - fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { +impl Accept for Either +where + A: Accept, + B: Accept, +{ + type Metadata = A::Metadata; + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll> { match self { Either::Left(a) => a.poll_accept(cx), Either::Right(b) => b.poll_accept(cx), @@ -75,7 +138,11 @@ impl Accept for Either { } } impl Accept for Option { - fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + type Metadata = A::Metadata; + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll> { match self { None => Poll::Pending, Some(a) => a.poll_accept(cx), @@ -98,12 +165,15 @@ impl Acceptor { self.acceptor.poll_changed(cx) } - fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + fn poll_accept( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll> { let _ = self.poll_changed(cx); self.acceptor.peek_mut(|a| a.poll_accept(cx)) } - async fn accept(&mut self) -> Result { + async fn accept(&mut self) -> Result<(A::Metadata, AcceptStream), Error> { std::future::poll_fn(|cx| self.poll_accept(cx)).await } } @@ -115,19 +185,14 @@ impl Acceptor> { } } -pub type UpgradableListener = - Option>; - -impl Acceptor { - pub fn bind_upgradable(listener: SelfContainedNetworkInterfaceListener) -> Self { - Self::new(Some(Either::Left(listener))) - } -} - pub struct WebServerAcceptorSetter { acceptor: Watch, } -impl WebServerAcceptorSetter>> { +impl WebServerAcceptorSetter>> +where + A: Accept, + B: Accept, +{ pub fn try_upgrade Result>(&self, f: F) -> Result<(), Error> { let mut res = Ok(()); self.acceptor.send_modify(|a| { @@ -154,20 +219,24 @@ impl Deref for WebServerAcceptorSetter { pub struct WebServer { shutdown: oneshot::Sender<()>, - router: Watch>, + router: Watch, acceptor: Watch, thread: NonDetachingJoinHandle<()>, } -impl WebServer { +impl WebServer +where + A: Accept + Send + Sync + 'static, + for<'a> A::Metadata: Visit> + Send + Sync + 'static, +{ pub fn acceptor_setter(&self) -> WebServerAcceptorSetter { WebServerAcceptorSetter { acceptor: self.acceptor.clone(), } } - pub fn new(mut acceptor: Acceptor) -> Self { + pub fn new(mut acceptor: Acceptor, router: Router) -> Self { let acceptor_send = acceptor.acceptor.clone(); - let router = Watch::>::new(None); + let router = Watch::new(router); let service = router.clone_unseen(); let (shutdown, shutdown_recv) = oneshot::channel(); let thread = NonDetachingJoinHandle::from(tokio::spawn(async move { @@ -190,13 +259,14 @@ impl WebServer { } } - struct SwappableRouter { - router: Watch>, - redirect: bool, - local_addr: SocketAddr, - peer_addr: SocketAddr, + struct SwappableRouter { + router: Watch, + metadata: M, } - impl hyper::service::Service> for SwappableRouter { + impl Visit> + Send + Sync + 'static> + hyper::service::Service> + for SwappableRouter + { type Response = , >>::Response; @@ -210,19 +280,10 @@ impl WebServer { fn call(&self, mut req: hyper::Request) -> Self::Future { use tower_service::Service; - req.extensions_mut() - .insert(ConnectInfo((self.peer_addr, self.local_addr))); + self.metadata + .visit(&mut ExtensionVisitor(req.extensions_mut())); - if self.redirect { - redirecter().call(req) - } else { - let router = self.router.read(); - if let Some(mut router) = router { - router.call(req) - } else { - refresher().call(req) - } - } + self.router.read().call(req) } } @@ -249,18 +310,15 @@ impl WebServer { let mut err = None; for _ in 0..5 { if let Err(e) = async { - let accepted = acceptor.accept().await?; - let src = accepted.stream.peer_addr().ok(); + let (metadata, stream) = acceptor.accept().await?; queue.add_job( graceful.watch( server .serve_connection_with_upgrades( - TokioIo::new(accepted.stream), + TokioIo::new(stream), SwappableRouter { router: service.clone(), - redirect: accepted.https_redirect, - peer_addr: accepted.peer_addr, - local_addr: accepted.local_addr, + metadata, }, ) .into_owned(), @@ -314,7 +372,7 @@ impl WebServer { } pub fn serve_router(&mut self, router: Router) { - self.router.send(Some(router)) + self.router.send(router) } pub fn serve_ui_for(&mut self, ctx: C) { diff --git a/core/startos/src/tunnel/api.rs b/core/startos/src/tunnel/api.rs index 238ffe075..46d352a09 100644 --- a/core/startos/src/tunnel/api.rs +++ b/core/startos/src/tunnel/api.rs @@ -3,8 +3,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; use clap::Parser; use imbl_value::InternedString; use ipnet::Ipv4Net; -use models::GatewayId; -use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use crate::context::CliContext; @@ -13,11 +12,11 @@ use crate::prelude::*; use crate::tunnel::context::TunnelContext; use crate::tunnel::db::GatewayPort; use crate::tunnel::wg::{ClientConfig, WgConfig, WgSubnetClients, WgSubnetConfig}; -use crate::util::serde::{HandlerExtSerde, display_serializable}; +use crate::util::serde::{display_serializable, HandlerExtSerde}; pub fn tunnel_api() -> ParentHandler { ParentHandler::new() - .subcommand("web", super::web::web_api()) + .subcommand("web", super::web::web_api::()) .subcommand( "db", super::db::db_api::() @@ -165,9 +164,11 @@ pub async fn add_subnet( .db .mutate(|db| { let map = db.as_wg_mut().as_subnets_mut(); - if !map.contains_key(&subnet)? { - map.insert(&subnet, &WgSubnetConfig::new(name))?; - } + map.upsert(&subnet, || { + Ok(WgSubnetConfig::new(InternedString::default())) + })? + .as_name_mut() + .ser(&name)?; db.as_wg().de() }) .await @@ -312,7 +313,8 @@ pub async fn show_config( }: ShowConfigParams, SubnetParams { subnet }: SubnetParams, ) -> Result { - let wg = ctx.db.peek().await.into_wg(); + let peek = ctx.db.peek().await; + let wg = peek.as_wg(); let client = wg .as_subnets() .as_idx(&subnet) @@ -329,16 +331,19 @@ pub async fn show_config( } }) { wan_addr + } else if let Some(webserver) = peek.as_webserver().de()? { + webserver.ip() } else { ctx.net_iface - .ip_info() - .into_iter() - .find_map(|(_, info)| { - info.public() - .then_some(info.ip_info) - .flatten() - .into_iter() - .find_map(|info| info.subnets.into_iter().next()) + .peek(|i| { + i.iter().find_map(|(_, info)| { + info.ip_info + .as_ref() + .filter(|_| info.public()) + .iter() + .find_map(|info| info.subnets.iter().next()) + .copied() + }) }) .or_not_found("a public IP address")? .addr() diff --git a/core/startos/src/tunnel/auth.rs b/core/startos/src/tunnel/auth.rs index f4de58961..371dceb62 100644 --- a/core/startos/src/tunnel/auth.rs +++ b/core/startos/src/tunnel/auth.rs @@ -1,15 +1,13 @@ -use std::net::IpAddr; - use clap::Parser; use imbl::HashMap; -use imbl_value::{InternedString, json}; +use imbl_value::InternedString; use itertools::Itertools; use patch_db::HasModel; -use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use ts_rs::TS; -use crate::auth::{Sessions, check_password}; +use crate::auth::{check_password, Sessions}; use crate::context::CliContext; use crate::middleware::auth::AuthContext; use crate::middleware::signature::SignatureAuthContext; @@ -18,7 +16,7 @@ use crate::rpc_continuations::OpenAuthedContinuations; use crate::sign::AnyVerifyingKey; use crate::tunnel::context::TunnelContext; use crate::tunnel::db::TunnelDatabase; -use crate::util::serde::{HandlerExtSerde, display_serializable}; +use crate::util::serde::{display_serializable, HandlerExtSerde}; use crate::util::sync::SyncMutex; impl SignatureAuthContext for TunnelContext { @@ -31,14 +29,18 @@ impl SignatureAuthContext for TunnelContext { async fn sig_context( &self, ) -> impl IntoIterator + Send, Error>> + Send { - self.addrs - .iter() - .filter(|a| !match a { - IpAddr::V4(a) => a.is_loopback() || a.is_unspecified(), - IpAddr::V6(a) => a.is_loopback() || a.is_unspecified(), - }) - .map(|a| InternedString::from_display(&a)) - .map(Ok) + let peek = self.db().peek().await; + peek.as_webserver() + .de() + .map(|a| a.as_ref().map(InternedString::from_display)) + .transpose() + .into_iter() + .chain( + std::iter::from_fn(move || Some(peek.as_certificates().keys())) + .flatten_ok() + .map_ok(|h| h.0) + .flatten_ok(), + ) } fn check_pubkey( db: &Model, @@ -77,7 +79,7 @@ impl AuthContext for TunnelContext { &self.open_authed_continuations } fn check_password(db: &Model, password: &str) -> Result<(), Error> { - check_password(&db.as_password().de()?, password) + check_password(&db.as_password().de()?.unwrap_or_default(), password) } } @@ -204,7 +206,8 @@ pub async fn set_password_rpc( password.as_bytes(), &rand::random::<[u8; 16]>(), &argon2::Config::rfc9106_low_mem(), - )?; + ) + .with_kind(ErrorKind::PasswordHashGeneration)?; ctx.db .mutate(|db| db.as_password_mut().ser(&Some(pwhash))) .await @@ -234,7 +237,7 @@ pub async fn set_password_cli( context .call_remote::( &parent_method.iter().chain(method.iter()).join("."), - to_value(SetPasswordParams { password }), + to_value(&SetPasswordParams { password })?, ) .await?; diff --git a/core/startos/src/tunnel/context.rs b/core/startos/src/tunnel/context.rs index 7b2bc97fa..c03e6d9f9 100644 --- a/core/startos/src/tunnel/context.rs +++ b/core/startos/src/tunnel/context.rs @@ -1,5 +1,5 @@ use std::collections::{BTreeMap, BTreeSet}; -use std::net::{IpAddr, Ipv6Addr, SocketAddr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -23,16 +23,16 @@ use url::Url; use crate::auth::Sessions; use crate::context::config::ContextConfig; use crate::context::{CliContext, RpcContext}; -use crate::db::model::public::NetworkInterfaceType; +use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType}; use crate::middleware::auth::AuthContext; use crate::net::forward::PortForwardController; use crate::net::gateway::{IdFilter, InterfaceFilter, NetworkInterfaceWatcher}; use crate::prelude::*; use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations}; use crate::tunnel::db::{GatewayPort, TunnelDatabase}; -use crate::tunnel::TUNNEL_DEFAULT_PORT; +use crate::tunnel::TUNNEL_DEFAULT_LISTEN; use crate::util::io::read_file_to_string; -use crate::util::sync::SyncMutex; +use crate::util::sync::{SyncMutex, Watch}; use crate::util::Invoke; #[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)] @@ -67,16 +67,14 @@ impl TunnelConfig { pub struct TunnelContextSeed { pub listen: SocketAddr, - pub addrs: BTreeSet, pub db: TypedPatchDb, pub datadir: PathBuf, pub rpc_continuations: RpcContinuations, pub open_authed_continuations: OpenAuthedContinuations>, pub ephemeral_sessions: SyncMutex, - pub net_iface: NetworkInterfaceWatcher, + pub net_iface: Watch>, pub forward: PortForwardController, pub active_forwards: SyncMutex>>, - pub masquerade_thread: NonDetachingJoinHandle<()>, pub shutdown: Sender<()>, } @@ -101,12 +99,9 @@ impl TunnelContext { || async { Ok(Default::default()) }, ) .await?; - let listen = config.tunnel_listen.unwrap_or(SocketAddr::new( - Ipv6Addr::UNSPECIFIED.into(), - TUNNEL_DEFAULT_PORT, - )); - let net_iface = NetworkInterfaceWatcher::new(async { OrdMap::new() }, []); - let forward = PortForwardController::new(net_iface.subscribe()); + let listen = config.tunnel_listen.unwrap_or(TUNNEL_DEFAULT_LISTEN); + let net_iface = Watch::new(crate::net::utils::load_network_interface_info().await?); + let forward = PortForwardController::new(net_iface.clone_unseen()); Command::new("sysctl") .arg("-w") @@ -114,55 +109,45 @@ impl TunnelContext { .invoke(ErrorKind::Network) .await?; - let mut masquerade_net_iface = net_iface.subscribe(); - let masquerade_thread = tokio::spawn(async move { - loop { - for iface in masquerade_net_iface.peek(|i| { - i.iter() - .filter(|(_, info)| { - dbg!(info).ip_info.as_ref().map_or(false, |i| { - dbg!(i).device_type != Some(NetworkInterfaceType::Wireguard) - }) - }) - .map(|(name, _)| name) - .cloned() - .collect::>() - }) { - if Command::new("iptables") - .arg("-t") - .arg("nat") - .arg("-C") - .arg("POSTROUTING") - .arg("-o") - .arg(iface.as_str()) - .arg("-j") - .arg("MASQUERADE") - .invoke(ErrorKind::Network) - .await - .is_err() - { - tracing::info!("Adding masquerade rule for interface {}", iface); - Command::new("iptables") - .arg("-t") - .arg("nat") - .arg("-A") - .arg("POSTROUTING") - .arg("-o") - .arg(iface.as_str()) - .arg("-j") - .arg("MASQUERADE") - .invoke(ErrorKind::Network) - .await - .log_err(); - } - } - - masquerade_net_iface.changed().await; - - tracing::info!("Network interfaces changed, updating masquerade rules"); + for iface in net_iface.peek(|i| { + i.iter() + .filter(|(_, info)| { + dbg!(info).ip_info.as_ref().map_or(false, |i| { + dbg!(i).device_type != Some(NetworkInterfaceType::Wireguard) + }) + }) + .map(|(name, _)| name) + .cloned() + .collect::>() + }) { + if Command::new("iptables") + .arg("-t") + .arg("nat") + .arg("-C") + .arg("POSTROUTING") + .arg("-o") + .arg(iface.as_str()) + .arg("-j") + .arg("MASQUERADE") + .invoke(ErrorKind::Network) + .await + .is_err() + { + tracing::info!("Adding masquerade rule for interface {}", iface); + Command::new("iptables") + .arg("-t") + .arg("nat") + .arg("-A") + .arg("POSTROUTING") + .arg("-o") + .arg(iface.as_str()) + .arg("-j") + .arg("MASQUERADE") + .invoke(ErrorKind::Network) + .await + .log_err(); } - }) - .into(); + } let peek = db.peek().await; peek.as_wg().de()?.sync().await?; @@ -178,11 +163,6 @@ impl TunnelContext { Ok(Self(Arc::new(TunnelContextSeed { listen, - addrs: crate::net::utils::all_socket_addrs_for(listen.port()) - .await? - .into_iter() - .map(|(_, a)| a.ip()) - .collect(), db, datadir, rpc_continuations: RpcContinuations::new(), @@ -191,7 +171,6 @@ impl TunnelContext { net_iface, forward, active_forwards: SyncMutex::new(active_forwards), - masquerade_thread, shutdown, }))) } @@ -222,6 +201,14 @@ impl CallRemote for CliContext { params: Value, _: Empty, ) -> Result { + let (tunnel_addr, addr_from_config) = if let Some(addr) = self.tunnel_addr { + (addr, true) + } else if let Some(addr) = self.tunnel_listen { + (addr, true) + } else { + (TUNNEL_DEFAULT_LISTEN, false) + }; + let local = if let Ok(local) = read_file_to_string(TunnelContext::LOCAL_AUTH_COOKIE_PATH).await { self.cookie_store @@ -229,11 +216,11 @@ impl CallRemote for CliContext { .unwrap() .insert_raw( &Cookie::build(("local", local)) - .domain("localhost") + .domain(&tunnel_addr.ip().to_string()) .expires(Expiration::Session) .same_site(SameSite::Strict) .build(), - &"http://localhost".parse()?, + &format!("http://{tunnel_addr}").parse()?, ) .with_kind(crate::ErrorKind::Network)?; true @@ -241,24 +228,12 @@ impl CallRemote for CliContext { false }; - let tunnel_addr = if let Some(addr) = self.tunnel_addr { - Some(addr) - } else if let Some(addr) = self.tunnel_listen { - Some(addr) - } else { - None - }; - let (url, sig_ctx) = if let Some(tunnel_addr) = tunnel_addr { + let (url, sig_ctx) = if local && tunnel_addr.ip().is_loopback() { + (format!("http://{tunnel_addr}/rpc/v0").parse()?, None) + } else if addr_from_config { ( format!("https://{tunnel_addr}/rpc/v0").parse()?, - Some(InternedString::from_display( - &self.tunnel_listen.unwrap_or(tunnel_addr).ip(), - )), - ) - } else if local { - ( - format!("http://localhost:{TUNNEL_DEFAULT_PORT}/rpc/v0").parse()?, - None, + Some(InternedString::from_display(&tunnel_addr.ip())), ) } else { return Err(Error::new(eyre!("`--tunnel` required"), ErrorKind::InvalidRequest).into()); diff --git a/core/startos/src/tunnel/db.rs b/core/startos/src/tunnel/db.rs index c71efed67..1de5a26b5 100644 --- a/core/startos/src/tunnel/db.rs +++ b/core/startos/src/tunnel/db.rs @@ -2,18 +2,18 @@ use std::collections::{BTreeMap, BTreeSet}; use std::net::{IpAddr, SocketAddr, SocketAddrV4}; use std::path::PathBuf; -use clap::Parser; use clap::builder::ValueParserFactory; +use clap::Parser; use imbl::HashMap; use imbl_value::InternedString; use itertools::Itertools; use models::{FromStrParser, GatewayId}; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; -use patch_db::Dump; use patch_db::json_ptr::{JsonPointer, ROOT}; +use patch_db::Dump; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; @@ -28,7 +28,7 @@ use crate::tunnel::context::TunnelContext; use crate::tunnel::web::TunnelCertData; use crate::tunnel::wg::WgServer; use crate::util::serde::{ - HandlerExtSerde, Pem, apply_expr, deserialize_from_str, serialize_display, + apply_expr, deserialize_from_str, serialize_display, HandlerExtSerde, Pem, }; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -84,7 +84,7 @@ pub struct TunnelDatabase { pub sessions: Sessions, pub password: Option, pub auth_pubkeys: HashMap, - pub certificate: BTreeMap>, TunnelCertData>, + pub certificates: BTreeMap>, TunnelCertData>, pub wg: WgServer, pub port_forwards: PortForwards, } diff --git a/core/startos/src/tunnel/mod.rs b/core/startos/src/tunnel/mod.rs index f35995dca..99e81a947 100644 --- a/core/startos/src/tunnel/mod.rs +++ b/core/startos/src/tunnel/mod.rs @@ -1,3 +1,5 @@ +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use axum::Router; use futures::future::ready; use rpc_toolkit::Server; @@ -17,6 +19,10 @@ pub mod web; pub mod wg; pub const TUNNEL_DEFAULT_PORT: u16 = 5960; +pub const TUNNEL_DEFAULT_LISTEN: SocketAddr = SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new(127, 0, 59, 60), + TUNNEL_DEFAULT_PORT, +)); pub fn tunnel_router(ctx: TunnelContext) -> Router { use axum::extract as x; diff --git a/core/startos/src/tunnel/web.rs b/core/startos/src/tunnel/web.rs index 543c35dab..a855ecc74 100644 --- a/core/startos/src/tunnel/web.rs +++ b/core/startos/src/tunnel/web.rs @@ -1,24 +1,22 @@ -use std::{ - collections::BTreeSet, - net::{Ipv4Addr, Ipv6Addr, SocketAddr}, -}; +use std::collections::{BTreeSet, VecDeque}; +use std::io::Write; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; -use crate::{ - context::CliContext, net::ssl::SANInfo, prelude::*, tunnel::context::TunnelContext, - util::serde::Pem, -}; use clap::Parser; -use futures::AsyncWriteExt; -use imbl_value::{InternedString, json}; +use imbl_value::{json, InternedString}; use itertools::Itertools; -use openssl::{ - pkey::{PKey, Private}, - x509::{GeneralName, X509}, -}; -use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; +use openssl::pkey::{PKey, Private}; +use openssl::x509::X509; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::io::{AsyncBufReadExt, BufReader}; +use crate::context::CliContext; +use crate::net::ssl::SANInfo; +use crate::prelude::*; +use crate::tunnel::context::TunnelContext; +use crate::util::serde::Pem; + #[derive(Debug, Deserialize, Serialize, Parser)] #[serde(rename_all = "camelCase")] pub struct TunnelCertData { @@ -39,11 +37,18 @@ pub fn web_api() -> ParentHandler { ) .subcommand( "import-certificate", - from_fn_async(set_certificate) + from_fn_async(import_certificate) .with_about("Import a certificate to use for the webserver") .no_display() .with_call_remote::(), ) + // .subcommand( + // "forget-certificate", + // from_fn_async(forget_certificate) + // .with_about("Forget a certificate that was imported into the webserver") + // .no_display() + // .with_call_remote::(), + // ) .subcommand( "uninit", from_fn_async(uninit_web) @@ -53,7 +58,10 @@ pub fn web_api() -> ParentHandler { ) } -pub async fn set_certificate(ctx: TunnelContext, cert_data: TunnelCertData) -> Result<(), Error> { +pub async fn import_certificate( + ctx: TunnelContext, + cert_data: TunnelCertData, +) -> Result<(), Error> { let mut saninfo = BTreeSet::new(); let leaf = cert_data.cert.get(0).ok_or_else(|| { Error::new( @@ -66,18 +74,20 @@ pub async fn set_certificate(ctx: TunnelContext, cert_data: TunnelCertData) -> R saninfo.insert(dns.into()); } if let Some(ip) = san.ipaddress() { - if ip.len() == 4 { - saninfo.insert(InternedString::from_display(&Ipv4Addr::new( - ip[0], ip[1], ip[2], ip[3], + if let Ok::<[u8; 4], _>(ip) = ip.try_into() { + saninfo.insert(InternedString::from_display(&Ipv4Addr::from_bits( + u32::from_be_bytes(ip), + ))); + } else if let Ok::<[u8; 16], _>(ip) = ip.try_into() { + saninfo.insert(InternedString::from_display(&Ipv6Addr::from_bits( + u128::from_be_bytes(ip), ))); - } else if ip.len() == 16 { - saninfo.insert(InternedString::from_display(&Ipv6Addr::from_bits(bits))) } } } ctx.db .mutate(|db| { - db.as_certificate_mut() + db.as_certificates_mut() .insert(&JsonKey(saninfo), &cert_data) }) .await @@ -85,7 +95,7 @@ pub async fn set_certificate(ctx: TunnelContext, cert_data: TunnelCertData) -> R Ok(()) } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Parser)] pub struct InitWebParams { listen: SocketAddr, } @@ -96,7 +106,7 @@ pub async fn init_web_rpc( ) -> Result<(), Error> { ctx.db .mutate(|db| { - if db.as_certificate().de()?.is_empty() { + if db.as_certificates().de()?.is_empty() { return Err(Error::new( eyre!("No certificate available"), ErrorKind::OpenSsl, @@ -119,7 +129,7 @@ pub async fn init_web_rpc( pub async fn uninit_web(ctx: TunnelContext) -> Result<(), Error> { ctx.db - .mutate(|db| db.as_webserver_mut().ser(&false)) + .mutate(|db| db.as_webserver_mut().ser(&None)) .await .result } @@ -129,18 +139,19 @@ pub async fn init_web_cli( context, parent_method, method, + params: InitWebParams { listen }, .. - }: HandlerArgs, + }: HandlerArgs, ) -> Result<(), Error> { loop { match context - .call_remote( + .call_remote::( &parent_method.iter().chain(method.iter()).join("."), - json!({}), + to_value(&InitWebParams { listen })?, ) .await { - Ok(a) => println!("Webserver Initialized"), + Ok(_) => println!("Webserver Initialized"), Err(e) if e.code == ErrorKind::OpenSsl as i32 => { println!( "StartTunnel has not been set up with an SSL Certificate yet. Setting one up now..." @@ -155,11 +166,15 @@ pub async fn init_web_cli( let self_signed; loop { match readline.readline().await.with_kind(ErrorKind::Filesystem)? { - rustyline_async::ReadlineEvent::Line(l) if l.trim() == "1" => { + rustyline_async::ReadlineEvent::Line(l) + if l.trim_matches(|c: char| c.is_whitespace() || c == '"') == "1" => + { self_signed = true; break; } - rustyline_async::ReadlineEvent::Line(l) if l.trim() == "2" => { + rustyline_async::ReadlineEvent::Line(l) + if l.trim_matches(|c: char| c.is_whitespace() || c == '"') == "2" => + { self_signed = false; break; } @@ -167,21 +182,47 @@ pub async fn init_web_cli( readline.clear_history(); readline.add_history_entry("1".into()); readline.add_history_entry("2".into()); - writer - .write_all(b"Invalid response. Enter either \"1\" or \"2\".") - .await?; + writeln!(writer, "Invalid response. Enter either \"1\" or \"2\".")?; } _ => return Err(Error::new(eyre!("Aborted"), ErrorKind::Unknown)), } } - drop((readline, writer)); if self_signed { + writeln!( + writer, + "Enter the name(s) to sign the certificate for, separated by commas." + )?; + readline.clear_history(); + readline + .update_prompt(&format!("Subject Alternative Name(s) [{}]: ", listen.ip())) + .with_kind(ErrorKind::Filesystem)?; + let mut saninfo = BTreeSet::new(); + loop { + match readline.readline().await.with_kind(ErrorKind::Filesystem)? { + rustyline_async::ReadlineEvent::Line(l) if !l.trim().is_empty() => { + saninfo.extend(l.split(",").map(|h| h.trim().into())); + break; + } + rustyline_async::ReadlineEvent::Line(_) => { + readline.clear_history(); + } + _ => return Err(Error::new(eyre!("Aborted"), ErrorKind::Unknown)), + } + } let key = crate::net::ssl::gen_nistp256()?; - let cert = crate::net::ssl::make_self_signed(( - &key, - &SANInfo::new(&[].into_iter().collect()), - ))?; + let cert = crate::net::ssl::make_self_signed((&key, &SANInfo::new(&saninfo)))?; + + context + .call_remote::( + "web.import-certificate", + to_value(&TunnelCertData { + key: Pem(key), + cert: Pem(vec![cert]), + })?, + ) + .await?; } else { + drop((readline, writer)); println!("Please paste in your PEM encoded private key: "); let mut stdin_lines = BufReader::new(tokio::io::stdin()).lines(); let mut key_string = String::new(); @@ -225,7 +266,7 @@ pub async fn init_web_cli( } context - .call_remote( + .call_remote::( "web.import-certificate", to_value(&TunnelCertData { key, @@ -235,7 +276,19 @@ pub async fn init_web_cli( .await?; } } - Err(e) if e.code == ErrorKind::Authorization as i32 => {} + Err(e) if e.code == ErrorKind::Authorization as i32 => { + println!("A password has not been setup yet. Setting one up now..."); + + super::auth::set_password_cli(HandlerArgs { + context: context.clone(), + parent_method: vec!["auth", "set-password"].into(), + method: VecDeque::new(), + params: Empty {}, + inherited_params: Empty {}, + raw_params: json!({}), + }) + .await?; + } Err(e) => return Err(e.into()), } } diff --git a/core/startos/src/util/serde.rs b/core/startos/src/util/serde.rs index efcdf2164..2b462e5fd 100644 --- a/core/startos/src/util/serde.rs +++ b/core/startos/src/util/serde.rs @@ -1201,7 +1201,7 @@ impl PemEncoding for X509 { impl PemEncoding for Vec { fn from_pem(pem: &str) -> Result { - X509::stack_from_pem(pem).map_err(E::custom) + X509::stack_from_pem(pem.as_bytes()).map_err(E::custom) } fn to_pem(&self) -> Result { self.iter()