NETProtocols
Low-level implementations of common networking protocols, in pure Python with zero dependencies.
Decode raw header bytes into typed, immutable protocol objects — or build those objects from field values and serialize them back to their exact on-wire form. Every header is a frozen dataclass whose fields mirror the wire format, so decoded traffic is introspectable, comparable, and round-trippable.
>>> from netprotocols import Ethernet
>>> eth = Ethernet.decode(frame)
>>> eth
Ethernet(dst='ff:ff:ff:ff:ff:ff', src='00:07:0d:af:f4:54', ethertype=2054)
>>> eth.ethertype_name
'ARP'
>>> eth.next_protocol()
<class 'netprotocols.layer2.arp.ARP'>
>>> bytes(eth) == frame[:eth.header_len]
True
Installation
pip install netprotocols
Requires Python 3.12+. Fully typed (py.typed, mypy strict).
Protocol coverage
| Layer | Protocol | Class | Notes |
|---|---|---|---|
| 2 | Ethernet II | Ethernet |
IEEE 802.3 |
| 2 | IEEE 802.1Q VLAN tag (802.1ad QinQ) | VLAN |
802.1Q-2018 §9.6, PCP/DEI/VID, tag stacking |
| 2 | ARP | ARP |
RFC 826, IPv4-over-Ethernet binding |
| 3 | IPv4 | IPv4 |
RFC 791, IHL/options aware |
| 3 | IPv6 | IPv6 |
RFC 8200 |
| 3 | IPv6 Hop-by-Hop Options | IPv6HopByHopOptions |
RFC 8200 §4.3 |
| 3 | IPv6 Routing | IPv6Routing |
RFC 8200 §4.4 |
| 3 | IPv6 Fragment | IPv6Fragment |
RFC 8200 §4.5, first-fragment chaining |
| 3 | IPv6 Destination Options | IPv6DestinationOptions |
RFC 8200 §4.6 |
| 3 | ICMPv4 | ICMPv4 |
RFC 792, 8-byte header |
| 3 | ICMPv6 | ICMPv6 |
RFC 4443, 8-byte header |
| 3 | IGMP | IGMP |
RFC 1112/2236/3376, IPv4 multicast management; IGMPv3 report group records + query fields |
| 3 | GRE | GRE |
RFC 2784/2890, IP protocol 47; payload chains onward by EtherType |
| 4 | TCP | TCP |
RFC 9293, data-offset/options aware |
| 4 | UDP | UDP |
RFC 768 |
| 7 | DNS | DNS |
RFC 1035, over UDP and TCP (DNSOverTCP length shim); on-demand name decompression + resource-record parsing |
| 7 | DHCP | DHCP |
RFC 2131/2132, over UDP 67/68; TLV options parsed on demand |
Decoding a captured frame
Each class decodes its own header from the start of a buffer and
tolerates trailing bytes, so you can walk a whole frame with two pieces
of information every header provides: header_len (how many bytes it
consumed) and next_protocol() (which class decodes what follows).
from netprotocols import Ethernet
def decode_frame(frame: bytes) -> list:
layers, cursor, protocol = [], 0, Ethernet
while protocol is not None:
header = protocol.decode(frame[cursor:])
layers.append(header)
cursor += header.header_len
protocol = header.next_protocol()
return layers # e.g. [Ethernet(...), IPv4(...), TCP(...)]
Malformed input raises exceptions rooted at a single base class:
from netprotocols import ProtocolError, TruncatedHeaderError, InvalidFieldError
try:
layers = decode_frame(frame)
except TruncatedHeaderError: # buffer shorter than the header claims
...
except InvalidFieldError: # nonsense field values (IHL < 5, bad address)
...
except ProtocolError: # catches every library error
...
Building and serializing headers
Constructors take friendly values (string MAC/IP addresses, integer
fields) and validate them; bytes() emits the exact on-wire form.
Packet concatenates a stack of layers:
from netprotocols import ARP, Ethernet, Packet, random_mac
sha = random_mac()
frame = Packet(
Ethernet(dst="ff:ff:ff:ff:ff:ff", src=sha, ethertype=0x0806),
ARP(htype=1, ptype=0x0800, hlen=6, plen=4, oper=1,
sha=sha, spa="192.168.1.96",
tha="00:00:00:00:00:00", tpa="192.168.1.254"),
)
raw = bytes(frame) # ready for a raw socket
Checksums are computed and verified on request — never silently:
compute()/verify() in netprotocols.checksum, and
Packet.with_checksums() to fill a whole stack before sending.
Display helpers
Every class exposes human-readable properties next to the raw fields:
Ethernet.ethertype_name, IPv4.protocol_name, IPv4.flags_name,
TCP.flags_str ("SYN ACK"), ARP.oper_name, ICMPv4.type_name,
and hexadecimal renderings such as checksum_hex_str. Unknown values
degrade gracefully ("0x88cc", "unknown (47)") instead of raising.
How it works
See ARCHITECTURE.md for a guided tour: how a header
byte layout maps onto a dataclass, the decode contract, the
next_protocol() chain, and a cookbook for adding a new protocol.
This library is the engine behind RootWire, a network traffic monitor built on it (formerly Packet-Sniffer).
Roadmap
The next wave is decoder depth on already-supported layers, tracked by #67:
- TCP option parsing — MSS, window scale, SACK, timestamps (#59).
- ICMP message bodies — echo identifier/sequence, the embedded original packet in error messages (#60).
- IPv6 Neighbor Discovery (NDP) messages and options (#61).
- TLV parsing inside IPv6 extension-header options (#62).
- A GRE arm for
netprotocols.checksum(#63). - A real-capture DNS-over-TCP corpus fixture (#64).
- Richer address accessors (
ipaddressobjects alongside thestrfields) (#65). - IPv4 option parsing — Router Alert, Record Route, Timestamp (#66).
Contributing
Development uses uv: uv sync, then
uv run pytest, uv run mypy, and uv run ruff check — all three are
enforced by CI on Python 3.12–3.14. The test suite is anchored by a
97-frame corpus of real captured traffic across 17 scenarios
(tests/fixtures/MANIFEST.md) plus
property-based fuzzing of the decode path. See
CONTRIBUTING.md for the full workflow and
ARCHITECTURE.md for the design and the
add-a-protocol cookbook.
License
Metadata
Release files for netprotocols 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| netprotocols-1.3.0.tar.gz | 149.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| netprotocols-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 201.5 kB
Release files / netprotocols-1.3.0.tar.gz
| Download URL | netprotocols-1.3.0.tar.gz |
|---|---|
| Size | 149.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
259190134a1115a65265da48a20b884df796498d62e6045aa035eb83d30f11c5
|
|
BLAKE2b-256 checksum How to use checksums |
ce19bc47fdffeafbc20512c90b83c69e2d53c9c3db2c231a62d7c57ab9c03528
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / netprotocols-1.3.0-py3-none-any.whl
| Download URL | netprotocols-1.3.0-py3-none-any.whl |
|---|---|
| Size | 51.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a5537a6726ae03705d04d705d3d85a5abba64649cdd6c07e724504252296eba3
|
|
BLAKE2b-256 checksum How to use checksums |
f6fae6de853d7bd2d93ba61f1bf14770ecf19492ef8607be43dbfd14cc10f0ae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|