use std::net::SocketAddr;
use std::sync::Arc;
use futures::channel::mpsc;
pub use nym_ip_packet_requests::IpPair;
use nym_sdk::ipr_wrapper::IpMixStream;
use nym_smol_core::{ChannelDevice, Stack, StackConfig};
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tracing::info;
use crate::bridge::{BridgeShutdownHandle, NymIprBridge};
use crate::SmolmixError;
pub use nym_sdk::mixnet::Recipient;
pub use nym_smol_core::{TcpStream, UdpSocket};
struct ShutdownState {
bridge_shutdown: BridgeShutdownHandle,
bridge_handle: JoinHandle<Result<(), SmolmixError>>,
}
struct TunnelInner {
stack: Stack,
allocated_ips: IpPair,
shutdown: Mutex<Option<ShutdownState>>,
}
#[derive(Clone)]
pub struct Tunnel {
inner: Arc<TunnelInner>,
}
pub struct TunnelBuilder {
ipr_address: Option<Recipient>,
}
impl TunnelBuilder {
pub fn ipr_address(mut self, addr: Recipient) -> Self {
self.ipr_address = Some(addr);
self
}
pub async fn build(self) -> Result<Tunnel, SmolmixError> {
let stream = match self.ipr_address {
Some(addr) => IpMixStream::new_with_ipr(addr).await?,
None => IpMixStream::new().await?,
};
Tunnel::from_stream(stream).await
}
}
impl Tunnel {
pub fn builder() -> TunnelBuilder {
TunnelBuilder { ipr_address: None }
}
pub async fn new() -> Result<Self, SmolmixError> {
Self::builder().build().await
}
pub async fn new_with_ipr(ipr_address: Recipient) -> Result<Self, SmolmixError> {
Self::builder().ipr_address(ipr_address).build().await
}
pub async fn from_stream(ipr_stream: IpMixStream) -> Result<Self, SmolmixError> {
ipr_stream
.check_connected()
.map_err(|_| SmolmixError::NotConnected)?;
let allocated_ips = *ipr_stream.allocated_ips();
let negotiated_mtu = ipr_stream.negotiated_mtu();
let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
let (incoming_tx, incoming_rx) = mpsc::unbounded();
let (bridge, bridge_shutdown) = NymIprBridge::new(ipr_stream, outgoing_rx, incoming_tx);
let bridge_handle = tokio::spawn(bridge.run());
let device = ChannelDevice::new(
incoming_rx,
outgoing_tx,
negotiated_mtu.map(|mtu| mtu as usize),
);
let stack = Stack::new(device, StackConfig::new(allocated_ips.ipv4));
info!("Tunnel ready, allocated IP: {}", allocated_ips.ipv4);
Ok(Self {
inner: Arc::new(TunnelInner {
stack,
allocated_ips,
shutdown: Mutex::new(Some(ShutdownState {
bridge_shutdown,
bridge_handle,
})),
}),
})
}
pub async fn tcp_connect(&self, addr: SocketAddr) -> Result<TcpStream, SmolmixError> {
Ok(self.inner.stack.tcp_connect(addr).await?)
}
pub async fn udp_socket(&self) -> Result<UdpSocket, SmolmixError> {
Ok(self.inner.stack.udp_socket().await?)
}
pub async fn udp_socket_on(&self, port: u16) -> Result<UdpSocket, SmolmixError> {
Ok(self.inner.stack.udp_socket_on(port).await?)
}
pub fn allocated_ips(&self) -> IpPair {
self.inner.allocated_ips
}
pub async fn shutdown(&self) {
let mut state = self.inner.shutdown.lock().await;
if let Some(s) = state.take() {
info!("Shutting down tunnel");
s.bridge_shutdown.shutdown();
let _ = s.bridge_handle.await;
info!("Tunnel shut down");
}
}
}