#tun-tap #cross-platform #tun #tap #tunnel #networking #setting-up

tun-rs

A cross-platform infrastructure of Tun/Tap device creation and handling

29 stable releases

new 1.5.0 Dec 5, 2024
1.4.9 Dec 2, 2024
1.4.8 Nov 27, 2024
1.4.3 Oct 15, 2024
1.0.1 Aug 30, 2024

#134 in Network programming

Download history 230/week @ 2024-08-26 564/week @ 2024-09-02 839/week @ 2024-09-09 328/week @ 2024-09-16 666/week @ 2024-09-23 128/week @ 2024-09-30 1037/week @ 2024-10-07 969/week @ 2024-10-14 112/week @ 2024-10-21 378/week @ 2024-10-28 295/week @ 2024-11-04 25/week @ 2024-11-11 313/week @ 2024-11-18 254/week @ 2024-11-25 474/week @ 2024-12-02

1,078 downloads per month
Used in 2 crates

Apache-2.0

280KB
6.5K SLoC

Tun/Tap interfaces

Crates.io tun-rs Apache-2.0

This crate allows the creation and usage of Tun/Tap interfaces(supporting both Ipv4 and ipv6), aiming to make this cross-platform.

Features:

  1. Supporting TUN and TAP
  2. Supporting both IPv4 and IPv6
  3. Supporting Synchronous and Asynchronous API
  4. Supporting Tokio and async-std asynchronous runtimes
  5. All platforms have consistent IP packets(macOS's 4-byte head information can be eliminated)
  6. Experimentally supporting shutdown for Synchronous version
  7. Supporting Offload on the Linux platform
  8. 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 = "1"

If you want to use the TUN interface with asynchronous runtimes, you need to enable the async(aliased as async_tokio), or async_std feature:

[dependencies]
# tokio
tun_rs = { version = "1", features = ["async"] }

# async-std
tun_rs = { version = "1", features = ["async_std"] }

Example

The following example creates and configures a TUN interface and reads packets from it synchronously.

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
    let mut config = tun_rs::Configuration::default();
    config
        .address_with_prefix((10, 0, 0, 9), 24u8)
        //.destination((10, 0, 0, 1))
        .up();

    let dev = tun_rs::create(&config)?;
    // let shared = Arc::new(dev);
    dev.add_address_v6(
        "CDCD:910A:2222:5498:8475:1111:3900:2024"
            .parse::<IpAddr>()
            .unwrap(),
        64
    )?;
    //dev_t.remove_network_address(vec![(ip,prefix)])?;
    let mut buf = [0; 4096];

    loop {
        let amount = dev.recv(&mut buf)?;
        println!("{:?}", &buf[0..amount]);
    }
}

An example of asynchronously reading packets from an interface

#[tokio::main]
async fn main(mut quit: Receiver<()>) -> Result<(), BoxError> {
    let mut config = tun_rs::Configuration::default();

    config
        .address_with_prefix((10, 0, 0, 9), 24)
        .mtu(tun_rs::DEFAULT_MTU)
        .up();

    let dev = Arc::new(tun_rs::create_as_async(&config)?);
    // ignore the head 4bytes packet information for calling `recv` and `send` on macOS
    #[cfg(target_os = "macos")]
    dev.set_ignore_packet_info(true);

    let mut buf = vec![0; 1500];
    loop {
        let len = dev.recv(&mut buf).await?;
        println!("pkt: {:?}", &buf[..len]);
        //dev.send(buf).await?;
    }
    Ok(())
}

Offload is supported on the Linux platform, enable it via the config

#[cfg(target_os = "linux")]
config
.platform_config( | config| {
config.offload(true);
});
  1. Synchronous example
  2. Asynchronous example

Linux

You will need the tun-rs module to be loaded and root is required to create interfaces.

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) {
    let mut rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        // This is safe if the provided fd is valid
        let tun = unsafe { tun_rs::AsyncDevice::from_raw_fd(fd) };
        let mut buf = [0u8; 1500];
        while let Ok(packet) = tun.recv(&mut buf).await {
            ...
        }
    });
}

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
~192K SLoC