https://github.com/tapman104/zig-nacap
Idiomatic Zig wrapper for Npcap — packet capture and protocol decoding on Windows
https://github.com/tapman104/zig-nacap
ncap networking packet-capture windows zig
Last synced: 9 days ago
JSON representation
Idiomatic Zig wrapper for Npcap — packet capture and protocol decoding on Windows
- Host: GitHub
- URL: https://github.com/tapman104/zig-nacap
- Owner: tapman104
- License: mit
- Created: 2026-04-15T03:30:30.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-15T15:33:35.000Z (2 months ago)
- Last Synced: 2026-04-15T17:09:08.735Z (2 months ago)
- Topics: ncap, networking, packet-capture, windows, zig
- Language: Zig
- Homepage:
- Size: 46.9 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Roadmap: docs/roadmap.md
Awesome Lists containing this project
README
# npcap_zig
Idiomatic Zig 0.16 wrapper for [Npcap](https://npcap.com/) on Windows.
Zero-allocation packet capture with pure-Zig protocol decoders.
No libc, no C++ headers, no global state leaking into userspace.
---
## Requirements
| Requirement | Details |
|---|---|
| **OS** | Windows 10 / 11 (64-bit) |
| **Npcap** | [npcap.com](https://npcap.com/#download) — must be installed before running |
| **Npcap SDK** | [npcap-sdk-1.13.zip](https://npcap.com/dist/npcap-sdk-1.13.zip) extracted to `C:\npcap-sdk` |
| **Zig** | 0.16.0 |
| **Privileges** | Run executables **as Administrator** |
---
## Adding as a Dependency
1. In your project's `build.zig.zon`, add:
```zig
.dependencies = .{
.npcap_zig = .{
.url = "https://github.com/youruser/npcap_zig/archive/refs/tags/v0.1.0.tar.gz",
.hash = "",
},
},
```
2. In your `build.zig`, fetch and import the module:
```zig
const npcap_dep = b.dependency("npcap_zig", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("npcap_zig", npcap_dep.module("npcap_zig"));
```
---
## Quick-Start Usage
```zig
const std = @import("std");
const npcap_zig = @import("npcap_zig");
const capture = npcap_zig.capture;
const proto = npcap_zig.proto;
pub fn main() !void {
const allocator = std.heap.c_allocator;
// 1. List network interfaces
const devices = try capture.listDevices(allocator);
defer capture.freeDevices(allocator, devices);
// 2. Open the first non-loopback interface
var chosen: ?capture.Device = null;
for (devices) |dev| {
if (!dev.is_loopback) { chosen = dev; break; }
}
const dev = chosen orelse return;
const name_z = try allocator.dupeZ(u8, dev.name);
defer allocator.free(name_z);
var cap = try capture.openDevice(name_z, 65535, true, 1000);
defer cap.close();
// 3. Optional BPF filter
try cap.setFilter("tcp port 80");
// 4. Capture loop
while (true) {
const pkt = cap.nextPacket() orelse continue;
// 5. Decode ETH → IPv4 → TCP
const eth = try proto.eth.parseEthernet(pkt.data);
if (eth.ether_type != .ipv4) continue;
const ip = try proto.ipv4.parseIpv4(eth.payload);
if (ip.proto != .tcp) continue;
const tcp = try proto.tcp.parseTcp(ip.payload);
var ib1: [15]u8 = undefined;
var ib2: [15]u8 = undefined;
std.debug.print("TCP {s}:{d} → {s}:{d} payload={d}b\n", .{
proto.ipv4.formatIp(ip.src, &ib1), tcp.src_port,
proto.ipv4.formatIp(ip.dst, &ib2), tcp.dst_port,
tcp.payload.len,
});
}
}
```
---
## Protocol Support
| Layer | Protocol | Function | Return type |
|---|---|---|---|
| L2 | Ethernet | `proto.eth.parseEthernet` | `ParseError!EthernetFrame` |
| L2 | ARP | `proto.arp.parseArp` | `ParseError!ArpPacket` |
| L3 | IPv4 | `proto.ipv4.parseIpv4` | `ParseError!Ipv4Header` |
| L3 | IPv6 | `proto.ipv6.parseIpv6` | `ParseError!Ipv6Header` |
| L4 | ICMPv4 | `proto.icmpv4.parseIcmp` | `ParseError!IcmpMessage` |
| L4 | ICMPv6 | `proto.icmpv6.parseIcmpv6` | `ParseError!Icmpv6Message` |
| L4 | TCP | `proto.tcp.parseTcp` | `ParseError!TcpSegment` |
| L4 | UDP | `proto.udp.parseUdp` | `ParseError!UdpDatagram` |
| L7 | DNS | `proto.dns.parseDns` | `ParseError!DnsMessage` |
| L7 | HTTP/1.x | `proto.http.detect` | `?HttpHint` |
All parsers are **pure functions**: no allocator, no I/O, no side effects.
All string/slice results point into the **original packet buffer** — zero-copy.
---
## TCP Flow Tracking
The library includes a stateful TCP flow tracker that handles normalization (client/server detection) and connection state transitions.
```zig
const flow = npcap_zig.flow;
// ... inside capture loop ...
const status = flow.processTcp(&flow_table, pkt, ip.src, ip.dst, tcp, ip.payload.len);
// status: "flow=NEW", "flow=ESTABLISHED", "flow=FIN_WAIT", etc.
```
---
## Examples
| File | Build step | Description |
|---|---|---|
| [`examples/basic_capture.zig`](examples/basic_capture.zig) | `zig build run` | Full multi-protocol sniffer with flow tracking |
| [`examples/dns_monitor.zig`](examples/dns_monitor.zig) | `zig build dns_monitor` | BPF-filtered DNS query logger |
### Building
```powershell
# Build + run the full sniffer (20 packets)
zig build run
# Build + run the DNS logger (runs until Ctrl+C)
zig build dns_monitor
# Build everything without running
zig build
```
---
## Module Layout
```
src/
root.zig ← Public API re-exports
capture.zig ← Npcap live/file capture engine
packet.zig ← ParsedPacket and Layer3/4 unions
flow/
tracker.zig ← TCP flow tracking state machine
proto/
errors.zig ← Shared parsing error set
eth.zig ← Ethernet II frame parser
ipv4.zig ← IPv4 header parser
ipv6.zig ← IPv6 header + extension headers
arp.zig ← Address Resolution Protocol (ARP)
tcp.zig ← TCP segment parser
udp.zig ← UDP datagram parser
icmpv4.zig ← ICMPv4 message parser
icmpv6.zig ← ICMPv6 / NDP parser
dns.zig ← DNS (RFC 1035) Question/Answer parser
http.zig ← HTTP/1.1 method/host detector
backend/
npcap_raw.zig ← Low-level pcap DLL wrapper
```
---
## License
[MIT](LICENSE)