TTR Networks

Intermediate · IPv6 on the Debian router · section 3 of 7

The firewall for IPv6

✓ Lab-tested 5 November 2026 on Debian 13.7, systemd 257, nftables 1.1.3, Kea 2.6.3

The inet table in /etc/nftables.conf already handles IPv4 and IPv6 together: the same chains, the same verdict map between VLANs. IPv6 needs only two additions to the input chain.

  • ICMPv6: neighbour solicitations and advertisements (IPv6's ARP), router solicitations from the office's devices, packet too big (IPv6 routers never fragment; senders rely on this message to send smaller packets), and the other error messages. Echo request keeps ping working.
  • DHCPv6 replies: the ISP's DHCPv6 server answers from its link-local address to UDP port 546. Connection tracking doesn't match that answer to the router's request (the request went to a multicast address), so it needs its own rule, limited to the WAN and to link-local sources.

There's no nat table for IPv6, and no masquerade: every device has its own public address. That's what makes the forward chain's policy essential: without it, every office device would be reachable from the internet. The verdict map already allows only the pairs in the policy, so nothing from the WAN can start a connection inward, for IPv6 as for IPv4.

The prefix arrives

With the new rules loaded and networkctl reconfigure eth0, the router asks again, and this time it gets 2001:db8:ab00:500::/56. Each VLAN immediately has its /64, numbered by SubnetId (510, 520, 530), with the router at ::1.

  • networkd logs the delegated prefix, and adds an unreachable route for the whole /56. Any part of the prefix not given to a VLAN is dropped by the router instead of being sent back to the ISP, which would send it back again, in a loop.
  • The ISP now has a route for the /56 via the router's link-local address, and Kea's lease file records the delegation.
/etc/nftables.conf on the router
#!/usr/sbin/nft -f
# /etc/nftables.conf on the office router.
# Check, then load:  nft -c -f /etc/nftables.conf && systemctl reload nftables
flush ruleset

define WAN = "eth0"

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    iif lo accept
    icmp type { echo-request, destination-unreachable, time-exceeded } accept
    # IPv6 doesn't work without these: neighbour discovery replaces ARP, and
    # packet-too-big replaces fragmentation on the way.
    icmpv6 type { echo-request, nd-neighbor-solicit, nd-neighbor-advert, nd-router-solicit,
                  packet-too-big, destination-unreachable, time-exceeded, parameter-problem } accept
    iifname $WAN udp dport 546 ip6 saddr fe80::/10 accept comment "DHCPv6 replies from the ISP"
    iifname "staff" tcp dport 22 accept comment "SSH from staff only"
    iifname { "staff", "guest", "servers" } udp dport { 53, 67 } accept comment "DNS and DHCP for the office"
    iifname { "staff", "guest", "servers" } tcp dport 53 accept comment "DNS over TCP, for large answers"
    counter comment "everything else to the router"
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    # Who may start connections to whom: one line per allowed pair.
    iifname . oifname vmap {
      "staff"   . $WAN      : accept,
      "staff"   . "servers" : accept,
      "guest"   . $WAN      : accept,
      "servers" . $WAN      : accept,
    }
    counter comment "every other pair is dropped"
  }
}

table ip nat {
  chain postrouting {
    type nat hook postrouting priority srcnat;
    oifname $WAN masquerade comment "the office shares the router's public address"
  }
}
  • Lines 15–18: The ICMPv6 IPv6 needs: neighbour discovery, router solicitations from the office, and the error messages, including packet-too-big.
  • Line 19: DHCPv6 replies from the ISP's link-local address. Connection tracking can't match them to the multicast request.
  • Lines 31–36: The same policy table, now for IPv6 too: the inet family covers both.
  • Lines 41–46: NAT is for IPv4 only. With IPv6 every device has its own address, so the forward chain is the only protection.
The prefix arrives (real output)
root@router:~# nft -c -f /etc/nftables.conf && systemctl reload nftables && networkctl reconfigure eth0 && sleep 5 && ip -6 -br addr show
lo               UNKNOWN        ::1/128 
eth0@if5289      UP             2001:db8:f::2/64 fe80::ff:fe00:100/64 
staff@eth1       UP             2001:db8:ab00:510::1/64 metric 256 fe80::ff:fe00:101/64 
guest@eth1       UP             2001:db8:ab00:520::1/64 metric 256 fe80::ff:fe00:101/64 
servers@eth1     UP             2001:db8:ab00:530::1/64 metric 256 fe80::ff:fe00:101/64 

root@router:~# networkctl status eth0 --no-pager | grep -iE "dhcp|prefix|address"; ip -6 route show proto dhcp; ip -6 route show default
            Hardware Address: 02:00:00:00:01:00
IPv6 Address Generation Mode: eui64
                     Address: 203.0.113.2
          DHCPv6 Client IAID: 0x4093152
          DHCPv6 Client DUID: DUID-EN/Vendor:0000ab11b78b41d63b2b2d25
Nov 04 22:14:20 router systemd-networkd[65]: eth0: DHCP: received delegated prefix 2001:db8:ab00:500::/56
unreachable 2001:db8:ab00:500::/56 dev lo metric 1024 pref medium
default via 2001:db8:f::1 dev eth0 proto static metric 1024 pref medium

root@isp:~# ip -6 route | grep ab00; cut -d, -f1,2,9 /var/lib/kea/kea-leases6.csv | tail -2
2001:db8:ab00:500::/56 via fe80::ff:fe00:100 dev eth0 metric 1024 pref medium
address,duid,prefix_len
2001:db8:ab00:500::,00:02:00:00:ab:11:b7:8b:41:d6:3b:2b:2d:25,56
  • Lines 1–6: New rules loaded, WAN reconfigured: every VLAN has a /64 from the /56, by its SubnetId, with the router at ::1.
  • Line 14: The delegated prefix, as networkd logged it.
  • Line 15: An unreachable route for the whole /56: unused parts are dropped here, not bounced back to the ISP.
  • Line 16: The default route from the fixed gateway in 10-wan.network.
  • Line 19: On the ISP: a route for the /56 via the router's link-local address.
  • Lines 20–21: Kea's lease: the prefix, the router's DUID, and the length.

Check your understanding

The office devices now have public IPv6 addresses. What stops someone on the internet connecting to the file server?

  • The forward chain: no line in the verdict map allows the WAN to start connections to any VLAN.
  • The addresses are random, so nobody can find them.
  • Router advertisements are only sent inside the office.
  • IPv6 addresses aren't reachable from outside the /64.