This crate allows the creation and usage of Tun/Tap interfaces(supporting both Ipv4 and ipv6), aiming to make this cross-platform.
- Supporting TUN and TAP
- Supporting both IPv4 and IPv6
- Supporting Synchronous and Asynchronous API
- Supports choosing between Tokio and async-io for asynchronous I/O operations.
- All platforms have consistent IP packets(macOS's 4-byte head information can be eliminated)
- Experimentally supporting shutdown for Synchronous version
- Supporting Offload (
TSO
/GSO
) on Linux - Supporting
multi-queue
on Linux - Having a consistent behavior of setting up routes when creating a device
Platform | TUN | TAP |
---|---|---|
Windows | ✅ | ✅ |
Linux | ✅ | ✅ |
macOS | ✅ | ⬜ |
FreeBSD | ✅ | ✅ |
Android | ✅ | ⬜ |
iOS | ✅ | ⬜ |
First, add the following to your Cargo.toml
:
[dependencies]
tun-rs = "2"
If you want to use the TUN interface with asynchronous runtimes, you need to enable the async
(aliased
as async_tokio
), or async_io
feature:
[dependencies]
# tokio
tun-rs = { version = "2", features = ["async"] }
# async-io
#tun-rs = { version = "2", features = ["async_io"] }
The following example creates and configures a TUN interface and reads packets from it synchronously.
use tun_rs::DeviceBuilder;
fn main() -> std::io::Result<()> {
let dev = DeviceBuilder::new()
.ipv4("10.0.0.1", 24, None)
.ipv6("CDCD:910A:2222:5498:8475:1111:3900:2021", 64)
.mtu(1400)
.build_sync()?;
let mut buf = [0; 4096];
loop {
let amount = dev.recv(&mut buf)?;
println!("{:?}", &buf[0..amount]);
}
Ok(())
}
An example of asynchronously reading packets from an interface
use tun_rs::DeviceBuilder;
#[tokio::main]
async fn main() -> std::io::Result<()> {
let dev = DeviceBuilder::new()
.ipv4("10.0.0.1", 24, None)
.build_async()?;
let mut buf = vec![0; 1500];
loop {
let len = dev.recv(&mut buf).await?;
println!("pkt: {:?}", &buf[..len]);
//dev.send(buf).await?;
}
Ok(())
}
On Unix, a device can also be directly created using a file descriptor (fd).
use tun_rs::SyncDevice;
fn main() -> std::io::Result<()> {
// Pass a valid fd value
let fd = 0;
let dev = unsafe { SyncDevice::from_fd(fd) };
// let async_dev = unsafe { tun_rs::AsyncDevice::from_fd(fd)?};
let mut buf = [0; 4096];
loop {
let amount = dev.recv(&mut buf)?;
println!("{:?}", &buf[0..amount]);
}
Ok(())
}
More examples are here
You will need the tun-rs
module to be loaded and root is required to create
interfaces.
TSO
/GSO
and multi-queue
is supported on the Linux platform, enable it via the config
use tun_rs::DeviceBuilder;
#[cfg(target_os = "linux")]
use tun_rs::{GROTable, IDEAL_BATCH_SIZE, VIRTIO_NET_HDR_LEN};
#[cfg(target_os = "linux")]
fn main() -> std::io::Result<()> {
let builder = DeviceBuilder::new()
// enable `multi-queue`
// .multi_queue(true)
// enable Offload (`TSO`/`GSO`)
.offload(true)
.ipv4("10.0.0.1", 24, None)
.ipv6("CDCD:910A:2222:5498:8475:1111:3900:2021", 64)
.mtu(1400);
let dev = builder.build_sync()?;
// use `multi-queue`
// let dev_clone = dev.try_clone()?;
let mut original_buffer = vec![0; VIRTIO_NET_HDR_LEN + 65535];
let mut bufs = vec![vec![0u8; 1500]; IDEAL_BATCH_SIZE];
let mut sizes = vec![0; IDEAL_BATCH_SIZE];
let mut gro_table = GROTable::default();
loop {
let num = dev.recv_multiple(&mut original_buffer, &mut bufs, &mut sizes, 0)?;
for i in 0..num {
println!("num={num},bytes={:?}", &bufs[i][..sizes[i]]);
}
}
Ok(())
}
tun-rs
will automatically set up a route according to the provided configuration, which does a similar thing like
this:
sudo route -n add -net 10.0.0.0/24 10.0.0.1
You can pass the file descriptor of the TUN device to tun-rs
to create the interface.
Here is an example to create the TUN device on iOS and pass the fd
to tun-rs
:
// Swift
class PacketTunnelProvider: NEPacketTunnelProvider {
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
let tunnelNetworkSettings = createTunnelSettings() // Configure TUN address, DNS, mtu, routing...
setTunnelNetworkSettings(tunnelNetworkSettings) { [weak self] error in
// The tunnel of this tunFd is contains `Packet Information` prifix.
let tunFd = self?.packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32
DispatchQueue.global(qos: .default).async {
start_tun(tunFd)
}
completionHandler(nil)
}
}
}
#[no_mangle]
pub extern "C" fn start_tun(fd: std::os::raw::c_int) {
// This is safe if the provided fd is valid
let tun = unsafe { tun_rs::SyncDevice::from_raw_fd(fd) };
let mut buf = [0u8; 1500];
while let Ok(packet) = tun.recv(&mut buf) {
...
}
}
// use android.net.VpnService
private void startVpn(DeviceConfig config) {
Builder builder = new Builder();
builder
.allowFamily(OsConstants.AF_INET)
.addAddress("10.0.0.2", 24);
ParcelFileDescriptor vpnInterface = builder.setSession("tun-rs")
.establish();
int fd = vpnInterface.getFd();
// Pass the fd to tun-rs using JNI
// example: let tun = unsafe { tun_rs::SyncDevice::from_raw_fd(fd) };
}
You need to copy the wintun.dll file which matches your architecture to the same directory as your executable and run your program as administrator.
When using the tap network interface, you need to manually install tap-windows that matches your architecture.