rmw_upnp/
lib.rs

1// #![feature(const_socketaddr)]
2
3use std::{
4  net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpStream},
5  time::Duration,
6};
7
8use anyhow::{Error, Result};
9use async_std::task::sleep;
10use igd::{aio::search_gateway, AddPortError::PortInUse};
11use log::info;
12use thiserror::Error;
13
14#[derive(Error, Debug)]
15pub enum UpnpError {
16  #[error("upnp not support ipv6")]
17  Ipv6,
18}
19
20pub async fn upnp(name: &str, port: u16, duration: u32) -> Result<(SocketAddrV4, Ipv4Addr)> {
21  let gateway = search_gateway(Default::default()).await?;
22  let gateway_addr = gateway.addr;
23  let stream = TcpStream::connect(gateway_addr)?;
24  let addr = stream.local_addr()?;
25  let ip = addr.ip();
26  drop(stream);
27
28  if let IpAddr::V4(ip) = ip {
29    let mut retry = true;
30    loop {
31      match gateway
32        .add_port(
33          igd::PortMappingProtocol::UDP,
34          port,
35          SocketAddrV4::new(ip, port),
36          duration,
37          name,
38        )
39        .await
40      {
41        Err(err) => {
42          if let PortInUse = err {
43            if retry {
44              retry = false;
45              match gateway
46                .remove_port(igd::PortMappingProtocol::UDP, port)
47                .await
48              {
49                Err(err) => {
50                  info!("upnp remove port {} error {}", port, err);
51                }
52                Ok(_) => {
53                  continue;
54                }
55              }
56            }
57          }
58          //info!("upnp {} > {}", gateway_addr, err);
59          return Err(err.into());
60        }
61        Ok(_) => {
62          return Ok((gateway_addr, ip));
63        }
64      }
65    }
66  } else {
67    return Err(UpnpError::Ipv6.into());
68  }
69}
70
71pub trait Watch {
72  fn ok(&self, addr: SocketAddrV4, gateway: SocketAddrV4);
73  fn err(&self, err: Error);
74}
75
76pub struct Log;
77
78impl Watch for Log {
79  fn ok(&self, addr: SocketAddrV4, gateway: SocketAddrV4) {
80    info!("upnp {} gateway {}", addr, gateway);
81  }
82  fn err(&self, err: Error) {
83    info!("upnp err : {}", err);
84  }
85}
86
87pub async fn daemon(name: &str, port: u16, sleep_seconds: u32) {
88  daemon_watch(name, port, sleep_seconds, Log).await;
89}
90
91pub async fn daemon_watch(name: &str, port: u16, sleep_seconds: u32, watch: impl Watch) {
92  let mut local_ip = Ipv4Addr::UNSPECIFIED;
93  let mut pre_gateway = SocketAddrV4::new(local_ip, 0);
94  let seconds = Duration::from_secs(sleep_seconds.into());
95  let duration = sleep_seconds + 60;
96  loop {
97    match upnp(name, port, duration).await {
98      Ok((gateway, ip)) => {
99        if ip != local_ip || gateway != pre_gateway {
100          local_ip = ip;
101          pre_gateway = gateway;
102          watch.ok(SocketAddrV4::new(ip, port), gateway);
103        }
104      }
105      Err(err) => {
106        local_ip = Ipv4Addr::UNSPECIFIED;
107        watch.err(err);
108      }
109    }
110    sleep(seconds).await;
111  }
112}