Open-source project
libpnet/libpnet avatar
libpnet/libpnet

libpnet: low level packet crafting and datalink access in Rust

Cross-platform, low level networking using the Rust programming language.

2,591 stars329 forksRustApache-2.0

At a glance

What is it?
libpnet exposes packet construction, transport protocol scaffolding and raw datalink sockets behind a safe Rust API. It is a fit for protocol experiments and network utilities, and a poor fit for anything that just needs a TCP connection.
Who is it for?
Adopt libpnet when you need to build or inspect packets below the socket API: a custom transport protocol, a ping or traceroute style tool, or a capture path that sees frames as they are on the wire. Do not adopt it for ordinary request and response networking; std::net and an async runtime cover that with far less setup.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 153 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What libpnet is for, and who actually needs it

libpnet provides a cross-platform API for low level networking in Rust. The README frames the motivation around three jobs. The first is developing transport protocols: writing a new transport layer either in a scripting language, which is quick to prototype but slow in production, or in C, which is fast but brings manual memory management, thread safety hazards and a low level of abstraction. libpnet aims at the middle, with Rust's memory and thread safety plus higher level abstractions. The second job is network utilities. The README notes that tools such as ping and traceroute depend on manipulating network and transport headers, which the standard networking stacks do not allow. The third is the data link layer: seeing packets as they are on the wire, for network diagnostics, packet capture and traffic shaping.

The audience follows from that list. If you are writing an application that talks HTTP to a server, this is not your crate. If you are writing the thing that measures whether the HTTP connection is behaving, or you are implementing a protocol that does not exist yet in any stack, the four components are the point. The packet module builds and manipulates packets safely, pnet_macros supplies the infrastructure the packet module is generated from, the transport module lets you implement transport protocols, and the datalink module sends and receives data link packets directly.

How the crates fit together and where packets flow

The repository is a workspace, and the top level pnet crate is mostly an assembly of the parts. Cargo.toml lists pnet_base, pnet_sys, pnet_datalink, pnet_transport and pnet_packet as path dependencies, all pinned to 0.35.0. The std feature is on by default and pulls in pnet_base/std, pnet_sys, pnet_datalink/std, pnet_transport and ipnetwork. Turning std off leaves you with pnet_base and pnet_packet, which is the packet parsing and construction layer without any ability to touch a real interface.

The platform backends are feature gated rather than compiled in unconditionally. pcap maps to pnet_datalink/pcap, winpcap maps to pnet_datalink/winpcap, winpkfilter maps to pnet_datalink/winpkfilter, and netmap maps to pnet_datalink/netmap_sys and pnet_datalink/netmap. That layout tells you something practical: the datalink layer is a thin Rust surface over a capture or injection library chosen at build time, not a from-scratch driver. If the underlying library is missing, the feature cannot be enabled and the datalink module is not available.

Data flow is therefore two directional and fairly direct. On the receive path, a datalink channel hands you a buffer that the packet module parses into typed headers. On the send path, you build a packet with the packet module and write it to the channel, which passes it to the platform backend. The transport module sits above the datalink layer for code that wants to implement protocol behaviour rather than just move frames. The examples directory mirrors that split: list_interfaces and ip_to_mac for discovery, listen and receive_packets for the receive side, send_packets for the send side, packetdump for capture, fanout for multi-threaded distribution and transport_echo_server for the transport layer.

Installing libpnet and sending your first frame

Add the dependency to Cargo.toml exactly as the README shows. The version there is 0.35.0, which matches the package version in the repository manifest.

toml
[dependencies.pnet]
version = "0.35.0"

The README states that libpnet should work with the latest stable version of Rust. On Linux, the build needs no extra vendor library, but opening raw sockets at runtime does. The Makefile comments are the clearest statement of the permissions situation: on Linux, with CAP_NET_RAW set, tests can run without root. On FreeBSD and OS X, read and write access to /dev/bpf lets layer 2 run without root, while higher layers still need root because of SOCK_RAW and TransportStream. On Windows, everything runs as admin. The Makefile also notes that on every OS except Linux, PNET_TEST_IFACE must be set to an interface to test on.

Before writing any packet code, list the interfaces the library can see. The repository ships this as examples/list_interfaces.rs, and the manifest marks it as requiring the std feature. The project's own build entry points are the Makefile targets, which call bash build.sh with an argument: make build runs the plain build, make test runs bash build.sh test, and make doc runs bash build.sh doc.

bash
make build
make test

A successful build means the workspace crates compiled and the packet macros generated. A failed test run on a machine without the right privileges is expected rather than a sign of a broken checkout, per the README's note about networking tests. Once the build works, examples/list_interfaces.rs is the first thing to run, because it tells you the interface names and addresses that the datalink module can bind to. The ip_to_mac example, also gated on std, resolves an IP address to a MAC address on a chosen interface, which is a useful sanity check that your capture permissions and your interface selection agree.

For a receive path, examples/listen.rs and examples/receive_packets.rs show the channel API, and examples/packetdump.rs shows parsing captures into readable headers. For a send path, examples/send_packets.rs builds and transmits. Two build-time notes matter here. The pcap and winpcap features are not enabled by default, so a Windows build needs the winpcap feature plus the WinPcap or npcap installation described below. And the packet module's generated code comes from pnet_macros, so a build failure in that crate usually means a toolchain mismatch rather than a networking problem.

Windows builds: WinPcap, npcap and Packet.lib

The README lists three requirements for building on Windows, and they are stricter than the Linux path. You must use a version of Rust which uses the MSVC toolchain. You must have WinPcap or npcap installed, and the README notes it was tested with WinPcap 4.1.3. If you use npcap, the README says to install it with the "Install Npcap in WinPcap API-compatible Mode" option. Third, you must place Packet.lib from the WinPcap Developers pack in a directory named lib at the root of the repository, or in any location listed in the %LIB% or $Env:LIB environment variables. For the 64 bit toolchain the file is at WpdPack/Lib/x64/Packet.lib, and for the 32 bit toolchain at WpdPack/Lib/Packet.lib.

That third requirement is the one that catches people, because it is a manual file placement rather than something Cargo resolves. It also means a fresh clone on a Windows machine will not build until the developer pack is downloaded and the library file is in the right place. The npcap compatibility mode requirement is a second, quieter trap: npcap installed in its native mode is not the same as an API-compatible WinPcap replacement, and the README is explicit that the compatible mode is what is expected.

Permissions, test failures and other things that will bite

The README is unusually direct about the test suite: a number of networking tests will likely fail, and the easiest workaround is to run cargo test as a root or administrative user, though this can often be avoided in a more involved way. That is an honest statement of a real constraint, and it should shape how you use the crate. Elevated privileges are not a testing inconvenience you can ignore; they are the normal operating condition for raw socket and datalink work. If your deployment target cannot grant CAP_NET_RAW on Linux or read and write access to /dev/bpf on the BSDs and macOS, the datalink and transport modules are simply unavailable, and no amount of configuration fixes that.

The feature matrix is a second limitation. With default features off, you keep pnet_base and pnet_packet and lose every module that touches an interface. That is fine for parsing a packet you already have in memory, and useless for capture or injection. The pcap, winpcap, winpkfilter and netmap features are opt-in and each depends on a different underlying library, so portability across platforms is not free: you are choosing a backend per target.

A third case where libpnet is the wrong tool is anything that fits the standard socket API. If your problem is opening a TCP connection, sending a request and reading a response, libpnet adds a capture library dependency, a permissions requirement and a packet parsing layer for no benefit. The README's own framing puts the crate at the level where you manipulate network and transport headers, which is below where most applications live. There is also a maturity signal worth reading plainly: the most recent release is v0.35.0 from 2024-05-30, with v0.34.0 in 2023 and v0.33.0 in 2023. The last push to the repository was on 2026-05-01, so the project is not dormant, but release cadence is slow and you should expect to read the repository rather than a stream of announcements.

How libpnet differs from smoltcp and from the standard socket API

The closest comparison in the same language is smoltcp, and the difference is architectural rather than cosmetic. smoltcp is a standalone TCP/IP stack: it owns the protocol state machine, the buffers and the timers, and you drive it by feeding it packets and asking it to poll. libpnet does not implement a stack for you. It gives you the packet types, the datalink channel and the transport scaffolding, and you write the protocol behaviour. If you want a working TCP implementation to run over a virtual or embedded interface, smoltcp is the natural choice. If you want to build a protocol that does not exist, or to see and shape frames on a real interface, libpnet is the one that gives you the primitives.

The other comparison is with Rust's own std::net, which the README implicitly positions against when it notes that ping and traceroute need header manipulation that standard stacks do not allow. std::net gives you TCP and UDP sockets with no capture library, no elevated permissions and no packet parsing. That is the right default for almost all application code. libpnet earns its place only when the socket abstraction is the thing standing in your way. Between those two poles, the practical question is whether you need to see or construct headers; if the answer is no, neither the packet module nor the datalink module will pay for itself.

Licence, maintenance and the cost of upgrading

The repository carries two licence files, LICENSE-APACHE and LICENSE-MIT, and the package manifest declares license = "MIT OR Apache-2.0". The dual licence means you choose either term, which is the common arrangement for Rust crates and generally the least friction for both open source and commercial consumers. This is a statement of what the files say, not legal advice; if your organisation has licence policy, the two files in the repository root are what your reviewers will want to read.

Upgrade cost is shaped by the workspace structure. The internal crates are version locked together at 0.35.0 through path dependencies, so you cannot mix pnet_packet 0.35 with pnet_datalink 0.34 in a normal build; the version bump moves the whole set. Because releases are infrequent, the gap between them can be wide, and the changelog between v0.33.0 and v0.35.0 is where you should look before moving. Pin the version in Cargo.toml rather than tracking the repository, and treat a feature flag change as a build change: adding or removing pcap, winpcap, winpkfilter or netmap alters which platform library your build needs, which is exactly the kind of change that breaks CI on one operating system and not another. The build.sh script and Makefile are the project's own entry points for building, testing, generating docs and running benchmarks, and they encode the per-OS test requirements described above.

Editorial conclusion

Adopt libpnet when you need to build or inspect packets below the socket API: a custom transport protocol, a ping or traceroute style tool, or a capture path that sees frames as they are on the wire. Do not adopt it for ordinary request and response networking; std::net and an async runtime cover that with far less setup. Before committing, verify three things on your own machines: that your account can open raw sockets or read /dev/bpf, that the Windows build can find Packet.lib and an API-compatible npcap or WinPcap installation, and that the interfaces returned by the list_interfaces example are the ones you expect to bind. The crate is at 0.35.0 with a last push on 2026-05-01, so pin the version and read the changelog between releases rather than tracking main.

Frequently asked questions

What is libpnet used for?

It provides a cross-platform API for low level networking in Rust, covering safe packet construction and manipulation, transport protocol implementation and direct sending and receiving of data link packets. The README names three uses: developing transport protocols, building network utilities such as ping and traceroute, and working at the data link layer for diagnostics, capture and traffic shaping.

How do I install libpnet in a Rust project?

Add it to Cargo.toml under a dependencies.pnet section with version 0.35.0, as the README shows. The README states it should work with the latest stable version of Rust. On Windows you also need the MSVC toolchain, WinPcap or npcap installed in WinPcap API-compatible mode, and Packet.lib placed in a lib directory at the repository root or on the LIB path.

Why do libpnet tests fail without root or administrator privileges?

The README says a number of networking tests will likely fail and that the easiest workaround is to run cargo test as a root or administrative user. The Makefile comments explain the per-OS rules: Linux can run without root when CAP_NET_RAW is set, FreeBSD and OS X need read and write access to /dev/bpf for layer 2 while higher layers still need root, and Windows runs everything as admin.

Official sources

  1. Issues
  2. libpnet/libpnet on GitHub
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/libpnet-libpnet.svg)](https://hysenlabs.com/projects/libpnet-libpnet)