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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns
This commit is contained in:
rustdesk
2026-09-18 16:05:50 +08:00
co-authored by Claude Fable 5.1
parent f688d4116c
commit 0bc83361d6
+17 -5
View File
@@ -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<SocketAddr> {
/// 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<SocketAddr> {
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<SocketAddr> {
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<UdpSocket>, 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))