{} The Go Reference

Offensive · Security · Advanced

Packet Analysis

Reading the wire — parsing IP/TCP headers from raw bytes by hand, live capture with gopacket, and using packet analysis defensively for intrusion detection and anomaly monitoring.

Offensive Advanced ⏱ 4 min read Complete

✉️ Analogy

A packet is a letter inside an envelope inside a mailbag. The mailbag (Ethernet) says which truck; the envelope (IP) says which building; the address line (TCP/UDP port) says which apartment; and the letter inside (the payload) is the message. Packet analysis is sorting the mail: you read each layer’s label to know where the next one goes. Even when the letter is sealed (TLS), the envelope and bag still tell you who’s talking to whom, how much, and when.

Packets are layers

A captured frame is encapsulated — each layer wraps the next:

graph LR
ETH["Ethernet<br/>(MACs)"] --> IP["IPv4/IPv6<br/>(src/dst IP, proto)"]
IP --> TCP["TCP/UDP<br/>(src/dst port, flags)"]
TCP --> APP["payload<br/>(HTTP, DNS, …)"]

To analyze a packet you peel the layers in order. Each header is a fixed-ish byte layout defined by an RFC, with multi-byte fields in big-endian (network byte order) — which is exactly what encoding/binary reads.

See it: parse IP + TCP headers by hand

No capture library needed to parse bytes — this runs right here. We decode a real IPv4 header and the TCP header inside it, pulling out addresses, protocol, ports, and flags:

parse.go — editable & runnable
package main

import (
"encoding/binary"
"fmt"
"net"
)

func main() {
// A captured IPv4 packet: 20-byte IP header + 20-byte TCP header.
pkt := []byte{
	// --- IPv4 header ---
	0x45, 0x00, 0x00, 0x28, // ver=4, IHL=5 (20B), total len=40
	0x1c, 0x46, 0x40, 0x00, // id, flags/frag
	0x40, 0x06, 0xb1, 0xe6, // TTL=64, proto=6 (TCP), checksum
	192, 168, 1, 10, // src IP
	93, 184, 216, 34, // dst IP
	// --- TCP header ---
	0xd4, 0x31, // src port 54321
	0x01, 0xbb, // dst port 443
	0x00, 0x00, 0x00, 0x00, // seq
	0x00, 0x00, 0x00, 0x00, // ack
	0x50, 0x02, // data offset=5, flags=0x02 (SYN)
	0x72, 0x10, 0x00, 0x00, 0x00, 0x00, // window, checksum, urg
}

ihl := int(pkt[0]&0x0f) * 4 // IP header length in bytes
proto := pkt[9]
srcIP := net.IP(pkt[12:16])
dstIP := net.IP(pkt[16:20])
fmt.Printf("IPv4  %s -> %s  proto=%d (TCP)\n", srcIP, dstIP, proto)

tcp := pkt[ihl:] // TCP segment starts after the IP header
srcPort := binary.BigEndian.Uint16(tcp[0:2])
dstPort := binary.BigEndian.Uint16(tcp[2:4])
flags := tcp[13]
fmt.Printf("TCP   %d -> %d  flags=0x%02x %s\n", srcPort, dstPort, flags, decodeFlags(flags))
}

func decodeFlags(f byte) string {
names := []struct {
	bit  byte
	name string
}{{0x02, "SYN"}, {0x10, "ACK"}, {0x01, "FIN"}, {0x04, "RST"}, {0x08, "PSH"}}
out := ""
for _, n := range names {
	if f&n.bit != 0 {
		out += n.name + " "
	}
}
return out
}

That flags=0x02 SYN to port 443 is the opening packet of a TLS connection — the same SYN a SYN scan sends. Reading flags is how you tell a connection attempt from established traffic, and how an IDS recognizes a scan.

Live capture with gopacket

Parsing bytes is pure Go; capturing them off an interface needs privileges and the C libpcap library, via github.com/google/gopacket. This is fenced — it requires root, cgo, and a real NIC:

// Fenced: needs gopacket + libpcap, root/CAP_NET_RAW, a real interface.
handle, _ := pcap.OpenLive("eth0", 65536, true, pcap.BlockForever)
defer handle.Close()
handle.SetBPFFilter("tcp and port 443") // kernel-side filter

src := gopacket.NewPacketSource(handle, handle.LinkType())
for pkt := range src.Packets() {
	if ip := pkt.Layer(layers.LayerTypeIPv4); ip != nil {
		v, _ := ip.(*layers.IPv4)
		fmt.Printf("%s -> %s\n", v.SrcIP, v.DstIP)
	}
}

gopacket does the layer-peeling for you and supports BPF filters (compiled in the kernel, so you only copy the packets you care about). It’s the foundation of most Go-based network tools.

The defensive heart: detection and monitoring

🐹 Packet analysis is mostly a blue-team skill

The biggest use of reading packets isn’t attacking — it’s defending. Intrusion-detection systems (Snort, Suricata, Zeek) and network monitors watch traffic for scan patterns (many SYNs, no ACKs), known-bad signatures, unexpected protocols on odd ports, beaconing intervals that smell like malware, or sudden volume that looks like exfiltration. Go + gopacket is a common stack for building these — from a quick “alert on port scans” sniffer to a full flow-analysis pipeline. Learning to read packets makes you a better defender far more than a better attacker.

⚠️ Encryption hides content, not metadata

Sniffing TLS traffic won’t give you the HTTP request — the payload is encrypted. But don’t conclude the conversation is private: the IP headers (who is talking to whom), the ports, and the packet sizes and timing are all visible, and traffic analysis can infer a surprising amount (which page was loaded, when a user is active). Until Encrypted Client Hello is universal, the TLS SNI field even leaks the hostname in plaintext. Capturing other people’s traffic is also heavily regulated — wiretap laws apply — so only sniff networks you own or are authorized to monitor.

See also

Next: doing cryptography right, starting with hashing and password storage — hashing & passwords.

Check your understanding

Score: 0 / 5

1. When you capture a raw packet off the wire, how is it structured?

Packets are layered (encapsulation): the link layer (Ethernet) carries the network layer (IPv4/IPv6), which carries the transport layer (TCP/UDP), which carries the application payload. To analyze a packet you peel the layers in order, each header telling you how to interpret the next — exactly what encoding/binary or gopacket does for you.

2. Why does parsing binary headers use encoding/binary with a fixed byte order?

Internet protocols specify big-endian for multi-byte integers (the RFCs call it network byte order). Your CPU may be little-endian, so reading a port or length with the wrong order gives garbage. binary.BigEndian.Uint16(b) reads it correctly regardless of host architecture.

3. Why does live packet capture (libpcap/gopacket) usually require root and break Go's static-binary superpower?

Reading all traffic on an interface requires elevated privileges (root or CAP_NET_RAW) and a raw/AF_PACKET socket. The popular gopacket library wraps the C libpcap, so it needs cgo — meaning you lose the static, cross-compiled binary unless you use a pure-Go capture backend. For pure analysis of already-captured bytes, no privileges or cgo are needed.

4. What is the primary DEFENSIVE use of packet analysis?

Reading packets is how IDS/IPS systems (Snort, Suricata, Zeek) and monitoring tools work: they watch traffic for scan patterns, known-bad signatures, unexpected protocols, or volume anomalies that signal exfiltration. Defensive packet analysis is a huge field — and Go (via gopacket) is a common language for building these tools.

5. You're sniffing HTTPS traffic. What can you actually see?

TLS encrypts the payload, so you can't read the HTTP body or headers. But the lower layers are still visible: source/destination IPs and ports, packet sizes and timing (traffic analysis), and — until Encrypted Client Hello is universal — the SNI field naming the host being visited. Encryption protects content, not the existence and shape of the conversation.

Comments

Sign in with GitHub to join the discussion.