socket_client: only the controller's NAT test takes the resolver's address

`new_direct_udp_for` is back on `test_target`. The punch reply the
controlled side sends with it goes to the address it picks, and that reply
lost is a device online and unreachable - so it keeps the handshake that
proves the address, as the registration does. The controller's NAT test,
once per connection, is what could not afford the proof, a round trip
right before the connection it then opens to the same host; it alone takes
the resolver's preferred address, as `new_direct_udp_for_unverified`, and
a wrong one there costs that connection its UDP punch and nothing more.

That lookup is the first on the controller's connection path and the one
not under CONNECT_TIMEOUT, so it gets a second: a resolver that hangs
would otherwise hold the connection for its whole retry schedule before
`connect_tcp` even starts, and what a slower lookup loses is, again, only
the NAT test.

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-19 00:18:16 +08:00
co-authored by Claude Fable 5.1
parent 0bc83361d6
commit 7a5ad5252e
+17 -4
View File
@@ -204,8 +204,8 @@ pub fn ipv4_to_ipv6(addr: String, ipv4: bool) -> String {
}
/// 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.
/// synthesized IPv6 leads on a v6-only host. Preferred, not proven reachable: what `test_target`
/// falls back to, and what the controller's NAT test takes as is to spare itself the proof.
async fn resolve_target(target: &str) -> ResultType<SocketAddr> {
tokio::net::lookup_host(target)
.await?
@@ -215,7 +215,7 @@ async fn resolve_target(target: &str) -> ResultType<SocketAddr> {
/// 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.
/// would not do. For the rendezvous registration and the punch reply, which live on what it 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() {
@@ -227,7 +227,20 @@ async fn test_target(target: &str) -> ResultType<SocketAddr> {
#[inline]
pub async fn new_direct_udp_for(target: &str) -> ResultType<(Arc<UdpSocket>, SocketAddr)> {
let peer_addr = resolve_target(target).await?;
let peer_addr = test_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))
}
/// `new_direct_udp_for` on the resolver's preferred address, with no handshake to prove it and a
/// second at most for the lookup: for the controller's NAT test, once per connection, where the
/// proof cost a round trip right before the connection it then opens to the same host.
#[inline]
pub async fn new_direct_udp_for_unverified(
target: &str,
) -> ResultType<(Arc<UdpSocket>, SocketAddr)> {
let peer_addr = super::timeout(1000, 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))