NETWORKING MASTERY · PHASE 1 · MODULE 04 · WEEK 3
🔵 IPv6
128-bit addressing · Header format · Address types · NDP · SLAAC · Dual-stack · Transition mechanisms
Beginner → Intermediate Prerequisite: M03 IPv4 RFC 8200 128-bit addresses No broadcast 2 Labs

THE ADDRESS EXHAUSTION PROBLEM AND THE IPv6 SOLUTION

📉

IPv4 Address Exhaustion

MOTIVATION

IPv4 uses 32-bit addresses — providing a theoretical maximum of 2³² = 4,294,967,296 (about 4.3 billion) unique addresses. When IPv4 was designed in 1981, this seemed enormous. But the explosive growth of the internet — billions of smartphones, IoT devices, cloud servers, home routers — consumed this space far faster than anticipated.

IANA (the global IP address authority) exhausted its IPv4 pool on 3 February 2011. Regional registries ran out of free allocations between 2011–2019. Today, obtaining new public IPv4 addresses requires buying them on the secondary market at premium prices.

Several stopgap measures delayed the crisis:

  • CIDR — replaced wasteful classful allocation, made address usage more efficient
  • RFC 1918 private addresses + NAT — allowed millions of devices to share a single public IP. A home router with one public IP can serve 100+ internal devices. This is why your home network uses 192.168.x.x
  • CGN (Carrier-Grade NAT) — ISPs now put entire neighbourhoods behind a single public IPv4 address

NAT solved the exhaustion problem temporarily, but at a cost: it breaks end-to-end connectivity, complicates application protocols (FTP, SIP, WebRTC need ALGs to work through NAT), and adds latency. IPv6 eliminates NAT by giving every device a globally unique address.

🚀

IPv6 — The Long-Term Solution

SOLUTION

IPv6 (Internet Protocol version 6) was standardised in RFC 2460 (1998), updated by RFC 8200 (2017). It solves address exhaustion and simultaneously redesigns several IPv4 pain points:

What IPv6 Fixes

  • 128-bit addresses — 2¹²⁸ ≈ 3.4 × 10³⁸ unique addresses. Enough to give every atom on Earth its own IP address
  • No broadcast — IPv4 broadcast is replaced with targeted multicast, reducing noise on large networks
  • Built-in security — IPsec support is mandatory in the specification (optional in IPv4)
  • Simplified header — fixed 40-byte header, no checksum, no fragmentation in transit, cleaner extension header chain
  • Stateless autoconfiguration (SLAAC) — devices can configure their own addresses without a DHCP server
  • No NAT required — every device gets a globally routable address

IPv6 Adoption Today

  • Google reports ~45% of global traffic over IPv6 (2024)
  • Mobile networks (Jio, T-Mobile, AT&T) are predominantly IPv6-only internally
  • All major cloud providers (AWS, GCP, Azure) fully support IPv6
  • Most modern OSes (Linux, Windows, macOS, Android, iOS) prefer IPv6 when available
  • For NGFW development, IPv6 support is not optional — your firewall must handle both
📏 Analogy — Street Addresses

IPv4 is like a city with 4.3 billion street addresses that is now completely full — every address is taken. To fit more people, residents are crammed into apartment buildings (NAT) where many people share one address and are distinguished by their apartment number (port). IPv6 is like being given an entirely new planet with 340 undecillion addresses — so vast that every grain of sand on Earth gets its own unique address, with quintillions to spare. No apartments needed: every person (device) gets their own unique address.

IPv6 HEADER — 40 BYTES FIXED, SIMPLER THAN IPv4

📦

IPv6 Header Layout

HEADER FORMAT

The IPv6 header is always exactly 40 bytes — fixed, no options, no IHL field. This simplicity is intentional: routers can process it faster because they always know exactly where the header ends. IPv4 options (rare but requiring variable-length parsing) are replaced by a clean extension header chain.

Row 1
Ver
4 bits = 6
Traffic Class
8 bits
Flow Label
20 bits
Row 2
Payload Length
16 bits
Next Header
8 bits
Hop Limit
8 bits
Rows 3–6
Source IPv6 Address
128 bits — 16 bytes
Rows 7–10
Destination IPv6 Address
128 bits — 16 bytes
🔍

Every Field Explained

FIELD REFERENCE

Version (4 bits) = 0110 = 6

Always 6 for IPv6. Receivers check this first to know which IP version to process. Same position as IPv4's Version field — allows a parser to distinguish v4 from v6 without any other context.

Traffic Class (8 bits)

Equivalent to IPv4's DSCP/TOS field. The upper 6 bits are DSCP for QoS marking; the lower 2 bits are ECN. Same semantics as IPv4 — allows routers to prioritise packets based on service class. Your NGFW policy engine uses this for QoS marking.

Flow Label (20 bits) — NEW in IPv6

A 20-bit value identifying a specific flow (sequence of packets from the same source to the same destination, e.g., a single TCP connection or video stream). Routers can use it for fast-path flow-based forwarding without inspecting the full address pair on every packet. This is particularly valuable for ECMP (Equal-Cost Multi-Path) load balancing — all packets of the same flow get the same hash → same path → in-order delivery.

For your DPDK/VPP work: the Flow Label is used in RSS (Receive Side Scaling) hash computation to distribute flows across worker threads.

Payload Length (16 bits)

Length of everything after the 40-byte fixed header — extension headers + upper-layer data. Unlike IPv4's Total Length (which included the header), Payload Length excludes the fixed header. Maximum: 65,535 bytes. For Jumbograms (packets >65,535 bytes), this is set to 0 and a Jumbo Payload option in an extension header carries the actual length.

Next Header (8 bits) — Replaces IPv4 Protocol field

Identifies what follows the fixed IPv6 header. Uses the same protocol number values as IPv4's Protocol field, plus new values for extension headers:

  • 6 — TCP (directly follows)
  • 17 — UDP (directly follows)
  • 58 — ICMPv6 (directly follows)
  • 43 — Routing extension header follows
  • 44 — Fragment extension header follows
  • 0 — Hop-by-Hop Options header follows
  • 59 — No next header (empty payload)
  • 50 — ESP (IPsec, directly follows)

Extension headers form a chain: each extension header has its own Next Header field pointing to the next. The last in the chain points to the actual L4 protocol (TCP=6, UDP=17).

Hop Limit (8 bits) — IPv4's TTL, renamed

Same semantics as IPv4 TTL: decremented by 1 at each router hop, packet discarded when it reaches 0. Renamed "Hop Limit" because it is now accurately named — it was never a time limit, always a hop count.

Source and Destination Addresses (128 bits each = 16 bytes each)

The IPv6 addresses. At 16 bytes each, they dominate the header — 32 of the 40 bytes are just addresses. This is the cost of the larger address space.

🔗

Extension Headers — Replacing IPv4 Options

EXTENSION HEADERS

IPv4 had an Options field in the main header — complex, variable-length, required all routers to check it. IPv6 replaces this with a cleaner chain of extension headers that most routers skip entirely (only the destination processes most of them):

Extension HeaderNext Header ValuePurposeProcessed by
Hop-by-Hop Options0Options every router must read (rare — e.g., Router Alert for RSVP)Every router
Destination Options60Options for the destination host onlyDestination only
Routing Header43Loose/strict source routing — list of intermediate nodesEach listed node
Fragment Header44Fragmentation info (IPv6 only fragments at source)Destination only
Auth Header (AH)51IPsec authenticationDestination only
ESP Header50IPsec encryptionDestination only

💡 Key difference from IPv4: In IPv6, routers do not fragment packets. If a packet is too large for a link, the router drops it and sends ICMPv6 "Packet Too Big" (Type 2) back to the source. Only the source can fragment, using the Fragment extension header. This puts fragmentation complexity at endpoints where it belongs, keeping routers fast.

IPv6 ADDRESS FORMAT — 128 BITS, 8 GROUPS OF 16 BITS

🏷️

IPv6 Address Notation

FORMAT

An IPv6 address is 128 bits written as 8 groups of 4 hexadecimal digits, separated by colons. Each group represents 16 bits (2 bytes):

2001
bits 0–15
:
0db8
bits 16–31
:
85a3
bits 32–47
:
0000
bits 48–63
:
0000
bits 64–79
:
8a2e
bits 80–95
:
0370
bits 96–111
:
7334
bits 112–127
Full address: 2001:0db8:85a3:0000:0000:8a2e:0370:7334

Abbreviation Rules (RFC 5952)

IPv6 addresses are long — two abbreviation rules make them manageable:

Rule 1 — Drop leading zeros within each group:

  • 0db8db8
  • 00000
  • 00011

Rule 2 — Replace longest consecutive run of all-zero groups with :: (only once per address):

/* Full notation */
2001:0db8:85a3:0000:0000:8a2e:0370:7334

/* Step 1: Drop leading zeros in each group */
2001:db8:85a3:0:0:8a2e:370:7334

/* Step 2: Compress the run of zeros (0:0) with :: */
2001:db8:85a3::8a2e:370:7334  ← final compressed form

/* More examples */
fe80:0000:0000:0000:0204:61ff:fe9d:f156
→  fe80::204:61ff:fe9d:f156           # 4 consecutive zero groups compressed

0000:0000:0000:0000:0000:0000:0000:0001
→  ::1                                 # loopback address

0000:0000:0000:0000:0000:0000:0000:0000
→  ::                                  # unspecified address

/* :: can only be used ONCE per address */
2001:db8::1:0:0:1   # valid — one :: compresses middle zeros
2001::db8::1        # INVALID — two :: is ambiguous

/* Prefix notation — same as IPv4 CIDR */
2001:db8::/32       # network prefix /32 bits
fe80::/10           # link-local prefix
2001:db8::1/128     # single host (/128 = one address)
🧮

IPv6 in C — Structures and Functions

CODE
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

/* IPv6 address structure: 16 bytes = 128 bits */
struct in6_addr addr;

/* Parse a string into binary */
inet_pton(AF_INET6, "2001:db8::1", &addr);

/* Print binary as string */
char buf[INET6_ADDRSTRLEN];   /* 46 bytes: enough for any IPv6 string */
inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
printf("%s\n", buf);           /* prints: 2001:db8::1 */

/* Access raw bytes (useful for masking) */
uint8_t *bytes = addr.s6_addr;  /* 16-byte array */
printf("First byte: %02x\n", bytes[0]);

/* Check if address is in a prefix (e.g., fe80::/10 link-local) */
int is_link_local(struct in6_addr *a) {
    /* fe80::/10 — first 10 bits = 1111 1110 10 */
    return (a->s6_addr[0] == 0xfe) && ((a->s6_addr[1] & 0xc0) == 0x80);
}

/* sockaddr for IPv6 connections */
struct sockaddr_in6 sa6 = {0};
sa6.sin6_family = AF_INET6;
sa6.sin6_port   = htons(80);
inet_pton(AF_INET6, "2001:db8::1", &sa6.sin6_addr);
connect(sock, (struct sockaddr *)&sa6, sizeof(sa6));

IPv6 ADDRESS TYPES — NO BROADCAST, THREE MAIN TYPES

🗂️

Three Address Types — Unicast, Multicast, Anycast

OVERVIEW

IPv6 eliminates broadcast entirely. The three address types are:

  • Unicast — one sender to one specific receiver. The majority of IPv6 traffic.
  • Multicast — one sender to a group of receivers (all addresses starting FF). Replaces broadcast for all use cases.
  • Anycast — one sender to the nearest of multiple receivers sharing the same address. Used for DNS root servers, CDN edge nodes.

There is no broadcast in IPv6. What used to be broadcast (e.g., ARP requests) is now done with targeted multicast (NDP Solicited-Node multicast). This is one of the most important architectural improvements.

📋

Unicast Address Types — Know Each One

UNICAST
TypePrefixExampleScopeUse
Global Unicast (GUA)2000::/32001:db8::1Internet-widePublicly routable addresses — the IPv6 equivalent of public IPv4
Link-Localfe80::/10fe80::1Single link onlyAutomatically configured on every IPv6 interface. Used for NDP, router discovery. Never routed beyond a single link.
Unique Local (ULA)fc00::/7 (usually fd00::/8)fd00::1Organisation-wideIPv6's equivalent of RFC 1918 private addresses. Not routable on internet. Used for internal networks.
Loopback::1/128::1Host-localEquivalent to 127.0.0.1. Used for local host communication.
Unspecified::/128::N/AEquivalent to 0.0.0.0. Used before address assignment.
IPv4-Mapped::ffff:0:0/96::ffff:192.0.2.1N/ARepresents an IPv4 address in IPv6 notation. Used by dual-stack APIs.

Global Unicast Address Structure

A GUA is divided into three parts:

/* Global Unicast Address: 2001:db8:1234:5678:abcd:ef01:2345:6789 */

|←── Global Routing Prefix ──→|←─ Subnet ID ─→|←───── Interface ID ─────→|
  2001 : 0db8 : 1234            : 5678            : abcd : ef01 : 2345 : 6789
  (assigned by ISP/RIR)          (you define)       (interface-specific)
  typically 48 bits               16 bits            64 bits

/* The /64 boundary is the standard interface prefix */
/* Network: 2001:db8:1234:5678::/64 */
/* Host:    anything in the lower 64 bits */

💡 Why /64 everywhere? The 64-bit interface ID boundary is standard in IPv6 for several reasons: SLAAC (address autoconfiguration) uses a 64-bit EUI-64 derived from the MAC address as the interface ID; NDP Solicited-Node multicast uses the lower 24 bits of the interface ID; and /64 subnets give enough space that you'll never run out of host addresses within a subnet.

📣

Multicast Addresses — Replacing Broadcast

MULTICAST

All IPv6 multicast addresses start with FF. The second byte encodes the lifetime (permanent vs transient) and scope (how far the multicast travels):

AddressNameReplaces (IPv4)Used by
ff02::1All-nodes (link-local)255.255.255.255 broadcastGeneral link-local broadcast equivalent
ff02::2All-routers (link-local)224.0.0.2Router discovery, DHCPv6 relay
ff02::5OSPFv3 All-routers224.0.0.5OSPFv3 hello packets
ff02::6OSPFv3 DR/BDR224.0.0.6OSPFv3 DR/BDR
ff02::1:ff00:0/104Solicited-Node MulticastARP broadcastNDP — neighbour address resolution
ff05::2All-routers (site-local)N/ARouter discovery across site

Solicited-Node Multicast — The ARP Replacement

This is how IPv6 avoids broadcast for address resolution. Each interface automatically joins a Solicited-Node multicast group derived from its own IPv6 address:

/* Solicited-Node Multicast formula */
Prefix: ff02::1:ff00:0/104
Last 24 bits: lower 24 bits of the interface's IPv6 address

/* Example */
Interface IPv6: 2001:db8::abcd:ef01
Lower 24 bits:  cd:ef:01
Solicited-Node: ff02::1:ffcd:ef01

/* Why this is better than ARP broadcast */
ARP: sent to FF:FF:FF:FF:FF:FF — EVERY device on the segment must wake up and process it
NDP: sent to ff02::1:ffcd:ef01 multicast — only devices whose address ends in cd:ef:01 process it
     (statistically, only 1–2 devices on any given segment share the same lower 24 bits)
     Much lower CPU overhead on large segments

NDP — NEIGHBOUR DISCOVERY PROTOCOL (REPLACES ARP + MORE)

🤝

NDP — IPv6's Supercharged ARP

OVERVIEW

Neighbour Discovery Protocol (NDP, RFC 4861) replaces ARP and adds additional functions that required separate protocols in IPv4. It runs over ICMPv6 (Type 133–137) and uses multicast instead of broadcast.

NDP provides five functions:

  • Address Resolution — maps IPv6 address to MAC address (replaces ARP)
  • Router Discovery — hosts find routers and their prefixes automatically
  • Prefix Discovery — hosts learn the network prefix for SLAAC autoconfiguration
  • Redirect — routers inform hosts of better first-hop routes
  • Duplicate Address Detection (DAD) — verifies an address is unique before using it
📋

NDP Message Types

MESSAGES
ICMPv6 TypeNameDirectionPurpose
133Router Solicitation (RS)Host → All-routers (ff02::2)Host asks "any routers out there?" on startup
134Router Advertisement (RA)Router → All-nodes (ff02::1)Router announces prefix, MTU, default gateway, flags
135Neighbour Solicitation (NS)Host → Solicited-Node multicastAddress resolution (like ARP request) + DAD
136Neighbour Advertisement (NA)Host → Requester (unicast)Address resolution reply (like ARP reply)
137RedirectRouter → HostBetter route available via different next-hop
🔄

NDP Address Resolution — Step by Step

PROCESS

Scenario: Host A (2001:db8::1) wants to send a packet to Host B (2001:db8::2). It doesn't know B's MAC address.

A checks its Neighbour Cache
Linux maintains a Neighbour Cache (equivalent to IPv4 ARP cache) mapping IPv6 → MAC. If a valid entry for 2001:db8::2 exists, skip to step 5.
ip -6 neigh show
A computes B's Solicited-Node Multicast address
B's address ends in 00:00:02 → Solicited-Node = ff02::1:ff00:0002. The Ethernet multicast MAC for this is derived as 33:33:ff:00:00:02 (IPv6 multicast MAC prefix is 33:33 followed by the last 4 bytes of the multicast group).
A sends Neighbour Solicitation (NS)
ICMPv6 Type 135: Target = 2001:db8::2. Source link-layer option = A's MAC. Sent to Solicited-Node multicast (not broadcast!). Only B — and any other device whose address ends in 00:02 — receives this.
ICMPv6 NS: src=2001:db8::1 dst=ff02::1:ff00:2 target=2001:db8::2
B sends Neighbour Advertisement (NA)
ICMPv6 Type 136: Target = 2001:db8::2, Target link-layer option = B's MAC. Sent directly to A's unicast address (not multicast).
ICMPv6 NA: src=2001:db8::2 dst=2001:db8::1 MAC=bb:bb:bb:bb:bb:bb
A caches the mapping and sends the original packet
Neighbour cache entry: 2001:db8::2 → bb:bb:bb:bb:bb:bb (REACHABLE). All subsequent packets use this cache entry until it expires (default 30 seconds before reachability probe).
🔍

Duplicate Address Detection (DAD)

DAD

Before using a new IPv6 address, a host must verify it's unique on the link via DAD. This is especially important for SLAAC-derived addresses (two devices could theoretically derive the same EUI-64 from different MAC addresses, though this is extremely rare).

/* DAD process */
1. Host tentatively assigns the address (marks as TENTATIVE in neighbour cache)
2. Host sends NS with:
   - Source IP = :: (unspecified — not yet using the new address)
   - Destination = Solicited-Node multicast of the tentative address
   - Target = the tentative address itself
3. If no NA is received within RetransTimer (1 second default):
   → Address is unique → set to PREFERRED state → use it
4. If an NA IS received (someone already has this address):
   → Address conflict detected → DAD fails
   → Interface stays without this address
   → Kernel logs: "IPv6: DAD failed for address 2001:db8::1"

/* On Linux you can observe DAD: */
$ ip -6 addr show dev eth0
   inet6 2001:db8::1/64 scope global tentative  ← DAD in progress
   inet6 2001:db8::1/64 scope global            ← DAD passed, address active
📡

Router Advertisement — The Key to Autoconfiguration

RA MESSAGE

Router Advertisements (ICMPv6 Type 134) are sent periodically by routers to ff02::1 (all-nodes) and contain everything a host needs to configure itself:

/* Key fields in a Router Advertisement */
Cur Hop Limit:    64        # recommended Hop Limit for outgoing packets
Flags:            M=0 O=0  # M=1: use DHCPv6 for address; O=1: use DHCPv6 for options
Router Lifetime:  1800s     # how long to use this router as default gateway
Reachable Time:   0         # time to assume neighbour is reachable after last confirmation

Prefix Information Option:
  Prefix:         2001:db8::/64
  Valid Lifetime: 2592000s  # 30 days
  Preferred Lifetime: 604800s # 7 days
  L flag = 1                # prefix is on-link
  A flag = 1                # use for SLAAC autoconfiguration

MTU Option:       1500      # link MTU
Source Link-Layer: aa:bb:cc:dd:ee:ff  # router's MAC

# Receiving host uses this to:
# 1. Know it's on prefix 2001:db8::/64
# 2. Auto-configure its own address (SLAAC)
# 3. Set the router as default gateway (fe80::router_mac)

SLAAC AND DHCPv6 — ADDRESS AUTOCONFIGURATION

SLAAC — Stateless Address Autoconfiguration

SLAAC

SLAAC (RFC 4862) is IPv6's mechanism for devices to configure their own IP address without any server. A device with just a MAC address and a connected link can generate a globally routable IPv6 address in seconds — no DHCP server, no manual configuration.

SLAAC process:

Interface comes up — auto-configure Link-Local address
Every IPv6 interface automatically generates a Link-Local address (fe80::/10) using EUI-64 from the MAC address (see below). This address is used for NDP before any global address is assigned.
fe80::[EUI-64 derived from MAC]
DAD on Link-Local address
Sends NS with src=:: to the Solicited-Node multicast of the tentative fe80 address. If no conflict: Link-Local address becomes PREFERRED.
Send Router Solicitation
ICMPv6 Type 133 sent to ff02::2 (all-routers): "Any routers? Please send me a Router Advertisement."
RS: src=fe80::[eui64] dst=ff02::2
Receive Router Advertisement
Router replies with RA containing the network prefix (e.g., 2001:db8::/64), MTU, and configuration flags. Host records the router's link-local address as the default gateway.
RA: prefix=2001:db8::/64 A=1 L=1
Generate Global Unicast Address
Combine the /64 prefix from RA with the EUI-64 interface identifier: 2001:db8::[EUI-64]. This is the SLAAC address. Run DAD before using it.
GUA = prefix (64 bits) + EUI-64 (64 bits)

EUI-64 Interface Identifier Generation

/* Derive 64-bit EUI-64 from 48-bit MAC address */

MAC:    aa:bb:cc : dd:ee:ff
        ↓
Split:  aa:bb:cc | dd:ee:ff
Insert: aa:bb:cc : ff:fe : dd:ee:ff    # insert ff:fe in the middle
Flip:   a8:bb:cc : ff:fe : dd:ee:ff    # flip bit 6 (Universal/Local bit) of first byte
                                        # aa = 10101010 → bit 6 flip → 10101000 = a8

/* Example */
MAC:          00:1a:2b:3c:4d:5e
EUI-64:       02:1a:2b:ff:fe:3c:4d:5e
Link-Local:   fe80::021a:2bff:fe3c:4d5e

/* Privacy concern */
# EUI-64 embeds the MAC — tracking device across networks
# RFC 8981 "Temporary Address Extensions" generates random interface IDs
# Linux uses privacy extensions by default: random interface ID, rotated periodically
$ sysctl net.ipv6.conf.eth0.use_tempaddr   # 2 = prefer temporary addresses
🖥️

DHCPv6 — Stateful and Stateless

DHCPv6

DHCPv6 (RFC 8415) provides more control than SLAAC. It comes in two modes, controlled by flags in the Router Advertisement:

Stateless DHCPv6 (O flag = 1)

Host uses SLAAC for its address but queries DHCPv6 for additional configuration: DNS server addresses, domain search list, NTP servers. The DHCPv6 server doesn't track assignments.

Use when: You want SLAAC's simplicity but need to push DNS/NTP config centrally.

Stateful DHCPv6 (M flag = 1)

Host gets its entire address from DHCPv6 server (not SLAAC). Server maintains a lease database. Gives full control over address assignment — needed for environments requiring fixed address-to-host mapping.

Use when: You need to control exactly which address each device gets (servers, NGFW trusted hosts).

DUAL-STACK AND IPv4-to-IPv6 TRANSITION MECHANISMS

🔄

Dual-Stack — Running Both Simultaneously

DUAL-STACK

The most common and recommended transition approach is dual-stack: every node runs both IPv4 and IPv6 simultaneously. Each interface has both an IPv4 address and one or more IPv6 addresses. Applications connect using whichever version the network supports for the destination, with IPv6 preferred (Happy Eyeballs algorithm, RFC 8305).

Application
HTTP, SSH, DNS
TCP/UDP
Transport
IPv4
10.0.0.5
+
IPv6
2001:db8::5
Ethernet NIC
Single interface, one MAC
# Configure dual-stack on Linux
ip addr add 10.0.0.5/24   dev eth0    # IPv4
ip addr add 2001:db8::5/64 dev eth0    # IPv6 (manual)
# Or let SLAAC configure IPv6 automatically

# Check dual-stack status
ip addr show eth0
# inet  10.0.0.5/24 brd 10.0.0.255 scope global eth0
# inet6 2001:db8::5/64 scope global
# inet6 fe80::a00:27ff:fe4e:66a1/64 scope link

# Connect to a dual-stack server — OS picks IPv6 first (Happy Eyeballs)
curl -v https://google.com
# Look for: Connected to google.com (2a00:1450:4009:820::200e) port 443

# Force IPv4
curl -4 https://google.com
# Force IPv6
curl -6 https://google.com
🌉

Transition Mechanisms — When Dual-Stack Isn't Available

TUNNELLING

Sometimes you need IPv6 connectivity over an IPv4-only network, or vice versa. Several tunnelling and translation mechanisms handle this:

MechanismTypeHow It WorksUse Case
6in4 (SIT tunnel)TunnelIPv6 packet encapsulated in IPv4 (Protocol 41). Manual configuration of endpoints.Connecting IPv6 islands over IPv4 backbone
6to4TunnelAutomatic encapsulation using anycast relay. Embeds IPv4 address in IPv6 prefix 2002::/16.Legacy — largely deprecated
TeredoTunnelIPv6 over UDP/IPv4. Works through NAT. Used by Windows historically.Legacy — deprecated
ISATAPTunnelIntra-Site Automatic Tunnel — IPv6 over IPv4 within an organisation.Enterprise internal tunnelling
DS-LiteTunnelIPv4-in-IPv6 tunnelling + NAT. ISPs deploy this to serve IPv4 over IPv6-only infrastructure.ISP transition — CGN replacement
NAT64 + DNS64TranslationNAT64 translates IPv6 packets to IPv4 and back. DNS64 synthesises AAAA records for IPv4-only hosts. IPv6-only clients can reach IPv4 servers.IPv6-only mobile networks accessing IPv4 internet
MAP-T / MAP-ETranslation / TunnelStateless mapping between IPv4 and IPv6 — no per-connection state in the provider network.Modern ISP deployment at scale

💡 For your NGFW: NAT64 and DNS64 are the most important transition mechanisms to support. Mobile operators (Jio included) run IPv6-only core networks with NAT64 gateways to reach IPv4 content. Your NGFW must be capable of processing both IPv6 traffic and NAT64-translated traffic correctly.

IPv4 vs IPv6 — SIDE-BY-SIDE COMPARISON

Feature IPv4 IPv6
Address size 32 bits (4 bytes) 128 bits (16 bytes)
Address space ~4.3 billion ~3.4 × 10³⁸ (340 undecillion)
Header size 20–60 bytes (variable — IHL) 40 bytes (fixed)
Header checksum Yes — recomputed at every hop (TTL change) No — removed. Upper layers have their own checksum
Fragmentation By any router along the path Source only — routers send ICMPv6 Packet Too Big
Broadcast Yes — 255.255.255.255 and subnet broadcast No broadcast — replaced by targeted multicast
Address resolution ARP (L2 broadcast) NDP (ICMPv6 multicast — more targeted)
Address configuration Manual or DHCP Manual, SLAAC (automatic), or DHCPv6
NAT Required (address exhaustion) Not needed — every device gets global address
IPsec Optional Mandatory (specification requires support)
Router discovery Manual or DHCP option 3 (default gateway) SLAAC via RA — automatic, no DHCP needed
Options/extensions In-header options (variable length, every router reads) Extension headers (separate, most routers skip)
TTL/Hop Limit TTL (8 bits) Hop Limit (8 bits) — same mechanism, better name
Flow identification No dedicated field 20-bit Flow Label — enables hardware-accelerated per-flow routing
Link-local addresses 169.254.0.0/16 (APIPA, failure indicator) fe80::/10 — always present, used for NDP/routing
Loopback 127.0.0.1 (127.0.0.0/8) ::1/128 (single address)
NGFW complexity Well-understood, mature tooling Extension header chain requires careful inspection to avoid evasion
🛡️

IPv6 Security Considerations for NGFW

NGFW SECURITY

IPv6 introduces new attack surfaces that NGFWs must handle. Many legacy firewalls only inspect IPv4 — IPv6 traffic passes uninspected, creating a bypass route. This is called an "IPv6 dark hole".

  • Extension header abuse — attackers can hide payload content or confuse stateless firewalls by inserting many extension headers (Hop-by-Hop with large options, multiple Destination headers). NGFW must traverse the full extension header chain to find L4 headers.
  • Routing Header type 0 (RH0) — deprecated (RFC 5095) but still sent by attackers. Allowed source routing of packets through arbitrary nodes — a DDoS amplification vector. Drop all packets with RH0.
  • ICMPv6 must be allowed selectively — unlike IPv4 where you can block most ICMP, IPv6 depends on ICMPv6 for basic operation (NDP). Blocking it entirely breaks the network. Allow: Types 133–136 (NDP), 2 (Packet Too Big). Block: Type 137 (Redirect from external).
  • Rogue RA attacks — any device can send a Router Advertisement claiming to be the default gateway, redirecting all traffic through itself. NGFW/switches should implement RA Guard (RFC 6105).
  • Tunnelled IPv6 (6in4, Teredo) — IPv6-in-IPv4 tunnels can bypass IPv6 firewall rules. Inspect Protocol 41 packets and block unauthorised 6in4 tunnels at the perimeter.
  • IPv6 fragmentation attacks — overlapping fragments, tiny fragments hiding L4 headers. NGFW must fully reassemble IPv6 fragments before inspection.
LAB 1

Explore IPv6 on Linux — Addresses, NDP, and SLAAC

Objective: Examine a live IPv6 stack, observe NDP in action, understand SLAAC address generation, and decode IPv6 packets with Wireshark.

1
Check your IPv6 addresses: ip -6 addr show. Identify: which are link-local (fe80::), which are global unicast (2001: or similar), which are temporary privacy addresses. If no global address exists, check if your router sends RAs: sudo rdisc6 eth0.
2
Manually derive your expected link-local EUI-64 from your MAC: take ip link show eth0 MAC address, split it in half, insert ff:fe in the middle, flip bit 6 of the first byte, prepend fe80::. Compare with the actual fe80 address shown in ip addr. Do they match? If not, Linux privacy extensions may be in use.
3
Observe the NDP neighbour cache: ip -6 neigh show. Ping your IPv6 default gateway (from ip -6 route show default) and watch the neighbour cache update. Compare with IPv4: ip neigh show — notice the structural similarity (IPv6 neigh = IPv4 ARP cache).
4
Capture NDP in Wireshark: filter icmpv6. Run sudo ip -6 neigh flush all to clear the cache, then ping6 -c 1 [your_gateway_ipv6]. You should see: NS (Neighbour Solicitation) to the Solicited-Node multicast, NA (Neighbour Advertisement) back. Expand each message and identify all ICMPv6 fields.
5
Capture a Router Advertisement: filter icmpv6.type == 134 in Wireshark. You may need to wait up to 200 seconds for the next periodic RA, or trigger one with sudo ndisc6 eth0 ff02::2. Expand the RA and find: Prefix Information option (prefix, valid/preferred lifetime, A flag), MTU option, source link-layer address.
6
Bonus — Send a manual Router Solicitation with Scapy: from scapy.all import *; sendp(Ether(dst="33:33:00:00:00:02")/IPv6(dst="ff02::2")/ICMPv6ND_RS(), iface="eth0"). Capture the RA response that follows — you triggered SLAAC manually.
LAB 2

IPv6 Socket Programming and Dual-Stack in C

Objective: Write a dual-stack TCP server in C that accepts both IPv4 and IPv6 connections. Understand the AF_INET6 socket API and how IPv4-mapped addresses work.

1
Write a minimal IPv6 TCP server. Create socket with AF_INET6, SOCK_STREAM. Bind to :: (all interfaces, both IPv4 and IPv6) on port 8080. On Linux, binding to :: with IPV6_V6ONLY=0 creates a dual-stack socket that accepts both IPv4 and IPv6 connections.
2
Key code pattern:
int sock = socket(AF_INET6, SOCK_STREAM, 0);
int v6only = 0;
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only));
struct sockaddr_in6 addr = {0};
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(8080);
addr.sin6_addr = in6addr_any;   /* :: — listen on all interfaces */
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
listen(sock, 10);
Compile and run. Connect with telnet localhost 8080 (IPv4) and telnet -6 ::1 8080 (IPv6).
3
In the accept() loop, print the connecting client's address. When an IPv4 client connects to the dual-stack socket, its address appears as an IPv4-mapped IPv6 address: ::ffff:127.0.0.1. Detect and distinguish IPv4 vs IPv6 clients by checking the first 12 bytes of the address for the IPv4-mapped prefix (::ffff:0:0/96).
4
Capture the connections in Wireshark. The IPv4 connection uses a regular TCP/IPv4 packet. The IPv6 connection uses TCP/IPv6. Compare the packet sizes: IPv6 header is 40 bytes (fixed) vs IPv4 header 20 bytes (minimum) — IPv6 packets have 20 more bytes of overhead per packet just from the larger addresses.
5
Bonus — getaddrinfo for protocol-agnostic code: Replace direct socket creation with getaddrinfo(NULL, "8080", &hints, &res) where hints.ai_family = AF_UNSPEC. This returns both IPv4 and IPv6 addresses — loop through and bind to all. This is how production servers handle dual-stack without caring about the specific protocol.

M04 MASTERY CHECKLIST

Phase 1 Complete! You now have solid L1–L3 foundations: OSI model, Ethernet/L2, IPv4, and IPv6. Move to Phase 2 — Transport and Application Protocols, starting with M05 - TCP Internals. TCP is the most important transport protocol to understand deeply — it underpins HTTP, TLS, SSH, and every stateful NGFW connection.

← M03 IPv4 🗺️ Roadmap Next: M05 - TCP Internals →