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.
Build log · MikroTik RB5009 · per-VLAN IPv6
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.
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:
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 |
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.
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=noBoth 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-30sThe 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.
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)"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.
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 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
Comments are powered by GitHub Discussions and require a free GitHub account to post.