use std::{
collections::HashSet,
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
time::{Duration, Instant},
};
use log::debug;
use crate::{
multiaddr::Multiaddr,
utils::{is_reachable, multiaddr_to_socketaddr},
};
#[cfg(not(windows))]
use self::unix::get_local_net_state;
#[cfg(windows)]
use self::windows::get_local_net_state;
use std::collections::HashMap;
#[cfg(not(windows))]
mod unix;
#[cfg(windows)]
mod windows;
#[derive(Copy, Clone, Debug)]
pub struct Network {
address: Ipv4Addr,
net_mask: Ipv4Addr,
}
pub struct IgdClient {
gateway: igd::Gateway,
state: Network,
only_leases_support: bool,
succeeded: HashSet<SocketAddr>,
leases: HashMap<SocketAddr, Option<Instant>>,
}
impl IgdClient {
pub fn new() -> Option<Self> {
let gateway = match igd::search_gateway(Default::default()) {
Err(err) => {
debug!("get gateway error: {:?}", err);
return None;
}
Ok(gateway) => {
if is_reachable((gateway.addr.ip()).into()) {
return None;
}
match gateway.get_external_ip() {
Ok(ip) => {
if is_reachable(ip.into()) {
gateway
} else {
return None;
}
}
Err(err) => {
debug!("get external ip error: {:?}", err);
return None;
}
}
}
};
let state = get_local_net_state().ok().and_then(|networks| {
networks.into_iter().find(|network| {
if let std::net::IpAddr::V4(ip) = gateway.addr.ip() {
in_same_subnet(network.address, ip, network.net_mask)
} else {
false
}
})
})?;
Some(IgdClient {
gateway,
state,
only_leases_support: false,
succeeded: HashSet::default(),
leases: HashMap::default(),
})
}
pub fn register(&mut self, address: &Multiaddr) {
if let Some(addr) = multiaddr_to_socketaddr(address) {
if self.succeeded.contains(&addr) || self.leases.contains_key(&addr) {
return;
}
if addr.ip().is_loopback() || addr.ip().is_multicast() {
return;
}
if self.only_leases_support {
self.leases.insert(addr, None);
self.process_only_leases_support();
} else {
match self.gateway.add_port(
igd::PortMappingProtocol::TCP,
addr.port(),
SocketAddrV4::new(self.state.address, addr.port()).into(),
0, "p2p",
) {
Err(err) => match err {
igd::AddPortError::OnlyPermanentLeasesSupported => {
self.leases.insert(addr, None);
self.process_only_leases_support();
self.only_leases_support = true;
}
err => debug!("register upnp error: {:?}", err),
},
Ok(_) => {
self.succeeded.insert(addr);
}
}
}
}
}
pub fn remove(&mut self, address: &Multiaddr) {
if let Some(addr) = multiaddr_to_socketaddr(address) {
if self.succeeded.remove(&addr) || self.leases.remove(&addr).is_some() {
let _ignore = self
.gateway
.remove_port(igd::PortMappingProtocol::TCP, addr.port());
}
}
}
pub fn process_only_leases_support(&mut self) {
for (addr, interval) in self.leases.iter_mut() {
let register = interval
.map(|inner| {
Instant::now().saturating_duration_since(inner) > Duration::from_secs(40)
})
.unwrap_or(true);
if register {
let _ignore = self.gateway.add_port(
igd::PortMappingProtocol::TCP,
addr.port(),
SocketAddrV4::new(self.state.address, addr.port()).into(),
60, "p2p",
);
*interval = Some(Instant::now())
}
}
}
pub fn clear(&mut self) {
for addr in self
.succeeded
.drain()
.chain(self.leases.drain().map(|item| item.0))
{
let _ignore = self
.gateway
.remove_port(igd::PortMappingProtocol::TCP, addr.port());
}
}
}
impl Drop for IgdClient {
fn drop(&mut self) {
self.clear();
}
}
fn in_same_subnet(addr1: Ipv4Addr, addr2: Ipv4Addr, subnet_mask: Ipv4Addr) -> bool {
addr1
.octets()
.iter()
.zip(subnet_mask.octets().iter())
.map(|(o1, o2)| o1 & o2)
.eq(addr2
.octets()
.iter()
.zip(subnet_mask.octets().iter())
.map(|(o1, o2)| o1 & o2))
}
#[cfg(test)]
mod test {
use super::in_same_subnet;
#[test]
fn test_is_same_subnet() {
vec![
("202.194.128.9", "202.194.128.14", "255.255.255.0"),
("220.230.1.1", "220.255.230.1", "255.192.0.0"),
("100.0.0.1", "100.0.0.100", "255.255.255.128"),
("100.200.2.100", "100.200.14.230", "255.255.240.0"),
("10.50.100.100", "10.50.200.70", "255.255.0.0"),
]
.into_iter()
.map(|(a, b, c)| (a.parse().unwrap(), b.parse().unwrap(), c.parse().unwrap()))
.for_each(|(a, b, c)| assert!(in_same_subnet(a, b, c)));
vec![
("202.194.128.9", "202.193.128.14", "255.255.255.0"),
("220.230.1.1", "220.0.230.1", "255.192.0.0"),
("100.0.0.1", "100.200.0.100", "255.255.255.128"),
("100.200.2.100", "100.100.14.230", "255.255.240.0"),
("10.50.100.100", "10.0.0.70", "255.255.0.0"),
]
.into_iter()
.map(|(a, b, c)| (a.parse().unwrap(), b.parse().unwrap(), c.parse().unwrap()))
.for_each(|(a, b, c)| assert!(!in_same_subnet(a, b, c)));
}
}