The most surprising thing about tcpdump and IPv6 is how little changes from IPv4, despite the massive address space and header differences.
Let’s see it in action. Imagine you want to capture all ICMPv6 traffic on your local network interface, eth0. This is how you’d do it:
sudo tcpdump -i eth0 icmp6
That’s it. tcpdump doesn’t care if it’s IPv4 or IPv6 for basic protocol captures.
But what if you want to be more specific? IPv6 has a lot of different protocols running over it besides just TCP and UDP. For example, Neighbor Discovery Protocol (NDP) is crucial for IPv6 link-local communication, similar to ARP in IPv4. You can capture that with:
sudo tcpdump -i eth0 -vvv 'icmp6 and ip6[40] == 133'
Here, icmp6 filters for ICMPv6 messages. The ip6[40] == 133 part is a bit more advanced. It’s looking at the IPv6 header. The IPv6 header is 40 bytes long. The byte at offset 40 (ip6[40]) is the "Next Header" field for the first extension header, or the protocol field if there are no extension headers. For ICMPv6, this field will contain the protocol number for ICMPv6, which is 58. However, within ICMPv6 itself, there are different types. NDP messages have specific ICMPv6 types. For instance, Neighbor Solicitation is type 135, Neighbor Advertisement is type 136, Router Solicitation is type 133, and Router Advertisement is type 134. So, to capture Router Solicitation messages, we’d use ip6[40] == 133. The -vvv flag gives us much more verbose output, showing more header details.
Let’s break down the mental model. tcpdump works by listening to a network interface and capturing packets that match a given filter expression. This filter expression is processed by the Berkeley Packet Filter (BPF) engine, which is highly optimized to drop packets that don’t match as early as possible, minimizing overhead.
The core of tcpdump’s filtering power lies in its ability to inspect packet headers. For IPv6, the header structure is different from IPv4. The IPv6 header is a fixed 40 bytes. It contains fields like the Version (always 6), Traffic Class, Flow Label, Payload Length, Next Header, Hop Limit, Source Address, and Destination Address.
When you use a filter like icmp6, tcpdump checks the "Next Header" field (at offset 6 in the IPv6 header) to see if it indicates ICMPv6 (protocol number 58). If there are IPv6 extension headers, the "Next Header" field of the preceding header points to the type of the next header. tcpdump can follow these headers. For example, to capture TCP over IPv6, you’d use tcp and ip6 proto 6. The ip6 proto 6 part tells tcpdump to look at the IPv6 header and check if the "Next Header" field (offset 6) indicates TCP (protocol number 6).
What if you want to filter by IPv6 address? You can do that directly:
sudo tcpdump -i eth0 'ip6 host 2001:db8::1'
This captures all traffic where either the source or destination IPv6 address is 2001:db8::1. You can also specify source and destination:
sudo tcpdump -i eth0 'src ip6 2001:db8::1 and dst port 80'
This captures traffic originating from 2001:db8::1 destined for port 80.
A common misconception is that IPv6 traffic is inherently "harder" to capture or filter. In reality, tcpdump’s BPF syntax is quite flexible. For instance, you can filter based on the IPv6 Flow Label:
sudo tcpdump -i eth0 'ip6[1:3] & 0x000fffff = 0x12345'
This command captures packets where the Flow Label (bytes 1 through 3 of the IPv6 header, which is 3 bytes long) matches the value 0x12345. The & 0x000fffff is a bitmask to ensure we only compare the Flow Label bits. The IPv6 header is structured as follows: Version (4 bits), Traffic Class (8 bits), Flow Label (20 bits). So, the Flow Label starts at byte 1 of the header and is 20 bits (2.5 bytes) long. The expression ip6[1:3] refers to bytes 1, 2, and 3. Byte 1 contains the lower 4 bits of the Traffic Class and the first 4 bits of the Flow Label. Byte 2 contains the next 8 bits of the Flow Label. Byte 3 contains the last 8 bits of the Flow Label. The bitwise AND operation is essential to isolate the Flow Label bits correctly.
The one thing most people don’t realize is how tcpdump handles IPv6 extension headers. When tcpdump sees an IPv6 packet, it checks the "Next Header" field. If it’s an extension header (like Hop-by-Hop Options, Destination Options, Routing, Fragment, or Authentication Header), it follows that header to find the next header type. This process continues until it reaches the payload protocol (like TCP, UDP, or ICMPv6). The BPF filter can be written to inspect fields within these extension headers, though it requires careful byte-offset calculations based on the specific extension header type. For example, to filter on a specific option within an IPv6 Destination Options header, you’d need to know the offset of that option within the Destination Options header, and then calculate the offset from the start of the IPv6 packet.
Once you’ve mastered filtering IPv6 traffic, you’ll likely want to explore how to analyze the captured data further, perhaps by using tools like Wireshark or tshark to dissect the complex IPv6 header chains.