use anyhow::Result;
use async_std::task::sleep;
use igd::aio::search_gateway;
use igd::AddPortError::{self, PortInUse};
use std::net::TcpStream;
use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
use std::time::Duration;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("not ipv4")]
NotIpv4,
}
pub async fn daemon(
name: &str,
port: u16,
duration: u32,
resolve: Box<dyn Fn(SocketAddrV4, u16, Ipv4Addr) + Send>,
reject: Box<dyn Fn(&dyn std::error::Error) + Send>,
) {
let mut local_ip = Ipv4Addr::UNSPECIFIED;
let mut pre_gateway = SocketAddrV4::new(local_ip, 0);
let seconds = Duration::from_secs(duration.into());
let duration = duration + 9;
let mut ext_port = port;
loop {
match upnp(name, port, ext_port, duration).await {
Ok((gateway, ip)) => {
if ip != local_ip || gateway != pre_gateway {
local_ip = ip;
pre_gateway = gateway;
resolve(gateway, ext_port, ip);
}
}
Err(err) => {
let err = err.root_cause();
if let Some(PortInUse) = err.downcast_ref::<AddPortError>() {
if ext_port == 65535 {
ext_port = 1025;
} else {
ext_port += 1;
}
continue;
} else {
reject(err);
}
}
}
sleep(seconds).await;
}
}
pub async fn upnp(
name: &str,
port: u16,
ext_port: u16,
duration: u32,
) -> Result<(SocketAddrV4, Ipv4Addr)> {
let gateway = search_gateway(Default::default()).await?;
let gateway_addr = gateway.addr;
let stream = TcpStream::connect(gateway_addr)?;
let addr = stream.local_addr()?;
drop(stream);
if let IpAddr::V4(ip) = addr.ip() {
gateway
.add_port(
igd::PortMappingProtocol::UDP,
ext_port,
SocketAddrV4::new(ip, port),
duration,
name,
)
.await?;
Ok((gateway_addr, ip))
} else {
Err(Error::NotIpv4.into())
}
}