Files
SoulStream/crates/soulseek-client/src/upnp.rs
T
KeelrLeek a667b4b28e Baseline: full tree snapshot after git repository loss
The previous .git directory was found gutted (only info/ remained), so
history is unrecoverable locally. This commit restores version control
with the tree exactly as it stands: the working app, the preview-tracks
launcher on the artist hero, the scrollable-track-previews design bundle
(docs + approved mockups), and the Phase 0 validation artifacts.
2026-07-22 17:57:00 -05:00

58 lines
2.0 KiB
Rust

//! Best-effort UPnP port mapping so firewalled peers can reach our
//! listener directly. Failure is non-fatal — Soulseek's indirect
//! (server-relayed) connection path still works without it.
use std::net::SocketAddrV4;
use std::time::Duration;
/// Try to map `port` (TCP) on the local gateway to ourselves. Returns the
/// external address on success. Runs on a blocking thread.
pub(crate) async fn try_map_port(port: u16) {
let result = tokio::task::spawn_blocking(move || map_blocking(port)).await;
match result {
Ok(Ok(external)) => log::info!("UPnP: mapped port {port} (external {external})"),
Ok(Err(e)) => log::info!("UPnP unavailable ({e}); relying on indirect connections"),
Err(_) => log::debug!("UPnP task panicked"),
}
}
fn map_blocking(port: u16) -> Result<SocketAddrV4, String> {
use igd_next::{search_gateway, PortMappingProtocol, SearchOptions};
let gateway = search_gateway(SearchOptions {
timeout: Some(Duration::from_secs(5)),
..Default::default()
})
.map_err(|e| e.to_string())?;
// Find our LAN address facing the gateway.
let local_ip = local_ipv4().ok_or_else(|| "no local IPv4".to_string())?;
let local = SocketAddrV4::new(local_ip, port);
gateway
.add_port(
PortMappingProtocol::TCP,
port,
std::net::SocketAddr::V4(local),
0, // lease 0 = indefinite (until router reboots)
"SoulStream",
)
.map_err(|e| e.to_string())?;
match gateway.get_external_ip() {
Ok(std::net::IpAddr::V4(ip)) => Ok(SocketAddrV4::new(ip, port)),
_ => Ok(local),
}
}
/// Discover our LAN IPv4 by opening a UDP socket toward a public address
/// (no packets are actually sent).
fn local_ipv4() -> Option<std::net::Ipv4Addr> {
let sock = std::net::UdpSocket::bind("0.0.0.0:0").ok()?;
sock.connect("8.8.8.8:80").ok()?;
match sock.local_addr().ok()?.ip() {
std::net::IpAddr::V4(ip) => Some(ip),
_ => None,
}
}