use tokio::io::{AsyncRead, AsyncWrite};
pub mod adapter;
pub mod handle;
pub mod packets;
pub mod stream;
#[cfg(feature = "pcap")]
pub type PcapLog = std::sync::Arc<tokio::sync::Mutex<tokio::fs::File>>;
#[cfg(not(feature = "pcap"))]
#[derive(Debug, Clone, Copy)]
pub struct PcapLog;
#[allow(unused_imports)]
pub(crate) mod time {
#[cfg(not(target_arch = "wasm32"))]
pub use tokio::time::*;
#[cfg(target_arch = "wasm32")]
pub use wasmtimer::std::Instant;
#[cfg(target_arch = "wasm32")]
pub use wasmtimer::tokio::*;
}
#[allow(dead_code)]
pub(crate) fn spawn<F>(fut: F)
where
F: std::future::Future<Output = ()> + Send + 'static,
{
#[cfg(not(target_arch = "wasm32"))]
{
tokio::spawn(fut);
}
#[cfg(target_arch = "wasm32")]
{
wasm_bindgen_futures::spawn_local(fut);
}
}
pub trait ReadWrite: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug {}
impl<T: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug> ReadWrite for T {}
#[cfg(feature = "pcap")]
pub(crate) fn log_packet(file: &PcapLog, packet: &[u8]) {
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::io::AsyncWriteExt;
use tracing::trace;
trace!("Logging {} byte packet", packet.len());
let packet = packet.to_vec();
let file = file.to_owned();
let now = SystemTime::now();
tokio::task::spawn(async move {
let mut file = file.lock().await;
file.write_all(&(now.duration_since(UNIX_EPOCH).unwrap().as_secs() as u32).to_le_bytes())
.await
.unwrap();
let micros = now.duration_since(UNIX_EPOCH).unwrap().as_micros() % 1_000_000_000;
file.write_all(&(micros as u32).to_le_bytes())
.await
.unwrap();
file.write_all(&(packet.len() as u32).to_le_bytes())
.await
.unwrap();
file.write_all(&(packet.len() as u32).to_le_bytes())
.await
.unwrap();
file.write_all(&packet).await.unwrap();
});
}
#[cfg(not(feature = "pcap"))]
#[allow(dead_code)]
pub(crate) fn log_packet(_file: &PcapLog, _packet: &[u8]) {}
#[cfg(test)]
mod tests {
use std::{
net::{IpAddr, Ipv6Addr},
str::FromStr,
};
use super::*;
use adapter::Adapter;
use std::{
pin::Pin,
task::{Context, Poll},
};
use stream::AdapterStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tun_rs::DeviceBuilder;
use bytes::BytesMut;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tun_rs::AsyncDevice;
pub struct AsyncDeviceWrapper {
device: AsyncDevice,
buffer: BytesMut,
}
impl std::fmt::Debug for AsyncDeviceWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncDeviceWrapper")
.field("buffer", &self.buffer)
.finish()
}
}
impl AsyncRead for AsyncDeviceWrapper {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
let this = self.get_mut();
if !this.buffer.is_empty() {
let bytes_to_copy = std::cmp::min(this.buffer.len(), buf.remaining());
let data_to_copy = this.buffer.split_to(bytes_to_copy);
buf.put_slice(&data_to_copy);
return Poll::Ready(Ok(()));
}
let mut temp_buf = vec![0u8; 65536];
match this.device.poll_recv(cx, &mut temp_buf) {
Poll::Ready(Ok(n)) => {
if n > 0 {
let bytes_to_copy = std::cmp::min(n, buf.remaining());
buf.put_slice(&temp_buf[..bytes_to_copy]);
if n > bytes_to_copy {
this.buffer.extend_from_slice(&temp_buf[bytes_to_copy..n]);
}
Poll::Ready(Ok(()))
} else {
Poll::Ready(Ok(()))
}
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Pending => Poll::Pending,
}
}
}
impl AsyncWrite for AsyncDeviceWrapper {
fn poll_write(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
self.device.poll_send(cx, buf)
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
std::task::Poll::Ready(Ok(()))
}
}
const SERVER_PORT: u16 = 5555;
#[tokio::test]
async fn local_tcp() {
let our_ip = Ipv6Addr::from_str("fd12:3456:789a::1").unwrap();
let their_ip = Ipv6Addr::from_str("fd12:3456:789a::2").unwrap();
let dev = DeviceBuilder::new()
.ipv6(their_ip, "ffff:ffff:ffff:ffff::")
.mtu(1420)
.build_async()
.expect("Failed to create tunnel. Are you root?");
println!("Created tunnel [{:?}] {}", dev.name(), their_ip);
let mut adapter = Adapter::new(
Box::new(AsyncDeviceWrapper {
device: dev,
buffer: BytesMut::new(),
}),
IpAddr::V6(our_ip),
IpAddr::V6(their_ip),
);
adapter.pcap("./local_tcp.pcap").await.expect("no pcap");
tokio::task::spawn(async move {
let listener = tokio::net::TcpListener::bind(format!("[::0]:{SERVER_PORT}"))
.await
.unwrap();
while let Ok((mut stream, addr)) = listener.accept().await {
println!("Accepted connection from {addr:?}");
tokio::task::spawn(async move {
loop {
let mut buf = [0; 1024];
let read_len = stream.read(&mut buf).await.unwrap();
stream.write_all(&buf[..read_len]).await.unwrap();
}
});
}
});
println!("Attach Wireshark, press enter to continue\n");
let mut buf = Vec::new();
let _ = tokio::io::stdin().read(&mut buf).await.unwrap();
let mut stream = match AdapterStream::connect(&mut adapter, SERVER_PORT).await {
Ok(s) => s,
Err(e) => {
println!("no connect: {e:?}");
return;
}
};
if let Err(e) = stream.write_all(&[1, 2, 3, 4, 5]).await {
println!("no send: {e:?}");
} else {
let mut buf = [0u8; 4];
match stream.read_exact(&mut buf).await {
Ok(_) => println!("recv'd {buf:?}"),
Err(e) => println!("no recv: {e:?}"),
}
}
if let Err(e) = stream.write_all(&[69, 69, 42, 0, 1]).await {
println!("no send: {e:?}");
} else {
let mut buf = [0u8; 6];
match stream.read_exact(&mut buf).await {
Ok(_) => println!("recv'd {buf:?}"),
Err(e) => println!("no recv: {e:?}"),
}
}
if let Err(e) = stream.close().await {
println!("no close: {e:?}");
}
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
println!("\n\npress enter");
let mut buf = Vec::new();
let _ = tokio::io::stdin().read(&mut buf).await.unwrap();
}
const SPEED_PORT: u16 = 5556;
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn handle_speed() {
const TOTAL_BYTES: usize = 16 << 20; const CHUNK_SIZE: usize = 4096;
const MTU: u16 = 16000;
const MSS: usize = MTU as usize - 40 - 20;
let our_ip = Ipv6Addr::from_str("fd12:3456:789b::1").unwrap();
let their_ip = Ipv6Addr::from_str("fd12:3456:789b::2").unwrap();
let dev = DeviceBuilder::new()
.ipv6(their_ip, "ffff:ffff:ffff:ffff::")
.mtu(MTU)
.build_async()
.expect("Failed to create tunnel. Are you root?");
println!("Created tunnel [{:?}] {}", dev.name(), their_ip);
let mut adapter = Adapter::new(
Box::new(AsyncDeviceWrapper {
device: dev,
buffer: BytesMut::new(),
}),
IpAddr::V6(our_ip),
IpAddr::V6(their_ip),
);
adapter.set_mss(MSS);
tokio::task::spawn(async move {
let listener = tokio::net::TcpListener::bind(format!("[::0]:{SPEED_PORT}"))
.await
.unwrap();
while let Ok((mut stream, _)) = listener.accept().await {
tokio::task::spawn(async move {
let mut buf = vec![0u8; 16 * 1024];
loop {
let n = match stream.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(n) => n,
};
if stream.write_all(&buf[..n]).await.is_err() {
return;
}
}
});
}
});
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let mut handle = adapter.to_async_handle();
let stream = match handle.connect(SPEED_PORT).await {
Ok(s) => s,
Err(e) => panic!("connect failed: {e:?}"),
};
let (mut reader, mut writer) = tokio::io::split(stream);
let payload = vec![0xABu8; CHUNK_SIZE];
let start = std::time::Instant::now();
let write_task = tokio::task::spawn(async move {
let chunks = TOTAL_BYTES / CHUNK_SIZE;
for _ in 0..chunks {
writer.write_all(&payload).await.expect("write_all");
}
writer.flush().await.expect("flush");
});
let read_task = tokio::task::spawn(async move {
let mut buf = vec![0u8; CHUNK_SIZE];
let mut got = 0usize;
while got < TOTAL_BYTES {
let n = reader.read(&mut buf).await.expect("read");
if n == 0 {
panic!("eof at {got}/{TOTAL_BYTES}");
}
got += n;
}
});
write_task.await.expect("writer panicked");
read_task.await.expect("reader panicked");
let total_elapsed = start.elapsed();
let mib = TOTAL_BYTES as f64 / (1024.0 * 1024.0);
println!(
"handle_speed: {mib:.2} MiB echo in {CHUNK_SIZE}-byte chunks \
{:>7.2} ms {:>7.3} MiB/s",
total_elapsed.as_secs_f64() * 1000.0,
mib / total_elapsed.as_secs_f64(),
);
handle.close().await.ok();
}
}