From 0bc83361d6f5e9730a3208e778880cb63103ca9c Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 18 Sep 2026 16:05:50 +0800 Subject: [PATCH] socket_client: the rendezvous registration keeps its proven address f688d41 replaced the TCP handshake in `test_target` with the resolver's first answer for every caller. The two are the same when an IPv6 route is missing, or leads to nothing: a v6-only host gets the synthesized address first either way, and the handshake sat out its second on a black hole and fell back to that same first answer. They differ when the server refuses IPv6 outright - an AAAA published for a daemon bound to 0.0.0.0, a firewall that rejects - where the handshake failed fast and moved on to the A record, and the resolver's order stays on the AAAA. That matters on one path. `new_udp_for` and `rebind_udp_for` choose the address the rendezvous registration lives on, and `start_udp` never leaves UDP for TCP nor asks for a second candidate: a rebind after `MAX_FAILS2` calls the same function and gets the same address. A self-hosted server with such an AAAA would never see the controlled side register. Those two keep the handshake, one round trip per server connection, under the old name and its old behaviour. `new_direct_udp_for`, per connection and against servers whose records are ours, takes the resolver's answer as `resolve_target`: the round trip it was spared stays spared. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns --- src/socket_client.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/socket_client.rs b/src/socket_client.rs index 2cb92cc52..e18062521 100644 --- a/src/socket_client.rs +++ b/src/socket_client.rs @@ -203,19 +203,31 @@ pub fn ipv4_to_ipv6(addr: String, ipv4: bool) -> String { addr } -/// The address of `target` this machine sends to: the resolver's first, ordered as RFC 6724 has -/// it with a route looked up for each, so NAT64's synthesized IPv6 comes first on a v6-only host. -/// A handshake used to ask the server the same, a round trip a connection and 1 s past a dead v6. -async fn test_target(target: &str) -> ResultType { +/// The resolver's preferred address for `target`: first in getaddrinfo's RFC 6724 order, so NAT64's +/// synthesized IPv6 leads on a v6-only host. Preferred, not proven reachable: the per-connection +/// UDP paths take it as is, and spare themselves the round trip that proving it costs. +async fn resolve_target(target: &str) -> ResultType { tokio::net::lookup_host(target) .await? .next() .context(format!("Failed to look up host for {target}")) } +/// The address of `target` a TCP connection reaches, the resolver's candidates tried in order until +/// one answers: an AAAA the server refuses is passed over for its A record, which the order alone +/// would not do. For the rendezvous registration, which lives on what this picks. +async fn test_target(target: &str) -> ResultType { + if let Ok(Ok(s)) = super::timeout(1000, tokio::net::TcpStream::connect(target)).await { + if let Ok(addr) = s.peer_addr() { + return Ok(addr); + } + } + resolve_target(target).await +} + #[inline] pub async fn new_direct_udp_for(target: &str) -> ResultType<(Arc, SocketAddr)> { - let peer_addr = test_target(target).await?; + let peer_addr = resolve_target(target).await?; let local_addr = Config::get_any_listen_addr(peer_addr.is_ipv4()); let socket = UdpSocket::bind(local_addr).await?; Ok((Arc::new(socket), peer_addr))