bump webrtc fork: one IPv6 host candidate per interface and prefix (#16263)

* bump webrtc fork: one IPv6 host candidate per interface and prefix

ICE gathered every IPv6 address on every interface. Beside the temporary
address that privacy extensions rotate, a prefix usually carries a stable
one, often derived from the MAC, that the OS never picks as a source: a host
candidate for it hands the peer an identifier that outlives every rotation
and that nothing else this machine sends out ever shows.

RFC 8445 §5.1.1.1 has the trackable addresses of an interface and prefix
left out once a privacy one is gathered. There is no portable way to tell
the two apart, so the fork's `local_interfaces` stands in for that rule
with a best-effort approximation: of an interface's addresses in one
prefix, it asks the OS which one it sends from - a UDP `connect` inside the
group's own prefix, nothing sent, nothing outside this machine's own
prefixes involved - and if the answer is one of them, keeps that one alone.
If the answer is none of them, the whole group is kept, as it was: the
probe is bound to no interface, so where Ethernet and Wi-Fi share a LAN the
route picks one of them and the answer for the other is an address it does
not hold, and the enumeration order would be no better a guess - on macOS
its first address is the stable one. Every other interface and prefix keeps
its address, a VPN's unique-local one among them. Two static addresses in
one prefix keep one, the recorded price of the stand-in; the interface a
shared prefix's route bypasses keeps both of its addresses, a gap the
stand-in leaves open rather than a regression.

The Windows enumeration, which named every adapter "" with no mask, now
carries the adapter's name and on-link prefix, without which Ethernet and
Wi-Fi on one LAN would have been a single group. That reaches IPv4 too:
its addresses carry the adapter's name and mask where they were "" at /32,
so the candidates gathered are the same but `interface_filter` sees the
real names. hbb_common is untouched: its fe80::/10 filter still applies to
what the fork keeps.

Two more fork commits ride along, found by the same review. SCTP never
reported a DATA chunk received again: `handle_data` asks `can_push` before
`push`, and `push` was where a duplicate was noted, so the no-cwnd sender's
reordering window, which widens on reported duplicates, never heard of one
from another of these endpoints; and the SACK, its gap blocks and now its
duplicates unbounded, could outgrow the MTU the DATA chunks keep to under a
thousand chunks in flight with holes among them. Duplicates are now listed
and SACKed at once (RFC 9260 §6.2), and the SACK reports the lowest gap
blocks that fit (§6.7). And the Windows adapter struct, split into nested
parts, read `Ipv6IfIndex` eight bytes late on x64 - only into a scope id
that `Interface::convert` drops, so nothing gathered wrongly, but the field
an interface-bound probe would need; it is flat now, with a compile-time
check written for Rust 1.75, the version this crate builds with -
`offset_of!` would have wanted 1.77.

rustdesk-org/webrtc 80d5a20..49c89bd8, six commits: the heuristic, the
fail-open it grew in review, its comments brought in line with that, the
SCTP duplicates and SACK bound, the flat Windows adapter struct, and its
check made to build on 1.75.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns

* pin hbb_common to main 7a5ad52

0eb1759..7a5ad52, six commits: the message cap's tests on WebSocket and
WebRTC with the fragmented WebSocket message bounded too (e999dce,
f0f1548); hyper_util's debug logs out of the default filters (#608); and
`new_direct_udp_for_unverified`, the controller's UDP socket on the
resolver's preferred address for the rendezvous server, with a second for
the lookup and without the TCP connection `test_target` opens and drops to
prove it - while `new_direct_udp_for`, `new_udp_for` and `rebind_udp_for`,
which the controlled side's punch reply and its registration live on, keep
that proof (f688d41, 0bc8336, 7a5ad52).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns

* ipv6 punch: ask the kernel for the route without resolving a name first

`test_ipv6` is awaited on the connection path by both sides of a punch, and
before it looked for a public IPv6 address in the background it resolved
the STUN hosts' names inline - racing the four, so that one resolver that
hangs would not decide. It could still: `select_ok` returns the first
success or the last failure, so with no resolver answering the probe waits
for the slowest lookup to give up, as long as the system resolver takes,
once a minute, on the first connection of that minute.

The name was never needed. `connect` on a UDP socket sends nothing; it has
the kernel pick a route and a source address for the destination, and any
global address serves, so the probe now names one - the one libwebrtc's
QueryDefaultLocalAddress asks for - and touches no network at all: a bind,
a connect, a local_addr. A machine without an IPv6 route learns so from the
connect's error, at once, as before.

Two smaller things beside it. The minute's gate read the timestamp under
one lock and set it under another, so two connections arriving together
both found it over and both probed; it is one critical section now. And the
background STUN probe, bounded so far by the STUN client's own ten seconds
and the resolver's, has a deadline of its own, five seconds: a probe that
outlived the minute could write an earlier network's address over a later
probe's.

Test: the route probe completes within a second, an address found or not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns

* controller: the NAT test takes the resolver's address for the server

`new_direct_udp_for` learned which address to send the UDP NAT test to by
opening a TCP connection to the rendezvous server and dropping it - a
handshake, one round trip, on every connection the controller starts,
right before `_start_inner` opens the connection it keeps to the same
host. The NAT test now takes the resolver's preferred address,
`new_direct_udp_for_unverified`, which spends no round trip proving it and
gives its lookup a second: an address the server does not answer on costs
this connection its UDP punch, which the TCP punch and the relay cover, as
they do whenever the test finds no port. The controlled side's punch reply
keeps `new_direct_udp_for` and its proof - a reply sent to such an address
is a device online and unreachable.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
RustDesk
2026-09-19 11:20:23 +08:00
committed by GitHub
parent ea04f04f9d
commit 7a367f7cc6
5 changed files with 73 additions and 56 deletions

31
Cargo.lock generated
View File

@@ -4121,7 +4121,7 @@ dependencies = [
[[package]] [[package]]
name = "interceptor" name = "interceptor"
version = "0.14.0" 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 = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
@@ -7007,7 +7007,7 @@ dependencies = [
[[package]] [[package]]
name = "rtcp" name = "rtcp"
version = "0.13.0" 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 = [ dependencies = [
"bytes", "bytes",
"thiserror 1.0.61", "thiserror 1.0.61",
@@ -7017,7 +7017,7 @@ dependencies = [
[[package]] [[package]]
name = "rtp" name = "rtp"
version = "0.13.0" 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 = [ dependencies = [
"bytes", "bytes",
"memchr", "memchr",
@@ -7463,7 +7463,7 @@ dependencies = [
[[package]] [[package]]
name = "sdp" name = "sdp"
version = "0.8.0" 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 = [ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"substring", "substring",
@@ -8035,7 +8035,7 @@ dependencies = [
[[package]] [[package]]
name = "stun" name = "stun"
version = "0.8.0" 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 = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"crc", "crc",
@@ -8916,7 +8916,7 @@ dependencies = [
[[package]] [[package]]
name = "turn" name = "turn"
version = "0.10.0" 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 = [ dependencies = [
"async-trait", "async-trait",
"base64 0.22.1", "base64 0.22.1",
@@ -9579,7 +9579,7 @@ dependencies = [
[[package]] [[package]]
name = "webrtc" name = "webrtc"
version = "0.13.0" 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 = [ dependencies = [
"arc-swap", "arc-swap",
"async-trait", "async-trait",
@@ -9622,7 +9622,7 @@ dependencies = [
[[package]] [[package]]
name = "webrtc-data" name = "webrtc-data"
version = "0.11.0" 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 = [ dependencies = [
"bytes", "bytes",
"log", "log",
@@ -9636,7 +9636,7 @@ dependencies = [
[[package]] [[package]]
name = "webrtc-dtls" name = "webrtc-dtls"
version = "0.12.0" 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 = [ dependencies = [
"aes", "aes",
"aes-gcm", "aes-gcm",
@@ -9672,11 +9672,12 @@ dependencies = [
[[package]] [[package]]
name = "webrtc-ice" name = "webrtc-ice"
version = "0.13.0" 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 = [ dependencies = [
"arc-swap", "arc-swap",
"async-trait", "async-trait",
"crc", "crc",
"ipnet",
"log", "log",
"portable-atomic", "portable-atomic",
"rand 0.8.5", "rand 0.8.5",
@@ -9696,7 +9697,7 @@ dependencies = [
[[package]] [[package]]
name = "webrtc-mdns" name = "webrtc-mdns"
version = "0.9.0" 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 = [ dependencies = [
"log", "log",
"socket2 0.5.10", "socket2 0.5.10",
@@ -9708,7 +9709,7 @@ dependencies = [
[[package]] [[package]]
name = "webrtc-media" name = "webrtc-media"
version = "0.10.0" 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 = [ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
@@ -9720,7 +9721,7 @@ dependencies = [
[[package]] [[package]]
name = "webrtc-sctp" name = "webrtc-sctp"
version = "0.12.0" 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 = [ dependencies = [
"arc-swap", "arc-swap",
"async-trait", "async-trait",
@@ -9737,7 +9738,7 @@ dependencies = [
[[package]] [[package]]
name = "webrtc-srtp" name = "webrtc-srtp"
version = "0.15.0" 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 = [ dependencies = [
"aead", "aead",
"aes", "aes",
@@ -9759,7 +9760,7 @@ dependencies = [
[[package]] [[package]]
name = "webrtc-util" name = "webrtc-util"
version = "0.11.0" 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 = [ dependencies = [
"async-trait", "async-trait",
"bitflags 1.3.2", "bitflags 1.3.2",

View File

@@ -220,7 +220,14 @@ exclude = ["vdi/host"]
libxdo-sys = { path = "libs/libxdo-sys-stub" } libxdo-sys = { path = "libs/libxdo-sys-stub" }
# One branch off upstream v0.13.0, the tag whose crate versions match this stack. # 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 # 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, # 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 # 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 # 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 # 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 # 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 # 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. # 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 # 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. # long the answerer keeps checking a pair that has not answered yet.
webrtc = { 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 = "80d5a20532cf58f5d4d237c437a98ceb85ee40dc" } webrtc-util = { git = "https://github.com/rustdesk-org/webrtc", rev = "49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" }
webrtc-sctp = { git = "https://github.com/rustdesk-org/webrtc", rev = "80d5a20532cf58f5d4d237c437a98ceb85ee40dc" } webrtc-sctp = { git = "https://github.com/rustdesk-org/webrtc", rev = "49c89bd8d30e6e62e4c1a96cee38fdc11f79ef63" }
[package.metadata.winres] [package.metadata.winres]
LegalCopyright = "Copyright © 2026 Purslane Tech Pte. Ltd. All rights reserved." LegalCopyright = "Copyright © 2026 Purslane Tech Pte. Ltd. All rights reserved."

View File

@@ -57,7 +57,7 @@ use hbb_common::{
rand, rand,
rendezvous_proto::*, rendezvous_proto::*,
sha2::{Digest, Sha256}, 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}, sodiumoxide::{base64, crypto::sign},
timeout, timeout,
tokio::{ tokio::{
@@ -490,7 +490,7 @@ impl Client {
// no need to care about multiple rendezvous servers case, since it is acutally not used any more. // no need to care about multiple rendezvous servers case, since it is acutally not used any more.
// Shared state for UDP NAT test result // Shared state for UDP NAT test result
if crate::get_udp_punch_enabled() && !interface.is_force_relay() { 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 udp_port = Arc::new(Mutex::new(0));
let up_cloned = udp_port.clone(); let up_cloned = udp_port.clone();
let socket_cloned = socket.clone(); let socket_cloned = socket.clone();

View File

@@ -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<SocketAddr> { async fn test_bind_ipv6() -> ResultType<SocketAddr> {
use hbb_common::futures::future::FutureExt;
let local_addr = SocketAddr::from(([0u16; 8], 0)); // [::]:0 let local_addr = SocketAddr::from(([0u16; 8], 0)); // [::]:0
let socket = UdpSocket::bind(local_addr).await?; let socket = UdpSocket::bind(local_addr).await?;
// Nothing is sent - `connect` only makes the kernel pick a route and a source address - so any // Nothing is sent - `connect` only makes the kernel pick a route and a source address - so
// resolvable target answers equally and the whole cost is DNS. Race the lookups rather than // the target can be any global address, and given as a number it is: this is awaited on the
// walk them: this is awaited inline on the connection path, not every STUN host publishes a // connection path, and resolving a STUN host's name first was the one thing on it that
// AAAA, and one resolver that hangs must not decide whether this host has v6. // could wait on the network - for as long as the resolver takes, when there is none.
let lookups = hbb_common::webrtc::WebRTCStream::default_stun_servers() socket
.into_iter() .connect(SocketAddr::from((IPV6_ROUTE_PROBE, 53)))
.map(|stun| { .await?;
(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::<SocketAddr, hbb_common::anyhow::Error>(addr)
})
.boxed()
})
.collect::<Vec<_>>();
let (addr, _) = hbb_common::futures::future::select_ok(lookups).await?;
socket.connect(addr).await?;
Ok(socket.local_addr()?) Ok(socket.local_addr()?)
} }
pub async fn test_ipv6() -> Option<tokio::task::JoinHandle<()>> { pub async fn test_ipv6() -> Option<tokio::task::JoinHandle<()>> {
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 { match test_bind_ipv6().await {
Ok(mut addr) => { Ok(mut addr) => {
@@ -2623,8 +2620,8 @@ pub async fn test_ipv6() -> Option<tokio::task::JoinHandle<()>> {
.map(|stun| stun_ipv6_test(stun).boxed()) .map(|stun| stun_ipv6_test(stun).boxed())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match select_ok(tests).await { match hbb_common::timeout(STUN_IPV6_TIMEOUT_MS, select_ok(tests)).await {
Ok(res) => { Ok(Ok(res)) => {
let mut addr = res.0 .0; let mut addr = res.0 .0;
addr.set_port(0); // Set port to 0 to avoid conflicts addr.set_port(0); // Set port to 0 to avoid conflicts
PUBLIC_IPV6_ADDR.lock().unwrap().0 = Some(addr); PUBLIC_IPV6_ADDR.lock().unwrap().0 = Some(addr);
@@ -2634,9 +2631,12 @@ pub async fn test_ipv6() -> Option<tokio::task::JoinHandle<()>> {
addr addr
); );
} }
Err(e) => { Ok(Err(e)) => {
log::error!("Failed to get public IPv6 address: {}", 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. // non-WebRTC handshakes.
assert_eq!(decode_id_pk(&signed, &pk).unwrap(), (id, their_pk)); 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());
}
} }