Skip to main content

8.2 IPv6 Address Types and Special Addresses

In the previous section, we broke down the IPv6 header structure. That massive 128-bit address space brings unprecedented address freedom, but it also introduces classification complexity. In the IPv4 era, we were used to distinguishing between unicast, broadcast, and multicast, but in IPv6, the rules of the game have changed.

If you're expecting to find a broadcast address like 255.255.255.255 in IPv6, you'll be disappointed. This isn't an oversight—it's by design.

In this section, we won't just build an intuition for IPv6 address types; we'll also dive into the special addresses that frequently appear in kernel code (especially in struct in6_addr)—including that love-hate relationship with the Solicited-Node address.


8.2.1 Three Communication Models

The IPv6 address architecture is defined in RFC 4291. The address type determines the fate of a packet: whether it goes to one person, the nearest person, or a group of people.

1. Unicast

The most intuitive model. A unicast address uniquely identifies an interface. Send a packet to a unicast address, and it gets delivered to that single interface.

2. Anycast

This is a concept that doesn't exist in IPv4. You can think of it as a "unicast address pointing to a group of interfaces."

When you send a packet to an anycast address, routers will deliver it to the nearest interface in that group (based on routing protocol metrics).

This is a very clever design. Imagine you have a group of content delivery servers with mirrors all over the world. You can assign the same anycast address to all servers in this group. When a user initiates a request, the packet is automatically routed to the topologically nearest server, and the client doesn't even need to know about this layer—it just knows it's communicating with a single address.

3. Multicast

A multicast address identifies a group of interfaces (usually on different nodes). A packet sent to a multicast address is received by all members of that group.

Here's the key turning point: IPv6 eliminates broadcast.

Why? Because broadcast is too noisy. In an IPv4 network, ARP requests shout out via broadcast, "Who owns 192.168.1.1?" All devices on the same link are forced to wake up and process this packet, whether it's relevant to them or not.

IPv6 doesn't want to be this noisy. It replaces broadcast with multicast. When you want to shout something out, you no longer shout at everyone; you shout at a "specific group." If you're not a member of that group, you can sleep soundly.

This also explains why IPv6 deprecated ARP in favor of the Neighbor Discovery (NDISC) protocol. NDISC is based on ICMPv6. Instead of shouting across the link layer with broadcast, it elegantly uses multicast addresses to resolve L3-to-L2 address mappings.


8.2.2 Address Representation and Prefixes

Writing out IPv6 addresses is tedious work. The 128 bits are divided into 8 blocks of 16 bits each (4 hexadecimal digits), separated by colons:

xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx

But always writing this many x will drive you crazy, so there are two compression rules (the magic of ::):

  1. Leading zeros can be omitted.
  2. Consecutive all-zero blocks can be replaced with :: (but it can only be used once, otherwise it can't be uniquely reversed).

As for prefixes, they are the same thing as IPv4 subnet masks, just written differently. We use ipv6-address/prefix-length to denote them.

For example, 2001:0da7::/32 means all addresses starting with 2001:0da7 belong to this prefix. /32 means the first 32 bits are the network portion, and the rest is the host portion.


8.2.3 Special Addresses You Must Remember

Some addresses flood the codebase. When you see them, you need to know who they are by reflex.

Every interface should have one. The prefix is fe80::/64.

Its scope is limited to the current link. When a router sees a packet with a source or destination starting with fe80::, it drops it immediately and never forwards it.

It's the cornerstone of normal IPv6 operation—Neighbor Discovery and auto-configuration both rely on it. It's the "whisper" between devices; only the immediate neighbor can hear it.

2. Global Unicast Address

This is the public ID card. The format is interesting, like a layered cake:

[Global Routing Prefix (n bits)] [Subnet ID (m bits)] [Interface ID (128-n-m bits)]
  • Global Routing Prefix: The address block allocated to your network.
  • Subnet ID: For further subdividing subnets within your network.
  • Interface ID: The interface identifier, unique within a subnet.

The definition is primarily based on RFC 3587.

3. Loopback Address

::1. Just like IPv4's 127.0.0.1. Don't overthink it; it just refers to yourself.

4. Unspecified Address

All zeros, ::.

This thing must never be used as a destination address. It mainly appears during the DAD (Duplicate Address Detection) process, sent as the source address of an NS message, meaning "I don't have an address yet, I'm trying to claim this spot." If you try to manually configure it using the ip command, it won't work—the kernel will block you.

5. IPv4-Mapped Addresses and Compatible Addresses

These are products of the transition period.

  • IPv4-mapped IPv6: ::ffff:192.0.2.128. The first 80 bits are 0, followed by 16 bits of 1s (ffff), and the last 32 bits are the IPv4 address. This is used to represent an IPv4 client within a pure IPv6 socket structure.
  • IPv4-compatible: ::192.0.2.128. This was an early idea and is now deprecated. RFC 4291 explicitly states that this format is obsolete.

6. Site-Local Addresses

Once the IPv6 private addresses (similar to 10.0.0.0/8). The prefix was fec0::/10. But RFC 3879 killed it in 2004. Because its definition was ambiguous, it actually caused routing confusion. Now, Global Unicast Addresses paired with Unique Local Addresses (ULA, fc00::/7) are used to replace private address functionality.


8.2.4 Representation in the Kernel: in6_addr

Since we're hacking on the kernel, we need to look at how this thing is stored in code. Directly stuffing a __u8[16] is too rigid—sometimes we need to operate on it byte by byte, sometimes by u16, and sometimes by u32.

So Linux uses a union:

struct in6_addr {
union {
__u8 u6_addr8[16];
__be16 u6_addr16[8];
__be32 u6_addr32[4];
} in6_u;
#define s6_addr in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};

(include/uapi/linux/in6.h)

This design makes bit operations extremely smooth, eliminating the need for a bunch of forced type conversions.


8.2.5 Multicast Addresses in Detail

Multicast is a first-class citizen in IPv6. All multicast addresses start with FF (the first 8 bits).

The remaining structure is divided into three parts:

  1. Flags (4 bits): Control flags.
  2. Scope (4 bits): Controls the range.
  3. Group ID (112 bits): The group ID.

The meaning of each bit in the Flags field (currently we mainly look at the upper 4 bits of the low 4 bits, i.e., bits 0-3, noting the bit numbering convention):

  • Bit 0: Permanently reserved.
  • Bit 1 (R-flag): Whether a Rendezvous Point is embedded. This is an advanced multicast routing topic involving PIM-SM, etc., and is beyond the scope of this book (see RFC 3956 for details).
  • Bit 2 (P-flag): Whether it's allocated based on a network prefix. Defined by RFC 3306.
  • Bit 3 (T-flag): 0 means permanently assigned by IANA (well-known addresses); 1 means transiently assigned.

The Scope field determines how far this multicast packet can travel. The kernel defines these scopes using macros, which correspond to the values in Table 8-1:

Hex ValueDescriptionLinux Kernel Macro
0x01Node-localIPV6_ADDR_SCOPE_NODELOCAL
0x02Link-localIPV6_ADDR_SCOPE_LINKLOCAL
0x05Site-localIPV6_ADDR_SCOPE_SITELOCAL
0x08Organization-localIPV6_ADDR_SCOPE_ORGLOCAL
0x0eGlobalIPV6_ADDR_SCOPE_GLOBAL

8.2.6 "Special Multicast Addresses" You Must Know

Some multicast addresses are hardcoded into the protocols. You need to memorize them like you'd memorize 127.0.0.1.

1. All-Nodes Address

ff01::1 (node-local) and ff02::1 (link-local). Purpose: This is like the public address system in a new building. The Neighbor Advertisements sent by ndisc_send_na(), mentioned in the previous section, are shouted to this address.

2. All-Routers Address

ff01::2, ff02::2, ff05::2. Purpose: When you need to talk to a router (for example, sending an RS to request a Router Advertisement), you send it here.

3. MLDv2 Routers Address

ff02::16. This is specifically prepared for MLDv2-capable routers. We'll see Version 2 Multicast Listener Reports sent here when we discuss MLD later.


8.2.7 Solicited-Node Multicast Address

This is an exquisitely designed mechanism, and it's the core reason why ARP was replaced by NDISC.

The Problem: In the IPv4 world, if I want to find the MAC address of 192.168.1.5, I send a broadcast. All devices on the entire network wake up, take a look to see if it's for them, and drop it if it's not. Too noisy.

IPv6's Solution: Can I just let 192.168.1.5 wake up, while everyone else keeps sleeping?

This is where the Solicited-Node Multicast Address comes in.

When an interface is configured with a unicast or anycast address, it must simultaneously calculate and join a corresponding multicast group. The calculation method is as follows:

  1. Take the low 24 bits of that unicast/anycast address.
  2. Prepend the prefix ff02:0:0:0:0:1:ff00::/104.

The result is a multicast address in the range from ff02:0:0:0:0:1:ff00:0000 to ff02:0:0:0:0:1:ffff:ffff.

Why design it this way?

Suppose you have a unicast address of 2001:db8::1234:5678. The low 24 bits are 34:56:78 (assuming truncation for alignment). The corresponding Solicited-Node address is ff02::1:ff34:5678.

When you want to resolve the MAC address for this address, you don't need to shout. You only need to send an NS message to ff02::1:ff34:5678. Although theoretically, other devices might happen to have colliding low 24 bits (a probability of 1/2^24), this is basically negligible on a local network. Even if there is a collision, it's an extremely low-probability event. Compared to waking everyone up with a broadcast every time, this trade-off is incredibly worthwhile.

How does the kernel do it?

The code is in addrconf_addr_solict_mult():

/*
* 算法核心:保留低 24 位,拼上固定前缀
*/
static void addrconf_addr_solict_mult(const struct in6_addr *addr, struct in6_addr *solicited)
{
memset(solicited, 0, sizeof(struct in6_addr));
solicited->s6_addr32[0] = __constant_htonl(0xff020000);
solicited->s6_addr32[1] = __constant_htonl(0x00000001);
solicited->s6_addr32[2] = __constant_htonl(0xff000000);
solicited->s6_addr[12] = addr->s6_addr[12];
solicited->s6_addr[13] = addr->s6_addr[13];
solicited->s6_addr[14] = addr->s6_addr[14];
solicited->s6_addr[15] = addr->s6_addr[15];
}

(include/net/addrconf.h)

And the act of joining this multicast group is handled by addrconf_join_solict() (net/ipv6/addrconf.c).


Section Echoes

In this section, we built the IPv6 communication model: from the three basic address types (unicast, anycast, multicast), to that flexible in6_addr union in the kernel, to the various multicast magic that replaces IPv4 broadcast.

Remember the "noise" metaphor from the beginning? IPv6 attempts to transform noisy LAN communication into precise "room-to-room calls" by introducing mechanisms like the Solicited-Node Multicast Address. It trades a minuscule collision probability for a massive efficiency gain. This engineering philosophy is vividly embodied in the small address structure of ff02::1:ffxx:xxxx.

With addresses, we can locate the other party. But an address is just the destination; how do we get there? We need a route map.

In the next section, we'll dive into IPv6 Routing and see how the Linux kernel finds the next hop for packets in that vast 128-bit space using fib6_lookup().