Tutorial: Capturing Network Packets with pnet in Rust

Cyprien Avico
3 min readOct 11, 2023

Introduction

Capturing network packets is a crucial part of network analysis, security, and debugging. While there are many tools out there to accomplish this task, in this tutorial, we’ll explore how to build a packet capturing tool using Rust and the pnet library. Rust's safety guarantees make it a suitable language for system-level operations like packet capturing.

Who Is This For?

  • Network administrators interested in monitoring their networks
  • Software developers working on networking applications
  • Cybersecurity professionals
  • Anyone interested in learning Rust for networking

Prerequisites

  • Basic understanding of Rust language
  • Some familiarity with networking concepts
  • Installed Rust and Cargo (Rust’s package manager)

Setting up the Environment

First, ensure you have an existing Rust project. Open your Cargo.toml file and add pnet as a dependency:

let interface = interfaces[5].clone();

Note: You can list available interfaces using pnet::datalink::interfaces() and pick one according to your needs.

Creating a Datalink Channel

A datalink channel is required for capturing packets. It establishes a low-level link to the network interface.

let (_, mut rx) = match datalink::channel(&interface, Default::default()) {
Ok(Ethernet(tx, rx)) => (tx, rx),
Ok(_) => panic!("Unhandled channel type: {}", &interface),
Err(e) => panic!("An error occurred when creating the datalink channel: {}", e),
};

Capturing Packets

Let’s set up a loop to read incoming packets continuously.

use pnet::packet::ethernet::EthernetPacket;

println!("Start reading packets: ");
loop {
match rx.next() {
Ok(packet) => {
if let Some(ethernet_packet) = EthernetPacket::new(packet) {
println!("New packet:");
println!("{:?}", ethernet_packet);
}
},
Err(e) => {
panic!("An error occurred while reading: {}", e);
}
}
}

Running with Superuser Permissions

Packet capturing usually requires superuser (root) permissions.

How to run your program as a superuser:

  1. Build the program: cargo build
  2. Run with sudo: sudo ./target/debug/pnet_tutorial

This is the result you should have:

Start reading packet: 
New packet:
EthernetPacket { destination : 0a:87:c7:e6:6f:64, source : 0a:87:c7:6e:e9:f1, ethertype : EtherType(34525), }
New packet:
EthernetPacket { destination : 0a:87:c7:e6:6f:64, source : 0a:87:c7:6e:e9:f1, ethertype : EtherType(2048), }

You may notice that EtherType is not displayed correctly. So replace the println :

println!("{} => {}: {}",
ethernet_packet.get_destination(),
ethernet_packet.get_source(),
ethernet_packet.get_ethertype());

You should now see it correctly:

New packet:
0a:87:c7:e6:6f:64 => 0a:87:c7:6e:e9:f1: Ipv6
New packet:
0a:87:c7:e6:6f:64 => 0a:87:c7:6e:e9:f1: Ipv4

Let’s dive deeper with :

use pnet::packet::Packet;
use pnet::packet::FromPacket;
let packet = ethernet_packet.packet();
let payload = ethernet_packet.payload();
let from_packet = ethernet_packet.from_packet();
//println!("---");
println!("packet: {:?}", packet);
// print the full packet as an array of u8
println!("payload: {:?}", payload);
// print the payload as an array of u8
println!("from_packet: {:?}", from_packet);
// print the hearder infos: mac address, ethertype, ...
// and the payload as an array of u8
println!("---");

Important Note on Security

Be cautious when running programs with superuser privileges. Make sure you understand the code and trust the libraries you’re using.

Conclusion

Capturing network packets doesn’t have to be complicated. With Rust and the pnet library, you can build a secure and efficient tool to capture and analyze network traffic. This opens up a plethora of opportunities in network monitoring, cybersecurity, and application development. Happy coding!

--

--

Cyprien Avico

I'm Cyprien, a Software Engineer Trainee passionate about Rust. Currently sharpening my skills working on a network packet analyzer project.