feat(net): auto-map the listen port via UPnP-IGD and NAT-PMP
Behind a home router the client is unreachable for incoming peer connections, so browsing/downloading firewalled peers (and the server-brokered connect-back) times out. Add a best-effort port mapper to the binary crate that, when listening is enabled, makes us reachable: - NAT-PMP (RFC 6886) hand-rolled on std::net::UdpSocket, zero deps, with an injectable gateway address so it is fully testable. - UPnP-IGD via the blocking igd-next crate as the primary path. - A background PortMapper that discovers off the main thread (never blocks startup), renews at half the lease, and removes the mapping on drop. Any failure is non-fatal and logged. - Maps the EXACT listen port only (we advertise it verbatim via SetWaitPort, so a different external port would be unreachable) and warns the user to forward manually otherwise. Wired into the TUI, search, and CLI browse (the P2P modes); chat/rooms/ messaging are server-mediated and skip it. e2e-tested against an in-process mock NAT-PMP gateway: the map request bytes, response parsing, result-code/timeout handling, and the full spawn -> map -> drop -> unmap service lifecycle.
This commit is contained in:
@@ -2,6 +2,7 @@ mod cli;
|
||||
mod config;
|
||||
mod directories;
|
||||
mod models;
|
||||
mod port_mapping;
|
||||
mod ui;
|
||||
|
||||
use clap::Parser;
|
||||
@@ -160,6 +161,12 @@ fn main() -> Result<()> {
|
||||
shared_directory,
|
||||
};
|
||||
|
||||
// Best-effort: make ourselves reachable behind a home router so
|
||||
// firewalled peers can connect back. Kept alive for the session.
|
||||
let _port_mapper = settings
|
||||
.enable_listen
|
||||
.then(|| port_mapping::PortMapper::spawn(settings.listen_port));
|
||||
|
||||
let mut client = Client::with_settings(settings);
|
||||
client.connect().map_err(|e| {
|
||||
color_eyre::eyre::eyre!("Failed to connect: {}", e)
|
||||
@@ -193,6 +200,9 @@ fn main() -> Result<()> {
|
||||
fn browse_user(settings: &ClientSettings, target: &str) -> Result<()> {
|
||||
use std::time::Instant;
|
||||
|
||||
let _port_mapper = settings
|
||||
.enable_listen
|
||||
.then(|| port_mapping::PortMapper::spawn(settings.listen_port));
|
||||
let mut client = Client::with_settings(settings.clone());
|
||||
client
|
||||
.connect()
|
||||
@@ -378,6 +388,10 @@ fn search_and_download(config: SearchConfig) -> Result<()> {
|
||||
shared_directory: config.shared_directory.clone(),
|
||||
};
|
||||
|
||||
let _port_mapper = settings
|
||||
.enable_listen
|
||||
.then(|| port_mapping::PortMapper::spawn(settings.listen_port));
|
||||
|
||||
let mut client = Client::with_settings(settings);
|
||||
client
|
||||
.connect()
|
||||
|
||||
Reference in New Issue
Block a user