原文出自http://www.devdungeon.com/content/packet-capture-injection-and-analysis-gopacket

Overview

  • Intro

  • Before Starting

  • Find Devices

  • Open Device for Live Capture

  • Write Pcap File

  • Open Pcap File

  • Setting Filters

  • Decoding Packet Layers

  • Creating and Sending Packets

  • More on Creating/Decoding Packets

  • Custom Layers

  • Decoding Packets Faster

  • TCP Stream Reassembly

  • Additional References

Intro

The gopacket package provides a Go wrapper for libpcap written in C. It is more than just a simple wrapper though. It provides additional functionality and takes advantage of Go things like interfaces, which makes it incredibly powerful.

Before Starting

Install the prerequisites. You will need golibpcap and the gopacket package. Since gopacket is built on top of libpcap, I highly recommend you understand how that library works. You can learn how to use libpcap in C for a deeper understanding. These examples should work in Linux/Mac using libpcap and on Windows with WinPcap. You may need to set GOARCH=386 if you get an error like cc1.exe: sorry, unimplemented: 64-bit mode not compiled in.

# Get the gopacket package from GitHubgo get github.com/google/gopacket# Pcap dev headers might be necessarysudo apt-get install libpcap-dev

You might also want to check out the gopacket project on GitHub and documentation onGoDoc gopacket.

Find devices

package mainimport (    "fmt"    "log"    "github.com/google/gopacket/pcap")func main() {    // Find all devices    devices, err := pcap.FindAllDevs()    if err != nil {        log.Fatal(err)    }    // Print device information    fmt.Println("Devices found:")    for _, device := range devices {        fmt.Println("\nName: ", device.Name)        fmt.Println("Description: ", device.Description)        fmt.Println("Devices addresses: ", device.Description)        for _, address := range device.Addresses {            fmt.Println("- IP address: ", address.IP)            fmt.Println("- Subnet mask: ", address.Netmask)        }    }}

Open Device for Live Capture

package mainimport (    "fmt"    "github.com/google/gopacket"    "github.com/google/gopacket/pcap"    "log"    "time")var (    device       string = "eth0"    snapshot_len int32  = 1024    promiscuous  bool   = false    err          error    timeout      time.Duration = 30 * time.Second    handle       *pcap.Handle)func main() {    // Open device    handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)    if err != nil {log.Fatal(err) }    defer handle.Close()    // Use the handle as a packet source to process all packets    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())    for packet := range packetSource.Packets() {        // Process packet here        fmt.Println(packet)    }}

Write Pcap File

To write a pcap format file, we must use the gopacket/pcapgo package. This comes with a Writer and two useful functions: WriteFileHeader() and WritePacket().

package mainimport (	"fmt"	"os"	"time"	"github.com/google/gopacket"	"github.com/google/gopacket/layers"	"github.com/google/gopacket/pcap"	"github.com/google/gopacket/pcapgo")var (	deviceName  string = "eth0"	snapshotLen int32  = 1024	promiscuous bool   = false	err         error	timeout     time.Duration = -1 * time.Second	handle      *pcap.Handle	packetCount int = 0)func main() {	// Open output pcap file and write header 	f, _ := os.Create("test.pcap")	w := pcapgo.NewWriter(f)	w.WriteFileHeader(snapshotLen, layers.LinkTypeEthernet)	defer f.Close()	// Open the device for capturing	handle, err = pcap.OpenLive(deviceName, snapshotLen, promiscuous, timeout)	if err != nil {		fmt.Printf("Error opening device %s: %v", deviceName, err)		os.Exit(1)	}	defer handle.Close()	// Start processing packets	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())	for packet := range packetSource.Packets() {		// Process packet here		fmt.Println(packet)		w.WritePacket(packet.Metadata().CaptureInfo, packet.Data())		packetCount++				// Only capture 100 and then stop		if packetCount > 100 {			break		}	}}

Open Pcap File

Instead of opening a device for live capture we can also open a pcap file for inspection offline. You can use tcpdump to create a test file to use.

# Capture packets to test.pcap filesudo tcpdump -w test.pcap

Then open the file and go through the packets with this code.

package main// Use tcpdump to create a test file// tcpdump -w test.pcap// or use the example above for writing pcap filesimport (    "fmt"    "github.com/google/gopacket"    "github.com/google/gopacket/pcap"    "log")var (    pcapFile string = "test.pcap"    handle   *pcap.Handle    err      error)func main() {    // Open file instead of device    handle, err = pcap.OpenOffline(pcapFile)    if err != nil { log.Fatal(err) }    defer handle.Close()    // Loop through packets in file    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())    for packet := range packetSource.Packets() {        fmt.Println(packet)    }}

Setting Filters

This code example will only return tcp packets over port 80.

package mainimport (    "fmt"    "github.com/google/gopacket"    "github.com/google/gopacket/pcap"    "log"    "time")var (    device       string = "eth0"    snapshot_len int32  = 1024    promiscuous  bool   = false    err          error    timeout      time.Duration = 30 * time.Second    handle       *pcap.Handle)func main() {    // Open device    handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)    if err != nil {        log.Fatal(err)    }    defer handle.Close()    // Set filter    var filter string = "tcp and port 80"    err = handle.SetBPFFilter(filter)    if err != nil {        log.Fatal(err)    }    fmt.Println("Only capturing TCP port 80 packets.")    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())    for packet := range packetSource.Packets() {        // Do something with a packet here.        fmt.Println(packet)    }}

Decoding Packet Layers

We can take the raw packet and essentially try to cast it to known formats. It is compatible with different layers so we can access ethernet, IP, and TCP layers easily. The layers package is something new in the Go library that is not available in the underlying pcap library. This is an incredibly useful package that is part of the gopacket library. It allows us to easily identify if a packet contains a specific type of layer. This code example will show how to use the layers package to see if the packet is ethernet, IP, and TCP and to access the elements in those headers easily.

Finding the payload depends on all the layers involved. Each protocol is different and has to be calculated accordingly. This is where the power of the layers package comes in to play. The authors of gopacket took the time to create types for many known layers like ethernet, IP, UDP and TCP. The payload is part of the application layer.

package mainimport (    "fmt"    "github.com/google/gopacket"    "github.com/google/gopacket/layers"    "github.com/google/gopacket/pcap"    "log"    "strings"    "time")var (    device      string = "eth0"    snapshotLen int32  = 1024    promiscuous bool   = false    err         error    timeout     time.Duration = 30 * time.Second    handle      *pcap.Handle)func main() {    // Open device    handle, err = pcap.OpenLive(device, snapshotLen, promiscuous, timeout)    if err != nil {log.Fatal(err) }    defer handle.Close()    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())    for packet := range packetSource.Packets() {        printPacketInfo(packet)    }}func printPacketInfo(packet gopacket.Packet) {    // Let's see if the packet is an ethernet packet    ethernetLayer := packet.Layer(layers.LayerTypeEthernet)    if ethernetLayer != nil {        fmt.Println("Ethernet layer detected.")        ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)        fmt.Println("Source MAC: ", ethernetPacket.SrcMAC)        fmt.Println("Destination MAC: ", ethernetPacket.DstMAC)        // Ethernet type is typically IPv4 but could be ARP or other        fmt.Println("Ethernet type: ", ethernetPacket.EthernetType)        fmt.Println()    }    // Let's see if the packet is IP (even though the ether type told us)    ipLayer := packet.Layer(layers.LayerTypeIPv4)    if ipLayer != nil {        fmt.Println("IPv4 layer detected.")        ip, _ := ipLayer.(*layers.IPv4)        // IP layer variables:        // Version (Either 4 or 6)        // IHL (IP Header Length in 32-bit words)        // TOS, Length, Id, Flags, FragOffset, TTL, Protocol (TCP?),        // Checksum, SrcIP, DstIP        fmt.Printf("From %s to %s\n", ip.SrcIP, ip.DstIP)        fmt.Println("Protocol: ", ip.Protocol)        fmt.Println()    }    // Let's see if the packet is TCP    tcpLayer := packet.Layer(layers.LayerTypeTCP)    if tcpLayer != nil {        fmt.Println("TCP layer detected.")        tcp, _ := tcpLayer.(*layers.TCP)        // TCP layer variables:        // SrcPort, DstPort, Seq, Ack, DataOffset, Window, Checksum, Urgent        // Bool flags: FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS        fmt.Printf("From port %d to %d\n", tcp.SrcPort, tcp.DstPort)        fmt.Println("Sequence number: ", tcp.Seq)        fmt.Println()    }    // Iterate over all layers, printing out each layer type    fmt.Println("All packet layers:")    for _, layer := range packet.Layers() {        fmt.Println("- ", layer.LayerType())    }    // When iterating through packet.Layers() above,    // if it lists Payload layer then that is the same as    // this applicationLayer. applicationLayer contains the payload    applicationLayer := packet.ApplicationLayer()    if applicationLayer != nil {        fmt.Println("Application layer/Payload found.")        fmt.Printf("%s\n", applicationLayer.Payload())        // Search for a string inside the payload        if strings.Contains(string(applicationLayer.Payload()), "HTTP") {            fmt.Println("HTTP found!")        }    }    // Check for errors    if err := packet.ErrorLayer(); err != nil {        fmt.Println("Error decoding some part of the packet:", err)    }}

Creating and Sending Packets

This example does a couple things. First it will show how to use the network device to send raw bytes. In that way, you can use it almost like a serial connection to send data. That's useful for really low level data transfer, but if you want to interact with an application you probably want to build a packet that other hard and software can recognize.

The next thing it does is show how to create a a packet with the ethernet, IP, and TCP layers. Everything is default and empty though so it doesn't really do anything.

To finish it off we create another packet but actually fill in some MAC addresses for the ethernet layer, some IP addresses for IPv4, and port numbers for the TCP layer. You should see how you can forge packets and impersonate devices with that.

The TCP layer struct has boolean SYN, FIN, and ACK flags that can be read or set. That is good for manipulating and fuzzing TCP handshakes, sessions, and port scanning.

The pcap library provides an easy way to send bytes, but the layers package in gopacket assists us in creating the byte structure for the many layers.

package mainimport (    "github.com/google/gopacket"    "github.com/google/gopacket/layers"    "github.com/google/gopacket/pcap"    "log"    "net"    "time")var (    device       string = "eth0"    snapshot_len int32  = 1024    promiscuous  bool   = false    err          error    timeout      time.Duration = 30 * time.Second    handle       *pcap.Handle    buffer       gopacket.SerializeBuffer    options      gopacket.SerializeOptions)func main() {    // Open device    handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)    if err != nil {log.Fatal(err) }    defer handle.Close()    // Send raw bytes over wire    rawBytes := []byte{10, 20, 30}    err = handle.WritePacketData(rawBytes)    if err != nil {        log.Fatal(err)    }    // Create a properly formed packet, just with    // empty details. Should fill out MAC addresses,    // IP addresses, etc.    buffer = gopacket.NewSerializeBuffer()    gopacket.SerializeLayers(buffer, options,        &layers.Ethernet{},        &layers.IPv4{},        &layers.TCP{},        gopacket.Payload(rawBytes),    )    outgoingPacket := buffer.Bytes()    // Send our packet    err = handle.WritePacketData(outgoingPacket)    if err != nil {        log.Fatal(err)    }    // This time lets fill out some information    ipLayer := &layers.IPv4{        SrcIP: net.IP{127, 0, 0, 1},        DstIP: net.IP{8, 8, 8, 8},    }    ethernetLayer := &layers.Ethernet{        SrcMAC: net.HardwareAddr{0xFF, 0xAA, 0xFA, 0xAA, 0xFF, 0xAA, 0xFA, 0xAA},        DstMAC: net.HardwareAddr{0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD},    }    tcpLayer := &layers.TCP{        SrcPort: layers.TCPPort(4321),        DstPort: layers.TCPPort(80),    }    // And create the packet with the layers    buffer = gopacket.NewSerializeBuffer()    gopacket.SerializeLayers(buffer, options,        ethernetLayer,        ipLayer,        tcpLayer,        gopacket.Payload(rawBytes),    )    outgoingPacket = buffer.Bytes()}

More on Creating/Decoding Packets

package mainimport (    "fmt"    "github.com/google/gopacket"    "github.com/google/gopacket/layers")func main() {    // If we don't have a handle to a device or a file, but we have a bunch    // of raw bytes, we can try to decode them in to packet information    // NewPacket() takes the raw bytes that make up the packet as the first parameter    // The second parameter is the lowest level layer you want to decode. It will    // decode that layer and all layers on top of it. The third layer    // is the type of decoding: default(all at once), lazy(on demand), and NoCopy    // which will not create a copy of the buffer    // Create an packet with ethernet, IP, TCP, and payload layers    // We are creating one we know will be decoded properly but    // your byte source could be anything. If any of the packets    // come back as nil, that means it could not decode it in to    // the proper layer (malformed or incorrect packet type)    payload := []byte{2, 4, 6}    options := gopacket.SerializeOptions{}    buffer := gopacket.NewSerializeBuffer()    gopacket.SerializeLayers(buffer, options,        &layers.Ethernet{},        &layers.IPv4{},        &layers.TCP{},        gopacket.Payload(payload),    )    rawBytes := buffer.Bytes()    // Decode an ethernet packet    ethPacket :=        gopacket.NewPacket(            rawBytes,            layers.LayerTypeEthernet,            gopacket.Default,        )    // with Lazy decoding it will only decode what it needs when it needs it    // This is not concurrency safe. If using concurrency, use default    ipPacket :=        gopacket.NewPacket(            rawBytes,            layers.LayerTypeIPv4,            gopacket.Lazy,        )    // With the NoCopy option, the underlying slices are referenced    // directly and not copied. If the underlying bytes change so will    // the packet    tcpPacket :=        gopacket.NewPacket(            rawBytes,            layers.LayerTypeTCP,            gopacket.NoCopy,        )    fmt.Println(ethPacket)    fmt.Println(ipPacket)    fmt.Println(tcpPacket)}

Custom Layers

This next program will show how to create your own layer. This is good for impelmenting a protocol that is not included with the gopacket layers package already. It is also useful if you want to create your own l33t protocol that does not even use TCP/IP or ethernet.

package mainimport (    "fmt"    "github.com/google/gopacket")// Create custom layer structuretype CustomLayer struct {    // This layer just has two bytes at the front    SomeByte    byte    AnotherByte byte    restOfData  []byte}// Register the layer type so we can use it// The first argument is an ID. Use negative// or 2000+ for custom layers. It must be uniquevar CustomLayerType = gopacket.RegisterLayerType(    2001,    gopacket.LayerTypeMetadata{        "CustomLayerType",        gopacket.DecodeFunc(decodeCustomLayer),    },)// When we inquire about the type, what type of layer should// we say it is? We want it to return our custom layer typefunc (l CustomLayer) LayerType() gopacket.LayerType {    return CustomLayerType}// LayerContents returns the information that our layer// provides. In this case it is a header layer so// we return the header informationfunc (l CustomLayer) LayerContents() []byte {    return []byte{l.SomeByte, l.AnotherByte}}// LayerPayload returns the subsequent layer built// on top of our layer or raw payloadfunc (l CustomLayer) LayerPayload() []byte {    return l.restOfData}// Custom decode function. We can name it whatever we want// but it should have the same arguments and return value// When the layer is registered we tell it to use this decode functionfunc decodeCustomLayer(data []byte, p gopacket.PacketBuilder) error {    // AddLayer appends to the list of layers that the packet has    p.AddLayer(&CustomLayer{data[0], data[1], data[2:]})    // The return value tells the packet what layer to expect    // with the rest of the data. It could be another header layer,    // nothing, or a payload layer.    // nil means this is the last layer. No more decoding    // return nil    // Returning another layer type tells it to decode    // the next layer with that layer's decoder function    // return p.NextDecoder(layers.LayerTypeEthernet)    // Returning payload type means the rest of the data    // is raw payload. It will set the application layer    // contents with the payload    return p.NextDecoder(gopacket.LayerTypePayload)}func main() {    // If you create your own encoding and decoding you can essentially    // create your own protocol or implement a protocol that is not    // already defined in the layers package. In our example we are just    // wrapping a normal ethernet packet with our own layer.    // Creating your own protocol is good if you want to create    // some obfuscated binary data type that was difficult for others    // to decode    // Finally, decode your packets:    rawBytes := []byte{0xF0, 0x0F, 65, 65, 66, 67, 68}    packet := gopacket.NewPacket(        rawBytes,        CustomLayerType,        gopacket.Default,    )    fmt.Println("Created packet out of raw bytes.")    fmt.Println(packet)    // Decode the packet as our custom layer    customLayer := packet.Layer(CustomLayerType)    if customLayer != nil {        fmt.Println("Packet was successfully decoded with custom layer decoder.")        customLayerContent, _ := customLayer.(*CustomLayer)        // Now we can access the elements of the custom struct        fmt.Println("Payload: ", customLayerContent.LayerPayload())        fmt.Println("SomeByte element:", customLayerContent.SomeByte)        fmt.Println("AnotherByte element:", customLayerContent.AnotherByte)    }}

Decoding Packets Faster

If we know what layers to expect, we can use existing structures to store the packet information instead of creating new structs for every packet which takes time and memory. It is faster to use DecodingLayerParser. It is like marshalling and unmarshalling data.

package mainimport (    "fmt"    "github.com/google/gopacket"    "github.com/google/gopacket/layers"    "github.com/google/gopacket/pcap"    "log"    "time")var (    device       string = "eth0"    snapshot_len int32  = 1024    promiscuous  bool   = false    err          error    timeout      time.Duration = 30 * time.Second    handle       *pcap.Handle    // Will reuse these for each packet    ethLayer layers.Ethernet    ipLayer  layers.IPv4    tcpLayer layers.TCP)func main() {    // Open device    handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)    if err != nil {        log.Fatal(err)    }    defer handle.Close()    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())    for packet := range packetSource.Packets() {        parser := gopacket.NewDecodingLayerParser(            layers.LayerTypeEthernet,            ðLayer,            &ipLayer,            &tcpLayer,        )        foundLayerTypes := []gopacket.LayerType{}        err := parser.DecodeLayers(packet.Data(), &foundLayerTypes)        if err != nil {            fmt.Println("Trouble decoding layers: ", err)        }        for _, layerType := range foundLayerTypes {            if layerType == layers.LayerTypeIPv4 {                fmt.Println("IPv4: ", ipLayer.SrcIP, "->", ipLayer.DstIP)            }            if layerType == layers.LayerTypeTCP {                fmt.Println("TCP Port: ", tcpLayer.SrcPort, "->", tcpLayer.DstPort)                fmt.Println("TCP SYN:", tcpLayer.SYN, " | ACK:", tcpLayer.ACK)            }        }    }}

TCP Stream Reassembly

The gopacket package provides some types called Flow and Endpoint. I have not had a chance to explore these very deeply but the documentation has an example of using it to send specific TCP streams to a channel of packets. GoDoc gopacket basic usage.

Additional References

For more information, check out the documentation on GoDoc gopacket, and thegopacket project on GitHub.