use std::net::{IpAddr, SocketAddr, SocketAddrV4};
extern crate igd;
use igd::aio::search_gateway;
use igd::{PortMappingProtocol, SearchOptions};
use dsf_core::types::*;
#[derive(Clone, Debug)]
pub enum UpnpCommand {
Register(SocketAddr, Option<u16>),
Deregister(IpAddr, u16),
}
#[derive(Clone, Debug)]
pub enum UpnpUpdate {
Empty,
Registered(SocketAddr),
Discovered(Vec<SocketAddr>),
Deregistered,
}
#[derive(Clone, Debug, PartialEq)]
pub enum UpnpError {
NoGateway,
NoLocalIp,
NoExternalIp,
RegisterFailed,
DeregisterFailed,
UnsupportedAddress,
}
pub struct UpnpPlugin {
id: Id,
}
impl UpnpPlugin {
pub fn new(id: Id) -> Self {
Self { id }
}
pub async fn register(
&mut self,
name: &str,
local_addr: SocketAddr,
ext_port: Option<u16>,
) -> Result<(), UpnpError> {
info!("registering local address: {}", local_addr);
let local_addr = match local_addr {
SocketAddr::V4(v4) => v4,
_ => {
warn!("IPv6 addresses not supported");
return Err(UpnpError::UnsupportedAddress);
}
};
let mut options = SearchOptions::default();
options.bind_addr = SocketAddr::V4(local_addr);
let gw = search_gateway(options)
.await
.map_err(|_e| UpnpError::NoGateway)?;
let external_ip = gw
.get_external_ip()
.await
.map_err(|_e| UpnpError::NoExternalIp)?;
info!("discovered public IP: {}", external_ip);
let port = match ext_port {
Some(port) => gw
.add_port(PortMappingProtocol::UDP, port, local_addr, 3600, &name)
.await
.map(|_| port)
.map_err(|_e| UpnpError::RegisterFailed)?,
None => gw
.add_any_port(PortMappingProtocol::UDP, local_addr, 3600, &name)
.await
.map_err(|_e| UpnpError::RegisterFailed)?,
};
info!(
"Registration OK (service: {} bind address: {} public address: {}:{}",
name, local_addr, external_ip, port
);
let _r = SocketAddr::V4(SocketAddrV4::new(external_ip, port));
Ok(())
}
pub async fn deregister(local_addr: IpAddr, ext_port: u16) -> Result<(), UpnpError> {
let mut options = SearchOptions::default();
options.bind_addr = SocketAddr::new(local_addr, 0);
let gw = search_gateway(options)
.await
.map_err(|_e| UpnpError::NoGateway)?;
if let Err(e) = gw.remove_port(PortMappingProtocol::UDP, ext_port).await {
warn!("[UPNP] Failed to de-register port: {}", e);
return Err(UpnpError::DeregisterFailed);
}
info!("[UPNP] De-registration OK");
Ok(())
}
}