Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
authors = ['Narrowlink <opensource@narrowlink.com>']
description = 'Asynchronous lightweight implementation of TCP/IP stack for Tun device'
name = "ipstack"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
license = "Apache-2.0"
repository = 'https://github.com/narrowlink/ipstack'
Expand All @@ -25,7 +25,7 @@ tracing = { version = "0.1.40", default-features = false }
udp-stream = { version = "0.0.9", default-features = false }

[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dev-dependencies]
tun = { version = "0.6", features = ["async"], default-features = false }
tun = { version = "0.6.1", features = ["async"], default-features = false }

[target.'cfg(target_os = "windows")'.dev-dependencies]
wintun = { version = "0.3", default-features = false }
Expand Down
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,48 @@
An asynchronous lightweight implementation of TCP/IP stack for Tun device.
Unstable, under development.
Unstable, under development.

### Usage
````rust
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use udp_stream::UdpStream;
use tokio::io{AsyncRead, AsyncWrite};
async fn copy_from_lhs_to_rhs(lhs:impl AsyncRead + AsyncWrite, rhs:impl AsyncRead + AsyncWrite){
let (lhs_reader,lhs_writer) = tokio::io::split(lhs);
let (rhs_reader, rhs_writer) = tokio::io::split(rhs);
tokio::join! {
tokio::io::copy(&mut lhs_reader, &mut rhs_writer) ,
tokio::io::copy(&mut rhs_reader, &mut lhs_writer),
}
}
#[tokio::main]
async fn main(){
const MTU: u16 = 1500;
let ipv4 = Ipv4Addr::new(10, 0, 0, 1);
let mut config = tun::Configuration::default();
config
.address(ipv4)
.netmask((255, 255, 255, 0))
.mtu(MTU as i32)
.up();

let mut ip_stack = ipstack::IpStack::new(tun::create_as_async(&config).unwrap(), MTU, true);
while let Ok(stream) = ip_stack.accept().await{
match stream{
IpStackStream::Tcp(tcp) => {
let rhs = TcpStream::connect("1.1.1.1:80").await.unwrap();
tokio::spawn(async move {
copy_from_lhs_to_rhs(tcp,rhs).await;
});
}
IpStackStream::Udp(udp) => {
let rhs = UdpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 53)).await.unwrap();
tokio::spawn(async move {
copy_from_lhs_to_rhs(udp,rhs).await;
});
}
}
}
}
````

We also suggest that you take a look at the complete [examples](https://github.com/narrowlink/ipstack/tree/main/examples).
103 changes: 13 additions & 90 deletions examples/tun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ const MTU: u16 = u16::MAX;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let ipv4 = Ipv4Addr::new(10, 0, 0, 1);
#[cfg(not(target_os = "windows"))]

let mut config = tun::Configuration::default();
#[cfg(not(target_os = "windows"))]
config
.address(ipv4)
.netmask((255, 255, 255, 0))
Expand All @@ -24,15 +23,20 @@ async fn main() {
config.packet_information(true);
});

#[cfg(not(target_os = "windows"))]
let mut ip_stack = ipstack::IpStack::new(tun::create_as_async(&config).unwrap(), MTU, true);

#[cfg(target_os = "windows")]
let mut ip_stack = ipstack::IpStack::new(
wintun::WinTunDevice::new(ipv4, Ipv4Addr::new(255, 255, 255, 0)),
MTU,
false,
);
#[cfg(target_os = "macos")]
{
let s = format!("sudo route -n add -net 10.0.0.0/24 {}", ipv4);
let command = std::process::Command::new("sh")
.arg("-c")
.arg(s)
.output()
.unwrap();
if !command.status.success() {
panic!("cannot establish route to tun device");
}
};

loop {
match ip_stack.accept().await.unwrap() {
Expand Down Expand Up @@ -64,84 +68,3 @@ async fn main() {
};
}
}

#[cfg(target_os = "windows")]
mod wintun {
use std::{net::Ipv4Addr, sync::Arc, task::ready, thread};

use tokio::io::{AsyncRead, AsyncWrite};

pub struct WinTunDevice {
session: Arc<wintun::Session>,
receiver: tokio::sync::mpsc::UnboundedReceiver<Vec<u8>>,
_task: thread::JoinHandle<()>,
}

impl WinTunDevice {
pub fn new(ip: Ipv4Addr, netmask: Ipv4Addr) -> WinTunDevice {
let wintun = unsafe { wintun::load() }.unwrap();
let adapter = wintun::Adapter::create(&wintun, "IpStack", "Tunnel", None).unwrap();
adapter.set_address(ip).unwrap();
adapter.set_netmask(netmask).unwrap();
let session = Arc::new(adapter.start_session(wintun::MAX_RING_CAPACITY).unwrap());
let (receiver_tx, receiver_rx) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
let session_reader = session.clone();
let task = thread::spawn(move || {
loop {
let packet = session_reader.receive_blocking().unwrap();
let bytes = packet.bytes().to_vec();
// dbg!(&bytes);
receiver_tx.send(bytes).unwrap();
}
});
WinTunDevice {
session,
receiver: receiver_rx,
_task: task,
}
}
}

impl AsyncRead for WinTunDevice {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
match ready!(self.receiver.poll_recv(cx)) {
Some(bytes) => {
buf.put_slice(&bytes);
std::task::Poll::Ready(Ok(()))
}
None => std::task::Poll::Ready(Ok(())),
}
}
}

impl AsyncWrite for WinTunDevice {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
let mut write_pack = self.session.allocate_send_packet(buf.len() as u16)?;
write_pack.bytes_mut().copy_from_slice(buf.as_ref());
self.session.send_packet(write_pack);
std::task::Poll::Ready(Ok(buf.len()))
}

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(()))
}
}
}
147 changes: 147 additions & 0 deletions examples/wintun.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};

use ipstack::stream::IpStackStream;
use tokio::{join, net::TcpStream};
use udp_stream::UdpStream;

// const MTU: u16 = 1500;
const MTU: u16 = u16::MAX;

#[tokio::main(flavor = "current_thread")]
async fn main() {
let ipv4 = Ipv4Addr::new(10, 0, 0, 1);
#[cfg(not(target_os = "windows"))]
let mut config = tun::Configuration::default();
#[cfg(not(target_os = "windows"))]
config
.address(ipv4)
.netmask((255, 255, 255, 0))
.mtu(MTU as i32)
.up();

#[cfg(target_os = "linux")]
config.platform(|config| {
config.packet_information(true);
});

#[cfg(not(target_os = "windows"))]
let mut ip_stack = ipstack::IpStack::new(tun::create_as_async(&config).unwrap(), MTU, true);

#[cfg(target_os = "windows")]
let mut ip_stack = ipstack::IpStack::new(
wintun::WinTunDevice::new(ipv4, Ipv4Addr::new(255, 255, 255, 0)),
MTU,
false,
);

loop {
match ip_stack.accept().await.unwrap() {
IpStackStream::Tcp(tcp) => {
let s = TcpStream::connect("1.1.1.1:80").await.unwrap();
let (mut t_rx, mut t_tx) = tokio::io::split(tcp);
let (mut s_rx, mut s_tx) = tokio::io::split(s);
tokio::spawn(async move {
join! {
tokio::io::copy(&mut t_rx, &mut s_tx) ,
tokio::io::copy(&mut s_rx, &mut t_tx),
}
});
}
IpStackStream::Udp(udp) => {
let s =
UdpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 53))
.await
.unwrap();
let (mut t_rx, mut t_tx) = tokio::io::split(udp);
let (mut s_rx, mut s_tx) = tokio::io::split(s);
tokio::spawn(async move {
join! {
tokio::io::copy(&mut t_rx, &mut s_tx) ,
tokio::io::copy(&mut s_rx, &mut t_tx),
}
});
}
};
}
}

#[cfg(target_os = "windows")]
mod wintun {
use std::{net::Ipv4Addr, sync::Arc, task::ready, thread};

use tokio::io::{AsyncRead, AsyncWrite};

pub struct WinTunDevice {
session: Arc<wintun::Session>,
receiver: tokio::sync::mpsc::UnboundedReceiver<Vec<u8>>,
_task: thread::JoinHandle<()>,
}

impl WinTunDevice {
pub fn new(ip: Ipv4Addr, netmask: Ipv4Addr) -> WinTunDevice {
let wintun = unsafe { wintun::load() }.unwrap();
let adapter = wintun::Adapter::create(&wintun, "IpStack", "Tunnel", None).unwrap();
adapter.set_address(ip).unwrap();
adapter.set_netmask(netmask).unwrap();
let session = Arc::new(adapter.start_session(wintun::MAX_RING_CAPACITY).unwrap());
let (receiver_tx, receiver_rx) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
let session_reader = session.clone();
let task = thread::spawn(move || {
loop {
let packet = session_reader.receive_blocking().unwrap();
let bytes = packet.bytes().to_vec();
// dbg!(&bytes);
receiver_tx.send(bytes).unwrap();
}
});
WinTunDevice {
session,
receiver: receiver_rx,
_task: task,
}
}
}

impl AsyncRead for WinTunDevice {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
match ready!(self.receiver.poll_recv(cx)) {
Some(bytes) => {
buf.put_slice(&bytes);
std::task::Poll::Ready(Ok(()))
}
None => std::task::Poll::Ready(Ok(())),
}
}
}

impl AsyncWrite for WinTunDevice {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
let mut write_pack = self.session.allocate_send_packet(buf.len() as u16)?;
write_pack.bytes_mut().copy_from_slice(buf.as_ref());
self.session.send_packet(write_pack);
std::task::Poll::Ready(Ok(buf.len()))
}

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(()))
}
}
}