Build log · MikroTik RB5009 · per-VLAN IPv6
Per-VLAN IPv6 on RouterOS
GUA + ULA + RA RDNSS per VLAN, IPv6 forward-chain isolation, and SLAAC anti-spoof — the LAN-side layer after either routed-IPv6 path.
Build log · MikroTik RB5009 · per-VLAN IPv6
GUA + ULA + RA RDNSS per VLAN, IPv6 forward-chain isolation, and SLAAC anti-spoof — the LAN-side layer after either routed-IPv6 path.
The step after either path post in the RB5009 CGNAT series: plumb the routable IPv6 you just stood up through to every VLAN. At this point the router has outbound routable IPv6 (a working default route over WireGuard, with either eBGP or a static gateway) but no LAN client has a GUA. This post fixes that: per-VLAN GUA + ULA + RA RDNSS, IPv6 forward-chain isolation, and anti-spoof enforcement against the address-list pattern this series uses.
Path-agnostic: the same snippets work whether your routed prefix is a /48 from a VPS, a /56 from Route64, or a self-originated /48 from the multi-homed build. The reader resolves the prefix into three per-VLAN /64 placeholders once at the top, and every snippet below reads cleanly.
Prerequisites:
bridge,
vlan-iot, vlan-guest interfaces and the bridge VLAN table.advertise-dns=self posture below assumes that piece is in place.Three per-VLAN /64 prefixes are placeholder-driven so the snippet stays literal regardless of /48 or /56 origin. Resolve each once before pasting:
| Placeholder | Meaning |
|---|---|
<GUA_LAN> | The /64 prefix for VLAN 1 (main LAN), written so <GUA_LAN>::1/64 is the router IP. |
<GUA_IOT> | The /64 prefix for VLAN 10 (IoT), written so <GUA_IOT>::1/64 is the router IP. |
<GUA_GUEST> | The /64 prefix for VLAN 20 (Guest), written so <GUA_GUEST>::1/64 is the router IP. |
<ULA_PREFIX> | Same ULA from the index §2. |
Fill from the path post you just completed:
| From this path | <GUA_LAN> | <GUA_IOT> | <GUA_GUEST> |
|---|---|---|---|
VPS /48 (<LAN_PREFIX>=2001:db8) | <LAN_PREFIX>:1 | <LAN_PREFIX>:10 | <LAN_PREFIX>:20 |
Route64 /56 (<R64_56>=2001:db8:abcd:c0) | <R64_56>01 | <R64_56>10 | <R64_56>20 |
Multi-homed /48 (<OWN_48>=2001:db8:eff9) | <OWN_48>:1 | <OWN_48>:10 | <OWN_48>:20 |
The VLAN numbers reused as IPv6 slice IDs (:1, :10, :20 or 01,
10, 20) mean the mapping stays obvious in tcpdump regardless of which
path delivered the prefix.
Each VLAN gets a GUA from the routed prefix and a stable ULA from the
locally-generated fd… prefix. RA RDNSS uses advertise-dns=self, so the
router advertises the address it holds on the interface rather than a
hardcoded one — the DNS companion post
explains why that survives a renumber and how the ULA stays the memorized
resolver.
/ipv6/address
add interface=bridge address=<GUA_LAN>::1/64 advertise=yes comment="LAN GUA"
add interface=bridge address=<ULA_PREFIX>:1::1/64 advertise=yes comment="LAN ULA"
add interface=vlan-iot address=<GUA_IOT>::1/64 advertise=yes comment="IOT GUA"
add interface=vlan-iot address=<ULA_PREFIX>:10::1/64 advertise=yes comment="IOT ULA"
add interface=vlan-guest address=<GUA_GUEST>::1/64 advertise=yes comment="GUEST GUA"
add interface=vlan-guest address=<ULA_PREFIX>:20::1/64 advertise=yes comment="GUEST ULA"
/ipv6/nd
add interface=bridge advertise-dns=self managed-address-configuration=no other-configuration=no
add interface=vlan-iot advertise-dns=self managed-address-configuration=no other-configuration=no
add interface=vlan-guest advertise-dns=self managed-address-configuration=no other-configuration=noThe index §5 ULA-only update
adds a ULA-only VLAN 30 on top of this base — same /ipv6/nd posture,
but no GUA, so its clients use native ISP IPv4 for anything
internet-facing. Apply it after this post if you want the main client
SSID off the metered (VPS) or broker (Route64) tunnel.
Mirror the IPv4 isolation the VLAN companion post established. IoT and Guest get on-link DHCPv6 and DNSv6 to the router but cannot reach the trusted LAN; Guest cannot reach IoT either.
/ipv6/firewall/filter
add chain=input action=accept in-interface=vlan-iot protocol=udp dst-port=547 comment="IOT: DHCPv6"
add chain=input action=accept in-interface=vlan-iot protocol=udp dst-port=53 comment="IOT: DNSv6"
add chain=input action=accept in-interface=vlan-guest protocol=udp dst-port=547 comment="GUEST: DHCPv6"
add chain=input action=accept in-interface=vlan-guest protocol=udp dst-port=53 comment="GUEST: DNSv6"
add chain=forward action=drop in-interface=vlan-iot out-interface=bridge connection-state=new comment="IOT !-> LAN (v6)"
add chain=forward action=drop in-interface=vlan-guest out-interface=bridge connection-state=new comment="GUEST !-> LAN (v6)"
add chain=forward action=drop in-interface=vlan-guest out-interface=vlan-iot connection-state=new comment="GUEST !-> IOT (v6)"SLAAC makes prefix forgery trivially cheap — a client on vlan-iot can
configure any source address it wants. The address-list filters make the
forgery not work: each interface only forwards traffic whose source falls
in that VLAN's legitimate /64 (the GUA, the ULA, or link-local).
/ipv6/firewall/address-list
add list=lan-legit address=<GUA_LAN>::/64
add list=lan-legit address=<ULA_PREFIX>:1::/64
add list=lan-legit address=fe80::/10
add list=iot-legit address=<GUA_IOT>::/64
add list=iot-legit address=<ULA_PREFIX>:10::/64
add list=iot-legit address=fe80::/10
add list=guest-legit address=<GUA_GUEST>::/64
add list=guest-legit address=<ULA_PREFIX>:20::/64
add list=guest-legit address=fe80::/10
/ipv6/firewall/filter
add chain=forward action=drop in-interface=bridge src-address-list=!lan-legit comment="LAN: anti-spoof"
add chain=forward action=drop in-interface=vlan-iot src-address-list=!iot-legit comment="IOT: anti-spoof"
add chain=forward action=drop in-interface=vlan-guest src-address-list=!guest-legit comment="GUEST: anti-spoof"Path-side verification (BGP session, birdc, netwatch) lives in the
post you just finished. This post adds the LAN-side checks. The
one-glance path-agnostic check is the screenshot in the
index §7.
# From a client on each VLAN, after a Wi-Fi reconnect or DHCP renew:
ip -6 addr show # expect a SLAAC GUA + ULA + link-local
ping6 -c 2 2606:4700:4700::1111 # Cloudflare DNS — verifies egress
ping6 -c 2 <GUA_LAN>::1 # router's GUA on this VLAN
# Inter-VLAN isolation (from IoT or Guest, MUST fail):
ping6 -c 2 <GUA_LAN>::1 # IoT/Guest -> LAN gateway: should drop
# Anti-spoof drop counter (on the router, watch it tick when a client
# forges a source address it shouldn't have):
/ipv6/firewall/filter/print stats where comment~"anti-spoof"
# DNS via the advertised ULA (RDNSS):
dig @<ULA_PREFIX>:1::1 cloudflare.com AAAAA client that gets a GUA, can ping a public v6 address, cannot reach the LAN gateway from IoT/Guest, and resolves via the ULA-advertised RDNSS has all four layers of this build working: tunnel up, default route learned, GUA assigned, isolation enforced.
advertise-dns=self postureComments