mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-20 19:31:00 +03:00
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
This commit is contained in:
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user