use anyhow::Result;
use async_std::task::{sleep, spawn};
use igd::aio::search_gateway;
use igd::AddPortError::{self, PortInUse};
use std::net::TcpStream;
use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
use std::sync::mpsc::{channel, Receiver};
use std::time::Duration;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("not ipv4")]
NotIpv4,
}
pub fn daemon<'a>(
name: String,
port: u16,
duration: u32,
) -> Receiver<Result<(u16, Ipv4Addr, SocketAddrV4), String>> {
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;
let (s, r) = channel();
spawn(async move {
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;
let _ = s.send(Ok((ext_port, ip, gateway)));
}
}
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 {
local_ip = Ipv4Addr::UNSPECIFIED;
let _ = s.send(Err(err.to_string()));
}
}
}
sleep(seconds).await;
}
});
r
}
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())
}
}