go-pcap

This is a native go packet processing library. It performs a function very similar to libpcap, or, for that matter, github.com/google/gopacket/pcap, except that it is 100% native go. This means that:

  • you can build it with CGO_ENABLED=0
  • cross-compiling is simple

It also include a simple binary called pcap that exercises the library by capturing packets and outputting some of the data.

Operating Systems

The current support is for Linux and macOS/Darwin. Eventually, we will port to other OSes (and happily will take Pull Requests).

How To Use It

Library

The library has single primary entrypoint, pcap.OpenLive. This will return a pcap.Handle that can be used to loop and read packets.

if handle, err = pcap.OpenLive(iface, 1600, true, 0); err != nil {
    log.Fatal(err)
}
for {
    data, captureInfo, error := handle.ReadPacketData()
}

ReadPacketData() will block until there is packet information available, or until timeout is reached. You can set an infinite timeout with 0.

The returned information will be the packet bytes themselves, excluding the system-defined headers, i.e. the Ethernet frame and all contents.

Handle is 100% compatible with gopacket.Handle; you can use it to process packets, analyze layers, and anything else you would want. Note that Handle copies packet data before passing them to gopacket in order to avoid possible race conditions with Handle.Close(), which when called un-maps buffer shared with the kernel containing captured data. This means that gopacket can be configured to not perform any additional copying.

import (
    pcap "github.com/packetcap/go-pcap"
    "github.com/google/gopacket"
)

if handle, err = pcap.OpenLive(iface, 1600, true, 0); err != nil {
        log.Fatal(err)
}
packetSource := gopacket.NewPacketSource(handle, layers.LinkTypeEthernet)
packetSource.NoCopy = true
for packet := range packetSource.Packets() {
        processPacket(packet)
}

If you know you don't want all of that overhead, you can use the Listen interface, which returns a chan to which it will send packets.

if handle, err = pcap.OpenLive(iface, 1600, true, 0); err != nil {
        log.Fatal(err)
}
for packet := range handle.Listen() {
        processPacket(packet.B)
}

pcap.Listen will start a separate goroutine, so you do not have to. pcap.Listen is a one-shot, "open a socket, listen for packets, send them down my channel" convenience.

Filters

The library (and CLI below) support using libpcap-style filters. You simply need to set the filter on the handle returned by pcap.OpenLive()

if handle, err = pcap.OpenLive(iface, 1600, true, 0); err != nil {
        log.Fatal(err)
}
err = handle.SetBPFFilter(filter)
if err != nil {
    // handle error
}
packetSource := gopacket.NewPacketSource(handle, layers.LinkTypeEthernet)
packetSource.NoCopy = true
for packet := range packetSource.Packets() {
        processPacket(packet)
}

If you are using the simple channel method pcap.Listen(), you also can filter it:

if handle, err = pcap.OpenLive(iface, 1600, true, 0); err != nil {
        log.Fatal(err)
}
err = handle.SetBPFFilter(filter)
if err != nil {
    // handle error
}
for packet := range pcap.Listen() {
        processPacket(packet.B)
}

The filter is a string that matches the tcpdump syntax from libcap.

Efficiency

The Linux implementation supports both syscall-based packet reads and mmap-based packet reads. The syscall read is fine for just a few packets, or a lightly loaded system. However, making a syscall to retrieve each packet can get very slow, very quickly. For faster purposes, you can use a shared mmap buffer with the kernel.

The OpenLive() call uses mmap by default.

CLI

There is a sample command-line utility included. To build it:

$ make build

Or for an alternate OS:

$ make build OS=linux

The binaries will be output as dist/pcap-<os>-<arch>. Additionally, if you are building for your local OS+arch, a binary named pcap will be deposited in the current directory, so you can just do ./pcap.

For options, run ./pcap --help. It also supports using filters.