This package provides a simple yet powerful interface to perform network packet capture / sniffing. It is focused on high performance / traffic throughput.
- Support for raw payload / IP layer packet capture via AF_PACKET (Linux) directly from network socket or using a ring buffer
- Minimal CPU usage and memory (allocation) footprint, support for zero-copy operations
- Virtual / mock capture sources including traffic replay from PCAP files (or even "chaining" multiple sources)
- Inherent support for packet type / direction detection
- Written in native Go (no
CGO
dependency)
Warning
This package does not perform any payload / network layer decoding
slimcap
is aimed at doing the heavy lifting of extracting up to the IP layer of network packets with the utmost performance possible (and hence limits itself to packets which actually have an IP layer). All further parsing / processing must
be done by the caller.
go get -u github.com/fako1024/slimcap
Perform simple capture of a few packets on a network interface using the various options of the capture.Source
interface, using a fixed capture length (i.e. snaplen) of 64 bytes:
listener, err := afpacket.NewSource("enp1s0",
afpacket.CaptureLength(link.CaptureLengthFixed(64)),
)
if err != nil {
// Error handling
}
// Capture a packet from the wire (allocate & copy)
p, err := listener.NextPacket(nil)
if err != nil {
// Error handling
}
fmt.Printf("Received packet on enp1s0 (total len %d): %v (inbound: %v)\n",
p.TotalLen(), p.Payload(), p.IsInbound())
// Capture a packet from the wire (copy to existing / reusable buffer packet)
pBuf := listener.NewPacket()
p, err := listener.NextPacket(pBuf)
if err != nil {
// Error handling
}
fmt.Printf("Received packet on enp1s0 (total len %d): %v (inbound: %v)\n",
p.TotalLen(), p.Payload(), p.IsInbound())
// Capture a packet from the wire (function execution)
if err := listener.NextPacketFn(func(payload []byte, totalLen uint32, pktType, ipLayerOffset byte) (err error) {
fmt.Printf("Received packet on enp1s0 (total len %d): %v (inbound: %v)\n",
totalLen, payload, pktType != capture.PacketOutgoing)
return
}); err != nil {
// Error handling
}
// Close the listener / the capture
if err := listener.Close(); err != nil {
// Error handling
}
Perform zero-copy capture of a few packets on a network interface using the various options of the capture.SourceZeroCopy
interface, using an optimal capture length (i.e. snaplen) to ensure any transport layer can be accessed for IPv4 & IPv6 packets and setting custom ring buffer size / number of blocks:
listener, err := afring.NewSource("enp1s0",
afring.CaptureLength(link.CaptureLengthMinimalIPv6Transport),
afring.BufferSize((1<<20), 4),
afring.Promiscuous(false),
)
if err != nil {
// Error handling
}
// Capture a raw packet (full payload) from the wire (zero-copy, no heap allocation)
payload, pktType, totalLen, err := listener.NextPayloadZeroCopy()
if err != nil {
// Error handling
}
fmt.Printf("Received payload on enp1s0 (total len %d): %v (inbound: %v)\n",
totalLen, payload, pktType != capture.PacketOutgoing)
// Capture a packet (IP layer only) from the wire (zero-copy, no heap allocation)
ipLayer, pktType, totalLen, err := listener.NextIPPacketZeroCopy()
if err != nil {
// Error handling
}
fmt.Printf("Received IP layer on enp1s0 (total len %d): %v (inbound: %v)\n",
totalLen, ipLayer, pktType != capture.PacketOutgoing)
// Close the listener / the capture
if err := listener.Close(); err != nil {
// Error handling
}
Warning
In zero-copy mode, any and all interaction with the payload / IP Layer must be concluded prior to the next invocation of Next...ZeroCopy()
since the calls provide direct access to the memory areas allocated by AF_PACKET (which may be overwritten by the next call)!
For further examples, please refer to the implementations in examples. A production-level project that uses slimcap
and showcases all its capabilities (including end-to-end testing using mock sources) is goProbe.
The following benchmarks (c.f. afring_mock_test.go) show the relative difference in general performance and memory allocation footprint of a single packet retrieval (obtained on a commodity Laptop using mock capture sources). For obvious reasons, zero-copy mode performs best and hence should be chosen in high-throughput scenarios:
goarch: amd64
pkg: github.com/fako1024/slimcap/capture/afpacket/afring
cpu: Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz
BenchmarkCaptureMethods
BenchmarkCaptureMethods/NextPacket 226401712 74.60 ns/op 64 B/op 1 allocs/op
BenchmarkCaptureMethods/NextPacketInPlace 353661006 32.17 ns/op 0 B/op 0 allocs/op
BenchmarkCaptureMethods/NextPayload 176332635 63.81 ns/op 48 B/op 1 allocs/op
BenchmarkCaptureMethods/NextPayloadInPlace 516911223 21.65 ns/op 0 B/op 0 allocs/op
BenchmarkCaptureMethods/NextPayloadZeroCopy 535092314 19.67 ns/op 0 B/op 0 allocs/op
BenchmarkCaptureMethods/NextIPPacket 179753388 64.18 ns/op 48 B/op 1 allocs/op
BenchmarkCaptureMethods/NextIPPacketInPlace 381187490 28.33 ns/op 0 B/op 0 allocs/op
BenchmarkCaptureMethods/NextIPPacketZeroCopy 567278034 19.67 ns/op 0 B/op 0 allocs/op
BenchmarkCaptureMethods/NextPacketFn 559334258 20.44 ns/op 0 B/op 0 allocs/op
In order to support extensive testing up to end-to-end level without having to rely on actual network interface capture, both plain AF_PACKET and ring buffer captures are provided with mock-level sources implementing / wrapping their actual implementations. Hence, it is possible to simply exchange any invocation of an afpacket
or afring
source with their respective mock counterpart, e.g. by changing:
listener, err := afring.NewSource("enp1s0",
// Options
)
to
listener, err := afring.NewMockSource("enp1s0",
// Options
)
By either generating synthetic packet data or piping previously captured packets from a PCAP file these mock sources can then be used just like actual capture sources, e.g. for testing purposes. Some good examples on how to perform test using mocks can be found in afpacket_mock_test.go and afring_mock_test.go, respectively.
Since the mock implementations incur a minor, yet significant performance overhead (even if not used), slimcap
supports a build tag that allows disabling mocks completely (which in turn will also remove the aforementioned overhead):
go build -tags slimcap_nomock
Using this build tag for high performance production environments is recommended.
Please use the issue tracker for bugs and feature requests (or any other matter).
See the LICENSE file for usage conditions.