34 stable releases
new 2.0.4 | Feb 20, 2025 |
---|---|
2.0.3 | Feb 13, 2025 |
1.5.0 | Dec 5, 2024 |
1.4.8 | Nov 27, 2024 |
1.0.1 | Aug 30, 2024 |
#105 in Network programming
1,651 downloads per month
Used in 3 crates
330KB
7K
SLoC
Tun/Tap interfaces
This crate allows the creation and usage of Tun/Tap interfaces(supporting both Ipv4 and ipv6), aiming to make this cross-platform.
Features:
- 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
Supported Platforms
Platform | TUN | TAP |
---|---|---|
Windows | ✅ | ✅ |
Linux | ✅ | ✅ |
macOS | ✅ | ⬜ |
FreeBSD | ✅ | ✅ |
Android | ✅ | ⬜ |
iOS | ✅ | ⬜ |
Usage
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"] }
Example
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
Linux
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(())
}
macOS & FreeBSD
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
iOS
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) {
...
}
}
Android
// 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) };
}
Windows
Tun:
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.
Tap:
When using the tap network interface, you need to manually install tap-windows that matches your architecture.
Dependencies
~0.7–14MB
~199K SLoC