Networking for beginners. Part 14: ICMPv6 and Neighbor Discovery

All parts of the series “Networking for beginners”

In part thirteen we learned that IPv6 has no broadcast, that every interface always has a link-local address, and that a host gets its default gateway not from configuration but "from a router advertisement". All of this rests on one protocol — ICMPv6, RFC 4443, which in IPv6 does the job of three protocols from the IPv4 block at once: ICMP (errors and ping), ARP (finding a neighbor's MAC address) and the part of DHCP that tells a host about its gateway. For the last two tasks, ICMPv6 contains a set of messages called the Neighbor Discovery Protocol, NDP, RFC 4861.

That is exactly why "block ICMP on the firewall", which in IPv4 broke only Path MTU Discovery, breaks everything in IPv6: without ICMPv6 a host can find neither its neighbor nor its router.


ICMPv6: format and types

ICMPv6 is Next Header 58 in the IPv6 header. The format is the same as ICMP: type, code, checksum (mandatory here and covering a pseudo-header with the addresses, as in UDP and TCP), then a body depending on the type:

 bytes:   1       1        2
        ┌──────┬──────┬──────────┬──────────────────────┐
        │ Type │ Code │ Checksum │ body                 │
        └──────┴──────┴──────────┴──────────────────────┘

Types are split by the high bit: 0–127 are errors, 128–255 are informational. Errors, as in IPv4, carry the beginning of the original packet, which lets the stack find the socket at fault.

Type Code What
1 0 Destination Unreachable: no route
1 1 administratively prohibited (firewall)
1 3 address unreachable: the neighbor did not answer the lookup — the counterpart of "Host Unreachable"
1 4 port unreachable (UDP)
2 0 Packet Too Big — with the next hop's MTU; the only PMTUD mechanism in IPv6
3 0 Time Exceeded — Hop Limit reached zero; traceroute relies on it
3 1 not all fragments arrived in time
4 0–2 Parameter Problem — an error in the header, including an unknown Next Header
128, 129 Echo Request, Echo Reply — ping
130, 131, 132, 143 MLD: query, report, leaving a group, MLDv2 report
133 Router Solicitation — a host looks for a router
134 Router Advertisement — a router announces itself
135 Neighbor Solicitation — a host looks for a neighbor (formerly the ARP request)
136 Neighbor Advertisement — the neighbor answers (formerly the ARP reply)
137 Redirect

Types 133–137 are NDP. They share one rule: Hop Limit = 255, and the receiver drops any of them with a lower value. The logic is simple: a packet with 255 could not have passed through a single router (it would have decremented it to 254), so it definitely came from its own segment and cannot be forged from the internet.

NDP message parameters are carried in options in "type, length, value" format: 1 — the sender's MAC, 2 — the target's MAC, 3 — prefix information, 4 — a copy of the redirected packet, 5 — MTU, 25 — DNS server addresses, 31 — search domains. We will meet them below.


Finding a neighbor: NS and NA

The task is the same as ARP's: learn a neighbor's MAC from its IPv6 address. But there is no broadcast, so you cannot ask "everyone". Instead there is a trick with solicited-node multicast.

For each of its unicast addresses, a host is subscribed to a group address derived from the lower 24 bits of that address:

address:          fe80::da47:32ff:fe0a:b0c
lower 24 bits:                        0a:0b:0c
solicited-node:   ff02::1:ff0a:b0c
group MAC:        33:33:ff:0a:0b:0c

A host that needs the MAC of the router fe80::da47:32ff:fe0a:b0c computes this group and sends the request to it. A frame to MAC address 33:33:ff:0a:0b:0c will be dropped in hardware by the network cards of the other hosts: they are not subscribed to that group. Two different addresses can match in the lower 24 bits and land in the same group, but that is rare, and an extra packet does no harm: the host compares the target address with its own and stays silent.

   host fe80::3e52:82ff:fe1a:2b3c                          router fe80::da47:32ff:fe0a:b0c
   MAC 3c:52:82:1a:2b:3c                                    MAC d8:47:32:0a:0b:0c
        │                                                         │
        ├── NS, to ff02::1:ff0a:b0c (MAC 33:33:ff:0a:0b:0c) ─────►│  (subscribers only)
        │   Target = fe80::da47:32ff:fe0a:b0c                     │
        │   option 1: my MAC 3c:52:82:1a:2b:3c                    │
        │                                                         │
        │◄── NA, unicast to fe80::3e52:82ff:fe1a:2b3c ────────────┤
        │   Target = fe80::da47:32ff:fe0a:b0c, flags R S O        │
        │   option 2: my MAC d8:47:32:0a:0b:0c                    │

Neighbor Solicitation (135) carries the address being looked up (Target) and option 1 with the sender's MAC — so the responder does not have to look it up with a request of its own, exactly like SHA in ARP.

Neighbor Advertisement (136) is sent as unicast, with the same Target and option 2 with its MAC. Three flags: S (solicited) — this is a reply to a request, not an unsolicited announcement; R (router) — the responder is a router; O (override) — an existing cache entry may be overwritten. An advertisement without S sent to ff02::1 is the counterpart of gratuitous ARP: that is how a MAC address change is announced.

In tcpdump:

$ sudo tcpdump -i eth0 -n icmp6
IP6 fe80::3e52:82ff:fe1a:2b3c > ff02::1:ff0a:b0c: ICMP6, neighbor solicitation, who has fe80::da47:32ff:fe0a:b0c, length 32
IP6 fe80::da47:32ff:fe0a:b0c > fe80::3e52:82ff:fe1a:2b3c: ICMP6, neighbor advertisement, tgt is fe80::da47:32ff:fe0a:b0c, length 32

The result goes into the same neighbor table as ARP, with the same states from part seven: INCOMPLETE, REACHABLE, STALE, DELAY, PROBE, FAILED. In IPv6 the liveness check of an entry has an explicit name — Neighbor Unreachability Detection: a STALE entry is confirmed before use by a unicast NS or an upper-layer reply, and only if three NS in a row go without an NA is the entry considered dead. On Linux, one command shows both versions:

$ ip -6 neigh
fe80::da47:32ff:fe0a:b0c dev eth0 lladdr d8:47:32:0a:0b:0c router REACHABLE
2001:db8:1234:5678::20 dev eth0 lladdr 00:11:22:33:44:55 STALE

The router mark comes from the R flag.


Finding a router: RS and RA

In IPv4 a host learned its gateway from DHCP or configuration. In IPv6 the router announces itself to everyone on the segment, and this is the most important difference in autoconfiguration.

A router sends Router Advertisements (134) to ff02::1 (all nodes) periodically, usually every few minutes, and in response to a request. It contains everything a host needs to know about the segment:

$ sudo tcpdump -i eth0 -n -v icmp6 and 'ip6[40] == 134'
IP6 (hlim 255, next-header ICMPv6 (58)) fe80::da47:32ff:fe0a:b0c > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 96
	hop limit 64, Flags [none], pref medium, router lifetime 1800s, reachable time 0ms, retrans timer 0ms
	  prefix info option (3), length 32 (4): 2001:db8:1234:5678::/64, Flags [onlink, auto], valid time 86400s, pref. time 14400s
	  mtu option (5), length 8 (1):  1500
	  rdnss option (25), length 24 (3):  lifetime 600s, addr: 2001:db8:1234:5678::1
	  dnssl option (31), length 24 (3):  lifetime 600s, domain(s): example.com.
	  source link-address option (1), length 8 (1): d8:47:32:0a:0b:0c

Line by line:

  • The sender is the router's link-local address. That is what becomes the default gateway for everyone who heard it: default via fe80::da47:32ff:fe0a:b0c proto ra from the previous part. Hosts do not need to know the router's global address.
  • hop limit 64 — what Hop Limit to put in their own packets.
  • Flags — two bits that decide whether the network needs DHCPv6: M (managed) — "get your addresses from DHCPv6", O (other) — "make your addresses yourself, but go to DHCPv6 for DNS and the rest". Both cleared — all configuration comes from the RA. Details in parts 15 and 16.
  • router lifetime 1800s — how long to treat this router as the default gateway if RAs stop arriving. Zero means "I am a router, but not a default gateway": that is how a prefix is announced without offering oneself as the way to the internet.
  • prefix info — the segment prefix with two flags: onlink (L) — addresses from this prefix are neighbors, send to them directly without a gateway; auto (A) — you may build yourself an address from this prefix. This is the entry point to SLAAC. Two lifetimes: valid — how long an address from the prefix is valid at all, preferred — how long it may be used for new connections.
  • mtu — the segment's MTU, if it is not standard.
  • rdnss, dnssl — DNS server addresses and search domains. Added later (RFC 8106) so you don't have to run DHCPv6 for a single option.
  • source link-address — the router's MAC, so it can go straight into the neighbor table.

A host sends a Router Solicitation (133) to ff02::2 (all routers) when an interface comes up, so it does not have to wait for the next periodic RA. The router answers with an RA immediately, as unicast or to everyone.

If there are several routers, the host keeps a list of default gateways, and the RA has a preference field (pref medium, high, low). So in a segment with a primary and a backup router, failover happens by itself: the primary stops sending RAs, and after router lifetime its route expires. More often, though, the backup router does not wait for that but simply takes over the same link-local address.


DAD: checking that an address is free

Before using any unicast address — link-local, built by SLAAC, assigned by DHCPv6 or entered by hand — a host must check that no one else has taken it. This is Duplicate Address Detection, and it is the same NS, only special:

IP6 :: > ff02::1:ff1a:2b3c: ICMP6, neighbor solicitation, who has 2001:db8:1234:5678:3e52:82ff:fe1a:2b3c, length 24
  • The sender is ::, the unspecified address: the host has no address of its own yet, and it cannot use the candidate.
  • The destination is the solicited-node group of the candidate: if someone has the address, they are subscribed to that group.
  • There is no option with the sender's MAC: there is nothing to update in other tables, the address is not confirmed.

The host waits a second. If an NA with this Target arrives, or someone else's NS for the same candidate from :: (two hosts checking the same address at once), the address is taken: it is marked dadfailed and not used; the stack will pick another identifier if it can. Silence — the address moves from tentative to usable. In ip -6 addr on a freshly brought-up interface you can see the tentative flag for the first second, and that is exactly why an IPv6 address appears slightly later than the interface.

If someone takes an address that passed DAD later on, the conflict shows up as in IPv4: two NAs for one NS, a flapping MAC in the neighbor table.


MLD: who listens to which groups

Since NDP is built on multicast, someone has to know which groups on a segment are needed by whom: a switch with MLD snooping does not flood multicast frames to all ports, only to those with subscribers, and a router forwards multicast traffic from other networks only to the segments where it is wanted. For this, hosts report their subscriptions with MLD (Multicast Listener Discovery, types 130–132 and 143): the router periodically sends a Query "who is listening to what", hosts answer with a Report listing their groups, and when subscribing to a new group they send a Report right away. In IPv4 the same job is done by a separate protocol, IGMP: hosts likewise report to the router which groups they are subscribed to; in IPv6 it was built into ICMPv6 and renamed.

You need to know about MLD for one reason: a switch with MLD snooping but no MLD router asking the questions may "forget" the subscriptions after a while and stop delivering NS to solicited-node groups. IPv6 on the segment stops working partially and unpredictably, and the logs are empty. If IPv6 breaks a few minutes after a switch comes up, check MLD snooping.


Redirect

Type 137, the same as in ICMP: a router tells a host that address X is closer through another router on the same segment, or that X is actually a neighbor. The body contains the address of the better next hop and a copy of the redirected packet (option 4). The acceptance rules are the same: only from the current gateway, only with Hop Limit 255, and disabled on many systems.


Why this is insecure

NDP, like ARP, trusts no one and verifies no one. The attacks are the same as in part seven, plus one new and very common one — rogue RA: any device that sends a Router Advertisement becomes the default gateway for the whole segment. More often than not it is not an attacker but a mistake: Windows with Internet Connection Sharing enabled, a virtual machine with radvd configured, a home router brought into the office. All hosts get a second gateway, half the traffic goes nowhere, and unlike DHCP, where the client takes one reply, here the host dutifully uses both routers in turn.

The protection is on the switch: RA Guard (RFC 6105) lets RAs through only from trusted ports, like DHCP snooping from part eleven. There is also a cryptographic version of NDP, SEND (Secure Neighbor Discovery): the address is derived from the host's public key and messages are signed, so an NA or RA cannot be forged in someone else's name. Mainstream systems barely support it, and you will not see SEND in real life.


What must not be filtered

The cheat sheet from part eight, rewritten for IPv6 (per RFC 4890):

Must pass everywhere, otherwise IPv6 does not work:

  • 1 Destination Unreachable, 2 Packet Too Big, 3 Time Exceeded, 4 Parameter Problem — errors;
  • 133–136 — RS, RA, NS, NA — within the segment (they will not leave it anyway because of Hop Limit 255 and link-local addresses, but a firewall on the host itself can kill them);
  • 130–132, 143 — MLD, if the network has switches with snooping.

May be restricted: 128 Echo Request from outside (as in IPv4, at the cost of diagnostics), 137 Redirect.

A "deny icmp any any" rule for IPv6 on a host makes it stop answering NS and, after half a minute, vanish for its neighbors: their entries for it go stale, NUD gets no confirmation, and even the router can no longer deliver packets to it. This is the most common cause of "IPv6 worked and then stopped" after a firewall is turned on.


Hands-on

The neighbor table and its states:

$ ip -6 neigh show nud reachable
$ ip -6 neigh show nud failed

Ask the routers manually and see the RA decoded (the ndisc6 package):

$ sudo rdisc6 eth0
Soliciting ff02::2 (ff02::2) on eth0...

Hop limit                 :           64 (      0x40)
Stateful address conf.    :           No
Stateful other conf.      :           No
Router preference         :       medium
Router lifetime           :         1800 (0x00000708) seconds
 Prefix                   : 2001:db8:1234:5678::/64
  On-link                 :          Yes
  Autonomous address conf.:          Yes
  Valid time              :        86400 (0x00015180) seconds
  Pref. time              :        14400 (0x00003840) seconds
 MTU                      :         1500 bytes (valid)
 Recursive DNS server     : 2001:db8:1234:5678::1
 Source link-layer address: D8:47:32:0A:0B:0C
 from fe80::da47:32ff:fe0a:b0c

If rdisc6 stays silent, there is no IPv6 router on the segment or its RAs are not getting through. Two replies from different addresses mean a rogue RA, and the second one is worth tracking down by its MAC.

Manual neighbor lookup is ndisc6 fe80::da47:32ff:fe0a:b0c eth0, the counterpart of arping. To stop accepting RAs on a machine that is itself a router: sysctl net.ipv6.conf.eth0.accept_ra=0. And a full NDP capture is tcpdump -i eth0 -n -v icmp6: in the first seconds after ip link set eth0 up you will see DAD for the link-local address, RS, RA, DAD for the global address and the first NS/NA — the whole protocol in ten lines.


Key takeaways

  • ICMPv6 (Next Header 58) combines errors, ping, neighbor and router discovery, and MLD. Errors are types up to 127, the rest start at 128.
  • Neighbor lookup: an NS to the solicited-node group ff02::1:ffXX:XXXX built from the lower 24 bits of the address, a frame to 33:33:ff:XX:XX:XX that other cards drop on their own; an NA as unicast with the S, R, O flags. The neighbor table and NUD work as in ARP.
  • A router announces itself with an RA to ff02::1: the sender's link-local address becomes the gateway, the M and O flags decide about DHCPv6, prefix info with the A flag is the entry to SLAAC, RDNSS gives DNS. An RS speeds up the first RA.
  • DAD: an NS from :: for your own future address; a second of silence and the address is usable.
  • All NDP messages carry Hop Limit 255, otherwise they are dropped. There is no protection against forgery: rogue RA is a common outage, RA Guard is the cure.
  • You must not filter ICMPv6 wholesale: types 1–4 and 133–136 must get through.

Next — Part 15: SLAAC: how a host builds itself an address from the prefix in an RA and an interface identifier without a single server, where the identifier comes from (EUI-64, stable random, temporary), what the valid and preferred lifetimes mean, how addresses are deprecated and replaced, and how to set it up on a Linux router.

Only registered users can leave a comment.

Sign in or register to leave a comment.