Build log · MikroTik RB5009 · per-VLAN IPv6

Per-VLAN IPv6 on the RB5009

GUA + ULA + RA RDNSS per VLAN, IPv6 forward-chain isolation, and SLAAC anti-spoof — the path-agnostic LAN-side layer after either the VPS or Route64 path.

Overview

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 or a /56 from Route64. The reader resolves the prefix into three per-VLAN /64 placeholders once at the top, and every snippet below reads cleanly.

Prerequisites:

  • VLAN companion post applied — bridge, vlan-iot, vlan-guest interfaces and the bridge VLAN table.
  • One of the two paths complete: VPS-routed /48 or Route64 /56 — you have a default IPv6 route over WireGuard and (VPS path) the bridge holds no GUA yet.
  • DNS companion post applied if you want RA RDNSS to advertise the router as the resolver — the advertise-dns=self posture below assumes that piece is in place.

1. Conventions and placeholders

Three per-VLAN /64 prefixes are placeholder-driven so the snippet stays literal regardless of /48 or /56 origin. Resolve each once before pasting:

PlaceholderMeaning
<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

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.

2. Per-VLAN addresses and RA RDNSS

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.

MikroTik — GUA + ULA + RA RDNSS per VLAN

bash

1/ipv6/address 2add interface=bridge address=<GUA_LAN>::1/64 advertise=yes comment="LAN GUA" 3add interface=bridge address=<ULA_PREFIX>:1::1/64 advertise=yes comment="LAN ULA" 4add interface=vlan-iot address=<GUA_IOT>::1/64 advertise=yes comment="IOT GUA" 5add interface=vlan-iot address=<ULA_PREFIX>:10::1/64 advertise=yes comment="IOT ULA" 6add interface=vlan-guest address=<GUA_GUEST>::1/64 advertise=yes comment="GUEST GUA" 7add interface=vlan-guest address=<ULA_PREFIX>:20::1/64 advertise=yes comment="GUEST ULA" 8 9/ipv6/nd 10add interface=bridge advertise-dns=self managed-address-configuration=no other-configuration=no 11add interface=vlan-iot advertise-dns=self managed-address-configuration=no other-configuration=no 12add interface=vlan-guest advertise-dns=self managed-address-configuration=no other-configuration=no

Both prefixes (GUA + ULA) are advertised on the same interface. Clients get a global address for off-net reachability and a ULA for on-link addressing that survives a prefix renumber or a tunnel outage. The ULA's on-link presence is also what makes RA RDNSS resilient: the router advertises whichever address is up.

If you came from the Route64 path: tune the RA interval on each GUA-bearing VLAN so the fast fail-to-IPv4 netwatch reaches clients within 30 s instead of the default 200–600 s window. This is the only knob bounding how fast a Router-Lifetime-0 change propagates. VPS-path readers don't need this — BGP withdraws the default route in seconds, and the RA only carries the prefix.

RA interval tuning — Route64 path only

bash

1/ipv6/nd set [find interface=bridge] ra-interval=10s-30s 2/ipv6/nd set [find interface=vlan-iot] ra-interval=10s-30s 3/ipv6/nd set [find interface=vlan-guest] ra-interval=10s-30s

The 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.

3. IPv6 forward-chain isolation

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.

MikroTik — IPv6 input + forward isolation

bash

1/ipv6/firewall/filter 2add chain=input action=accept in-interface=vlan-iot protocol=udp dst-port=547 comment="IOT: DHCPv6" 3add chain=input action=accept in-interface=vlan-iot protocol=udp dst-port=53 comment="IOT: DNSv6" 4add chain=input action=accept in-interface=vlan-guest protocol=udp dst-port=547 comment="GUEST: DHCPv6" 5add chain=input action=accept in-interface=vlan-guest protocol=udp dst-port=53 comment="GUEST: DNSv6" 6 7add chain=forward action=drop in-interface=vlan-iot out-interface=bridge connection-state=new comment="IOT !-> LAN (v6)" 8add chain=forward action=drop in-interface=vlan-guest out-interface=bridge connection-state=new comment="GUEST !-> LAN (v6)" 9add chain=forward action=drop in-interface=vlan-guest out-interface=vlan-iot connection-state=new comment="GUEST !-> IOT (v6)"

4. Anti-spoof

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).

MikroTik — IPv6 anti-spoof

bash

1/ipv6/firewall/address-list 2add list=lan-legit address=<GUA_LAN>::/64 3add list=lan-legit address=<ULA_PREFIX>:1::/64 4add list=lan-legit address=fe80::/10 5add list=iot-legit address=<GUA_IOT>::/64 6add list=iot-legit address=<ULA_PREFIX>:10::/64 7add list=iot-legit address=fe80::/10 8add list=guest-legit address=<GUA_GUEST>::/64 9add list=guest-legit address=<ULA_PREFIX>:20::/64 10add list=guest-legit address=fe80::/10 11 12/ipv6/firewall/filter 13add chain=forward action=drop in-interface=bridge src-address-list=!lan-legit comment="LAN: anti-spoof" 14add chain=forward action=drop in-interface=vlan-iot src-address-list=!iot-legit comment="IOT: anti-spoof" 15add chain=forward action=drop in-interface=vlan-guest src-address-list=!guest-legit comment="GUEST: anti-spoof"

This is the enforcement layer for the index §5 ULA-only design: removing a VLAN's GUA from its *-legit list is what makes a renumber safe — the client may still hold a stale GUA for the SLAAC valid-lifetime, but the forward chain drops it the moment it tries to leave. Same mechanism applies whenever you renumber the routed prefix from either path.

5. Verification

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.

LAN-side smoke tests

bash

1# From a client on each VLAN, after a Wi-Fi reconnect or DHCP renew: 2ip -6 addr show # expect a SLAAC GUA + ULA + link-local 3ping6 -c 2 2606:4700:4700::1111 # Cloudflare DNS — verifies egress 4ping6 -c 2 <GUA_LAN>::1 # router's GUA on this VLAN 5 6# Inter-VLAN isolation (from IoT or Guest, MUST fail): 7ping6 -c 2 <GUA_LAN>::1 # IoT/Guest -> LAN gateway: should drop 8 9# Anti-spoof drop counter (on the router, watch it tick when a client 10# forges a source address it shouldn't have): 11/ipv6/firewall/filter/print stats where comment~"anti-spoof" 12 13# DNS via the advertised ULA (RDNSS): 14dig @<ULA_PREFIX>:1::1 cloudflare.com AAAA

A 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.

References

Share

Comments

Comments are powered by GitHub Discussions and require a free GitHub account to post.