2 IPv6
Watersrc-stack edited this page 2026-10-14 04:00:21 +02:00

IPv6

IPv6 does IPv4's job with 128-bit addresses, so every device can have its own globally reachable address. Its header has a fixed 40-byte size. Options moved out of it into separate extension headers, chained one after the other.

The header

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class |              Flow Label               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Payload Length         |  Next Header  |   Hop Limit   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+                     Source Address (128 bits)                 +
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+                  Destination Address (128 bits)               +
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Offset Size Field Meaning
0 4 bits Version 6
0 8 bits Traffic Class Priority and ECN, like IPv4's DSCP / ECN.
1 20 bits Flow Label Marks the packets of one flow, so routers can keep them on the same path. Linux picks it from a hash of the connection.
4 2 Payload Length Everything after this 40-byte header, extension headers included.
6 1 Next Header What follows: 6 TCP, 17 UDP, 58 ICMPv6, or an extension header (0 Hop-by-Hop, 43 Routing, 44 Fragment, 60 Destination Options). Same numbers as IPv4's Protocol.
7 1 Hop Limit IPv4's TTL under its real name.
8 16 Source
24 16 Destination

Compared to IPv4, the header has:

  • no checksum: the link and transport layers already have one;
  • no fragmentation fields: only the sender fragments, with a Fragment extension header;
  • no header length: it's always 40 bytes.

Example: a DNS answer over IPv6

0000  00 00 5e 00 53 01 00 00 5e 00 53 02 86 dd 60 05
0010  52 92 00 41 11 40 20 01 0d b8 00 00 00 00 00 00
0020  00 00 00 00 00 02 20 01 0d b8 00 00 00 00 00 00
0030  00 00 00 00 00 01 00 35 d3 41 00 41 5b c7 ...

After the Ethernet header (86 dd = IPv6):

Bytes Field Value
60 05 52 92 Version, Traffic Class, Flow Label 0110 = 6; class 0x00; flow label 0x55292
00 41 Payload Length 65 bytes: 8 (UDP) + 57 (DNS)
11 Next Header 17, UDP
40 Hop Limit 64
20 01 0d b8 00 … 00 02 Source 2001:db8::2
20 01 0d b8 00 … 00 01 Destination 2001:db8::1

How the first four bytes split, in bits:

6    0    0    5    5    2    9    2
0110 0000 0000 0101 0101 0010 1001 0010
ver  traffic cl.  flow label: 0x55292

NetOtter's i:

  EtherType    IPv6 (0x86dd)
  Source       2001:db8::2
  Destination  2001:db8::1
  Next header  UDP  (17)
  Hop limit    64
  Flow label   0x55292
  Traffic cls  0x00

Writing addresses

128 bits are written as 8 groups of 4 hex digits: 2001:0db8:0000:0000:0000:0000:0000:0002.

  • Leading zeros in a group are dropped: 2001:db8:0:0:0:0:0:2.
  • The longest run of zero groups becomes ::, once per address: 2001:db8::2.

Extension headers

Each extension header starts with its own Next Header byte, so they form a chain that ends at the transport protocol:

flowchart LR
  ip["IPv6 header<br/>Next Header = 0"] --> hbh["Hop-by-Hop Options<br/>Next Header = 58"] --> icmp["ICMPv6 (MLD report)"]

Example: an MLD report with a Hop-by-Hop header

When an interface comes up, IPv6 tells the local routers which multicast groups it listens to, with an MLD report. That report must carry a Router Alert option, which goes in a Hop-by-Hop header. Here is the client's first packet once its cable was up:

0000  33 33 00 00 00 16 00 00 5e 00 53 01 86 dd 60 00
0010  00 00 00 38 00 01 00 00 00 00 00 00 00 00 00 00
0020  00 00 00 00 00 00 ff 02 00 00 00 00 00 00 00 00
0030  00 00 00 00 00 16 3a 00 05 02 00 00 01 00 8f 00
0040  1a 6e 00 00 00 02 04 00 00 00 ff 02 00 00 00 00
0050  00 00 00 00 00 01 ff 00 53 01 04 00 00 00 ff 02
0060  00 00 00 00 00 00 00 00 00 01 ff 00 00 01
Bytes Layer Meaning
33 33 00 00 00 16 Ethernet IPv6 multicast MAC: 33:33 + the last 4 bytes of ff02::16
00 38 00 01 IPv6 payload 56 bytes, Next Header 0 (Hop-by-Hop), hop limit 1 (never routed)
00 … 00 IPv6 source ::: the interface has no address yet (it's checking its address is unique)
ff 02 … 00 16 IPv6 destination ff02::16: all MLDv2-capable routers
3a 00 Hop-by-Hop next header 58 (ICMPv6); length 0 = 8 bytes in all
05 02 00 00 Hop-by-Hop option 5 (Router Alert), 2 bytes, value 0 = MLD
01 00 Hop-by-Hop option 1 (PadN), 0 bytes: pads to 8
8f 00 1a 6e 00 00 00 02 ICMPv6 type 143 (MLDv2 report), checksum, 2 records
04 00 00 00 ff 02 … ff 00 53 01 MLD record type 4 (change to exclude: "I join"), group ff02::1:ff00:5301
04 00 00 00 ff 02 … ff 00 00 01 MLD record the same, group ff02::1:ff00:1

The groups joined are solicited-node addresses: ff02::1:ff + the last 3 bytes of an address of the interface. ff02::1:ff00:5301 is the one of fe80::200:5eff:fe00:5301 (the link-local address, made from the MAC), ff02::1:ff00:1 the one of 2001:db8::1. Neighbour discovery (IPv6's ARP) sends its queries there.

NetOtter follows the chain:

  Next header  HOPO (0)
  Upper layer  ICM6 (58), after 8 bytes of extension headers
  Hop limit    1
  Flow label   0x00000
  Traffic cls  0x00
  MLD          v2 listener report (143)
  Group        ff02::1:ff00:5301 (solicited-node)  change to exclude, 0 sources
  Group        ff02::1:ff00:1 (solicited-node)  change to exclude, 0 sources

Addresses worth knowing

Prefix Use
::1 loopback
:: unspecified (no address yet)
fe80::/10 link-local: every interface has one, never routed
fc00::/7 (in practice fd00::/8) unique local, IPv6's private addresses
2000::/3 global unicast
ff00::/8 multicast; ff02::1 all nodes, ff02::2 all routers
2001:db8::/32 documentation (RFC 3849)

A machine usually has several IPv6 addresses at once: link-local, global, and temporary global addresses that change daily for privacy. NetOtter's me filter remembers all of them.

In NetOtter

  • Parsing: parse_ipv6() (Parsers) reads the fixed header. skip_ipv6_extensions() then walks up to 8 Hop-by-Hop, Routing and Destination Options headers, and stops at a Fragment header.
  • l: addresses in brackets with their port, hop instead of ttl, and the protocol after the extensions (MLD shows as ICM6, with [join …]).
  • Filters: ipv6src, ipv6dest, ipv6, proto (which matches Next Header or the upper protocol), etype ipv6.

Read more