rmw-upnp 0.1.6

upnp port map
Documentation
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())
  }
}