Skip to content
Open
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CI

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Setup test interface
run: sudo sh tests/setup.sh
- name: Build
run: cargo build --all-targets
- name: Test
run: sudo env "PATH=$PATH" "HOME=$HOME" cargo test
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Doc
run: cargo doc --no-deps
28 changes: 0 additions & 28 deletions .travis.yml

This file was deleted.

23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# 0.2.0

## Breaking Changes

* Async API completely rewritten: The `Async` struct no longer implements
`Stream` or `Sink`. It simply provides async version `recv` and `send` methods for reading/writing packets.
* `set_recv_bufsize` removed from `Async`; external buffer sizing is now caller-managed
via `AsyncRead`.
* Rename module `r#async` to `aio` to avoid conflict with Rust 2024 edition's `async` keyword.

## Dependency Upgrades

* `tokio-core` 0.1 → `tokio` 1.x (features: net, rt, rt-multi-thread, io-util, macros, time)
* `futures` 0.1 → removed
* `mio` 0.6 → removed (tokio's `AsyncFd` replaces raw mio)
* `etherparse` 0.9 → 0.17 (dev-dependency)
* `serial_test` 0.4 → 3.x (dev-dependency)

## Upstream Fixes (since 0.1.4)

* Fixed `cmd` function in examples to accept arbitrary command instead of hardcoded `"ip"` (#17)
* Fixed typo in doc comment (#16)

# 0.1.4

* Ability to set nonblocking without tokio (#12).
Expand Down
29 changes: 12 additions & 17 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "tun-tap"
# Also don't forget to update the version in the html_root_url attribute in src/lib.rs
version = "0.1.4"
edition = "2024"
version = "0.2.0"
authors = ["Michal 'vorner' Vaner <vorner@vorner.cz>"]
description = "TUN/TAP interface wrapper"
documentation = "https://docs.rs/tun-tap"
Expand All @@ -11,24 +11,19 @@ keywords = ["tun", "tap", "network"]
categories = ["network-programming"]
license = "Apache-2.0/MIT"

[badges]
travis-ci = { repository = "vorner/tuntap" }
maintenance = { status = "passively-maintained" }

[features]
default = ["tokio"]
tokio = ["futures", "libc", "mio", "tokio-core"]

[build-dependencies]
cc = "~1"
tokio = ["dep:tokio", "libc"]
libc = ["dep:libc"]

[dependencies]
futures = { version = "~0.1", optional = true }
libc = { version = "~0.2", optional = true }
mio = { version = "~0.6", optional = true }
tokio-core = { version = "~0.1", optional = true }
libc = { version = "0.2", optional = true }
tokio = { version = "1", features = ["net", "rt", "rt-multi-thread", "io-util", "macros", "time"], optional = true }

[build-dependencies]
cc = "1"

[dev-dependencies]
version-sync = "~0.9"
etherparse = "~0.9"
serial_test = "~0.4"
version-sync = "0.9"
etherparse = "0.17"
serial_test = "3"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TunTap

[![Travis Build Status](https://api.travis-ci.org/vorner/tuntap.png?branch=master)](https://travis-ci.org/vorner/tuntap)
[![CI](https://github.com/vorner/tuntap/actions/workflows/ci.yml/badge.svg)](https://github.com/vorner/tuntap/actions/workflows/ci.yml)

TUN/TAP wrapper for Rust.

Expand Down
2 changes: 0 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
extern crate cc;

use cc::Build;

fn main() {
Expand Down
7 changes: 4 additions & 3 deletions examples/dump_iface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
//! raw data of the packets that arrive.
//!
//! You really do want better error handling than all these unwraps.
extern crate tun_tap;

use std::process::Command;

Expand All @@ -28,8 +27,10 @@ fn main() {
// Configure the „local“ (kernel) endpoint.
cmd("ip", &["addr", "add", "dev", iface.name(), "10.107.1.2/24"]);
cmd("ip", &["link", "set", "up", "dev", iface.name()]);
println!("Created interface {}. Send some packets into it and see they're printed here",
iface.name());
println!(
"Created interface {}. Send some packets into it and see they're printed here",
iface.name()
);
println!("You can for example ping 10.107.1.3 (it won't answer)");
// That 1500 is a guess for the IFace's MTU (we probably could configure it explicitly). 4 more
// for TUN's „header“.
Expand Down
22 changes: 10 additions & 12 deletions examples/pingpong.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
//!
//! The `dump_iface` example is simpler (contains only the reading end), so you want to start with
//! that.
//!
//! You really do want better error handling than all these unwraps.

extern crate tun_tap;

use std::process::Command;
use std::sync::Arc;
Expand All @@ -19,10 +15,12 @@ use tun_tap::{Iface, Mode};

/// The packet data. Note that it is prefixed by 4 bytes ‒ two bytes are flags, another two are
/// protocol. 8, 0 is IPv4, 134, 221 is IPv6. <https://en.wikipedia.org/wiki/EtherType#Examples>.
const PING: &[u8] = &[0, 0, 8, 0, 69, 0, 0, 84, 44, 166, 64, 0, 64, 1, 247, 40, 10, 107, 1, 2, 10,
107, 1, 3, 8, 0, 62, 248, 19, 160, 0, 2, 232, 228, 34, 90, 0, 0, 0, 0, 216, 83, 3, 0, 0, 0, 0,
0, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55];
const PING: &[u8] = &[
0, 0, 8, 0, 69, 0, 0, 84, 44, 166, 64, 0, 64, 1, 247, 40, 10, 107, 1, 2, 10, 107, 1, 3, 8, 0,
62, 248, 19, 160, 0, 2, 232, 228, 34, 90, 0, 0, 0, 0, 216, 83, 3, 0, 0, 0, 0, 0, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
];

/// Run a shell command. Panic if it fails in any way.
fn cmd(cmd: &str, args: &[&str]) {
Expand Down Expand Up @@ -55,6 +53,8 @@ fn main() {
assert!(amount == PING.len());
}
});

#[allow(unused_variables)]
let reader = thread::spawn(move || {
// MTU + TUN header
let mut buffer = vec![0; 1504];
Expand All @@ -65,8 +65,6 @@ fn main() {
println!("Packet: {:?}", &buffer[4..size]);
}
});
writer.join()
.unwrap();
reader.join()
.unwrap();
writer.join().unwrap();
reader.join().unwrap();
}
65 changes: 38 additions & 27 deletions examples/tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,27 @@
//! This creates an interface, configures the kernel endpoint and sends pings to that endpoint. It
//! prints whatever packets come back (which should include the pong responses).
//!
//! This does essentialy the same as the `pingpoing` example. However, instead of threads, this
//! This does essentially the same as the `pingpong` example. However, instead of threads, this
//! uses tokio asynchronous event loop to multiplex between the two directions.
//!
//! You really do want better error handling than all these unwraps.

extern crate futures;
extern crate tokio_core;
extern crate tun_tap;

use std::process::Command;
use std::sync::Arc;
use std::time::Duration;

use futures::{Future, Stream};
use tokio_core::reactor::{Core, Interval};
use tokio::time;
use tun_tap::aio::Async;
use tun_tap::{Iface, Mode};
use tun_tap::async::Async;

/// The packet data. Note that it is prefixed by 4 bytes two bytes are flags, another two are
/// The packet data. Note that it is prefixed by 4 bytes two bytes are flags, another two are
/// protocol. 8, 0 is IPv4, 134, 221 is IPv6. <https://en.wikipedia.org/wiki/EtherType#Examples>.
const PING: &[u8] = &[0, 0, 8, 0, 69, 0, 0, 84, 44, 166, 64, 0, 64, 1, 247, 40, 10, 107, 1, 2, 10,
107, 1, 3, 8, 0, 62, 248, 19, 160, 0, 2, 232, 228, 34, 90, 0, 0, 0, 0, 216, 83, 3, 0, 0, 0, 0,
0, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55];
const PING: &[u8] = &[
0, 0, 8, 0, 69, 0, 0, 84, 44, 166, 64, 0, 64, 1, 247, 40, 10, 107, 1, 2, 10, 107, 1, 3, 8, 0,
62, 248, 19, 160, 0, 2, 232, 228, 34, 90, 0, 0, 0, 0, 216, 83, 3, 0, 0, 0, 0, 0, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
];

/// Run a shell command. Panic if it fails in any way.
fn cmd(cmd: &str, args: &[&str]) {
Expand All @@ -38,26 +36,39 @@ fn cmd(cmd: &str, args: &[&str]) {
assert!(ecode.success(), "Failed to execte {}", cmd);
}

fn main() {
#[tokio::main]
async fn main() {
let iface = Iface::new("testtun%d", Mode::Tun).unwrap();
eprintln!("Iface: {:?}", iface);
// Configure the „local“ (kernel) endpoint. Kernel is (the host) 10.107.1.3, we (the app)
// pretend to be 10.107.1.2.
cmd("ip", &["addr", "add", "dev", iface.name(), "10.107.1.3/24"]);
cmd("ip", &["link", "set", "up", "dev", iface.name()]);
let mut core = Core::new().unwrap();
let iface = Async::new(iface, &core.handle()).unwrap();
let (sink, stream) = iface.split();
let writer = Interval::new(Duration::from_secs(1), &core.handle())
.unwrap()
.map(|_| {
let iface = Arc::new(Async::new(iface).unwrap());
let write_task = {
let iface = iface.clone();
tokio::spawn(async move {
time::sleep(Duration::from_secs(1)).await;
println!("Sending ping");
PING.to_owned()
iface.send(PING).await.unwrap();
})
};
let read_task = {
let iface = iface.clone();
tokio::spawn(async move {
let mut buf = vec![0u8; 1504];
loop {
let n = iface.recv(&mut buf).await.unwrap();
// only catch `ping` from the writer, ignore other packets
// buf[2..4]: IPv4 protocol,
// buf[13]: the ICMP type (1 for echo request, 0 for echo reply)
// buf[24]: the ICMP code (0 for both echo request and reply)
if buf[2..4] == [8, 0] && buf[13] == 1 && buf[24] == 0 {
println!("Received: {:?}", &buf[..n]);
break;
}
}
})
.forward(sink);
let reader = stream.for_each(|packet| {
println!("Received: {:?}", packet);
Ok(())
});
core.run(reader.join(writer)).unwrap();
};
tokio::try_join!(write_task, read_task).unwrap();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the motivation behind making it run forever instead of just once? The PR seems to be about modernizing things, but you seem to be doing a lot more for unexplained reasons.

}
Loading