Protecting Stateless/Stateful DHCPv6 server with DHCPv6 Guard

In this lab I'm going to use the same simple topology which I used in this post. Except that we're going to take a look how we can turn a Cisco IOS router into a stateless/stateful DHCPv6 server, what's the difference and how we can protect it from malicious users by configuring DHCPv6 Guard on the L2 switch in the broadcast domain which is also an IPv6 FHS feature similarly to the RA Guard and IPv6 Snooping which I configured in this post.

DHCPv6 Guard Topology
DHCPv6 Topology

Like in the previous lab, here I also used the IOL_L2 image for the switch since the IOSv_L2 image doesn't support any IPv6 FHS feature, for the hosts and router I used the regular IOSv images. Now let's start with the stateless DHCPv6 server configuration:

DHCPv6_SERVER(config)#ipv6 unicast-routing 
DHCPv6_SERVER(config)#ipv6 dhcp pool POOL1
DHCPv6_SERVER(config-dhcpv6)#dns-server 8888::8
DHCPv6_SERVER(config-dhcpv6)#domain-name testing_dhcpv6guard.com

First I enabled ipv6 unicast-routing, so the router can provide prefix and default gateway information for the end hosts and respond to the Router Solicitation ICMPv6 messages. And that's the main point: the stateless DHCPv6 server doesn't provide prefix information for the hosts, it can only provide non-addressing information: like the DNS server's address, domain name, TFTP server's address etc. The default gateway and the prefix information is provided by the IPv6 router which can be the same or a different device in the broadcast domain (in this example the IPv6 router and the DHCPv6 server is the same device). And now let's configure the interface facing the end hosts:

DHCPv6_SERVER(config)#int g0/0
DHCPv6_SERVER(config-if)#ipv6 address fe80::1 link-local
DHCPv6_SERVER(config-if)#ipv6 address 2001:db8:a:a::1/64
DHCPv6_SERVER(config-if)#ipv6 nd other-config-flag 
DHCPv6_SERVER(config-if)#ipv6 dhcp server POOL1
DHCPv6_SERVER(config-if)#no shut

I assigned a custom link-local address and a global unicast prefix to the G0/0 interface. Furthermore we need to set the other-config-flagin the RA messages: it instructs the hosts to get non-addressing information using DHCPv6. So stateless DHCPv6 is really a combination of SLAAC and DHCPv6. Now let's configure HOST1 with SLAAC:

HOST1(config)#int g0/0
HOST1(config-if)#ipv6 address fe80::1111 link-local 
HOST1(config-if)#ipv6 address autoconfig 
HOST1(config-if)#no shut
HOST1(config-if)#
*Jul  4 11:39:40.327: %LINK-3-UPDOWN: Interface GigabitEthernet0/0, changed state to up
*Jul  4 11:39:41.327: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/0, changed state to up
*Jul  4 11:39:42.692: %PNP-6-PNP_DNS_SERVER_SET: DNS server (8888::8) set by (pid=308, pname=DHCPv6 client, time=11:39:42 UTC Fri Jul 4 2025)
*Jul  4 11:39:42.692: %PNP-6-PNP_DOMAIN_NAME_SET: Domain name (testing_dhcpv6guard.com) set on (GigabitEthernet0/0) by (pid=308, pname=DHCPv6 client, time=11:39:42 UTC Fri Jul 4 2025)

Besides the prefix HOST1 also successfully learned the DNS server's address and domain name information. Let's take a look what happened in the background, the DHCPv6_SRV and HOST1 exchanged the following messages:

Stateless DHCPv6 packet capture
Other-config flag is set in the RA message: as a result HOST1 sends an Information-Request to FF02::1:2 contacting the DHCPv6 server

First HOST1 (FE80::1111) sends a RS message to the All IPv6 router multicast address (FF02::2) since we configured SLAAC with autoconfig. The router (FE80::1) in the local segment responds with a RA: it provides the prefix information (2001:DB8:A:A::/64) for HOST1 and instructs HOST1 that it should use DHCPv6 to get additional non-addressing information (other-config-flag is set to 1).

Stateless DHCPv6 Reply message
Statless DHCPv6: server provides non-addressing information in the Reply message

HOST1 sends an Information-Request to the well-known all DHCPv6 multicast address (FF02::1:2) and the DHCPv6 responds with a Reply message: notice that there is no prefix information in the Reply message.

By default the switch forwards any DHCPv6 messages from any interface, so nothing stops the Attacker to respond to HOST1 with a Reply message. HOST1 can be tricked to use the DNS server/domain name provided by the Attacker, it really depends on pure luck which message HOST1 gets first, this way the Attacker can accomplish a MiTM attack. Now let's configure the switch to drop any DHCPv6 server message coming from any interface except the interface E0/0 which the legitimate DHCPv6 is connected to.

SW1(config)#ipv6 access-list DHCP_LINK_LOCAL 
SW1(config-ipv6-acl)#permit ipv6 host fe80::1 any
SW1(config)#ipv6 dhcp guard policy DHCP_SRV
SW1(config-dhcp-guard)#device-role ?
  client   Attached device is a client (default)
  monitor  Attached device is a monitor/sniffer
  server   Attached device is a dhcp server

SW1(config-dhcp-guard)#device-role server
SW1(config-dhcp-guard)#match server access-list DHCP_LINK_LOCAL

Similarly to other IPv6 FHS features we need two policies: one for the DHCP server and one for everyone else. For the server configuration I explicitly told the switch to only accept DHCPv6 server messages from the FE80::1 link-local address, if any other device tries to send DHCPv6 server messages on E0/0 the switch drops them. And here is the configuration for the hosts:

SW1(config)#ipv6 dhcp guard policy CLIENT
SW1(config-dhcp-guard)#device-role client 

We don't really need to configure the device-role explicitly since the client is the default option. Finally we apply the policies:

SW1(config)#vlan configuration 1
SW1(config-vlan-config)#ipv6 dhcp guard attach-policy CLIENT
SW1(config)#int e0/0
SW1(config-if)#ipv6 dhcp guard attach-policy DHCP_SRV

Like in the previous FHS post we apply the CLIENT policy to the whole VLAN 1 (every device is in VLAN 1), and the server policy to the interface. And because the interface configuration is more specific it takes precedence on E0/0. Now let's test it:

SW1#debug device-tracking dhcp-guard 
  Device-tracking - DHCP Guard debugging is on

I enabled the debug on the SW1 and did ashut, no shut on the G0/0 interface of HOST1, so it contacts the DHCPv6 server again:

SW1#
*Jul  4 11:59:36.736: SISF[DHG]: Et0/2 vlan 1 DHCP Client message for role dhcp client - Permit
*Jul  4 11:59:36.737: SISF[DHG]: Et0/0 vlan 1 DHCP guard process DHCP server Msg: access-list DHCP_LINK_LOCAL permit explicitly source FE80::1
*Jul  4 11:59:36.737: SISF[DHG]: Et0/0 vlan 1 DHCP Server message for role dhcp server - Permit

As we expected SW1 forwarded the DHCP server message from E0/0. We're going to take a look what happens if the Attacker tries to send DHCP server messages, but first let's configure the Stateful DHCP server.

DHCPv6_SERVER(config-if)#no ipv6 nd other-config-flag 
DHCPv6_SERVER(config-dhcpv6)#address prefix 2001:db8:a:a::/64

To do this I unset the other-config-flag in the RA messages, and added prefix information to the DHCPv6 configuration. That's all we need to do on the server. On the switch I further restrict the DHCP server policy: the switch only forwards the Reply messages if the DHCPv6 server provides the 2001:DB8:A:A::/64 prefix for the clients.

SW1(config)#ipv6 prefix-list IPV6_PREFIX permit 2001:db8:a:a::/64 le 128
SW1(config)#ipv6 dhcp guard policy DHCP_SRV
SW1(config-dhcp-guard)#match reply prefix-list IPV6_PREFIX

Now let's configure the Attacker who also runs a DHCPv6 server providing the 2001:DB8:BAD::/64 prefix:

ATTACKER(config)#int g0/0
ATTACKER(config-if)#ipv6 address fe80::bad link-local 
ATTACKER(config-if)#ipv6 address 2001:db8:bad::1/64
ATTACKER(config)#ipv6 dhcp pool POOL2
ATTACKER(config-dhcpv6)#address prefix 2001:db8:bad::/64
ATTACKER(config-dhcpv6)#dns-server 8888::bad
ATTACKER(config-dhcpv6)#domain-name bad_domain.com
ATTACKER(config)#int g0/0
ATTACKER(config-if)#ipv6 dhcp server POOL2

We configure HOST2 to get his IPv6 address via DHCP:

HOST2(config)#int g0/0
HOST2(config-if)#ipv6 address fe80::2222 link-local 
HOST2(config-if)#ipv6 address dhcp
HOST2(config-if)#no shut

And get back to SW2:

SW1#
*Jul  4 12:17:15.218: SISF[DHG]: Et0/3 vlan 1 DHCP Client message for role dhcp client - Permit
*Jul  4 12:17:15.219: SISF[DHG]: Et0/0 vlan 1 DHCP guard process DHCP server Msg: access-list DHCP_LINK_LOCAL permit explicitly source FE80::1
*Jul  4 12:17:15.219: SISF[DHG]: Et0/0 vlan 1 DHCP Server message for role dhcp server - Permit
*Jul  4 12:17:15.225: SISF[DHG]: Et0/1 vlan 1 DHCP Server message for role dhcp client - Deny
SW1#
*Jul  4 12:17:16.402: SISF[DHG]: Et0/3 vlan 1 DHCP Client message for role dhcp client - Permit
*Jul  4 12:17:16.403: SISF[DHG]: Et0/0 vlan 1 DHCP guard process DHCP server Msg: access-list DHCP_LINK_LOCAL permit explicitly source FE80::1
*Jul  4 12:17:16.403: SISF[DHG]: Et0/0 vlan 1 DHCP guard process DHCP server Msg: prefix-list IPV6_PREFIX permit explicitly prefix 2001:DB8:A:A:5912:538D:39F8:D8E2
*Jul  4 12:17:16.403: SISF[DHG]: Et0/0 vlan 1 DHCP Server message for role dhcp server - Permit

DHCP server messages on E0/1 have been dropped, only the legitimate DHCP server was permitted to send DHCP server messages on E0/0. And finally let's take a look what happens if I change the address prefix on the legitimate DHCP server (remember that previously we've configured a match reply prefix-list statement):

DHCPv6_SERVER(config)#ipv6 dhcp pool POOL1
DHCPv6_SERVER(config-dhcpv6)#no address prefix 2001:DB8:A:A::/64
DHCPv6_SERVER(config-dhcpv6)#address prefix 2001:BAD:BAD::/64

And let's try to get an IPv6 address on HOST1 via DHCP:

HOST1(config-if)#ipv6 address dhcp
HOST1#debug ipv6 dhcp 

Now the switch drops both of the messages of the legitimate DHCP server and also the messages of the Attacker's DHCP server:

SW1(config)#
*Jul 10 08:30:28.415: SISF[DHG]: Et0/2 vlan 1 DHCP Client message for role dhcp client - Permit
*Jul 10 08:30:28.420: SISF[DHG]: Et0/0 vlan 1 DHCP guard process DHCP server Msg: access-list DHCP_LINK_LOCAL permit explicitly source FE80::1
*Jul 10 08:30:28.420: SISF[DHG]: Et0/0 vlan 1 DHCP Server message for role dhcp server - Permit
SW1(config)#
*Jul 10 08:30:29.517: SISF[DHG]: Et0/2 vlan 1 DHCP Client message for role dhcp client - Permit
*Jul 10 08:30:29.518: SISF[DHG]: Et0/0 vlan 1 DHCP guard process DHCP server Msg: access-list DHCP_LINK_LOCAL permit explicitly source FE80::1
*Jul 10 08:30:29.518: SISF[DHG]: Et0/0 vlan 1 DHCP guard process DHCP server Msg: prefix-list DHCP_PREFIX deny implicitly prefix 2001:BAD:BAD:0:1CC0:EE19:8E18:2D25
*Jul 10 08:30:29.518: SISF[DHG]: Et0/0 vlan 1 !DHCP Server Reply Msg dropped due to non-authorized prefix 2001:BAD:BAD:0:1CC0:EE19:8E18:2D25
*Jul 10 08:30:30.502: SISF[DHG]: Et0/2 vlan 1 DHCP Client message for role dhcp client - Permit

Just as the debug message says: "dropped due to non-authorized prefix". HOST1 can't get an address via DHCP:

*Jul 10 08:30:26.515: IPv6 DHCP: Sending SOLICIT to FF02::1:2 on GigabitEthernet0/0
*Jul 10 08:30:26.522: IPv6 DHCP: Received ADVERTISE from FE80::1 on GigabitEthernet0/0
*Jul 10 08:30:26.522: IPv6 DHCP: Adding server FE80::1
*Jul 10 08:30:27.617: IPv6 DHCP: Sending REQUEST to FF02::1:2 on GigabitEthernet0/0
*Jul 10 08:30:27.618: IPv6 DHCP: DHCPv6 address changes state from SOLICIT to REQUEST (ADDR_ADVERTISE_RECEIVED) on GigabitEthernet0/0
*Jul 10 08:30:28.603: IPv6 DHCP: Sending REQUEST to FF02::1:2 on GigabitEthernet0/0
*Jul 10 08:30:29.575: IPv6 DHCP: Sending REQUEST to FF02::1:2 on GigabitEthernet0/0
*Jul 10 08:30:31.596: IPv6 DHCP: Sending REQUEST to FF02::1:2 on GigabitEthernet0/0
*Jul 10 08:30:35.755: IPv6 DHCP: Sending REQUEST to FF02::1:2 on GigabitEthernet0/0

HOST1 keeps sending the DHCPv6 REQUEST messages, but he never receives the REPLY from the DHCPv6 server, because the switch drops the Reply messages because of the wrong prefix.