© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
N. TolaramSoftware Development with Gohttps://doi.org/10.1007/978-1-4842-8731-6_11

11. Google gopacket

Nanik Tolaram1  
(1)
Sydney, NSW, Australia
 

In the previous chapter, you learned about building networking tools using the Go standard library. In this chapter, you will go further and investigate an open source library network library from Google called gopacket. The library source code can be found at https://github.com/google/gopacket and the library documentation can be found at https://pkg.go.dev/github.com/google/gopacket. The source branch that you will be looking at in this chapter is the master branch.

gopacket provides low-level network packet manipulation that cannot be found inside the standard library. It provides developers with a simple API to manipulate different network layers’ information obtained from the network interface. In this chapter, you will learn
  • How gopacket works

  • How to use gopacket to write a network traffic sniffer

  • About network capture files

Source Code

The source code for this chapter is available from the https://github.com/Apress/Software-Development-Go repository.

gopacket

In this section, you will explore gopacket and learn about the main part of the library to understand how it works. This library provides the capability to write applications that need to capture and analyze network traffic. The library does the heavy lifting of communicating with the kernel to obtain all the network data and parse it and make it available to applications. gopacket uses a packet capture Linux library that has been part of the Linux toolbox for a long time called libpcap. More information can be found at www.tcpdump.org/index.html.

The libpcap library provides functionality to grab network packets from the network cards, which in turn are parsed and converted to the relevant protocols that are easily used by applications. gopacket provides two major types of data structures that applications can work with, namely Packet and Layer, which will be explored more in detail next.

Layer

In this section, you will look at the Layer interface. This interface is the main interface in the library that holds data in regard to the raw network data. The interface looks like the following:
type Layer interface {
  // LayerType is the gopacket type for this layer.
  LayerType() LayerType
  // LayerContents returns the set of bytes that make up this layer.
  LayerContents() []byte
  // LayerPayload returns the set of bytes contained within this layer, not
  // including the layer itself.
  LayerPayload() []byte
}

LayerContents contains the bytes representing a particular layer. For example, if this is an Ethernet layer, then it will contain bytes that make up the Ethernet frame, while LayerPayload will contain the bytes representing the Ethernet protocol.

The LayerType is defined as follows, which contains the type of layer it represents (for example: Ethernet, ARP, TCP, etc.):
type LayerType int64
The layertypes.go source contains the different network layers that are supported in the library, as shown in the code snippet here:
import (
  ...
)
var (
  LayerTypeARP                          = gopacket.RegisterLayerType(10, gopacket.LayerTypeMetadata{Name: "ARP", Decoder: gopacket.DecodeFunc(decodeARP)})
  LayerTypeCiscoDiscovery               = gopacket.RegisterLayerType(11, gopacket.LayerTypeMetadata{Name: "CiscoDiscovery", Decoder: gopacket.DecodeFunc(decodeCiscoDiscovery)})
  LayerTypeEthernetCTP                  = gopacket.RegisterLayerType(12, gopacket.LayerTypeMetadata{Name: "EthernetCTP", Decoder: gopacket.DecodeFunc(decodeEthernetCTP)})
...
  LayerTypeIPv4                         = gopacket.RegisterLayerType(20, gopacket.LayerTypeMetadata{Name: "IPv4", Decoder:
...
)
Different protocols using the Layer interface can be found inside the layers directory, shown in Figure 11-1.

A screeshot represent different protocols using the layer interface in the layer directory.

Figure 11-1

Layer struct implementation

The source code inside the layers directory contains implementations of each protocol and how to read them from the raw bytes obtained from the kernel.

TCP Layer

Let’s take a look at an example of a TCP protocol implementation that can be found inside the layers/tcp.go file. The TCP struct declaration that contains the TCP protocol information is shown here:
type TCP struct {
  BaseLayer
  SrcPort, DstPort                           TCPPort
  Seq                                        uint32
  Ack                                        uint32
  DataOffset                                 uint8
  FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS bool
  Window                                     uint16
  Checksum                                   uint16
  Urgent                                     uint16
  sPort, dPort                               []byte
  Options                                    []TCPOption
  Padding                                    []byte
  opts                                       [4]TCPOption
  tcpipchecksum
}
The following code shows the function DecodeFromBytes that reads the raw bytes and converts them into a TCP struct:
func (tcp *TCP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
  ...
  tcp.SrcPort = TCPPort(binary.BigEndian.Uint16(data[0:2]))
  tcp.sPort = data[0:2]
  tcp.DstPort = TCPPort(binary.BigEndian.Uint16(data[2:4]))
  tcp.dPort = data[2:4]
  tcp.Seq = binary.BigEndian.Uint32(data[4:8])
  tcp.Ack = binary.BigEndian.Uint32(data[8:12])
  ...
  ...
}

Going through each of the protocol source files, you will see the implementation of the different protocols that are supported by the library.

Packet

Packet is the primary type that your application will be working with. The data that has been read from the low-level libpcap library will end up here in a form that is easier to understand by the developer. Let’s take a look at the Packet struct, which is defined inside the packet.go file:
type Packet interface {
  String() string
  Dump() string
  Layers() []Layer
  Layer(LayerType) Layer
  LayerClass(LayerClass) Layer
  LinkLayer() LinkLayer
  NetworkLayer() NetworkLayer
  TransportLayer() TransportLayer
  ApplicationLayer() ApplicationLayer
  ErrorLayer() ErrorLayer
  Data() []byte
  Metadata() *PacketMetadata
}
The struct holds different functions that return the different types of Layer that you looked at in the previous section. To understand a bit better, let’s take a peek at the ApplicationLayer type that is returned by the ApplicationLayer() function, which is defined inside the same file, packet.go.
type ApplicationLayer interface {
  Layer
  Payload() []byte
}

The ApplicationLayer is an interface that holds the Layer type and the Payload() function that will return the original bytes relevant from the network capture for this particular layer. You will look at an example in the next section on how to use the different functions inside the Packet.

Using gopacket

In this section, you will look at examples of how to use gopacket. They will give you ideas of how to use the library and also show the library capabilities in reading network protocols.

pcap

Let’s take a moment to understand pcap. It stands for packet capture. Linux has tools that allow a developer or sysadmin to perform network troubleshooting, and one of those tools is a packet capture tool. The packet capture tools allow Linux root users to capture network traffic in the machine.

The traffic data can be saved into a file and later read to be analyzed. This kind of capability is super useful for performing auditing plus security and network troubleshooting in a cloud or local environment. In this chapter, you will capture and analyze the pcap file.

Installing libpcap

The code relies on a Linux library called libpcap (www.tcpdump.org/manpages/pcap.3pcap.html). This library is the main library that helps in performing network captures. Make sure you have the library installed on your local Linux machine. Use the following command to install the library:
sudo apt-get install libpcap-dev

You will need to reboot your machine once it is successfully installed.

Networking Sniffer

For this section example, you will look at an example of a network sniffer application using the library. The sample application can be found inside the chapter11/gopacket/sniffer folder. The sample code will sniff out your local network and print out the following:
  • IPv4 information

  • DNS information

  • TCP information

  • UDP information

  • Application layer protocol information

Before running the application, make sure you change the following line of code to use the correct network interface that exists in your machine:
const (
  iface = "enp7s0"
  ...
)
In my case, it’s called enp7s0, which can be found by running the ifconfig tool. The following is the output of running ifconfig on my machine:
enp7s0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether ...... txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
         ......
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
wlp6s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.17  netmask 255.255.255.0  broadcast 192.168.1.255
        ......
        TX errors 239484  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 18
Change your directory to the chapter11/gopacket/sniffer folder and compile the app.
go build -o sniffer
Run the app with the root account.
sudo ./sniffer
Once the app runs, you will see output like the following:
2022/03/12 21:11:19 (TCP) Source address : 100.24.164.135, Destination address : 192.168.1.6
2022/03/12 21:11:19 (TCP) From port 443 to 35232
2022/03/12 21:11:19 (TCP) Source address : 192.168.1.6, Destination address : x.x.x.x
2022/03/12 21:11:19 (TCP) From port 35232 to 443
2022/03/12 21:11:20 (TCP) Source address : 192.168.1.6, Destination address : x.x.x.x
2022/03/12 21:11:20 (TCP) From port 45988 to 443
2022/03/12 21:11:20 (TCP) Source address : x.x.x.x, Destination address : x.x.x.x
...
2022/03/12 21:24:03 ----------------------
2022/03/12 21:24:03 (TCP) Source address : x.x.x.x, Destination address : 192.168.1.6
2022/03/12 21:24:03 (TCP) From port 80 to 36910
2022/03/12 21:24:03 HTTP Application layer
2022/03/12 21:24:03 ----------------------
2022/03/12 21:24:03 HTTP/1.1 404 Not Found
Date: Sat, 12 Mar 2022 10:24:03 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Length: 283
Content-Type: text/html; charset=iso-8859-1
...
2022/03/12 21:24:03 ----------------------
2022/03/12 21:24:03 (TCP) Source address : 192.168.1.6, Destination address : x.x.x.x
2022/03/12 21:24:03 (TCP) From port 36364 to 443
...

Code Walkthrough

Let’s take a look step by step at the different parts of the app to understand how it uses gopacket . The following code shows the process of initializing the library to sniff the network traffic using the network interface specified:
func main() {
  f, _ := os.Create(fName)
  ...
  handle, err := pcap.OpenLive(iface, sLen, true, -1)
  if err != nil {
     log.Fatal(err)
  }
  ...
}

The pcap.OpenLive function calls gopacket to open the network device and the true parameter sent indicates to the library that you want to open it in promiscuous mode.

Once the function returns without an error, it starts listening for incoming packets and processes them as follows:
func main() {
  f, _ := os.Create(fName)
  ...
  pSource := gopacket.NewPacketSource(handle, handle.LinkType())
  for packet := range pSource.Packets() {
     printPacketInfo(packet)
     ...
  }
}

As mentioned in the “Packet” section, the application interacts with the network data via the Packet type. In the sample code case, you create a new PacketSource that the app can use in a for .. range to extract all the incoming packets and process them inside the printPacketInfo(..) function. So far, you have successfully initialized and received the packet; now let’s dig further into how to use the information made available inside the Packet struct.

The following is the snippet of the printPacketInfo(..) function that shows how to use the Packet struct to check whether the network capture contains an HTTP protocol:
func printPacketInfo(packet gopacket.Packet) {
  ...
  applicationLayer := packet.ApplicationLayer()
  if applicationLayer != nil {
     // Search for a string inside the payload
     if strings.Contains(string(applicationLayer.Payload()), "HTTP") {
        //log.Println("HTTP found!")
        log.Println("HTTP Application layer")
        log.Println("----------------------")
        log.Println(fmt.Sprintf("%s", string(applicationLayer.Payload())))
        log.Println("----------------------")
     }
  }
  ...
}

The code uses the ApplicationLayer() function that instructs gopacket to return a layer that corresponds to the application layer, which corresponds to Layer 7 of the OSI network model. Once obtained, it will check whether the layer data is an HTTP request by checking for an HTTP string.

This shows the powerful functionality the library can provide when accessing the different network layers that are made available via the Packet struct.

Analyzing pcap Manually

The sample code not only prints out the capture network layers; it also stores them inside a file called test.pcap. The file is generated in the directory where you run the sample code; in this example, it is stored inside the gopacket/sniffer directory.

This file contains the raw network capture that can be viewed by other tools. In this section, you will look at one of the ways to view the captured file using another open source project, which can be found at https://github.com/mynameiscfed/go-cp-analyzer. Download and compile the file and run it as follows:
./go-cp-analyzer -r <directory_to_test.pcap>/filename.pcap
After a successful run, it will output something like the following:
+--------------------------------+----------------------+
|      Packet Distribution       |                      |
+--------------------------------+----------------------+
|  <= 66                         | 6474                 |
|  <= 128                        | 5831                 |
|  <= 256                        | 858                  |
|  <= 384                        | 698                  |
|  <= 512                        | 739                  |
|  <= 768                        | 538                  |
|  <= 1024                       | 77                   |
|  <= 1518                       | 3830                 |
|  <= 9000                       | 489                  |
+--------------------------------+----------------------+
+--------------------------------+----------------------+
|         Packet Metrics         |                      |
+--------------------------------+----------------------+
| Total pkts                     | 19534                |
| Avg pkt size                   | 446                  |
| Avg pkts/second                | 99                   |
| Avg thoughput (Mbps)           | 0.36                 |
+--------------------------------+----------------------+
+--------------------------------+----------------------+
|        Protocol Metrics        |                      |
+--------------------------------+----------------------+
| Ethernet                       | 19534                |
| TCP                            | 13019                |
| UDP                            | 6390                 |
| !Ethernet                      | 0                    |
| ARP                            | 11                   |
| IPv4                           | 19419                |
| IPv6                           | 6                    |
| LLC                            | 98                   |
+--------------------------------+----------------------+
+--------------------------------+----------------------+
|      Connections Metrics       |                      |
+--------------------------------+----------------------+
| TCP connections                | 359                  |
| TCP conns/sec (avg)            | 1                    |
| TCP peak conns/sec             | 12                   |
| UDP connections                | 138                  |
| UDP conns/sec (avg)            | 0                    |
| UDP peak conns/sec             | 6                    |
+--------------------------------+----------------------+

This shows that the raw captured files are compatible with other raw network analyzers that are available.

Analyzing pcap Using WireShark

In this section, you will use Wireshark to see if you can read the file you created using the sample app to prove that it is compatible. You will use Docker to run Wireshark and use browsers to load the UI. Use the following command to run Wireshark using Docker:
docker run --name=wireshark --cap-add=NET_ADMIN --security-opt seccomp=unconfined -e PUID=1000 -e PGID=1000 -p 3000:3000 -e TZ=Europe/London -v <your_directory_that_contains_pcap_file>:/config --restart unless-stopped lscr.io/linuxserver/wireshark:latest
Replace <your_directory_that_contains_pcap_file> with your local directory that contains the test.pcap file. Once Wireshark is up and running, you will see output that looks like the following:
WARNING: Published ports are discarded when using host network mode
...
-------------------------------------
          _         ()
         | |  ___   _    __
         | | / __| | |  /  
         | | \__ | | | () |
         |_| |___/ |_|  \__/
Brought to you by linuxserver.io
-------------------------------------
To support LSIO projects visit:
https://www.linuxserver.io/donate/
-------------------------------------
GID/UID
-------------------------------------
User uid:    1000
User gid:    1000
-------------------------------------
....
[cont-init.d] done.
[services.d] starting services
[services.d] done.
[guac-init] Auto start not set, application start on login
guacd[429]: INFO:       Guacamole proxy daemon (guacd) version 1.1.0 started
guacd[429]: INFO:       Listening on host 0.0.0.0, port 4822
Starting guacamole-lite websocket server
listening on *:3000
...
Wireshark is ready and is listening on port 3000. Open your browser and type in http://localhost:3000 and you will see a screen like Figure 11-2.

A screenshot of the user interface of Wireshark software represents tools named file, edit, view, go capture, analysis, statistics, and help. A popup window of welcome to Wireshark.

Figure 11-2

Wireshark UI

Open the test.pcap file by selecting File ➤ Open and you will see a screen like Figure 11-3. Select the test.pcap file from the selection of available files.

A user interface of Wireshark software represents tools named file, edit, view, go capture, analysis, statistics, and help. A popup window represents open capture file names.

Figure 11-3

Wireshark open file selection

Wireshark will successfully read the test.pcap file and will open it, as shown in Figure 11-4.

A user interface of the test dot pcap represents tools named file, edit, view, go capture, analysis, statistics, and help.

Figure 11-4

test.pcap inside Wireshark

In this section, you proved that the network capture performed by gopacket can be successfully read using two different tools. In the next section, you will look at how to use BPF (Berkeley Packet Filter) to filter the network traffic that you are interested in.

Capturing With BPF

gopacket provides the ability to filter network traffic that applications are interested in, and this is possible by using BPF. BPF stands for Berkeley Packet Filter and it allows an application to attach a filter to allow or disallow certain types of data through a socket. More detail information can be found at www.kernel.org/doc/html/latest/networking/filter.html.

The sample code can be found inside the chapter11/gopacket/http folder. It captures and prints only TCP traffic with a port destination of 80. Compile the code as follows:
go build -o httponly .
Run the code with root. Replace <network_device> with your local network device.
sudo ./httponly -i <network_device>
After a successful run, you will see output that looks like the following. You can see that it only prints TCP traffic connecting to an external server on port 80.
2022/03/14 17:10:39 Starting capture on interface "enp0s31f6"
2022/03/14 17:10:39 reading in packets
2022/03/14 17:10:39 -- Extracted Http Data --
2022/03/14 17:10:39 Accept text/*
2022/03/14 17:10:39 If-Modified-Since Thu, 20 May 2021 01:37:53 GMT
2022/03/14 17:10:39 User-Agent Debian APT-HTTP/1.3 (1.9.4)
2022/03/14 17:10:39 Cache-Control max-age=0
2022/03/14 17:10:39 Proto : HTTP/1.1, Host : ppa.launchpad.net, Method : GET, URI : ...
2022/03/14 17:10:39 -------------------------
2022/03/14 17:10:39 Received request from stream 192.168.1.6->x.x.x.x 59494->80 : &{GET ...} with 0 bytes in request body
2022/03/14 17:10:40 -- Extracted Http Data --
2022/03/14 17:10:40 Cache-Control max-age=0
2022/03/14 17:10:40 Accept text/*
2022/03/14 17:10:40 If-Modified-Since Tue, 24 Mar 2020 13:38:15 GMT
2022/03/14 17:10:40 User-Agent Debian APT-HTTP/1.3 (1.9.4)
2022/03/14 17:10:40 Proto : HTTP/1.1, Host : ppa.launchpad.net, Method : GET, URI : ...
2022/03/14 17:10:40 -------------------------
...
Let’s take a look at how the code uses BPF to filter the network capture. The following snippet shows what you learned in the previous section: how to perform packet capture using the gopacket OpenLive function:
if *fname != "" {
  ...
} else {
  log.Printf("Starting capture on interface %q", *iface)
  handle, err = pcap.OpenLive(*iface, int32(*snaplen), true, pcap.BlockForever)
}
...
Next, the code calls the SetBPFFilter function to specify the network filter that you want to apply.
var filter = flag.String("f", "tcp and dst port 80", "BPF filter for pcap")
...
func main() {
  ...
  if err := handle.SetBPFFilter(*filter); err != nil {
     log.Fatal(err)
  }
  ...
}

The filter variable contains a simple English-like filter rule, tcp and dst port 80, which means it is only interested in TCP traffic that is accessing port 80. Here is a link to more information about the different filters you can write: www.ibm.com/docs/en/qsip/7.4?topic=queries-berkeley-packet-filters.

The code specifies the filter that it wants. The next thing it needs to do is specify the parser that gopacket will use to parse the TCP raw data, and this is done by the httpStreamFactory struct type, which defines the New(..) and run() function. These two functions are called internally by gopacket every time there is data available for the application to consume.
type httpStreamFactory struct{}
func (h *httpStreamFactory) New(net, transport gopacket.Flow) tcpassembly.Stream {
  hstream := &httpStream{
     net:       net,
     transport: transport,
     r:         tcpreader.NewReaderStream(),
  }
  ...
}
func (h *httpStream) run() {
  buf := bufio.NewReader(&h.r)
  for {
     ...
  }
}
The main job of the run() function is to assemble and parsed the raw bytes into a more readable format to print out, as shown:
func (h *httpStream) run() {
  buf := bufio.NewReader(&h.r)
  for {
     req, err := http.ReadRequest(buf)
     if err == io.EOF {
        ...
     }
        ...
     else {
        log.Println("-- Extracted Http Data --")
        for k, v := range req.Header {
           log.Println(k, v[0])
        }
        log.Println(fmt.Sprintf("Proto : %s, Host : %s, Method : %s, URI : %s ", req.Proto, req.Host, req.Method, req.RequestURI))
        log.Println("-------------------------")
        ...
     }
  }
}

Summary

In this chapter, you learned about capturing raw networks using the open source gopacket project. The library provides a lot of functionality made available through its simple public API. You learned how to write applications using the library and use the information provided in the different structures.

You looked at BPF (Berkeley Packet Filter) and learned to use it inside your code to filter network captures using gopacket. Using BPF allows an application to process only the network capture that it is interested in rather than spending time processing all incoming traffic. This makes it easier to develop apps targeted for specific traffic.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.146.152