fix(net): bind all endpoints to [::]:0 for dual-stack IPv4+IPv6
Every QUIC endpoint was bound to 0.0.0.0:0 (IPv4-only). This silently killed ALL IPv6 host candidates: the Dialer couldn't send packets to [2a0d:...] addresses (wrong address family on the socket), and the Acceptor couldn't receive incoming IPv6 QUIC handshakes. The IPv6 candidates were gathered and advertised in DirectCallOffer/Answer but were completely non-functional. On same-LAN with dual-stack (which both test phones have), this meant: - JoinSet fanned out 3+ candidates (2× IPv6 + 1× IPv4) - IPv6 dials failed silently or timed out - IPv4 dial worked but competed with failed IPv6 for JoinSet attention - Sometimes the JoinSet returned an IPv6 failure before the IPv4 success, causing unnecessary fallback to relay Fix: bind to [::]:0 (IPv6 any) instead of 0.0.0.0:0. On dual-stack systems (Linux/Android default), [::]:0 creates a socket that handles BOTH: - IPv6 natively (global unicast, ULA) - IPv4 via v4-mapped addresses (::ffff:172.16.81.x) One socket, both protocols. All 7 bind sites updated: - register_signal (signal endpoint) - do_register_signal - ping_relay - probe_reflect_addr (fresh endpoint fallback) - dual_path::race (A-role fresh, D-role fresh, relay fresh) With this fix, same-LAN P2P should prefer the IPv6 path (no NAT, direct routing, lower latency) and fall through to IPv4 if IPv6 fails — relay is the last resort after ALL candidates are exhausted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -194,7 +194,7 @@ fn get_call_debug_logs() -> bool {
|
||||
async fn ping_relay(relay: String) -> Result<PingResult, String> {
|
||||
let addr: std::net::SocketAddr = relay.parse().map_err(|e| format!("bad address: {e}"))?;
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
let bind: std::net::SocketAddr = "0.0.0.0:0".parse().unwrap();
|
||||
let bind: std::net::SocketAddr = "[::]:0".parse().unwrap();
|
||||
let endpoint = wzp_transport::create_endpoint(bind, None).map_err(|e| format!("{e}"))?;
|
||||
let client_cfg = wzp_transport::client_config();
|
||||
|
||||
@@ -914,7 +914,13 @@ fn do_register_signal(
|
||||
// endpoints, which made MikroTik look symmetric and broke direct
|
||||
// P2P because the advertised reflex port was not the listening
|
||||
// port.
|
||||
let bind: std::net::SocketAddr = "0.0.0.0:0".parse().unwrap();
|
||||
// [::]:0 = dual-stack socket — handles IPv4 (via ::ffff:x.x.x.x
|
||||
// mapped addresses) AND native IPv6 on one socket. Critical for
|
||||
// Phase 5.5 ICE host candidates: without dual-stack, the IPv6
|
||||
// candidates advertised in DirectCallOffer/Answer are dead on
|
||||
// arrival — the Dialer can't send to them and the Acceptor can't
|
||||
// receive from them.
|
||||
let bind: std::net::SocketAddr = "[::]:0".parse().unwrap();
|
||||
let (server_cfg, _cert_der) = wzp_transport::server_config();
|
||||
let endpoint = wzp_transport::create_endpoint(bind, Some(server_cfg))
|
||||
.map_err(|e| format!("{e}"))?;
|
||||
|
||||
Reference in New Issue
Block a user