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
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.
Install the prerequisites. You will need go, libpcap 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.