diff --git a/Cargo.lock b/Cargo.lock index cb6b0e270..774ccfd0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4121,7 +4121,7 @@ dependencies = [ [[package]] name = "interceptor" version = "0.14.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "async-trait", "bytes", @@ -7007,7 +7007,7 @@ dependencies = [ [[package]] name = "rtcp" version = "0.13.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "bytes", "thiserror 1.0.61", @@ -7017,7 +7017,7 @@ dependencies = [ [[package]] name = "rtp" version = "0.13.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "bytes", "memchr", @@ -7463,7 +7463,7 @@ dependencies = [ [[package]] name = "sdp" version = "0.8.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "rand 0.8.5", "substring", @@ -8035,7 +8035,7 @@ dependencies = [ [[package]] name = "stun" version = "0.8.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "base64 0.22.1", "crc", @@ -8916,7 +8916,7 @@ dependencies = [ [[package]] name = "turn" version = "0.10.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "async-trait", "base64 0.22.1", @@ -9579,7 +9579,7 @@ dependencies = [ [[package]] name = "webrtc" version = "0.13.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "arc-swap", "async-trait", @@ -9622,7 +9622,7 @@ dependencies = [ [[package]] name = "webrtc-data" version = "0.11.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "bytes", "log", @@ -9636,7 +9636,7 @@ dependencies = [ [[package]] name = "webrtc-dtls" version = "0.12.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "aes", "aes-gcm", @@ -9672,11 +9672,12 @@ dependencies = [ [[package]] name = "webrtc-ice" version = "0.13.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "arc-swap", "async-trait", "crc", + "ipnet", "log", "portable-atomic", "rand 0.8.5", @@ -9696,7 +9697,7 @@ dependencies = [ [[package]] name = "webrtc-mdns" version = "0.9.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "log", "socket2 0.5.10", @@ -9708,7 +9709,7 @@ dependencies = [ [[package]] name = "webrtc-media" version = "0.10.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "byteorder", "bytes", @@ -9720,7 +9721,7 @@ dependencies = [ [[package]] name = "webrtc-sctp" version = "0.12.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "arc-swap", "async-trait", @@ -9737,7 +9738,7 @@ dependencies = [ [[package]] name = "webrtc-srtp" version = "0.15.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "aead", "aes", @@ -9759,7 +9760,7 @@ dependencies = [ [[package]] name = "webrtc-util" version = "0.11.0" -source = "git+https://github.com/rustdesk-org/webrtc?rev=80d5a20532cf58f5d4d237c437a98ceb85ee40dc#80d5a20532cf58f5d4d237c437a98ceb85ee40dc" +source = "git+https://github.com/rustdesk-org/webrtc?rev=49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63#49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" dependencies = [ "async-trait", "bitflags 1.3.2", diff --git a/Cargo.toml b/Cargo.toml index afd619322..47b7972bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,7 +220,14 @@ exclude = ["vdi/host"] libxdo-sys = { path = "libs/libxdo-sys-stub" } # One branch off upstream v0.13.0, the tag whose crate versions match this stack. # webrtc-util: reads the Windows adapter list's IPv6 addresses as host-order u16 groups, so every -# one comes out byte-swapped, fails to bind, and ICE gathers no IPv6 host candidate on Windows. +# one comes out byte-swapped, fails to bind, and ICE gathers no IPv6 host candidate on Windows; +# and names every Windows adapter "" with no mask, so ICE cannot tell one adapter's addresses +# from another's; and lays the adapter struct out in nested parts, whose padding on x64 shifts +# every field after OperStatus, Ipv6IfIndex first. +# webrtc-ice: best effort, one IPv6 host candidate per interface and prefix - the one the OS +# sends from when that is one of the group's, else all of them - since the stable address beside +# the temporary one is an identifier that outlives every rotation and that nothing else this +# machine sends out ever shows. # webrtc-sctp: RFC 4960's 1s RTO floor makes a single loss cost 1-3s on a link whose RTT is 24-64ms, # and fast retransmit cannot cover a request/response exchange; INITIAL_MTU 1228 also fragments on # IPv6; and its AIMD pins a lossy long-haul link to MSS/(RTT*sqrt(p)), so a switch sends without @@ -230,13 +237,15 @@ libxdo-sys = { path = "libs/libxdo-sys-stub" } # RTT at KCP's RTO floors, and bundles of small chunks stay within the MTU. A T3-rtx resends # everything outstanding when it packs into four packets and otherwise probes with one and lets # the SACK settle the rest (F-RTO), timed from the latest send, so a stall no longer resends the -# whole backlog behind itself while a short lost tail still comes back at once. +# whole backlog behind itself while a short lost tail still comes back at once. A DATA chunk +# received again is listed in the SACK, which never happened - the sender widens its reordering +# window on that - and the SACK keeps to the MTU too, the lowest gap blocks first. # Pinned by rev, not branch: a fork branch can be rewritten out from under the lockfile. # webrtc: SettingEngine cannot reach the ICE agent's max_binding_requests, which decides how # long the answerer keeps checking a pair that has not answered yet. -webrtc = { git = "https://github.com/rustdesk-org/webrtc", rev = "80d5a20532cf58f5d4d237c437a98ceb85ee40dc" } -webrtc-util = { git = "https://github.com/rustdesk-org/webrtc", rev = "80d5a20532cf58f5d4d237c437a98ceb85ee40dc" } -webrtc-sctp = { git = "https://github.com/rustdesk-org/webrtc", rev = "80d5a20532cf58f5d4d237c437a98ceb85ee40dc" } +webrtc = { git = "https://github.com/rustdesk-org/webrtc", rev = "49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" } +webrtc-util = { git = "https://github.com/rustdesk-org/webrtc", rev = "49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" } +webrtc-sctp = { git = "https://github.com/rustdesk-org/webrtc", rev = "49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" } [package.metadata.winres] LegalCopyright = "Copyright © 2026 Purslane Tech Pte. Ltd. All rights reserved." diff --git a/libs/hbb_common b/libs/hbb_common index 0eb175963..7a5ad5252 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 0eb175963c950cba8d2f4f2a480f49db27b42c39 +Subproject commit 7a5ad5252e6aa4933d28b714c9d224079ab3bcfd diff --git a/src/client.rs b/src/client.rs index 3c8837bf2..0fca4dfcd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -57,7 +57,7 @@ use hbb_common::{ rand, rendezvous_proto::*, sha2::{Digest, Sha256}, - socket_client::{connect_tcp, connect_tcp_local, ipv4_to_ipv6, new_direct_udp_for}, + socket_client::{connect_tcp, connect_tcp_local, ipv4_to_ipv6, new_direct_udp_for_unverified}, sodiumoxide::{base64, crypto::sign}, timeout, tokio::{ @@ -490,7 +490,7 @@ impl Client { // no need to care about multiple rendezvous servers case, since it is acutally not used any more. // Shared state for UDP NAT test result if crate::get_udp_punch_enabled() && !interface.is_force_relay() { - if let Ok((socket, addr)) = new_direct_udp_for(&rendezvous_server).await { + if let Ok((socket, addr)) = new_direct_udp_for_unverified(&rendezvous_server).await { let udp_port = Arc::new(Mutex::new(0)); let up_cloned = udp_port.clone(); let socket_cloned = socket.clone(); diff --git a/src/common.rs b/src/common.rs index 2d8f6e0e2..bf8964a55 100644 --- a/src/common.rs +++ b/src/common.rs @@ -2528,45 +2528,42 @@ async fn stun_ipv6_test(stun_server: String) -> ResultType<(SocketAddr, String)> }) } +/// A global address to ask the kernel for a route to; libwebrtc's QueryDefaultLocalAddress asks +/// for the same one. Nothing is ever sent to it. +const IPV6_ROUTE_PROBE: std::net::Ipv6Addr = + std::net::Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888); +/// The public IPv6 address the STUN servers report is looked for in the background, and for no +/// longer than this: a probe that outlived the minute could write an earlier network's address +/// over a later probe's. +const STUN_IPV6_TIMEOUT_MS: u64 = 5_000; + async fn test_bind_ipv6() -> ResultType { - use hbb_common::futures::future::FutureExt; let local_addr = SocketAddr::from(([0u16; 8], 0)); // [::]:0 let socket = UdpSocket::bind(local_addr).await?; - // Nothing is sent - `connect` only makes the kernel pick a route and a source address - so any - // resolvable target answers equally and the whole cost is DNS. Race the lookups rather than - // walk them: this is awaited inline on the connection path, not every STUN host publishes a - // AAAA, and one resolver that hangs must not decide whether this host has v6. - let lookups = hbb_common::webrtc::WebRTCStream::default_stun_servers() - .into_iter() - .map(|stun| { - (async move { - let addr = tokio::net::lookup_host(&stun) - .await? - .find(|x| x.is_ipv6()) - .ok_or_else(|| { - anyhow!("Failed to resolve STUN ipv6 server address: {}", stun) - })?; - Ok::(addr) - }) - .boxed() - }) - .collect::>(); - let (addr, _) = hbb_common::futures::future::select_ok(lookups).await?; - socket.connect(addr).await?; + // Nothing is sent - `connect` only makes the kernel pick a route and a source address - so + // the target can be any global address, and given as a number it is: this is awaited on the + // connection path, and resolving a STUN host's name first was the one thing on it that + // could wait on the network - for as long as the resolver takes, when there is none. + socket + .connect(SocketAddr::from((IPV6_ROUTE_PROBE, 53))) + .await?; Ok(socket.local_addr()?) } pub async fn test_ipv6() -> Option> { - if PUBLIC_IPV6_ADDR - .lock() - .unwrap() - .1 - .map(|x| x.elapsed().as_secs() < 60) - .unwrap_or(false) { - return None; + // One look and one claim of the minute, under one lock: two connections arriving + // together would otherwise both find it over and both probe. + let mut cached = PUBLIC_IPV6_ADDR.lock().unwrap(); + if cached + .1 + .map(|x| x.elapsed().as_secs() < 60) + .unwrap_or(false) + { + return None; + } + cached.1 = Some(Instant::now()); } - PUBLIC_IPV6_ADDR.lock().unwrap().1 = Some(Instant::now()); match test_bind_ipv6().await { Ok(mut addr) => { @@ -2623,8 +2620,8 @@ pub async fn test_ipv6() -> Option> { .map(|stun| stun_ipv6_test(stun).boxed()) .collect::>(); - match select_ok(tests).await { - Ok(res) => { + match hbb_common::timeout(STUN_IPV6_TIMEOUT_MS, select_ok(tests)).await { + Ok(Ok(res)) => { let mut addr = res.0 .0; addr.set_port(0); // Set port to 0 to avoid conflicts PUBLIC_IPV6_ADDR.lock().unwrap().0 = Some(addr); @@ -2634,9 +2631,12 @@ pub async fn test_ipv6() -> Option> { addr ); } - Err(e) => { + Ok(Err(e)) => { log::error!("Failed to get public IPv6 address: {}", e); } + Err(_) => { + log::warn!("No STUN server answered for IPv6 within {STUN_IPV6_TIMEOUT_MS}ms"); + } }; })) } @@ -3416,4 +3416,11 @@ mod tests { // non-WebRTC handshakes. assert_eq!(decode_id_pk(&signed, &pk).unwrap(), (id, their_pk)); } + + // The route probe is awaited on the connection path, so whatever it finds - an address, or + // no IPv6 route on this machine - it finds without waiting on the network. + #[tokio::test] + async fn test_ipv6_route_probe_does_not_wait_on_the_network() { + assert!(hbb_common::timeout(1_000, test_bind_ipv6()).await.is_ok()); + } }