darrenqu.net

Enterprise Network

A Rogue DHCPv6 Server Took Users Offline: A Post-Mortem

1734 words 9 min read

ipv6

An office network with no IPv6 deployment lost web access. The cause was a personal router advertising IPv6 — and a DHCP snooping configuration whose scope did not cover it.

on this page

An office network that had never deployed IPv6 started dropping web traffic. The cause was a consumer router someone had plugged into an access port, and the control that should have blocked it — DHCP snooping — was enabled, correctly configured, and did not apply to the protocol being abused.

This is a write-up of how it was found, why every IPv4-level check passed while users were offline, and why the obvious fix is only half of one.

Environment#

A standard campus access layer:

  • H3C access switches, single-homed user ports
  • DHCP relay to a central IPv4 DHCP server
  • dhcp snooping enabled globally and on the user VLANs, uplinks trusted
  • No IPv6 anywhere — no addressing plan, no DHCPv6 server, no RA Guard, no ND inspection

That last line is the whole incident. There was no IPv6 to protect, so nothing was protecting against IPv6.

Symptom#

Users reported that pages would not load; browsers returned connection timeouts rather than DNS errors. The failure was partial and did not hit everyone at once — the signature of something per-host rather than an upstream outage.

Why every IPv4 check passed#

The affected host had a correct DHCP-assigned address, subnet mask, gateway, and internal DNS servers. The gateway and both DNS servers answered ICMP. Nothing in the IPv4 configuration was wrong, and nothing in it ever became wrong.

The first real signal came from name resolution: an external name returned an AAAA record, and the host tried to use it. Checking the adapter showed a global unicast IPv6 address alongside the expected link-local one.

On a network with no IPv6 deployment, neither of those should have been possible.

RFC 6724 is why the host chose the broken path#

This is the part that makes the fault counter-intuitive. A dual-stack host does not “fall back” from IPv6 to IPv4 — it prefers IPv6, and it does so by a documented policy table. Windows ships the default table from RFC 6724, and you can read it directly:

netsh interface ipv6 show prefixpolicies

The default policy is:

PrefixPrecedenceLabelWhat it covers
::1/128500Loopback
::/0401Native global IPv6
::ffff:0:0/96354IPv4-mapped — i.e. all IPv4
2002::/163026to4
2001::/3255Teredo
fc00::/7313Unique local
fec0::/10111Deprecated site-local

Destination address selection picks the candidate with the highest precedence. Native IPv6 sits at 40; everything reachable over IPv4 sits at 35. So the moment a host holds a global IPv6 address and a name resolves to both A and AAAA, the IPv6 path wins — every time, by design, before a single packet is sent.

The rogue router handed out a global IPv6 address and IPv6 DNS. The host then preferred a path that terminated at a consumer router with no route anywhere. The IPv4 configuration stayed perfectly valid the entire time and was simply never used, which is exactly why it kept passing every check.

Happy Eyeballs (RFC 8305) is supposed to soften this by racing both families and falling back quickly. It helps in browsers when the IPv6 path fails fast — a RST or an ICMPv6 unreachable. It does not help when the path blackholes and connections sit until timeout, which is what produced the symptom users actually reported.

Confirming the source#

Two different mechanisms can put a global IPv6 address on a host, and they are worth separating because they are blocked by different features:

MechanismCarried inGives addressGives DNS
SLAACRouter Advertisement, ICMPv6 type 134Yes, from the RA prefixYes, via RDNSS (RFC 8106)
DHCPv6UDP 546/547, to ff02::1:2Yes, via IA_NAYes, via option 23

A consumer router plugged into a LAN typically does both: it sends RAs with the M/O flags set and answers DHCPv6. Which one your host actually used determines which control would have stopped it.

A capture settles it. The filters worth having on hand:

icmpv6.type == 134                      # Router Advertisements
icmpv6.nd.ra.flag.m == 1                # RA with Managed flag — "use DHCPv6"
dhcpv6                                   # any DHCPv6
udp.port == 546 || udp.port == 547       # DHCPv6 client / server-relay
dhcpv6.msgtype == 2                      # ADVERTISE — only a server sends this

dhcpv6.msgtype == 2 is the one that names a culprit directly. SOLICIT comes from clients and proves nothing; an ADVERTISE or REPLY on a user access port is by definition a device acting as a server, and the Ethernet source of that frame is the device you are looking for.

Locating the port#

With a MAC address the rest is mechanical. Getting the MAC was the awkward part.

The instinct is to ask the switch for its IPv6 neighbor table:

display ipv6 neighbors all

This returns nothing useful, because the switches have no IPv6 enabled. The same condition that allowed the incident also removed the tool normally used to investigate it.

The next instinct is the host’s ARP cache:

arp -a

This is a dead end, and worth stating plainly because it is a common wrong answer — including from search engines and from ChatGPT when I asked at the time. arp -a shows the ARP table, and ARP is IPv4-only. IPv6 has no ARP; it uses Neighbor Discovery, and those bindings live in a separate table:

netsh interface ipv6 show neighbors
Get-NetNeighbor -AddressFamily IPv6 | Where-Object State -ne Unreachable

Either returns the IPv6-to-MAC binding. In practice I had already pulled the MAC out of the capture by then, which is the better habit anyway: the capture shows what the device is actually transmitting, while a neighbor cache only shows what your host happened to record.

From there:

display mac-address <MAC>
interface GigabitEthernet 1/0/x
 shutdown

Service was restored the moment the port went down.

The device was a consumer router with a single uplink port — no separate WAN and LAN interfaces — so it was advertising IPv6 out of the one port that was facing the corporate network.

Root cause#

Two conditions had to hold simultaneously.

1. The host correctly preferred IPv6. Covered above. Nothing was misconfigured on the host; RFC 6724 behaved exactly as specified.

2. DHCP snooping did not cover DHCPv6. This is the part worth carrying into your next config review.

On H3C, dhcp snooping enable enables snooping for IPv4 only. DHCPv6 snooping is a separate feature behind a separate command:

ipv6 dhcp snooping enable
ipv6 dhcp snooping vlan 100 enable
interface GigabitEthernet 1/0/1
 ipv6 dhcp snooping trust      # uplinks only

On Huawei, the unversioned form enables both families:

dhcp snooping enable            # v4 and v6
dhcp snooping enable ipv6       # v6 only
IPv4 snoopingIPv6 snooping
H3C dhcp snooping enableyesno
H3C ipv6 dhcp snooping enablenoyes
Huawei dhcp snooping enableyesyes

Same command shape, different scope. A config audit that greps for dhcp snooping enable and reports “snooping: enabled” is telling you the truth and giving you the wrong answer. If your mental model came from Huawei gear, H3C will quietly disagree with you — and there is no warning, no log, and no counter to tell you the protection has a hole in it.

The fix — and why it is only half a fix#

The immediate remediation:

ipv6 dhcp snooping enable
ipv6 dhcp snooping vlan <user-vlans> enable

That closes the DHCPv6 path. It does nothing about Router Advertisements.

If the rogue device had handed out its prefix via SLAAC instead of DHCPv6 — and most consumer routers will do both — the host would still have acquired a global IPv6 address, still preferred it under RFC 6724, and still blackholed. DHCPv6 snooping inspects UDP 546/547; an RA is ICMPv6 type 134 and passes straight through it.

Blocking rogue IPv6 on an access port needs both:

ipv6 nd raguard policy HOST
 role host                       # reject RAs from this port
interface range GigabitEthernet 1/0/1 to 1/0/48
 ipv6 nd raguard apply policy HOST

RA Guard drops Router Advertisements arriving on ports configured as host-facing, which is the correct posture for every user access port whether or not you run IPv6. Huawei, Cisco and Aruba all have an equivalent; the syntax differs, the concept does not.

Verify the exact syntax against your platform and software version before applying — the feature name and the policy model vary between H3C releases, and RA Guard on some platforms requires ND snooping to be enabled first.

What to monitor afterwards#

A fix you cannot detect the failure of is not finished. Cheap things worth adding:

  • Alert on any DHCPv6 ADVERTISE or REPLY seen on an access port — on a network with no IPv6 deployment, the correct count is zero, which makes this a trivially clean signal
  • Alert on ICMPv6 type 134 from a host port, same reasoning
  • Track ipv6 dhcp snooping and RA Guard as explicit compliance items in the access-port template, not as assumed side effects of the IPv4 configuration

What I took away from this#

“We don’t run IPv6” is not the same as “IPv6 can’t hurt us.” Every modern host has IPv6 enabled and will prefer it the moment something offers it. A network with no IPv6 deployment is not IPv6-free; it is IPv6-unmonitored, which is strictly worse — you get all of the attack surface and none of the tooling. If you are not running it, you should still be explicitly blocking it. RA Guard and DHCPv6 snooping on access ports cost nothing and have no downside on an IPv4-only network.

Verify the scope of a security feature, not just its presence. “DHCP snooping: enabled” was true and misleading at the same time. This applies to any feature where a vendor splits the v4 and v6 implementations behind similar-looking commands — and it is not rare.

Know which table you are reading. arp -a looked like the right tool and confidently returned a table that could not contain the answer. IPv4 and IPv6 keep neighbour state in different places. When host tooling is ambiguous, a capture is not the last resort — it is the fastest way to get a source MAC you can trust, and the only way to tell SLAAC and DHCPv6 apart, which is the difference between fixing this and thinking you fixed it.


References

← more in Enterprise Network