Zero-copy L2/L3 packet handling library for Go.
pktkit provides primitives for building virtual network topologies: devices, hubs, adapters, and tunnels that move Ethernet frames and IP packets without copying buffers on the hot path.
- Zero-copy types:
FrameandPacketare[]bytealiases with typed header accessors — no wrapper allocation - Callback-based forwarding: synchronous
func(T) errorhandler callbacks matchingSendsignatures, no channels - L2Device / L3Device interfaces: uniform API for all network devices
- IPv4 and IPv6 support throughout the stack
- L2Hub: MAC-learning switch with 5-minute aging — forwards unicast to learned ports, floods unknown/broadcast/multicast
- L3Hub: routing hub with prefix-based unicast forwarding and broadcast/multicast delivery
- ConnectL2 / ConnectL3: point-to-point wiring helpers (
a.SetHandler(b.Send)) - L2Connector / L3Connector: lifecycle-managed device attachment with cleanup callbacks
- Serve: accept loop connecting incoming L2Devices to a connector, with automatic cleanup on disconnect
- L2Adapter: bridges an L3 device onto an L2 network with ARP (IPv4), NDP (IPv6), DHCP client, and gateway routing. Random MAC generated by default.
- DHCPServer: L2 device serving DHCP leases with configurable IP pool, router, DNS, and lease time. Handles DISCOVER, OFFER, REQUEST, ACK, RELEASE, DECLINE, and INFORM.
- slirp: userspace NAT stack implementing L3Device — routes virtual traffic to the real network via
net.Dial, with namespace-isolated connection tracking for multi-tenant use (ConnectL3). Supports IPv4/IPv6 TCP, UDP, and ICMPv6. - vclient: virtual network client implementing L3Device — provides
Dial,Listen,net.Conn, DNS resolution, andhttp.Client - vtcp: pure RFC-compliant TCP protocol engine (congestion control, SACK, timestamps, window scaling, SYN cookies)
- nat: packet-level IPv4 NAT between two virtual L3 networks with ALGs (FTP, SIP, H.323, PPTP, TFTP, IRC), NAT64, defragmentation, namespace isolation, and UPnP
- wg: WireGuard tunnel implementation with Noise IK handshake, per-peer L3 isolation, and adapter bridging peers to pktkit networks
- ovpn: OpenVPN server with TLS key exchange, AES-CBC/GCM encryption, per-peer L3/L2 isolation, and username/password authentication
- tuntap: OS-level TUN/TAP devices with IP address and route configuration (Linux and macOS)
- qemu: QEMU userspace network socket protocol (both client and server/listener)
import "github.com/KarpelesLab/pktkit"
tun1 := pktkit.NewPipeL3(netip.MustParsePrefix("10.0.0.1/24"))
tun2 := pktkit.NewPipeL3(netip.MustParsePrefix("10.0.0.2/24"))
pktkit.ConnectL3(tun1, tun2)hub := pktkit.NewL2Hub()
// DHCP server
dhcpSrv := pktkit.NewDHCPServer(pktkit.DHCPServerConfig{
ServerIP: netip.MustParseAddr("192.168.0.1"),
SubnetMask: net.CIDRMask(24, 32),
RangeStart: netip.MustParseAddr("192.168.0.10"),
RangeEnd: netip.MustParseAddr("192.168.0.100"),
Router: netip.MustParseAddr("192.168.0.1"),
DNS: []netip.Addr{netip.MustParseAddr("1.1.1.1")},
})
hub.Connect(dhcpSrv)
// NAT gateway (slirp routes to real internet)
stack := slirp.New()
stack.SetAddr(netip.MustParsePrefix("192.168.0.1/24"))
hub.Connect(pktkit.NewL2Adapter(stack, nil))
// Virtual client — gets IP via DHCP, can dial out
client := vclient.New()
adapter := pktkit.NewL2Adapter(client, nil)
hub.Connect(adapter)
adapter.StartDHCP()
// Use standard Go HTTP client over the virtual network
resp, _ := client.HTTPClient().Get("https://example.com")Each WireGuard peer gets a namespace-isolated NAT connection on a single slirp stack. Peers can share the same IP address without conflict.
stack := slirp.New()
stack.SetAddr(netip.MustParsePrefix("10.0.0.1/24"))
adapter, _ := wg.NewAdapter(wg.AdapterConfig{
Connector: stack, // each peer gets isolated NAT via ConnectL3
})
udp, _ := net.ListenPacket("udp4", ":51820")
go adapter.Serve(udp)
// Add authorized peers
adapter.AddPeer(clientPublicKey)Connect QEMU VMs via the socket protocol. Each accepted connection can join a shared hub or get an isolated namespace.
ln, _ := qemu.Listen("unix", "/tmp/qemu.sock")
hub := pktkit.NewL2Hub()
// hub.Connect(pktkit.NewL2Adapter(stack, nil)) // add a NAT gateway
pktkit.Serve(ln, hub) // accept loop: each VM joins the hubAll data-plane hot paths are zero-allocation. Benchmarks on an i9-14900K (32 threads):
| Path | ns/op | Throughput | Allocs |
|---|---|---|---|
| Frame accessors | 0.63 | 2.4 TB/s | 0 |
| Packet IPv4 accessors | 0.92 | 1.6 TB/s | 0 |
| Packet IPv6 accessors | 0.67 | 2.2 TB/s | 0 |
| L2Hub unicast forward | 50 | 30 GB/s | 0 |
| L2Hub forward (parallel) | 2.3 | 656 GB/s | 0 |
| L3Hub route | 9.2 | 163 GB/s | 0 |
| L2Adapter incoming (frame to packet) | 33 | 46 GB/s | 0 |
| L2Adapter outgoing (packet to frame, pooled) | 61 | 24 GB/s | 0 |
| Checksum (1500 B) | 273 | 5.5 GB/s | 0 |
| SendBuf PeekUnsent | 0.24 | 6.1 TB/s | 0 |
| ARP/NDP table lookup | 37 | - | 0 |
| Path | ns/op | Throughput | Allocs |
|---|---|---|---|
| NAT outbound TCP | 40 | 36 GB/s | 0 |
| NAT inbound TCP | 41 | 35 GB/s | 0 |
| NAT outbound UDP | 34 | 3.7 GB/s | 0 |
| NAT mapping lookup | 12 | - | 0 |
| Defrag (non-fragment fast path) | 1.4 | 1.0 TB/s | 0 |
| Path | ns/op | Throughput | Allocs |
|---|---|---|---|
| Encrypt (EncryptTo, zero-alloc) | 555 | 2.6 GB/s | 0 |
| Encrypt + Decrypt (zero-alloc) | 1131 | 1.3 GB/s | 0 |
| Encrypt (Encrypt, allocating) | 2417 | 588 MB/s | 1 |
| Decrypt (ProcessPacket) | 4135 | 343 MB/s | 1 |
| Handshake (full initiation + response) | 578k | - | 481 |
| Key generation | 57 | - | 0 |
| Replay filter | 9.1 | - | 0 |
| Peer lookup | 13 | - | 0 |
| Path | ns/op | Throughput | Allocs |
|---|---|---|---|
| GCM encrypt | 227 | 6.3 GB/s | 1 |
| GCM decrypt | 202 | 7.0 GB/s | 0 |
| CBC encrypt | 8307 | 171 MB/s | 15 |
| Replay window | 9.2 | - | 0 |
| PRF 1.2 (256 B) | 3497 | 73 MB/s | 25 |
| Options parse | 721 | - | 4 |
Both benchmarks use real UDP sockets on 127.0.0.1. Throughput is measured with a saturated pipeline (sender blasts, receiver counts); latency is per-packet synchronous send-wait.
| Metric | WireGuard (ChaCha20-Poly1305) | OpenVPN (AES-256-GCM) |
|---|---|---|
| Throughput | 299 MB/s (2.4 Gbps) | 678 MB/s (5.4 Gbps) |
| Latency | 23.9 us | 20.0 us |
OpenVPN GCM is faster end-to-end on x86 because AES-GCM uses AES-NI hardware acceleration, while ChaCha20-Poly1305 is a software cipher. On platforms without AES-NI (e.g. ARM without crypto extensions), WireGuard's ChaCha20 is typically faster.
Run benchmarks with go test -bench=. -benchmem ./....
MIT — see LICENSE.