fako1024 / slimcap

A high-performance network packet capture library
Apache License 2.0
7 stars 0 forks source link
afpacket packet-capture packet-sniffing ringbuffer

A high-performance network packet capture library

Github Release GoDoc Go Report Card Build/Test Status CodeQL

This package provides a simple yet powerful interface to perform network packet capture / sniffing. It is focused on high performance / traffic throughput.

Features

[!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.

Installation

go get -u github.com/fako1024/slimcap

Usage

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.

Performance

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

Capture Mocks / Testing

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.

Bug Reports & Feature Requests

Please use the issue tracker for bugs and feature requests (or any other matter).

License

See the LICENSE file for usage conditions.