#networking #ip #virtualization

netsim

Run tests in network-isolated threads. Intercept and meddle with their packets.

18 releases

0.3.0 Dec 18, 2024
0.2.5 Aug 13, 2018
0.2.3 Jul 26, 2018
0.2.2 Jun 6, 2018
0.1.2 Feb 27, 2018

#799 in Network programming

Download history 9/week @ 2024-09-18 10/week @ 2024-09-25 2/week @ 2024-12-04 10/week @ 2024-12-11 163/week @ 2024-12-18 4/week @ 2025-01-01

179 downloads per month

MIT OR BSD-3-Clause and LGPL-2.0

155KB
3.5K SLoC

netsim

netsim is a Rust library which allows you to:

  • Run tests in network-isolated threads.
  • Test networking code on simulated networks.
  • Capture and inspect packets produced by your code.
  • Inject and meddle with network packets.

Documentation

Examples

See the examples directory in this repo.

Limitations

netsim currently only supports Linux since it makes use of the Linux containerization APIs.

License

MIT or BSD-3-Clause at your option.


lib.rs:

netsim is a library for testing networking code. It allows you to run tests in network-isolated threads, on virtual network interfaces, in parallel, without tests interfering with each other or with your actual network. You can also connect these isolated environments to each other to create simulated networks on which you can capture and inject packets.

Example 1: Isolating tests.

Suppose you have multiple tests that need to bind to the same port for some reason. By using #[netsim::isolate] you can run your test suite without having to use --test-threads=1 and without having to stop any daemons running on your dev machine.

#[test]
#[netsim::isolate]
fn a_test() {
let _listener = std::net::TcpListener::bind("0.0.0.0:80").unwrap();
}

#[test]
#[netsim::isolate]
fn another_test_that_runs_in_parallel() {
let _listener = std::net::TcpListener::bind("0.0.0.0:80").unwrap();
}

Example 2: Capturing a packet.

The #[netsim::isolate] attribute showcased above is just a convenient way to setup a Machine and spawn a task onto it. To capture a packet you'll need to do these steps yourself. You'll also need to give the machine a network interface to send the packet on.

In this example we create a UDP socket and use it to send the message "hello" towards an arbitary address. The packet then arrives on our IpIface, hoping to be routed somewhere, and we can check that it still contains the correct message.

let local_addr: SocketAddrV4 = "10.1.2.3:5555".parse().unwrap();
let remote_addr: SocketAddrV4 = "1.1.1.1:53".parse().unwrap();
let machine = Machine::new().unwrap();
let mut iface = {
machine
.add_ip_iface()
.ipv4_addr(*local_addr.ip())
.ipv4_default_route()
.build()
.unwrap()
};
machine.spawn(async move {
let socket = UdpSocket::bind(local_addr).await.unwrap();
socket.send_to(b"hello", remote_addr).await.unwrap();
}).await.unwrap();

let packet = loop {
let packet = iface.next().await.unwrap().unwrap();
let IpPacketVersion::V4(packet) = packet.version_box() else { continue };
let Ipv4PacketProtocol::Udp(packet) = packet.protocol_box() else { continue };
break packet;
};
assert_eq!(packet.data(), b"hello");

More, longer examples.

Check out the examples directory in the repo.

Dependencies

~4–13MB
~135K SLoC