rmw-upnp 0.1.7

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