fix(network): SSDP discovery no longer parks a tokio worker for 3s

check_upnp_available uses a blocking std UdpSocket and, on a network
with no UPnP gateway (the normal case right after a node moves), runs
out its full 3s read timeout. Inline on the runtime that blocked a
worker on every call, from four call sites. Same class as the OpenWrt
SSH stall (e282c059), smaller blast radius — move it to spawn_blocking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-15 10:22:44 -04:00
co-authored by Claude Fable 5
parent e282c05911
commit 6e89acced7
+42 -31
View File
@@ -97,42 +97,53 @@ async fn get_wan_ip() -> Option<String> {
} }
/// Check if UPnP is available by attempting SSDP discovery. /// Check if UPnP is available by attempting SSDP discovery.
///
/// The socket is a blocking `std::net::UdpSocket`, and on a network with no
/// UPnP gateway — the normal case right after a node moves — the recv runs
/// out its full read timeout. Inline on the runtime that parked a tokio
/// worker for those 3s on every call, the same failure shape (smaller blast
/// radius) as the OpenWrt SSH connect that stalled the API on framework-pt.
/// Keep it on the blocking pool.
async fn check_upnp_available() -> bool { async fn check_upnp_available() -> bool {
use std::net::UdpSocket; tokio::task::spawn_blocking(|| {
use std::net::UdpSocket;
let ssdp_request = "M-SEARCH * HTTP/1.1\r\n\ let ssdp_request = "M-SEARCH * HTTP/1.1\r\n\
HOST: 239.255.255.250:1900\r\n\ HOST: 239.255.255.250:1900\r\n\
MAN: \"ssdp:discover\"\r\n\ MAN: \"ssdp:discover\"\r\n\
MX: 2\r\n\ MX: 2\r\n\
ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n\r\n"; ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n\r\n";
let socket = match UdpSocket::bind("0.0.0.0:0") { let socket = match UdpSocket::bind("0.0.0.0:0") {
Ok(s) => s, Ok(s) => s,
Err(_) => return false, Err(_) => return false,
}; };
if socket if socket
.set_read_timeout(Some(std::time::Duration::from_secs(3))) .set_read_timeout(Some(std::time::Duration::from_secs(3)))
.is_err() .is_err()
{ {
return false; return false;
}
if socket
.send_to(ssdp_request.as_bytes(), "239.255.255.250:1900")
.is_err()
{
return false;
}
let mut buf = [0u8; 2048];
match socket.recv_from(&mut buf) {
Ok((len, _)) => {
let response = String::from_utf8_lossy(&buf[..len]);
response.contains("InternetGatewayDevice") || response.contains("200 OK")
} }
Err(_) => false,
} if socket
.send_to(ssdp_request.as_bytes(), "239.255.255.250:1900")
.is_err()
{
return false;
}
let mut buf = [0u8; 2048];
match socket.recv_from(&mut buf) {
Ok((len, _)) => {
let response = String::from_utf8_lossy(&buf[..len]);
response.contains("InternetGatewayDevice") || response.contains("200 OK")
}
Err(_) => false,
}
})
.await
.unwrap_or(false)
} }
/// Add a port forward (stored locally; actual UPnP mapping done on request). /// Add a port forward (stored locally; actual UPnP mapping done on request).