#![allow(clippy::module_name_repetitions)]
use std::collections::{HashMap, VecDeque};
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet};
use smoltcp::phy::{self, Device, DeviceCapabilities, Medium};
use smoltcp::socket::tcp;
use smoltcp::time::Instant;
use smoltcp::wire::{HardwareAddress, IpCidr, Ipv4Address, Ipv6Address};
use tokio::sync::mpsc::error::{TryRecvError, TrySendError};
use tokio::sync::{mpsc, oneshot, Mutex, Notify};
use tokio::task::JoinHandle;
use crate::edge::{EdgeError, TcpConn, EDGE_EPHEMERAL_PORT_START};
use crate::tunn_loop::IpChannel;
use crate::OverlayError;
const PACKET_CHANNEL_BOUND: usize = 1024;
const CMD_CHANNEL_BOUND: usize = 64;
const CONN_CHANNEL_BOUND: usize = 128;
const RECV_CHUNK: usize = 16 * 1024;
const MAX_POLL_INTERVAL: Duration = Duration::from_secs(1);
const EGRESS_RETRY_INTERVAL: Duration = Duration::from_millis(5);
struct NetstackDevice {
rx: VecDeque<Vec<u8>>,
tx: VecDeque<Vec<u8>>,
mtu: usize,
}
impl Device for NetstackDevice {
type RxToken<'a> = NsRxToken;
type TxToken<'a> = NsTxToken<'a>;
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
let buffer = self.rx.pop_front()?;
let rx = NsRxToken { buffer };
let tx = NsTxToken { tx: &mut self.tx };
Some((rx, tx))
}
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
Some(NsTxToken { tx: &mut self.tx })
}
fn capabilities(&self) -> DeviceCapabilities {
let mut caps = DeviceCapabilities::default();
caps.medium = Medium::Ip;
caps.max_transmission_unit = self.mtu;
caps
}
}
struct NsRxToken {
buffer: Vec<u8>,
}
impl phy::RxToken for NsRxToken {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&[u8]) -> R,
{
f(&self.buffer)
}
}
struct NsTxToken<'a> {
tx: &'a mut VecDeque<Vec<u8>>,
}
impl phy::TxToken for NsTxToken<'_> {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let mut buffer = vec![0u8; len];
let result = f(&mut buffer);
self.tx.push_back(buffer);
result
}
}
pub struct NetstackChannel {
from_tunnel: mpsc::Sender<Vec<u8>>,
to_tunnel: Mutex<mpsc::Receiver<Vec<u8>>>,
wake: Arc<Notify>,
mtu: u32,
}
#[async_trait::async_trait]
impl IpChannel for NetstackChannel {
async fn recv_packet(&self, buf: &mut [u8]) -> Result<usize, OverlayError> {
let packet = {
let mut rx = self.to_tunnel.lock().await;
rx.recv().await
};
match packet {
Some(pkt) => {
let n = pkt.len().min(buf.len());
buf[..n].copy_from_slice(&pkt[..n]);
Ok(n)
}
None => Err(OverlayError::NetworkConfig(
"netstack egress channel closed".into(),
)),
}
}
async fn send_packet(&self, pkt: &[u8]) -> Result<(), OverlayError> {
match self.from_tunnel.try_send(pkt.to_vec()) {
Ok(()) => {
self.wake.notify_one();
Ok(())
}
Err(TrySendError::Full(_)) => {
tracing::trace!(
len = pkt.len(),
"netstack ingress queue full; dropping packet"
);
Ok(())
}
Err(TrySendError::Closed(_)) => Err(OverlayError::NetworkConfig(
"netstack ingress channel closed".into(),
)),
}
}
fn mtu(&self) -> u32 {
self.mtu
}
}
#[derive(Clone, Debug)]
pub struct NetstackConfig {
pub local_ip: IpAddr,
pub prefix_len: u8,
pub mtu: u32,
pub tcp_buffer_bytes: usize,
pub tcp_timeout: Duration,
}
enum NetstackCmd {
Connect {
dst: SocketAddr,
reply: oneshot::Sender<Result<TcpConn, EdgeError>>,
},
Listen {
port: u16,
reply: oneshot::Sender<Result<mpsc::Receiver<TcpConn>, EdgeError>>,
},
}
pub struct Netstack {
cmd_tx: mpsc::Sender<NetstackCmd>,
wake: Arc<Notify>,
}
impl Netstack {
#[must_use]
pub fn spawn(cfg: NetstackConfig) -> (Self, NetstackChannel, JoinHandle<()>) {
let (from_tunnel_tx, from_tunnel_rx) = mpsc::channel(PACKET_CHANNEL_BOUND);
let (to_tunnel_tx, to_tunnel_rx) = mpsc::channel(PACKET_CHANNEL_BOUND);
let (cmd_tx, cmd_rx) = mpsc::channel(CMD_CHANNEL_BOUND);
let wake = Arc::new(Notify::new());
let mtu = cfg.mtu;
let channel = NetstackChannel {
from_tunnel: from_tunnel_tx,
to_tunnel: Mutex::new(to_tunnel_rx),
wake: Arc::clone(&wake),
mtu,
};
let task = PollTask::new(cfg, from_tunnel_rx, to_tunnel_tx, cmd_rx, Arc::clone(&wake));
let handle = tokio::spawn(task.run());
(Self { cmd_tx, wake }, channel, handle)
}
pub async fn connect(&self, dst: SocketAddr) -> Result<TcpConn, EdgeError> {
let (reply, rx) = oneshot::channel();
self.cmd_tx
.send(NetstackCmd::Connect { dst, reply })
.await
.map_err(|_| EdgeError::Netstack("netstack task stopped".into()))?;
self.wake.notify_one();
match rx.await {
Ok(result) => result,
Err(_) => Err(EdgeError::Netstack("netstack dropped connect reply".into())),
}
}
pub async fn listen(&self, port: u16) -> Result<mpsc::Receiver<TcpConn>, EdgeError> {
let (reply, rx) = oneshot::channel();
self.cmd_tx
.send(NetstackCmd::Listen { port, reply })
.await
.map_err(|_| EdgeError::Netstack("netstack task stopped".into()))?;
self.wake.notify_one();
match rx.await {
Ok(result) => result,
Err(_) => Err(EdgeError::Netstack("netstack dropped listen reply".into())),
}
}
}
struct Conn {
to_host: Option<mpsc::Sender<Vec<u8>>>,
from_host: mpsc::Receiver<Vec<u8>>,
pending: Option<Vec<u8>>,
send_closed: bool,
}
struct Listener {
port: u16,
accept_tx: mpsc::Sender<TcpConn>,
}
struct PollTask {
iface: Interface,
device: NetstackDevice,
sockets: SocketSet<'static>,
conns: HashMap<SocketHandle, Conn>,
listeners: HashMap<SocketHandle, Listener>,
from_tunnel: mpsc::Receiver<Vec<u8>>,
to_tunnel: mpsc::Sender<Vec<u8>>,
cmd_rx: mpsc::Receiver<NetstackCmd>,
wake: Arc<Notify>,
cfg: NetstackConfig,
next_port: u16,
start: std::time::Instant,
ingress_open: bool,
cmd_open: bool,
}
impl PollTask {
fn new(
cfg: NetstackConfig,
from_tunnel: mpsc::Receiver<Vec<u8>>,
to_tunnel: mpsc::Sender<Vec<u8>>,
cmd_rx: mpsc::Receiver<NetstackCmd>,
wake: Arc<Notify>,
) -> Self {
let mtu = usize::try_from(cfg.mtu).unwrap_or(usize::MAX);
let mut device = NetstackDevice {
rx: VecDeque::new(),
tx: VecDeque::new(),
mtu,
};
let start = std::time::Instant::now();
let iface = build_interface(&cfg, &mut device, start);
Self {
iface,
device,
sockets: SocketSet::new(Vec::new()),
conns: HashMap::new(),
listeners: HashMap::new(),
from_tunnel,
to_tunnel,
cmd_rx,
wake,
next_port: EDGE_EPHEMERAL_PORT_START,
start,
cfg,
ingress_open: true,
cmd_open: true,
}
}
async fn run(mut self) {
loop {
self.poll_once();
let sleep = self.compute_sleep();
tokio::select! {
biased;
() = self.wake.notified() => {}
packet = self.from_tunnel.recv(), if self.ingress_open => match packet {
Some(pkt) => self.device.rx.push_back(pkt),
None => self.ingress_open = false,
},
cmd = self.cmd_rx.recv(), if self.cmd_open => match cmd {
Some(cmd) => self.handle_command(cmd),
None => self.cmd_open = false,
},
() = tokio::time::sleep(sleep) => {}
}
}
}
fn poll_once(&mut self) {
self.drain_ingress();
let now = self.now();
let _ = self.iface.poll(now, &mut self.device, &mut self.sockets);
self.accept_listeners();
self.pump_connections();
self.drain_egress();
}
fn drain_ingress(&mut self) {
loop {
match self.from_tunnel.try_recv() {
Ok(pkt) => self.device.rx.push_back(pkt),
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => {
self.ingress_open = false;
break;
}
}
}
}
fn drain_egress(&mut self) {
while let Some(pkt) = self.device.tx.pop_front() {
match self.to_tunnel.try_send(pkt) {
Ok(()) => {}
Err(TrySendError::Full(pkt)) => {
self.device.tx.push_front(pkt);
break;
}
Err(TrySendError::Closed(_)) => {
self.device.tx.clear();
break;
}
}
}
}
fn accept_listeners(&mut self) {
let ready: Vec<SocketHandle> = self
.listeners
.keys()
.copied()
.filter(|h| !self.sockets.get::<tcp::Socket>(*h).is_listening())
.collect();
for handle in ready {
let Some(listener) = self.listeners.remove(&handle) else {
continue;
};
let (conn, tcp_conn) = new_conn_pair();
if listener.accept_tx.try_send(tcp_conn).is_ok() {
self.conns.insert(handle, conn);
if let Err(e) = self.arm_listener(listener.port, listener.accept_tx.clone()) {
tracing::warn!(error = %e, port = listener.port, "failed to re-arm listener");
}
} else {
self.sockets.remove(handle);
}
}
}
fn pump_connections(&mut self) {
let handles: Vec<SocketHandle> = self.conns.keys().copied().collect();
let mut closed: Vec<SocketHandle> = Vec::new();
for handle in handles {
let socket = self.sockets.get_mut::<tcp::Socket>(handle);
let Some(conn) = self.conns.get_mut(&handle) else {
continue;
};
pump_recv(socket, conn);
pump_send(socket, conn);
if socket.state() == tcp::State::Closed {
closed.push(handle);
}
}
for handle in closed {
self.conns.remove(&handle);
self.sockets.remove(handle);
}
}
fn handle_command(&mut self, cmd: NetstackCmd) {
match cmd {
NetstackCmd::Connect { dst, reply } => {
let _ = reply.send(self.do_connect(dst));
}
NetstackCmd::Listen { port, reply } => {
let _ = reply.send(self.do_listen(port));
}
}
}
fn do_connect(&mut self, dst: SocketAddr) -> Result<TcpConn, EdgeError> {
let mut socket = new_tcp_socket(&self.cfg);
let local_port = self.alloc_ephemeral_port();
socket
.connect(self.iface.context(), dst, (self.cfg.local_ip, local_port))
.map_err(|e| EdgeError::Netstack(format!("connect {dst}: {e:?}")))?;
let handle = self.sockets.add(socket);
let (conn, tcp_conn) = new_conn_pair();
self.conns.insert(handle, conn);
Ok(tcp_conn)
}
fn do_listen(&mut self, port: u16) -> Result<mpsc::Receiver<TcpConn>, EdgeError> {
let (accept_tx, accept_rx) = mpsc::channel(CONN_CHANNEL_BOUND);
self.arm_listener(port, accept_tx)?;
Ok(accept_rx)
}
fn arm_listener(
&mut self,
port: u16,
accept_tx: mpsc::Sender<TcpConn>,
) -> Result<(), EdgeError> {
let mut socket = new_tcp_socket(&self.cfg);
socket
.listen(port)
.map_err(|e| EdgeError::Netstack(format!("listen {port}: {e:?}")))?;
let handle = self.sockets.add(socket);
self.listeners.insert(handle, Listener { port, accept_tx });
Ok(())
}
fn alloc_ephemeral_port(&mut self) -> u16 {
let port = self.next_port;
self.next_port = next_ephemeral(self.next_port);
port
}
fn now(&self) -> Instant {
smoltcp_now(self.start)
}
fn compute_sleep(&mut self) -> Duration {
let now = self.now();
let delay = self
.iface
.poll_delay(now, &self.sockets)
.map_or(MAX_POLL_INTERVAL, |d| {
Duration::from(d).min(MAX_POLL_INTERVAL)
});
if self.device.tx.is_empty() {
delay
} else {
delay.min(EGRESS_RETRY_INTERVAL)
}
}
}
fn build_interface(
cfg: &NetstackConfig,
device: &mut NetstackDevice,
start: std::time::Instant,
) -> Interface {
let mut config = Config::new(HardwareAddress::Ip);
config.random_seed = rand::random();
let mut iface = Interface::new(config, device, smoltcp_now(start));
iface.update_ip_addrs(|addrs| {
let _ = addrs.push(IpCidr::new(cfg.local_ip.into(), cfg.prefix_len));
});
let _ = iface
.routes_mut()
.add_default_ipv4_route(Ipv4Address::UNSPECIFIED);
let _ = iface
.routes_mut()
.add_default_ipv6_route(Ipv6Address::UNSPECIFIED);
iface
}
fn smoltcp_now(start: std::time::Instant) -> Instant {
let micros = start.elapsed().as_micros();
Instant::from_micros(i64::try_from(micros).unwrap_or(i64::MAX))
}
fn new_tcp_socket(cfg: &NetstackConfig) -> tcp::Socket<'static> {
let rx = tcp::SocketBuffer::new(vec![0u8; cfg.tcp_buffer_bytes]);
let tx = tcp::SocketBuffer::new(vec![0u8; cfg.tcp_buffer_bytes]);
let mut socket = tcp::Socket::new(rx, tx);
socket.set_timeout(Some(cfg.tcp_timeout.into()));
socket
}
fn next_ephemeral(current: u16) -> u16 {
if current == u16::MAX {
EDGE_EPHEMERAL_PORT_START
} else {
current + 1
}
}
fn new_conn_pair() -> (Conn, TcpConn) {
let (overlay_to_host, host_rx) = mpsc::channel(CONN_CHANNEL_BOUND);
let (host_tx, host_to_overlay) = mpsc::channel(CONN_CHANNEL_BOUND);
let conn = Conn {
to_host: Some(overlay_to_host),
from_host: host_to_overlay,
pending: None,
send_closed: false,
};
let tcp_conn = TcpConn {
tx: host_tx,
rx: host_rx,
};
(conn, tcp_conn)
}
fn pump_recv(socket: &mut tcp::Socket, conn: &mut Conn) {
let mut host_gone = false;
if let Some(tx) = conn.to_host.as_ref() {
while socket.can_recv() {
match tx.try_reserve() {
Ok(permit) => {
let mut buf = vec![0u8; RECV_CHUNK];
match socket.recv_slice(&mut buf) {
Ok(n) if n > 0 => {
buf.truncate(n);
permit.send(buf);
}
_ => break,
}
}
Err(TrySendError::Full(())) => break,
Err(TrySendError::Closed(())) => {
host_gone = true;
break;
}
}
}
}
if host_gone {
socket.abort();
conn.to_host = None;
}
let opening = matches!(
socket.state(),
tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived
);
if !opening && !socket.may_recv() {
conn.to_host = None;
}
}
fn pump_send(socket: &mut tcp::Socket, conn: &mut Conn) {
while socket.can_send() {
if let Some(pending) = conn.pending.take() {
match socket.send_slice(&pending) {
Ok(n) if n < pending.len() => {
conn.pending = Some(pending[n..].to_vec());
break;
}
Ok(_) => continue,
Err(_) => {
conn.pending = Some(pending);
break;
}
}
}
if conn.send_closed {
break;
}
match conn.from_host.try_recv() {
Ok(data) => match socket.send_slice(&data) {
Ok(n) if n < data.len() => {
conn.pending = Some(data[n..].to_vec());
break;
}
Ok(_) => {}
Err(_) => {
conn.pending = Some(data);
break;
}
},
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => {
socket.close();
conn.send_closed = true;
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
fn test_cfg(ip: Ipv4Addr) -> NetstackConfig {
NetstackConfig {
local_ip: IpAddr::V4(ip),
prefix_len: 24,
mtu: 1420,
tcp_buffer_bytes: 64 * 1024,
tcp_timeout: Duration::from_secs(10),
}
}
async fn wire(src: Arc<NetstackChannel>, dst: Arc<NetstackChannel>) {
let mut buf = vec![0u8; 65536];
loop {
match src.recv_packet(&mut buf).await {
Ok(n) if n > 0 => {
if dst.send_packet(&buf[..n]).await.is_err() {
break;
}
}
Ok(_) => {}
Err(_) => break,
}
}
}
#[tokio::test]
async fn netstack_echo_loopback() {
let client_ip = Ipv4Addr::new(10, 0, 0, 1);
let server_ip = Ipv4Addr::new(10, 0, 0, 2);
let port = 4000u16;
let (client, client_chan, client_task) = Netstack::spawn(test_cfg(client_ip));
let (server, server_chan, server_task) = Netstack::spawn(test_cfg(server_ip));
let client_chan = Arc::new(client_chan);
let server_chan = Arc::new(server_chan);
let w1 = tokio::spawn(wire(Arc::clone(&client_chan), Arc::clone(&server_chan)));
let w2 = tokio::spawn(wire(Arc::clone(&server_chan), Arc::clone(&client_chan)));
let mut accept_rx = server.listen(port).await.expect("listen");
let echo = tokio::spawn(async move {
if let Some(conn) = accept_rx.recv().await {
let TcpConn { tx, mut rx } = conn;
while let Some(chunk) = rx.recv().await {
if tx.send(chunk).await.is_err() {
break;
}
}
}
});
let dst = SocketAddr::new(IpAddr::V4(server_ip), port);
let conn = tokio::time::timeout(Duration::from_secs(10), client.connect(dst))
.await
.expect("connect timed out")
.expect("connect failed");
let TcpConn { tx, mut rx } = conn;
let payload = b"hello smoltcp userspace round trip".to_vec();
tx.send(payload.clone()).await.expect("send payload");
let mut got = Vec::new();
while got.len() < payload.len() {
match tokio::time::timeout(Duration::from_secs(10), rx.recv()).await {
Ok(Some(chunk)) => got.extend_from_slice(&chunk),
Ok(None) => break,
Err(e) => panic!("timed out waiting for echo (have {} bytes): {e}", got.len()),
}
}
assert_eq!(got, payload);
drop(tx);
for task in [w1, w2, echo, client_task, server_task] {
task.abort();
}
}
fn wg_keypair() -> ([u8; 32], [u8; 32]) {
use rand::RngCore;
let mut priv_bytes = [0u8; 32];
rand::rng().fill_bytes(&mut priv_bytes);
let public =
boringtun::x25519::PublicKey::from(&boringtun::x25519::StaticSecret::from(priv_bytes));
(priv_bytes, public.to_bytes())
}
fn build_peer_state(
our_priv: &[u8; 32],
peer_pub: &[u8; 32],
endpoint: Option<SocketAddr>,
keepalive: Option<u16>,
allowed_cidr: &str,
) -> (
crate::tunn_loop::PeerState,
Arc<std::sync::atomic::AtomicU64>,
) {
use crate::tunn_loop::{build_tunn, PeerState};
let last_hs = Arc::new(std::sync::atomic::AtomicU64::new(0));
let state = PeerState {
tunn: Arc::new(Mutex::new(build_tunn(our_priv, peer_pub, None, keepalive))),
endpoint: Arc::new(parking_lot::RwLock::new(endpoint)),
last_handshake_sec: Arc::clone(&last_hs),
allowed_ips: Arc::new(vec![allowed_cidr
.parse::<ipnet::IpNet>()
.expect("valid overlay cidr")]),
persistent_keepalive: keepalive,
};
(state, last_hs)
}
#[tokio::test]
async fn tunn_pipeline_encrypted_tcp_roundtrip() {
use crate::tunn_loop::{PeerState, TunnDriver};
use dashmap::DashMap;
use std::sync::atomic::Ordering;
use tokio::net::UdpSocket;
let udp_edge = Arc::new(UdpSocket::bind("127.0.0.1:0").await.expect("bind edge udp"));
let udp_node = Arc::new(UdpSocket::bind("127.0.0.1:0").await.expect("bind node udp"));
let addr_node = udp_node.local_addr().expect("node udp addr");
let (edge_priv, edge_pub) = wg_keypair();
let (node_priv, node_pub) = wg_keypair();
let node_ip = Ipv4Addr::new(10, 88, 0, 2);
let (netstack_node, chan_node, task_node) = Netstack::spawn(test_cfg(node_ip));
let chan_node = Arc::new(chan_node);
let (node_peer, node_last_hs) =
build_peer_state(&node_priv, &edge_pub, None, None, "10.88.0.1/32");
let peers_node: Arc<DashMap<[u8; 32], PeerState>> = Arc::new(DashMap::new());
peers_node.insert(edge_pub, node_peer);
let driver_node = TunnDriver::spawn(udp_node, chan_node, peers_node);
let edge_ip = Ipv4Addr::new(10, 88, 0, 1);
let (netstack_edge, chan_edge, task_edge) = Netstack::spawn(test_cfg(edge_ip));
let chan_edge = Arc::new(chan_edge);
let (edge_peer, edge_last_hs) = build_peer_state(
&edge_priv,
&node_pub,
Some(addr_node),
Some(25),
"10.88.0.2/32",
);
let peers_edge: Arc<DashMap<[u8; 32], PeerState>> = Arc::new(DashMap::new());
peers_edge.insert(node_pub, edge_peer);
let driver_edge = TunnDriver::spawn(udp_edge, chan_edge, peers_edge);
let port = 4100u16;
let mut accept_rx = netstack_node.listen(port).await.expect("listen");
let echo = tokio::spawn(async move {
if let Some(conn) = accept_rx.recv().await {
let TcpConn { tx, mut rx } = conn;
while let Some(chunk) = rx.recv().await {
if tx.send(chunk).await.is_err() {
break;
}
}
}
});
let dst = SocketAddr::new(IpAddr::V4(node_ip), port);
let conn = tokio::time::timeout(Duration::from_secs(20), netstack_edge.connect(dst))
.await
.expect("connect timed out")
.expect("connect failed");
let TcpConn { tx, mut rx } = conn;
let payload = b"boringtun-encrypted round trip across the edge data plane".to_vec();
tx.send(payload.clone()).await.expect("send payload");
let mut got = Vec::new();
while got.len() < payload.len() {
match tokio::time::timeout(Duration::from_secs(20), rx.recv()).await {
Ok(Some(chunk)) => got.extend_from_slice(&chunk),
Ok(None) => break,
Err(e) => panic!(
"timed out waiting for encrypted echo (have {} of {} bytes): {e}",
got.len(),
payload.len()
),
}
}
assert_eq!(
got, payload,
"echoed payload mismatch through the encrypted pipeline"
);
let edge_hs = edge_last_hs.load(Ordering::Relaxed);
let node_hs = node_last_hs.load(Ordering::Relaxed);
assert!(
edge_hs > 0 || node_hs > 0,
"no WireGuard handshake was recorded (edge={edge_hs}, node={node_hs}); \
the payload must have bypassed boringtun"
);
drop(tx);
driver_edge.abort_all();
driver_node.abort_all();
echo.abort();
task_edge.abort();
task_node.abort();
}
#[tokio::test]
async fn send_packet_drops_when_full() {
let (from_tunnel, mut from_tunnel_rx) = mpsc::channel::<Vec<u8>>(1);
let (_to_tunnel_tx, to_tunnel_rx) = mpsc::channel::<Vec<u8>>(1);
let chan = NetstackChannel {
from_tunnel,
to_tunnel: Mutex::new(to_tunnel_rx),
wake: Arc::new(Notify::new()),
mtu: 1420,
};
chan.send_packet(&[1, 2, 3]).await.expect("first send");
chan.send_packet(&[4, 5, 6])
.await
.expect("second send drops");
assert_eq!(from_tunnel_rx.recv().await, Some(vec![1, 2, 3]));
assert!(
from_tunnel_rx.try_recv().is_err(),
"second packet must be dropped"
);
}
#[tokio::test]
async fn recv_packet_errors_on_closed() {
let (from_tunnel, _from_tunnel_rx) = mpsc::channel::<Vec<u8>>(1);
let (to_tunnel_tx, to_tunnel_rx) = mpsc::channel::<Vec<u8>>(1);
drop(to_tunnel_tx);
let chan = NetstackChannel {
from_tunnel,
to_tunnel: Mutex::new(to_tunnel_rx),
wake: Arc::new(Notify::new()),
mtu: 1420,
};
let mut buf = [0u8; 64];
assert!(chan.recv_packet(&mut buf).await.is_err());
}
#[test]
fn ephemeral_port_wraps() {
assert_eq!(
next_ephemeral(EDGE_EPHEMERAL_PORT_START),
EDGE_EPHEMERAL_PORT_START + 1
);
assert_eq!(next_ephemeral(u16::MAX), EDGE_EPHEMERAL_PORT_START);
let mut p = u16::MAX;
p = next_ephemeral(p);
assert_eq!(p, EDGE_EPHEMERAL_PORT_START);
}
#[test]
fn config_plumbing() {
let cfg = test_cfg(Ipv4Addr::new(10, 0, 0, 5));
assert_eq!(cfg.local_ip, IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)));
assert_eq!(cfg.prefix_len, 24);
assert_eq!(cfg.mtu, 1420);
assert_eq!(cfg.tcp_buffer_bytes, 64 * 1024);
let socket = new_tcp_socket(&cfg);
assert_eq!(socket.recv_capacity(), 64 * 1024);
assert_eq!(socket.send_capacity(), 64 * 1024);
}
}