Overview of GoPacket
mastergopcap project.repository·master·Indexed 27 days ago
https://github.com/google/gopacketA high-performance Go library for packet decoding and analysis. It provides tools for capturing network traffic, decoding protocol layers, TCP stream reassembly, and IPv4 defragmentation. Key features include support for lazy and no-copy decoding, a DecodingLayerParser for high-performance preallocated decoding, and interfaces for Link, Network, Transport, and Application layers.
gopcap project.The minimum Go version required to use GoPacket is 1.5.
However, if you intend to use the following specific packages, you must use at least Go 1.9 due to x/sys/unix dependencies:
pcapgo/EthernetHandleafpacketbsdbpfTo read packets from an interface or file, construct a PacketSource. This typically involves using a PacketDataSource implementation (available in gopacket/pcap or gopacket/pfring). The easiest way to consume packets is via the Packets() method, which returns a channel that asynchronously writes new packets and closes when the source reaches EOF.
packetSource := ... // construct using pcap or pfring
for packet := range packetSource.Packets() {
handlePacket(packet) // do something with each packet
}The DecodingLayerParser provides a fast way to decode packet layers by reusing existing layer instances instead of allocating new ones for every packet. This is significantly faster than standard packet decoding.
To use it:
NewDecodingLayerParser, providing the starting LayerType and a list of DecodingLayer instances (e.g., pointers to your layer structs).LayerType to store the results of each successful decode.DecodeLayers(data, &decodedLayers) in your processing loop.decodedLayers slice and use a type switch to access the data in your layer instances.If the parser encounters a layer it doesn't recognize, it returns an UnsupportedLayerType error unless IgnoreUnsupported is set to true in DecodingLayerParserOptions.
var eth layers.Ethernet
var ip4 layers.IPv4
var ip6 layers.IPv6
var tcp layers.TCP
var udp layers.UDP
var payload gopacket.Payload
// Initialize parser with the first layer type and the layer instances
parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, ð, &ip4, &ip6, &tcp, &udp, &payload)
// Slice to hold the sequence of decoded layer types
decodedLayers := make([]gopacket.LayerType, 0, 10)
// Decode the data
err := parser.DecodeLayers(data, &decodedLayers)
for _, typ := range decodedLayers {
switch typ {
case layers.LayerTypeEthernet:
fmt.Println("Eth ", eth.SrcMAC, eth.DstMAC)
case layers.LayerTypeIPv4:
fmt.Println("IP4 ", ip4.SrcIP, ip4.DstIP)
// ... handle other types
}
}By using gopacket.Lazy as the decoding option in NewPacket, gopacket only decodes a layer when it is explicitly requested via a function call. This can save significant processing time if you only need to inspect specific parts of a packet.
Warning: Lazily-decoded packets are not concurrency-safe because calls to Layer() or Layers() may mutate the packet object to perform the decoding.
// Create a packet, but don't actually decode anything yet
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
// Now, decode the packet up to the first IPv4 layer found but no further.
ip4 := packet.Layer(layers.LayerTypeIPv4)
// Decode all layers and return them.
layers := packet.Layers()If you can guarantee that the underlying byte slice provided to NewPacket will not be modified, use gopacket.NoCopy. This tells gopacket to use the passed-in slice directly instead of creating a copy, reducing memory overhead.
// This channel returns new byte slices, each of which points to a new
// memory location that's guaranteed immutable for the duration of the
// packet.
for data := range myByteSliceChannel {
p := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
doSomethingWithPacket(p)
}To handle custom encapsulation, you can implement a custom decoder by:
gopacket.RegisterLayerType.Layer interface (LayerType(), LayerContents(), and LayerPayload()).DecodeFunc that uses PacketBuilder.AddLayer() to add your layer and PacketBuilder.NextDecoder() to continue the chain.// Create a layer type
var MyLayerType = gopacket.RegisterLayerType(12345, gopacket.LayerTypeMetadata{Name: "MyLayerType", Decoder: gopacket.DecodeFunc(decodeMyLayer)})
// Implement my layer
type MyLayer struct {
StrangeHeader []byte
payload []byte
}
func (m MyLayer) LayerType() gopacket.LayerType { return MyLayerType }
func (m MyLayer) LayerContents() []byte { return m.StrangeHeader }
func (m MyLayer) LayerPayload() []byte { return m.payload }
// Implement a decoder
func decodeMyLayer(data []byte, p gopacket.PacketBuilder) error {
p.AddLayer(&MyLayer{data[:4], data[4:]})
return p.NextDecoder(layers.LayerTypeEthernet)
}
// Decode
p := gopacket.NewPacket(data, MyLayerType, gopacket.Lazy)A PacketSource is used to read and decode packets from a PacketDataSource. There are two primary ways to consume packets:
Packets() channel (Convenient)Returns a channel of Packet objects. This is asynchronous and easy to iterate over. The channel closes when the source reaches io.EOF.
for packet := range packetSource.Packets() {
// handle packet
}NextPacket() method (Flexible/Fast)Returns the next decoded packet and an error. This is faster in tight loops and allows you to handle specific errors (like io.EOF or network errors) manually.
for {
packet, err := packetSource.NextPacket()
if err == io.EOF {
break
}
if err != nil {
// handle error
continue
}
// handle packet
}// Using the channel approach
for packet := range packetSource.Packets() {
handlePacket(packet)
}
// Using the NextPacket approach
for {
packet, err := packetSource.NextPacket()
if err == io.EOF {
break
} else if err != nil {
log.Println("Error:", err)
continue
}
handlePacket(packet)
}The DecodeOptions struct allows you to tune the performance and behavior of the packet decoder. You can use the provided package variables for common configurations:
| Option | Description |
|---|---|
Lazy | Decodes minimum layers required per call. Faster, but not concurrency-safe. |
NoCopy | Does not copy the input buffer. Faster, but requires the underlying slice to remain immutable. |
SkipDecodeRecovery | If true, panics during decoding are allowed to propagate instead of being caught and turned into an ErrorLayer. |
DecodeStreamsAsDatagrams | Enables routing of application-level layers in the TCP decoder. |
Predefined Options:
gopacket.Default: Safest/slowest (Eager + Copy).gopacket.Lazy: Optimized for single-threaded use.gopacket.NoCopy: Optimized for immutable buffers.Use SerializeOptions to control how layers are encoded during serialization:
FixLengths: If true, layers should fix the values for any length fields that depend on the payload.ComputeChecksums: If true, layers should recompute checksums based on their payloads.You can customize the behavior of a DecodingLayerParser using the DecodingLayerParserOptions struct:
IgnorePanic (bool): If true, panics occurring during decoding are caught and returned as errors from DecodeLayers. If false (default), panics will propagate up the stack. Enabling this adds slight latency but increases safety.IgnoreUnsupported (bool): If true, the parser stops decoding when it hits an unknown layer and returns nil instead of an UnsupportedLayerType error. If true, you must verify the contents of the decoded slice to ensure you got the layers you expected.To write multiple packet layers into a byte buffer, use SerializeLayers. This function clears the provided buffer and then serializes the layers in reverse order so that they correctly wrap each other (e.g., if you pass layers A, B, and C, the resulting buffer will contain the encoding for A(B(C))).
Note: SerializeLayers calls w.Clear(), which invalidates any byte slices previously returned by w.Bytes() from that buffer.
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{}
// layers a, b, c are SerializableLayer implementations
gopacket.SerializeLayers(buf, opts, a, b, c)
firstPayload := buf.Bytes() // contains byte representation of a(b(c))