1use 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 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}