diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80f3119f15..3ca43c6cc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,8 +160,21 @@ jobs: - name: Add wasm target run: rustup target add wasm32-unknown-unknown + - name: Install wasm-tools + uses: bytecodealliance/actions/wasm-tools/setup@v1 + - name: wasm32 build (iroh-base) - run: cargo build -p iroh-base --all-features --target wasm32-unknown-unknown + run: cargo build --target wasm32-unknown-unknown -p iroh-base --all-features + + - name: wasm32 build (iroh-relay) + run: cargo build --target wasm32-unknown-unknown -p iroh-relay --no-default-features + + # If the Wasm file contains any 'import "env"' declarations, then + # some non-Wasm-compatible code made it into the final code. + - name: Ensure no 'import "env"' in iroh-relay Wasm + run: | + ! wasm-tools print --skeleton target/wasm32-unknown-unknown/debug/iroh_relay.wasm | grep 'import "env"' + check_semver: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index c5eb39fdde..b7806d1027 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -645,6 +645,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -2072,8 +2082,6 @@ dependencies = [ "tokio", "tokio-rustls", "tokio-stream", - "tokio-tungstenite", - "tokio-tungstenite-wasm", "tokio-util", "tracing", "tracing-subscriber", @@ -2225,46 +2233,52 @@ dependencies = [ [[package]] name = "iroh-quinn" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ba75a5c57cff299d2d7ca1ddee053f66339d1756bd79ec637bcad5aa61100e" +checksum = "76c6245c9ed906506ab9185e8d7f64857129aee4f935e899f398a3bd3b70338d" dependencies = [ "bytes", + "cfg_aliases", "iroh-quinn-proto", "iroh-quinn-udp", "pin-project-lite", "rustc-hash", "rustls", "socket2", - "thiserror 1.0.69", + "thiserror 2.0.11", "tokio", "tracing", + "web-time", ] [[package]] name = "iroh-quinn-proto" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c869ba52683d3d067c83ab4c00a2fda18eaf13b1434d4c1352f428674d4a5d" +checksum = "929d5d8fa77d5c304d3ee7cae9aede31f13908bd049f9de8c7c0094ad6f7c535" dependencies = [ "bytes", + "getrandom", "rand", "ring", "rustc-hash", "rustls", + "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror 1.0.69", + "thiserror 2.0.11", "tinyvec", "tracing", + "web-time", ] [[package]] name = "iroh-quinn-udp" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfc0abc2fdf8cf18a6c72893b7cbebeac2274a3b1306c1760c48c0e10ac5e0" +checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" dependencies = [ + "cfg_aliases", "libc", "once_cell", "socket2", @@ -2278,15 +2292,12 @@ version = "0.31.0" dependencies = [ "anyhow", "bytes", + "cfg_aliases", "clap", "crypto_box", "dashmap 6.1.0", "data-encoding", "derive_more", - "futures-buffered", - "futures-lite", - "futures-sink", - "futures-util", "governor 0.7.0", "hickory-proto", "hickory-resolver", @@ -2300,6 +2311,7 @@ dependencies = [ "iroh-quinn-proto", "iroh-test 0.31.0", "lru", + "n0-future", "num_enum", "pin-project", "postcard", @@ -2320,7 +2332,6 @@ dependencies = [ "stun-rs", "testresult", "thiserror 2.0.11", - "time", "tokio", "tokio-rustls", "tokio-rustls-acme", @@ -2390,16 +2401,18 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", "thiserror 1.0.69", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -2619,6 +2632,28 @@ dependencies = [ "uuid", ] +[[package]] +name = "n0-future" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d4800d43db24b202e42bb129710ee5f809d46345d2e31ed702d5c6ed02601e" +dependencies = [ + "cfg_aliases", + "derive_more", + "futures-buffered", + "futures-lite", + "futures-sink", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -3851,12 +3886,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", @@ -3882,11 +3916,11 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.3.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" dependencies = [ - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "jni", "log", @@ -3897,8 +3931,8 @@ dependencies = [ "rustls-webpki", "security-framework", "security-framework-sys", - "webpki-roots", - "winapi", + "webpki-root-certs", + "windows-sys 0.52.0", ] [[package]] @@ -3983,15 +4017,14 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.7.0", - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "libc", - "num-bigint", "security-framework-sys", ] @@ -4017,6 +4050,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.217" @@ -4420,7 +4459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.7.0", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4659,9 +4698,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.21.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", @@ -4671,9 +4710,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite-wasm" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e57a65894797a018b28345fa298a00c450a574aa9671e50b18218a6292a55ac" +checksum = "e21a5c399399c3db9f08d8297ac12b500e86bca82e930253fdc62eaf9c0de6ae" dependencies = [ "futures-channel", "futures-util", @@ -4891,9 +4930,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.21.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", "bytes", @@ -4904,7 +4943,6 @@ dependencies = [ "rand", "sha1", "thiserror 1.0.69", - "url", "utf-8", ] @@ -5175,6 +5213,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd5da49bdf1f30054cfe0b8ce2958b8fbeb67c4d82c8967a598af481bef255c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.7" @@ -5314,6 +5361,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -5341,6 +5397,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -5372,6 +5443,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5384,6 +5461,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5396,6 +5479,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5414,6 +5503,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5426,6 +5521,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5438,6 +5539,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5450,6 +5557,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/iroh-net-report/Cargo.toml b/iroh-net-report/Cargo.toml index 25e0a30f29..23e0bd3648 100644 --- a/iroh-net-report/Cargo.toml +++ b/iroh-net-report/Cargo.toml @@ -27,7 +27,7 @@ iroh-metrics = { version = "0.31", default-features = false } iroh-relay = { version = "0.31", path = "../iroh-relay" } netwatch = { version = "0.3" } portmapper = { version = "0.3", default-features = false } -quinn = { package = "iroh-quinn", version = "0.12.0" } +quinn = { package = "iroh-quinn", version = "0.13.0" } rand = "0.8" reqwest = { version = "0.12", default-features = false } rustls = { version = "0.23", default-features = false } diff --git a/iroh-relay/Cargo.toml b/iroh-relay/Cargo.toml index 7f63deb9e1..ea715d5768 100644 --- a/iroh-relay/Cargo.toml +++ b/iroh-relay/Cargo.toml @@ -16,7 +16,6 @@ workspace = true [dependencies] anyhow = { version = "1" } bytes = "1.7" -clap = { version = "4", features = ["derive"], optional = true } derive_more = { version = "1.0.0", features = [ "debug", "display", @@ -24,19 +23,13 @@ derive_more = { version = "1.0.0", features = [ "try_into", "deref", ] } -futures-buffered = "0.2.9" -futures-lite = "2.5" -futures-sink = "0.3" -futures-util = "0.3" -governor = "0.7.0" -hickory-proto = { version = "=0.25.0-alpha.4" } -hickory-resolver = "=0.25.0-alpha.4" http = "1" http-body-util = "0.1.0" hyper = { version = "1", features = ["server", "client", "http1"] } hyper-util = "0.1.1" iroh-base = { version = "0.31.0", path = "../iroh-base", default-features = false, features = ["key", "relay"] } iroh-metrics = { version = "0.31", default-features = false } +n0-future = { version = "0.0.1" } num_enum = "0.7" pin-project = "1" postcard = { version = "1", default-features = false, features = [ @@ -44,53 +37,67 @@ postcard = { version = "1", default-features = false, features = [ "use-std", "experimental-derive", ] } -quinn = { package = "iroh-quinn", version = "0.12.0" } -quinn-proto = { package = "iroh-quinn-proto", version = "0.12.0" } +quinn = { package = "iroh-quinn", version = "0.13.0", default-features = false, features = ["rustls-ring"] } +quinn-proto = { package = "iroh-quinn-proto", version = "0.13.0" } rand = "0.8" -rcgen = { version = "0.13", optional = true } -regex = { version = "1.7.1", optional = true } -reloadable-state = { version = "0.1", optional = true } reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", ] } rustls = { version = "0.23", default-features = false, features = ["ring"] } -rustls-cert-reloadable-resolver = { version = "0.7.1", optional = true } -rustls-cert-file-reader = { version = "0.4.1", optional = true } -rustls-pemfile = { version = "2.1", optional = true } serde = { version = "1", features = ["derive", "rc"] } stun-rs = "0.1.5" thiserror = "2" -time = "0.3.20" tokio = { version = "1", features = [ "io-util", "macros", "sync", "rt", - "net", - "fs", - "io-std", - "signal", - "process", ] } tokio-rustls = { version = "0.26", default-features = false, features = [ "logging", "ring", ] } -tokio-rustls-acme = { version = "0.6", optional = true } -tokio-tungstenite = "0.21" # avoid duplicating this dependency as long as tokio-tungstenite-wasm isn't updated -tokio-tungstenite-wasm = "0.3" +tokio-tungstenite-wasm = "0.4" tokio-util = { version = "0.7", features = ["io-util", "io", "codec", "rt"] } -toml = { version = "0.8", optional = true } tracing = "0.1" -tracing-subscriber = { version = "0.3", features = [ - "env-filter", -], optional = true } url = { version = "2.5", features = ["serde"] } webpki = { package = "rustls-webpki", version = "0.102" } webpki-roots = "0.26" data-encoding = "2.6.0" lru = "0.12" + +# server feature +clap = { version = "4", features = ["derive"], optional = true } dashmap = { version = "6.1.0", optional = true } +governor = { version = "0.7.0", optional = true } +hickory-proto = { version = "=0.25.0-alpha.4", default-features = false, optional = true } +rcgen = { version = "0.13", optional = true } +regex = { version = "1.7.1", optional = true } +reloadable-state = { version = "0.1", optional = true } +rustls-cert-reloadable-resolver = { version = "0.7.1", optional = true } +rustls-cert-file-reader = { version = "0.4.1", optional = true } +rustls-pemfile = { version = "2.1", optional = true } +tokio-rustls-acme = { version = "0.6", optional = true } +tokio-tungstenite = { version = "0.24", default-features = false, optional = true } # keep version in sync with what tokio-tungstenite-wasm depends on +toml = { version = "0.8", optional = true } +tracing-subscriber = { version = "0.3", features = [ + "env-filter", +], optional = true } + +# non-wasm-in-browser dependencies +[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] +hickory-resolver = "=0.25.0-alpha.4" +tokio = { version = "1", features = [ + "io-util", + "macros", + "sync", + "rt", + "net", + "fs", + "io-std", + "signal", + "process", +] } [dev-dependencies] clap = { version = "4", features = ["derive"] } @@ -112,20 +119,29 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } iroh-test = { path = "../iroh-test" } serde_json = "1" +[build-dependencies] +cfg_aliases = { version = "0.2" } + [features] default = ["metrics"] server = [ - "dep:tokio-rustls-acme", "dep:clap", "dep:dashmap", - "dep:toml", - "dep:rustls-pemfile", - "dep:regex", - "dep:tracing-subscriber", + "dep:governor", + "dep:hickory-proto", "dep:rcgen", + "dep:regex", "dep:reloadable-state", - "dep:rustls-cert-reloadable-resolver", "dep:rustls-cert-file-reader", + "dep:rustls-cert-reloadable-resolver", + "dep:rustls-pemfile", + "dep:tokio-rustls-acme", + "dep:tokio-tungstenite", + "dep:toml", + "dep:tracing-subscriber", + "quinn/log", + "quinn/platform-verifier", + "quinn/runtime-tokio", ] metrics = ["iroh-metrics/metrics"] test-utils = [] diff --git a/iroh-relay/build.rs b/iroh-relay/build.rs new file mode 100644 index 0000000000..7aae56820c --- /dev/null +++ b/iroh-relay/build.rs @@ -0,0 +1,9 @@ +use cfg_aliases::cfg_aliases; + +fn main() { + // Setup cfg aliases + cfg_aliases! { + // Convenience aliases + wasm_browser: { all(target_family = "wasm", target_os = "unknown") }, + } +} diff --git a/iroh-relay/src/client.rs b/iroh-relay/src/client.rs index a73d8563d1..b7c10f6fba 100644 --- a/iroh-relay/src/client.rs +++ b/iroh-relay/src/client.rs @@ -3,52 +3,38 @@ //! Based on tailscale/derp/derphttp/derphttp_client.go use std::{ - future::Future, - net::{IpAddr, SocketAddr}, + net::SocketAddr, pin::Pin, sync::Arc, task::{self, Poll}, }; -use anyhow::{anyhow, bail, Context, Result}; -use bytes::Bytes; +use anyhow::{anyhow, bail, Result}; use conn::Conn; -use data_encoding::BASE64URL; -use futures_lite::Stream; -use futures_util::{ - stream::{SplitSink, SplitStream}, - Sink, StreamExt, -}; +#[cfg(not(wasm_browser))] use hickory_resolver::TokioResolver as DnsResolver; -use http_body_util::Empty; -use hyper::{ - body::Incoming, - header::{HOST, UPGRADE}, - upgrade::Parts, - Request, -}; -use hyper_util::rt::TokioIo; use iroh_base::{RelayUrl, SecretKey}; -use rustls::client::Resumption; -use streams::{downcast_upgrade, MaybeTlsStream, ProxyStream}; -use tokio::{ - io::{AsyncRead, AsyncWrite}, - net::TcpStream, +use n0_future::{ + split::{split, SplitSink, SplitStream}, + Sink, Stream, }; #[cfg(any(test, feature = "test-utils"))] use tracing::warn; -use tracing::{debug, error, event, info_span, trace, Instrument, Level}; +use tracing::{debug, event, trace, Level}; use url::Url; pub use self::conn::{ConnSendError, ReceivedMessage, SendMessage}; use crate::{ - defaults::timeouts::*, http::{Protocol, RELAY_PATH}, KeyCache, }; pub(crate) mod conn; +#[cfg(not(wasm_browser))] +mod connect_relay; +#[cfg(not(wasm_browser))] pub(crate) mod streams; +#[cfg(not(wasm_browser))] mod util; /// Build a Client. @@ -71,6 +57,7 @@ pub struct ClientBuilder { /// The secret key of this client. secret_key: SecretKey, /// The DNS resolver to use. + #[cfg(not(wasm_browser))] dns_resolver: DnsResolver, /// Cache for public keys of remote nodes. key_cache: KeyCache, @@ -78,16 +65,25 @@ pub struct ClientBuilder { impl ClientBuilder { /// Create a new [`ClientBuilder`] - pub fn new(url: impl Into, secret_key: SecretKey, dns_resolver: DnsResolver) -> Self { + pub fn new( + url: impl Into, + secret_key: SecretKey, + #[cfg(not(wasm_browser))] dns_resolver: DnsResolver, + ) -> Self { ClientBuilder { address_family_selector: None, is_prober: false, url: url.into(), - protocol: Protocol::Relay, + + // Resolves to websockets in browsers and relay otherwise + protocol: Protocol::default(), + #[cfg(any(test, feature = "test-utils"))] insecure_skip_cert_verify: false, + proxy_url: None, secret_key, + #[cfg(not(wasm_browser))] dns_resolver, key_cache: KeyCache::new(128), } @@ -143,45 +139,21 @@ impl ClientBuilder { /// Establishes a new connection to the relay server. pub async fn connect(&self) -> Result { - let roots = rustls::RootCertStore { - roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), - }; - let mut config = rustls::client::ClientConfig::builder_with_provider(Arc::new( - rustls::crypto::ring::default_provider(), - )) - .with_safe_default_protocol_versions() - .expect("protocols supported by ring") - .with_root_certificates(roots) - .with_no_client_auth(); - #[cfg(any(test, feature = "test-utils"))] - if self.insecure_skip_cert_verify { - warn!("Insecure config: SSL certificates from relay servers not verified"); - config - .dangerous() - .set_certificate_verifier(Arc::new(NoCertVerifier)); - } - config.resumption = Resumption::default(); - let tls_connector: tokio_rustls::TlsConnector = Arc::new(config).into(); - - let (conn, local_addr) = self.connect_0(tls_connector).await?; - - Ok(Client { conn, local_addr }) - } - - async fn connect_0( - &self, - tls_connector: tokio_rustls::TlsConnector, - ) -> Result<(Conn, Option)> { let (conn, local_addr) = match self.protocol { Protocol::Websocket => { let conn = self.connect_ws().await?; let local_addr = None; (conn, local_addr) } + #[cfg(not(wasm_browser))] Protocol::Relay => { - let (conn, local_addr) = self.connect_relay(tls_connector).await?; + let (conn, local_addr) = self.connect_relay().await?; (conn, Some(local_addr)) } + #[cfg(wasm_browser)] + Protocol::Relay => { + bail!("Can only connect to relay using websockets in browsers."); + } }; event!( @@ -191,8 +163,8 @@ impl ClientBuilder { protocol = ?self.protocol, ); - trace!("connect_0 done"); - Ok((conn, local_addr)) + trace!("connect done"); + Ok(Client { conn, local_addr }) } async fn connect_ws(&self) -> Result { @@ -211,94 +183,6 @@ impl ClientBuilder { Ok(conn) } - async fn connect_relay( - &self, - tls_connector: tokio_rustls::TlsConnector, - ) -> Result<(Conn, SocketAddr)> { - let url = self.url.clone(); - let tcp_stream = self.dial_url(&tls_connector).await?; - - let local_addr = tcp_stream - .local_addr() - .context("No local addr for TCP stream")?; - - debug!(server_addr = ?tcp_stream.peer_addr(), %local_addr, "TCP stream connected"); - - let response = if self.use_tls() { - debug!("Starting TLS handshake"); - let hostname = self - .tls_servername() - .ok_or_else(|| anyhow!("No tls servername"))?; - let hostname = hostname.to_owned(); - let tls_stream = tls_connector.connect(hostname, tcp_stream).await?; - debug!("tls_connector connect success"); - Self::start_upgrade(tls_stream, url).await? - } else { - debug!("Starting handshake"); - Self::start_upgrade(tcp_stream, url).await? - }; - - if response.status() != hyper::StatusCode::SWITCHING_PROTOCOLS { - bail!( - "Unexpected status code: expected {}, actual: {}", - hyper::StatusCode::SWITCHING_PROTOCOLS, - response.status(), - ); - } - - debug!("starting upgrade"); - let upgraded = hyper::upgrade::on(response) - .await - .context("Upgrade failed")?; - - debug!("connection upgraded"); - let conn = downcast_upgrade(upgraded)?; - - let conn = Conn::new_relay(conn, self.key_cache.clone(), &self.secret_key).await?; - - Ok((conn, local_addr)) - } - - /// Sends the HTTP upgrade request to the relay server. - async fn start_upgrade(io: T, relay_url: RelayUrl) -> Result> - where - T: AsyncRead + AsyncWrite + Send + Unpin + 'static, - { - let host_header_value = host_header_value(relay_url)?; - - let io = hyper_util::rt::TokioIo::new(io); - let (mut request_sender, connection) = hyper::client::conn::http1::Builder::new() - .handshake(io) - .await?; - tokio::spawn( - // This task drives the HTTP exchange, completes once connection is upgraded. - async move { - debug!("HTTP upgrade driver started"); - if let Err(err) = connection.with_upgrades().await { - error!("HTTP upgrade error: {err:#}"); - } - debug!("HTTP upgrade driver finished"); - } - .instrument(info_span!("http-driver")), - ); - debug!("Sending upgrade request"); - let req = Request::builder() - .uri(RELAY_PATH) - .header(UPGRADE, Protocol::Relay.upgrade_header()) - // https://datatracker.ietf.org/doc/html/rfc2616#section-14.23 - // > A client MUST include a Host header field in all HTTP/1.1 request messages. - // This header value helps reverse proxies identify how to forward requests. - .header(HOST, host_header_value) - .body(http_body_util::Empty::::new())?; - request_sender.send_request(req).await.map_err(From::from) - } - - fn tls_servername(&self) -> Option { - self.url - .host_str() - .and_then(|s| rustls::pki_types::ServerName::try_from(s).ok()) - } - fn use_tls(&self) -> bool { // only disable tls if we are explicitly dialing a http url #[allow(clippy::match_like_matches_macro)] @@ -308,146 +192,6 @@ impl ClientBuilder { _ => true, } } - - async fn dial_url(&self, tls_connector: &tokio_rustls::TlsConnector) -> Result { - if let Some(ref proxy) = self.proxy_url { - let stream = self.dial_url_proxy(proxy.clone(), tls_connector).await?; - Ok(ProxyStream::Proxied(stream)) - } else { - let stream = self.dial_url_direct().await?; - Ok(ProxyStream::Raw(stream)) - } - } - - async fn dial_url_direct(&self) -> Result { - debug!(%self.url, "dial url"); - let prefer_ipv6 = self.prefer_ipv6(); - let dst_ip = self - .dns_resolver - .resolve_host(&self.url, prefer_ipv6) - .await?; - - let port = url_port(&self.url).ok_or_else(|| anyhow!("Missing URL port"))?; - let addr = SocketAddr::new(dst_ip, port); - - debug!("connecting to {}", addr); - let tcp_stream = - tokio::time::timeout( - DIAL_NODE_TIMEOUT, - async move { TcpStream::connect(addr).await }, - ) - .await - .context("Timeout connecting")? - .context("Failed connecting")?; - tcp_stream.set_nodelay(true)?; - - Ok(tcp_stream) - } - - async fn dial_url_proxy( - &self, - proxy_url: Url, - tls_connector: &tokio_rustls::TlsConnector, - ) -> Result, MaybeTlsStream>> { - debug!(%self.url, %proxy_url, "dial url via proxy"); - - // Resolve proxy DNS - let prefer_ipv6 = self.prefer_ipv6(); - let proxy_ip = self - .dns_resolver - .resolve_host(&proxy_url, prefer_ipv6) - .await?; - - let proxy_port = url_port(&proxy_url).ok_or_else(|| anyhow!("Missing proxy url port"))?; - let proxy_addr = SocketAddr::new(proxy_ip, proxy_port); - - debug!(%proxy_addr, "connecting to proxy"); - - let tcp_stream = tokio::time::timeout(DIAL_NODE_TIMEOUT, async move { - TcpStream::connect(proxy_addr).await - }) - .await - .context("Timeout connecting")? - .context("Connecting")?; - - tcp_stream.set_nodelay(true)?; - - // Setup TLS if necessary - let io = if proxy_url.scheme() == "http" { - MaybeTlsStream::Raw(tcp_stream) - } else { - let hostname = proxy_url.host_str().context("No hostname in proxy URL")?; - let hostname = rustls::pki_types::ServerName::try_from(hostname.to_string())?; - let tls_stream = tls_connector.connect(hostname, tcp_stream).await?; - MaybeTlsStream::Tls(tls_stream) - }; - let io = TokioIo::new(io); - - let target_host = self - .url - .host_str() - .ok_or_else(|| anyhow!("Missing proxy host"))?; - - let port = url_port(&self.url).ok_or_else(|| anyhow!("invalid target port"))?; - - // Establish Proxy Tunnel - let mut req_builder = Request::builder() - .uri(format!("{}:{}", target_host, port)) - .method("CONNECT") - .header("Host", target_host) - .header("Proxy-Connection", "Keep-Alive"); - if !proxy_url.username().is_empty() { - // Passthrough authorization - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization - debug!( - "setting proxy-authorization: username={}", - proxy_url.username() - ); - let to_encode = format!( - "{}:{}", - proxy_url.username(), - proxy_url.password().unwrap_or_default() - ); - let encoded = BASE64URL.encode(to_encode.as_bytes()); - req_builder = req_builder.header("Proxy-Authorization", format!("Basic {}", encoded)); - } - let req = req_builder.body(Empty::::new())?; - - debug!("Sending proxy request: {:?}", req); - - let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await?; - tokio::task::spawn(async move { - if let Err(err) = conn.with_upgrades().await { - error!("Proxy connection failed: {:?}", err); - } - }); - - let res = sender.send_request(req).await?; - if !res.status().is_success() { - bail!("Failed to connect to proxy: {}", res.status()); - } - - let upgraded = hyper::upgrade::on(res).await?; - let Ok(Parts { io, read_buf, .. }) = upgraded.downcast::>() else { - bail!("Invalid upgrade"); - }; - - let res = util::chain(std::io::Cursor::new(read_buf), io.into_inner()); - - Ok(res) - } - - /// Reports whether IPv4 dials should be slightly - /// delayed to give IPv6 a better chance of winning dial races. - /// Implementations should only return true if IPv6 is expected - /// to succeed. (otherwise delaying IPv4 will delay the connection - /// overall) - fn prefer_ipv6(&self) -> bool { - match self.address_family_selector { - Some(ref selector) => selector(), - None => false, - } - } } /// A relay client. @@ -460,7 +204,7 @@ pub struct Client { impl Client { /// Splits the client into a sink and a stream. pub fn split(self) -> (ClientStream, ClientSink) { - let (sink, stream) = self.conn.split(); + let (sink, stream) = split(self.conn); ( ClientStream { stream, @@ -583,68 +327,6 @@ pub fn make_dangerous_client_config() -> rustls::ClientConfig { .with_no_client_auth() } -fn host_header_value(relay_url: RelayUrl) -> Result { - // grab the host, turns e.g. https://example.com:8080/xyz -> example.com. - let relay_url_host = relay_url.host_str().context("Invalid URL")?; - // strip the trailing dot, if present: example.com. -> example.com - let relay_url_host = relay_url_host.strip_suffix('.').unwrap_or(relay_url_host); - // build the host header value (reserve up to 6 chars for the ":" and port digits): - let mut host_header_value = String::with_capacity(relay_url_host.len() + 6); - host_header_value += relay_url_host; - if let Some(port) = relay_url.port() { - host_header_value += ":"; - host_header_value += &port.to_string(); - } - Ok(host_header_value) -} - -trait DnsExt { - fn lookup_ipv4( - &self, - host: N, - ) -> impl Future>>; - - fn lookup_ipv6( - &self, - host: N, - ) -> impl Future>>; - - fn resolve_host(&self, url: &Url, prefer_ipv6: bool) -> impl Future>; -} - -impl DnsExt for DnsResolver { - async fn lookup_ipv4(&self, host: N) -> Result> { - let addrs = tokio::time::timeout(DNS_TIMEOUT, self.ipv4_lookup(host)).await??; - Ok(addrs.into_iter().next().map(|ip| IpAddr::V4(ip.0))) - } - - async fn lookup_ipv6(&self, host: N) -> Result> { - let addrs = tokio::time::timeout(DNS_TIMEOUT, self.ipv6_lookup(host)).await??; - Ok(addrs.into_iter().next().map(|ip| IpAddr::V6(ip.0))) - } - - async fn resolve_host(&self, url: &Url, prefer_ipv6: bool) -> Result { - let host = url.host().context("Invalid URL")?; - match host { - url::Host::Domain(domain) => { - // Need to do a DNS lookup - let lookup = tokio::join!(self.lookup_ipv4(domain), self.lookup_ipv6(domain)); - let (v4, v6) = match lookup { - (Err(ipv4_err), Err(ipv6_err)) => { - bail!("Ipv4: {ipv4_err:?}, Ipv6: {ipv6_err:?}"); - } - (Err(_), Ok(v6)) => (None, v6), - (Ok(v4), Err(_)) => (v4, None), - (Ok(v4), Ok(v6)) => (v4, v6), - }; - if prefer_ipv6 { v6.or(v4) } else { v4.or(v6) }.context("No response") - } - url::Host::Ipv4(ip) => Ok(IpAddr::V4(ip)), - url::Host::Ipv6(ip) => Ok(IpAddr::V6(ip)), - } - } -} - /// Used to allow self signed certificates in tests #[cfg(any(test, feature = "test-utils"))] #[derive(Debug)] @@ -686,45 +368,3 @@ impl rustls::client::danger::ServerCertVerifier for NoCertVerifier { .supported_schemes() } } - -fn url_port(url: &Url) -> Option { - if let Some(port) = url.port() { - return Some(port); - } - - match url.scheme() { - "http" => Some(80), - "https" => Some(443), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use anyhow::Result; - - use super::*; - - #[test] - fn test_host_header_value() -> Result<()> { - let _guard = iroh_test::logging::setup(); - - let cases = [ - ( - "https://euw1-1.relay.iroh.network.", - "euw1-1.relay.iroh.network", - ), - ("http://localhost:8080", "localhost:8080"), - ]; - - for (url, expected_host) in cases { - let relay_url = RelayUrl::from_str(url)?; - let host = host_header_value(relay_url)?; - assert_eq!(host, expected_host); - } - - Ok(()) - } -} diff --git a/iroh-relay/src/client/conn.rs b/iroh-relay/src/client/conn.rs index a31d1d6ddf..918ce38c14 100644 --- a/iroh-relay/src/client/conn.rs +++ b/iroh-relay/src/client/conn.rs @@ -11,18 +11,17 @@ use std::{ use anyhow::{bail, Result}; use bytes::Bytes; -use futures_lite::Stream; -use futures_util::Sink; use iroh_base::{NodeId, SecretKey}; +use n0_future::{Sink, Stream}; use tokio_tungstenite_wasm::WebSocketStream; +#[cfg(not(wasm_browser))] use tokio_util::codec::Framed; use tracing::debug; use super::KeyCache; -use crate::{ - client::streams::MaybeTlsStreamChained, - protos::relay::{ClientInfo, Frame, RelayCodec, MAX_PACKET_SIZE, PROTOCOL_VERSION}, -}; +use crate::protos::relay::{ClientInfo, Frame, MAX_PACKET_SIZE, PROTOCOL_VERSION}; +#[cfg(not(wasm_browser))] +use crate::{client::streams::MaybeTlsStreamChained, protos::relay::RelayCodec}; /// Error for sending messages to the relay server. #[derive(Debug, thiserror::Error)] @@ -58,6 +57,7 @@ impl From for ConnSendError { /// invariants. #[derive(derive_more::Debug)] pub(crate) enum Conn { + #[cfg(not(wasm_browser))] Relay { #[debug("Framed")] conn: Framed, @@ -85,6 +85,7 @@ impl Conn { } /// Constructs a new websocket connection, including the initial server handshake. + #[cfg(not(wasm_browser))] pub(crate) async fn new_relay( conn: MaybeTlsStreamChained, key_cache: KeyCache, @@ -119,6 +120,7 @@ impl Stream for Conn { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => match Pin::new(conn).poll_next(cx) { Poll::Pending => Poll::Pending, Poll::Ready(Some(Ok(frame))) => { @@ -154,6 +156,7 @@ impl Sink for Conn { fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => Pin::new(conn).poll_ready(cx).map_err(Into::into), Self::Ws { ref mut conn, .. } => Pin::new(conn).poll_ready(cx).map_err(Into::into), } @@ -166,6 +169,7 @@ impl Sink for Conn { } } match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => Pin::new(conn).start_send(frame).map_err(Into::into), Self::Ws { ref mut conn, .. } => Pin::new(conn) .start_send(tokio_tungstenite_wasm::Message::binary( @@ -177,6 +181,7 @@ impl Sink for Conn { fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => Pin::new(conn).poll_flush(cx).map_err(Into::into), Self::Ws { ref mut conn, .. } => Pin::new(conn).poll_flush(cx).map_err(Into::into), } @@ -184,6 +189,7 @@ impl Sink for Conn { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => Pin::new(conn).poll_close(cx).map_err(Into::into), Self::Ws { ref mut conn, .. } => Pin::new(conn).poll_close(cx).map_err(Into::into), } @@ -195,6 +201,7 @@ impl Sink for Conn { fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => Pin::new(conn).poll_ready(cx).map_err(Into::into), Self::Ws { ref mut conn, .. } => Pin::new(conn).poll_ready(cx).map_err(Into::into), } @@ -208,6 +215,7 @@ impl Sink for Conn { } let frame = Frame::from(item); match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => Pin::new(conn).start_send(frame).map_err(Into::into), Self::Ws { ref mut conn, .. } => Pin::new(conn) .start_send(tokio_tungstenite_wasm::Message::binary( @@ -219,6 +227,7 @@ impl Sink for Conn { fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => Pin::new(conn).poll_flush(cx).map_err(Into::into), Self::Ws { ref mut conn, .. } => Pin::new(conn).poll_flush(cx).map_err(Into::into), } @@ -226,6 +235,7 @@ impl Sink for Conn { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match *self { + #[cfg(not(wasm_browser))] Self::Relay { ref mut conn } => Pin::new(conn).poll_close(cx).map_err(Into::into), Self::Ws { ref mut conn, .. } => Pin::new(conn).poll_close(cx).map_err(Into::into), } diff --git a/iroh-relay/src/client/connect_relay.rs b/iroh-relay/src/client/connect_relay.rs new file mode 100644 index 0000000000..06e1df1087 --- /dev/null +++ b/iroh-relay/src/client/connect_relay.rs @@ -0,0 +1,395 @@ +//! Functionality related to `ClientBuilder::connect_relay`. +//! +//! This is (1) likely to be phased out over time in favor of websockets, and +//! (2) doesn't work in the browser - thus separated into its own file. +//! +//! `connect_relay` uses a custom HTTP upgrade header value (see [`HTTP_UPGRADE_PROTOCOL`]), +//! as opposed to [`WEBSOCKET_UPGRADE_PROTOCOL`]. +//! However, this code path also contains support for HTTP(s) proxies, which is +//! why it still remains the default code path as of now. +//! +//! [`HTTP_UPGRADE_PROTOCOL`]: crate::http::HTTP_UPGRADE_PROTOCOL +//! [`WEBSOCKET_UPGRADE_PROTOCOL`]: crate::http::WEBSOCKET_UPGRADE_PROTOCOL + +// Based on tailscale/derp/derphttp/derphttp_client.go + +use std::{future::Future, net::IpAddr}; + +use anyhow::Context; +use bytes::Bytes; +use data_encoding::BASE64URL; +use http_body_util::Empty; +use hyper::{ + body::Incoming, + header::{HOST, UPGRADE}, + upgrade::Parts, + Request, +}; +use rustls::client::Resumption; +use tokio::io::{AsyncRead, AsyncWrite}; +use tracing::{error, info_span, Instrument}; + +use super::{ + streams::{downcast_upgrade, MaybeTlsStream, ProxyStream}, + *, +}; +use crate::defaults::timeouts::*; + +impl ClientBuilder { + /// Connects to configured relay using HTTP(S) with an upgrade header + /// set to [`HTTP_UPGRADE_PROTOCOL`]. + /// + /// [`HTTP_UPGRADE_PROTOCOL`]: crate::http::HTTP_UPGRADE_PROTOCOL + pub(super) async fn connect_relay(&self) -> Result<(Conn, SocketAddr)> { + let roots = rustls::RootCertStore { + roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), + }; + let mut config = rustls::client::ClientConfig::builder_with_provider(Arc::new( + rustls::crypto::ring::default_provider(), + )) + .with_safe_default_protocol_versions() + .expect("protocols supported by ring") + .with_root_certificates(roots) + .with_no_client_auth(); + #[cfg(any(test, feature = "test-utils"))] + if self.insecure_skip_cert_verify { + warn!("Insecure config: SSL certificates from relay servers not verified"); + config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertVerifier)); + } + config.resumption = Resumption::default(); + let tls_connector: tokio_rustls::TlsConnector = Arc::new(config).into(); + + let url = self.url.clone(); + let tcp_stream = self.dial_url(&tls_connector).await?; + + let local_addr = tcp_stream + .local_addr() + .context("No local addr for TCP stream")?; + + debug!(server_addr = ?tcp_stream.peer_addr(), %local_addr, "TCP stream connected"); + + let response = if self.use_tls() { + debug!("Starting TLS handshake"); + let hostname = self + .tls_servername() + .ok_or_else(|| anyhow!("No tls servername"))?; + let hostname = hostname.to_owned(); + let tls_stream = tls_connector.connect(hostname, tcp_stream).await?; + debug!("tls_connector connect success"); + Self::start_upgrade(tls_stream, url).await? + } else { + debug!("Starting handshake"); + Self::start_upgrade(tcp_stream, url).await? + }; + + if response.status() != hyper::StatusCode::SWITCHING_PROTOCOLS { + bail!( + "Unexpected status code: expected {}, actual: {}", + hyper::StatusCode::SWITCHING_PROTOCOLS, + response.status(), + ); + } + + debug!("starting upgrade"); + let upgraded = hyper::upgrade::on(response) + .await + .context("Upgrade failed")?; + + debug!("connection upgraded"); + let conn = downcast_upgrade(upgraded)?; + + let conn = Conn::new_relay(conn, self.key_cache.clone(), &self.secret_key).await?; + + Ok((conn, local_addr)) + } + + /// Sends the HTTP upgrade request to the relay server. + async fn start_upgrade(io: T, relay_url: RelayUrl) -> Result> + where + T: AsyncRead + AsyncWrite + Send + Unpin + 'static, + { + use hyper_util::rt::TokioIo; + let host_header_value = host_header_value(relay_url)?; + + let io = TokioIo::new(io); + let (mut request_sender, connection) = hyper::client::conn::http1::Builder::new() + .handshake(io) + .await?; + tokio::spawn( + // This task drives the HTTP exchange, completes once connection is upgraded. + async move { + debug!("HTTP upgrade driver started"); + if let Err(err) = connection.with_upgrades().await { + error!("HTTP upgrade error: {err:#}"); + } + debug!("HTTP upgrade driver finished"); + } + .instrument(info_span!("http-driver")), + ); + debug!("Sending upgrade request"); + let req = Request::builder() + .uri(RELAY_PATH) + .header(UPGRADE, Protocol::Relay.upgrade_header()) + // https://datatracker.ietf.org/doc/html/rfc2616#section-14.23 + // > A client MUST include a Host header field in all HTTP/1.1 request messages. + // This header value helps reverse proxies identify how to forward requests. + .header(HOST, host_header_value) + .body(http_body_util::Empty::::new())?; + request_sender.send_request(req).await.map_err(From::from) + } + + fn tls_servername(&self) -> Option { + self.url + .host_str() + .and_then(|s| rustls::pki_types::ServerName::try_from(s).ok()) + } + + async fn dial_url(&self, tls_connector: &tokio_rustls::TlsConnector) -> Result { + if let Some(ref proxy) = self.proxy_url { + let stream = self.dial_url_proxy(proxy.clone(), tls_connector).await?; + Ok(ProxyStream::Proxied(stream)) + } else { + let stream = self.dial_url_direct().await?; + Ok(ProxyStream::Raw(stream)) + } + } + + async fn dial_url_direct(&self) -> Result { + use tokio::net::TcpStream; + debug!(%self.url, "dial url"); + let prefer_ipv6 = self.prefer_ipv6(); + let dst_ip = self + .dns_resolver + .resolve_host(&self.url, prefer_ipv6) + .await?; + + let port = url_port(&self.url).ok_or_else(|| anyhow!("Missing URL port"))?; + let addr = SocketAddr::new(dst_ip, port); + + debug!("connecting to {}", addr); + let tcp_stream = + tokio::time::timeout( + DIAL_NODE_TIMEOUT, + async move { TcpStream::connect(addr).await }, + ) + .await + .context("Timeout connecting")? + .context("Failed connecting")?; + tcp_stream.set_nodelay(true)?; + + Ok(tcp_stream) + } + + async fn dial_url_proxy( + &self, + proxy_url: Url, + tls_connector: &tokio_rustls::TlsConnector, + ) -> Result, MaybeTlsStream>> { + use hyper_util::rt::TokioIo; + use tokio::net::TcpStream; + debug!(%self.url, %proxy_url, "dial url via proxy"); + + // Resolve proxy DNS + let prefer_ipv6 = self.prefer_ipv6(); + let proxy_ip = self + .dns_resolver + .resolve_host(&proxy_url, prefer_ipv6) + .await?; + + let proxy_port = url_port(&proxy_url).ok_or_else(|| anyhow!("Missing proxy url port"))?; + let proxy_addr = SocketAddr::new(proxy_ip, proxy_port); + + debug!(%proxy_addr, "connecting to proxy"); + + let tcp_stream = tokio::time::timeout(DIAL_NODE_TIMEOUT, async move { + TcpStream::connect(proxy_addr).await + }) + .await + .context("Timeout connecting")? + .context("Connecting")?; + + tcp_stream.set_nodelay(true)?; + + // Setup TLS if necessary + let io = if proxy_url.scheme() == "http" { + MaybeTlsStream::Raw(tcp_stream) + } else { + let hostname = proxy_url.host_str().context("No hostname in proxy URL")?; + let hostname = rustls::pki_types::ServerName::try_from(hostname.to_string())?; + let tls_stream = tls_connector.connect(hostname, tcp_stream).await?; + MaybeTlsStream::Tls(tls_stream) + }; + let io = TokioIo::new(io); + + let target_host = self + .url + .host_str() + .ok_or_else(|| anyhow!("Missing proxy host"))?; + + let port = url_port(&self.url).ok_or_else(|| anyhow!("invalid target port"))?; + + // Establish Proxy Tunnel + let mut req_builder = Request::builder() + .uri(format!("{}:{}", target_host, port)) + .method("CONNECT") + .header("Host", target_host) + .header("Proxy-Connection", "Keep-Alive"); + if !proxy_url.username().is_empty() { + // Passthrough authorization + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization + debug!( + "setting proxy-authorization: username={}", + proxy_url.username() + ); + let to_encode = format!( + "{}:{}", + proxy_url.username(), + proxy_url.password().unwrap_or_default() + ); + let encoded = BASE64URL.encode(to_encode.as_bytes()); + req_builder = req_builder.header("Proxy-Authorization", format!("Basic {}", encoded)); + } + let req = req_builder.body(Empty::::new())?; + + debug!("Sending proxy request: {:?}", req); + + let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await?; + tokio::task::spawn(async move { + if let Err(err) = conn.with_upgrades().await { + error!("Proxy connection failed: {:?}", err); + } + }); + + let res = sender.send_request(req).await?; + if !res.status().is_success() { + bail!("Failed to connect to proxy: {}", res.status()); + } + + let upgraded = hyper::upgrade::on(res).await?; + let Ok(Parts { io, read_buf, .. }) = upgraded.downcast::>() else { + bail!("Invalid upgrade"); + }; + + let res = util::chain(std::io::Cursor::new(read_buf), io.into_inner()); + + Ok(res) + } + + /// Reports whether IPv4 dials should be slightly + /// delayed to give IPv6 a better chance of winning dial races. + /// Implementations should only return true if IPv6 is expected + /// to succeed. (otherwise delaying IPv4 will delay the connection + /// overall) + fn prefer_ipv6(&self) -> bool { + match self.address_family_selector { + Some(ref selector) => selector(), + None => false, + } + } +} + +fn host_header_value(relay_url: RelayUrl) -> Result { + // grab the host, turns e.g. https://example.com:8080/xyz -> example.com. + let relay_url_host = relay_url.host_str().context("Invalid URL")?; + // strip the trailing dot, if present: example.com. -> example.com + let relay_url_host = relay_url_host.strip_suffix('.').unwrap_or(relay_url_host); + // build the host header value (reserve up to 6 chars for the ":" and port digits): + let mut host_header_value = String::with_capacity(relay_url_host.len() + 6); + host_header_value += relay_url_host; + if let Some(port) = relay_url.port() { + host_header_value += ":"; + host_header_value += &port.to_string(); + } + Ok(host_header_value) +} + +fn url_port(url: &Url) -> Option { + if let Some(port) = url.port() { + return Some(port); + } + + match url.scheme() { + "http" => Some(80), + "https" => Some(443), + _ => None, + } +} + +trait DnsExt { + fn lookup_ipv4( + &self, + host: N, + ) -> impl Future>>; + + fn lookup_ipv6( + &self, + host: N, + ) -> impl Future>>; + + fn resolve_host(&self, url: &Url, prefer_ipv6: bool) -> impl Future>; +} + +impl DnsExt for DnsResolver { + async fn lookup_ipv4(&self, host: N) -> Result> { + let addrs = tokio::time::timeout(DNS_TIMEOUT, self.ipv4_lookup(host)).await??; + Ok(addrs.into_iter().next().map(|ip| IpAddr::V4(ip.0))) + } + + async fn lookup_ipv6(&self, host: N) -> Result> { + let addrs = tokio::time::timeout(DNS_TIMEOUT, self.ipv6_lookup(host)).await??; + Ok(addrs.into_iter().next().map(|ip| IpAddr::V6(ip.0))) + } + + async fn resolve_host(&self, url: &Url, prefer_ipv6: bool) -> Result { + let host = url.host().context("Invalid URL")?; + match host { + url::Host::Domain(domain) => { + // Need to do a DNS lookup + let lookup = tokio::join!(self.lookup_ipv4(domain), self.lookup_ipv6(domain)); + let (v4, v6) = match lookup { + (Err(ipv4_err), Err(ipv6_err)) => { + bail!("Ipv4: {ipv4_err:?}, Ipv6: {ipv6_err:?}"); + } + (Err(_), Ok(v6)) => (None, v6), + (Ok(v4), Err(_)) => (v4, None), + (Ok(v4), Ok(v6)) => (v4, v6), + }; + if prefer_ipv6 { v6.or(v4) } else { v4.or(v6) }.context("No response") + } + url::Host::Ipv4(ip) => Ok(IpAddr::V4(ip)), + url::Host::Ipv6(ip) => Ok(IpAddr::V6(ip)), + } + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use anyhow::Result; + + use super::*; + + #[test] + fn test_host_header_value() -> Result<()> { + let _guard = iroh_test::logging::setup(); + + let cases = [ + ( + "https://euw1-1.relay.iroh.network.", + "euw1-1.relay.iroh.network", + ), + ("http://localhost:8080", "localhost:8080"), + ]; + + for (url, expected_host) in cases { + let relay_url = RelayUrl::from_str(url)?; + let host = host_header_value(relay_url)?; + assert_eq!(host, expected_host); + } + + Ok(()) + } +} diff --git a/iroh-relay/src/defaults.rs b/iroh-relay/src/defaults.rs index 3dd598934b..12043a0f36 100644 --- a/iroh-relay/src/defaults.rs +++ b/iroh-relay/src/defaults.rs @@ -28,8 +28,9 @@ pub const DEFAULT_METRICS_PORT: u16 = 9090; pub const DEFAULT_KEY_CACHE_CAPACITY: usize = 1024 * 1024; /// Contains all timeouts that we use in `iroh`. +#[cfg(not(wasm_browser))] pub(crate) mod timeouts { - use std::time::Duration; + use n0_future::time::Duration; /// Timeout used by the relay client while connecting to the relay server, /// using `TcpStream::connect` diff --git a/iroh-relay/src/http.rs b/iroh-relay/src/http.rs index 7e7e5177dd..aa89e81424 100644 --- a/iroh-relay/src/http.rs +++ b/iroh-relay/src/http.rs @@ -26,6 +26,15 @@ pub enum Protocol { Websocket, } +impl Default for Protocol { + fn default() -> Self { + #[cfg(wasm_browser)] + return Self::Websocket; + #[cfg(not(wasm_browser))] + return Self::Relay; + } +} + impl Protocol { /// The HTTP upgrade header used or expected. pub const fn upgrade_header(&self) -> &'static str { diff --git a/iroh-relay/src/main.rs b/iroh-relay/src/main.rs index 7f1b32d6e2..0f063f32b4 100644 --- a/iroh-relay/src/main.rs +++ b/iroh-relay/src/main.rs @@ -11,7 +11,6 @@ use std::{ use anyhow::{bail, Context as _, Result}; use clap::Parser; -use futures_lite::FutureExt; use iroh_base::NodeId; use iroh_relay::{ defaults::{ @@ -20,6 +19,7 @@ use iroh_relay::{ }, server::{self as relay, ClientRateLimit, QuicConfig}, }; +use n0_future::FutureExt; use serde::{Deserialize, Serialize}; use tokio_rustls_acme::{caches::DirCache, AcmeConfig}; use tracing::debug; diff --git a/iroh-relay/src/ping_tracker.rs b/iroh-relay/src/ping_tracker.rs index 428ae7567a..7abca76804 100644 --- a/iroh-relay/src/ping_tracker.rs +++ b/iroh-relay/src/ping_tracker.rs @@ -1,5 +1,4 @@ -use std::time::{Duration, Instant}; - +use n0_future::time::{self, Duration, Instant}; use tracing::debug; /// Maximum time for a ping response in the relay protocol. @@ -68,7 +67,7 @@ impl PingTracker { pub async fn timeout(&mut self) { match self.inner { Some(PingInner { deadline, data }) => { - tokio::time::sleep_until(deadline.into()).await; + time::sleep_until(deadline).await; debug!(?data, "Ping timeout."); self.inner = None; } diff --git a/iroh-relay/src/protos/relay.rs b/iroh-relay/src/protos/relay.rs index 6f70226e52..1179fa492a 100644 --- a/iroh-relay/src/protos/relay.rs +++ b/iroh-relay/src/protos/relay.rs @@ -12,19 +12,16 @@ //! * clients sends `FrameType::SendPacket` //! * server then sends `FrameType::RecvPacket` to recipient -#[cfg(feature = "server")] -use std::time::Duration; - use anyhow::{bail, ensure}; -use bytes::{Buf, BufMut, Bytes, BytesMut}; -#[cfg(any(test, feature = "server"))] -use futures_lite::{Stream, StreamExt}; -use futures_sink::Sink; -use futures_util::SinkExt; +use bytes::{BufMut, Bytes}; use iroh_base::{PublicKey, SecretKey, Signature}; +#[cfg(feature = "server")] +use n0_future::time::Duration; +use n0_future::{Sink, SinkExt}; +#[cfg(any(test, feature = "server"))] +use n0_future::{Stream, StreamExt}; use postcard::experimental::max_size::MaxSize; use serde::{Deserialize, Serialize}; -use tokio_util::codec::{Decoder, Encoder}; use crate::{client::conn::ConnSendError, KeyCache}; @@ -36,6 +33,7 @@ pub const MAX_PACKET_SIZE: usize = 64 * 1024; /// The maximum frame size. /// /// This is also the minimum burst size that a rate-limiter has to accept. +#[cfg(not(wasm_browser))] const MAX_FRAME_SIZE: usize = 1024 * 1024; /// The Relay magic number, sent in the FrameType::ClientInfo frame upon initial connection. @@ -209,11 +207,13 @@ pub(crate) async fn recv_client_key> + Un /// /// This is a framed protocol, using [`tokio_util::codec`] to turn the streams of bytes into /// [`Frame`]s. +#[cfg(not(wasm_browser))] #[derive(Debug, Clone)] pub(crate) struct RelayCodec { cache: KeyCache, } +#[cfg(not(wasm_browser))] impl RelayCodec { #[cfg(test)] pub fn test() -> Self { @@ -282,6 +282,7 @@ impl Frame { } /// Serialized length (without the frame header) + #[cfg(not(wasm_browser))] // Not needed with websocket framing - thus not needed in browsers pub(crate) fn len(&self) -> usize { match self { Frame::ClientInfo { @@ -496,72 +497,86 @@ impl Frame { } } -const HEADER_LEN: usize = 5; +// No need for framing when using websockets, thus this is cfg-ed out for browsers: +#[cfg(not(wasm_browser))] +// rustc doesn't figure out that the trait impls mean it's not unused +#[cfg_attr(not(wasm_browser), allow(unused))] +pub use framing::*; -impl Decoder for RelayCodec { - type Item = Frame; - type Error = anyhow::Error; +#[cfg(not(wasm_browser))] +mod framing { + use bytes::{Buf, BytesMut}; + use tokio_util::codec::{Decoder, Encoder}; - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - // Need at least 5 bytes - if src.len() < HEADER_LEN { - return Ok(None); - } + use super::*; - // Can't use the `Buf::get_*` APIs, as that advances the buffer. - let Some(frame_type) = src.first().map(|b| FrameType::from(*b)) else { - return Ok(None); // Not enough bytes - }; - let Some(frame_len) = src - .get(1..5) - .and_then(|s| TryInto::<[u8; 4]>::try_into(s).ok()) - .map(u32::from_be_bytes) - .map(|l| l as usize) - else { - return Ok(None); // Not enough bytes - }; + pub(super) const HEADER_LEN: usize = 5; - if frame_len > MAX_FRAME_SIZE { - anyhow::bail!("Frame of length {} is too large.", frame_len); - } + impl Decoder for RelayCodec { + type Item = Frame; + type Error = anyhow::Error; - if src.len() < HEADER_LEN + frame_len { - // Optimization: prereserve the buffer space - src.reserve(HEADER_LEN + frame_len - src.len()); + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + // Need at least 5 bytes + if src.len() < HEADER_LEN { + return Ok(None); + } - return Ok(None); - } + // Can't use the `Buf::get_*` APIs, as that advances the buffer. + let Some(frame_type) = src.first().map(|b| FrameType::from(*b)) else { + return Ok(None); // Not enough bytes + }; + let Some(frame_len) = src + .get(1..5) + .and_then(|s| TryInto::<[u8; 4]>::try_into(s).ok()) + .map(u32::from_be_bytes) + .map(|l| l as usize) + else { + return Ok(None); // Not enough bytes + }; - // advance the header - src.advance(HEADER_LEN); + if frame_len > MAX_FRAME_SIZE { + anyhow::bail!("Frame of length {} is too large.", frame_len); + } - let content = src.split_to(frame_len).freeze(); - let frame = Frame::from_bytes(frame_type, content, &self.cache)?; + if src.len() < HEADER_LEN + frame_len { + // Optimization: prereserve the buffer space + src.reserve(HEADER_LEN + frame_len - src.len()); - Ok(Some(frame)) - } -} + return Ok(None); + } + + // advance the header + src.advance(HEADER_LEN); -impl Encoder for RelayCodec { - type Error = std::io::Error; + let content = src.split_to(frame_len).freeze(); + let frame = Frame::from_bytes(frame_type, content, &self.cache)?; - fn encode(&mut self, frame: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> { - let frame_len: usize = frame.len(); - if frame_len > MAX_FRAME_SIZE { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Frame of length {} is too large.", frame_len), - )); + Ok(Some(frame)) } + } - let frame_len_u32 = u32::try_from(frame_len).expect("just checked"); + impl Encoder for RelayCodec { + type Error = std::io::Error; - dst.reserve(HEADER_LEN + frame_len); - dst.put_u8(frame.typ().into()); - dst.put_u32(frame_len_u32); - frame.write_to(dst); + fn encode(&mut self, frame: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> { + let frame_len: usize = frame.len(); + if frame_len > MAX_FRAME_SIZE { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Frame of length {} is too large.", frame_len), + )); + } - Ok(()) + let frame_len_u32 = u32::try_from(frame_len).expect("just checked"); + + dst.reserve(HEADER_LEN + frame_len); + dst.put_u8(frame.typ().into()); + dst.put_u32(frame_len_u32); + frame.write_to(dst); + + Ok(()) + } } } @@ -724,7 +739,9 @@ mod tests { #[cfg(test)] mod proptests { + use bytes::BytesMut; use proptest::prelude::*; + use tokio_util::codec::{Decoder, Encoder}; use super::*; diff --git a/iroh-relay/src/server.rs b/iroh-relay/src/server.rs index cb798d1f63..dfe2c4d488 100644 --- a/iroh-relay/src/server.rs +++ b/iroh-relay/src/server.rs @@ -20,7 +20,6 @@ use std::{fmt, future::Future, net::SocketAddr, num::NonZeroU32, pin::Pin, sync: use anyhow::{anyhow, bail, Context, Result}; use derive_more::Debug; -use futures_lite::{future::Boxed, StreamExt}; use http::{ response::Builder as ResponseBuilder, HeaderMap, Method, Request, Response, StatusCode, }; @@ -29,6 +28,7 @@ use iroh_base::NodeId; #[cfg(feature = "test-utils")] use iroh_base::RelayUrl; use iroh_metrics::inc; +use n0_future::{future::Boxed, StreamExt}; use tokio::{ net::{TcpListener, UdpSocket}, task::JoinSet, @@ -528,13 +528,13 @@ async fn relay_supervisor( ) -> Result<()> { let quic_enabled = quic_server.is_some(); let mut quic_fut = match quic_server { - Some(ref mut server) => futures_util::future::Either::Left(server.task_handle()), - None => futures_util::future::Either::Right(futures_lite::future::pending()), + Some(ref mut server) => n0_future::Either::Left(server.task_handle()), + None => n0_future::Either::Right(n0_future::future::pending()), }; let relay_enabled = relay_http_server.is_some(); let mut relay_fut = match relay_http_server { - Some(ref mut server) => futures_util::future::Either::Left(server.task_handle()), - None => futures_util::future::Either::Right(futures_lite::future::pending()), + Some(ref mut server) => n0_future::Either::Left(server.task_handle()), + None => n0_future::Either::Right(n0_future::future::pending()), }; let res = tokio::select! { biased; @@ -808,10 +808,9 @@ mod tests { use std::{net::Ipv4Addr, time::Duration}; use bytes::Bytes; - use futures_lite::FutureExt; - use futures_util::SinkExt; use http::header::UPGRADE; use iroh_base::{NodeId, SecretKey}; + use n0_future::{FutureExt, SinkExt}; use testresult::TestResult; use super::*; diff --git a/iroh-relay/src/server/client.rs b/iroh-relay/src/server/client.rs index 152d8883d6..a3c541b0cc 100644 --- a/iroh-relay/src/server/client.rs +++ b/iroh-relay/src/server/client.rs @@ -4,11 +4,9 @@ use std::{future::Future, num::NonZeroU32, pin::Pin, sync::Arc, task::Poll, time use anyhow::{bail, Context, Result}; use bytes::Bytes; -use futures_lite::FutureExt; -use futures_sink::Sink; -use futures_util::{SinkExt, Stream, StreamExt}; use iroh_base::NodeId; use iroh_metrics::{inc, inc_by}; +use n0_future::{FutureExt, Sink, SinkExt, Stream, StreamExt}; use rand::Rng; use tokio::{ sync::mpsc::{self, error::TrySendError}, diff --git a/iroh-relay/src/server/clients.rs b/iroh-relay/src/server/clients.rs index f7030a2ff1..6713dd0687 100644 --- a/iroh-relay/src/server/clients.rs +++ b/iroh-relay/src/server/clients.rs @@ -40,10 +40,8 @@ impl Clients { trace!("shutting down {} clients", keys.len()); let clients = keys.into_iter().filter_map(|k| self.0.clients.remove(&k)); - futures_buffered::join_all( - clients.map(|(_, client)| async move { client.shutdown().await }), - ) - .await; + n0_future::join_all(clients.map(|(_, client)| async move { client.shutdown().await })) + .await; } /// Builds the client handler and starts the read & write loops for the connection. diff --git a/iroh-relay/src/server/http_server.rs b/iroh-relay/src/server/http_server.rs index 68b0e5ebfb..690a19fbf0 100644 --- a/iroh-relay/src/server/http_server.rs +++ b/iroh-relay/src/server/http_server.rs @@ -5,8 +5,6 @@ use std::{ use anyhow::{bail, ensure, Context as _, Result}; use bytes::Bytes; use derive_more::Debug; -use futures_lite::FutureExt; -use futures_util::SinkExt; use http::{header::CONNECTION, response::Builder as ResponseBuilder}; use hyper::{ body::Incoming, @@ -16,6 +14,7 @@ use hyper::{ HeaderMap, Method, Request, Response, StatusCode, }; use iroh_metrics::inc; +use n0_future::{FutureExt, SinkExt}; use tokio::net::{TcpListener, TcpStream}; use tokio_rustls_acme::AcmeAcceptor; use tokio_tungstenite::{ @@ -703,9 +702,8 @@ mod tests { use anyhow::Result; use bytes::Bytes; - use futures_lite::StreamExt; - use futures_util::SinkExt; use iroh_base::{PublicKey, SecretKey}; + use n0_future::{SinkExt, StreamExt}; use reqwest::Url; use tracing::info; use tracing_subscriber::{prelude::*, EnvFilter}; diff --git a/iroh-relay/src/server/streams.rs b/iroh-relay/src/server/streams.rs index 12b00b7fc9..0d8f380ff3 100644 --- a/iroh-relay/src/server/streams.rs +++ b/iroh-relay/src/server/streams.rs @@ -6,8 +6,7 @@ use std::{ }; use anyhow::Result; -use futures_lite::Stream; -use futures_sink::Sink; +use n0_future::{Sink, Stream}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::{tungstenite, WebSocketStream}; use tokio_util::codec::Framed; diff --git a/iroh/Cargo.toml b/iroh/Cargo.toml index 1927cd8ae0..fc6b9537fc 100644 --- a/iroh/Cargo.toml +++ b/iroh/Cargo.toml @@ -55,9 +55,9 @@ pkarr = { version = "2", default-features = false, features = [ "relay", ] } portmapper = { version = "0.3", default-features = false } -quinn = { package = "iroh-quinn", version = "0.12.0" } -quinn-proto = { package = "iroh-quinn-proto", version = "0.12.0" } -quinn-udp = { package = "iroh-quinn-udp", version = "0.5.5" } +quinn = { package = "iroh-quinn", version = "0.13.0" } +quinn-proto = { package = "iroh-quinn-proto", version = "0.13.0" } +quinn-udp = { package = "iroh-quinn-udp", version = "0.5.7" } rand = "0.8" rcgen = "0.13" reqwest = { version = "0.12", default-features = false, features = [ @@ -86,8 +86,6 @@ tokio-rustls = { version = "0.26", default-features = false, features = [ "ring", ] } tokio-stream = { version = "0.1.15" } -tokio-tungstenite = "0.21" # avoid duplicating this dependency as long as tokio-tungstenite-wasm isn't updated -tokio-tungstenite-wasm = "0.3" tokio-util = { version = "0.7", features = ["io-util", "io", "codec", "rt"] } tracing = "0.1" url = { version = "2.5", features = ["serde"] } diff --git a/iroh/bench/Cargo.toml b/iroh/bench/Cargo.toml index 137ced6a72..6b3af918f3 100644 --- a/iroh/bench/Cargo.toml +++ b/iroh/bench/Cargo.toml @@ -11,7 +11,7 @@ bytes = "1.7" hdrhistogram = { version = "7.2", default-features = false } iroh = { path = ".." } iroh-metrics = "0.31" -quinn = { package = "iroh-quinn", version = "0.12" } +quinn = { package = "iroh-quinn", version = "0.13" } rcgen = "0.13" rustls = { version = "0.23", default-features = false, features = ["ring"] } clap = { version = "4", features = ["derive"] }