use futures::Stream;
use tokio::sync::{mpsc, oneshot};
use std::{
net::Ipv4Addr,
pin::Pin,
task::{Context, Poll},
};
mod nat_pmp;
mod upnp;
const LOG_TARGET: &str = "emissary-util::port-mapper";
#[derive(Debug)]
pub struct PortMapperConfig {
pub nat_pmp: bool,
pub upnp: bool,
pub name: String,
}
impl Default for PortMapperConfig {
fn default() -> Self {
Self {
nat_pmp: true,
upnp: true,
name: String::from("emissary"),
}
}
}
pub struct PortMapper {
address_rx: Option<mpsc::Receiver<Ipv4Addr>>,
shutdown_tx: Option<oneshot::Sender<oneshot::Sender<()>>>,
}
impl PortMapper {
pub fn new(
config: Option<PortMapperConfig>,
ntcp2_port: Option<u16>,
ssu2_port: Option<u16>,
) -> Self {
let (address_tx, address_rx) = mpsc::channel(64);
let (shutdown_tx, shutdown_rx) = oneshot::channel();
let config = match config {
None
| Some(PortMapperConfig {
upnp: false,
nat_pmp: false,
..
}) => {
tracing::debug!(
target: LOG_TARGET,
"port forwarding disabled",
);
return Self {
address_rx: Some(address_rx),
shutdown_tx: Some(shutdown_tx),
};
}
Some(config) => config,
};
tracing::info!(
target: LOG_TARGET,
?config,
?ntcp2_port,
?ssu2_port,
"starting port mapper",
);
match config {
PortMapperConfig { nat_pmp: true, .. } => {
tokio::spawn(
nat_pmp::PortMapper::new(
config,
ntcp2_port,
ssu2_port,
address_tx,
shutdown_rx,
)
.run(),
);
}
PortMapperConfig { nat_pmp: false, .. } => {
tokio::spawn(
upnp::PortMapper::new(config, ntcp2_port, ssu2_port, address_tx, shutdown_rx)
.run(),
);
}
}
Self {
address_rx: Some(address_rx),
shutdown_tx: Some(shutdown_tx),
}
}
pub async fn shutdown(&mut self) {
if let Some(tx) = self.shutdown_tx.take() {
let (recv_tx, recv_rx) = oneshot::channel();
let _ = tx.send(recv_tx);
let _ = recv_rx.await;
}
}
}
impl Stream for PortMapper {
type Item = Ipv4Addr;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.address_rx.as_mut() {
None => Poll::Pending,
Some(rx) => match futures::ready!(rx.poll_recv(cx)) {
None => {
self.address_rx = None;
Poll::Pending
}
Some(value) => Poll::Ready(Some(value)),
},
}
}
}