forked from Keelrleek/SoulStream
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.
58 lines
2.0 KiB
Rust
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,
|
|
}
|
|
}
|