FIREWALL / IPv6

IPv6.

IPv6 doesn't NAT — every host gets a routable global address. This is great for symmetry and end-to-end connectivity, and bad for accidental security because the "we're hidden behind NAT" assumption goes away. The rules below re-establish a one-way membrane between WAN and LAN at the firewall layer instead.

IPv6 firewall is in different places on v6 vs v7

RouterOS v6

RouterOS v6 ships IPv6 as a separate package (ipv6). The command tree lives at /ipv6 firewall filter with space-separated paths.

If /ipv6 firewall filter doesn't exist, the package isn't installed: /system package print and enable it via /system package enable ipv6, then reboot.

RouterOS v7

RouterOS v7 has IPv6 integrated. The command tree is /ipv6/firewall/filter with slash-separated paths and matchers that line up almost 1:1 with the IPv4 set. No separate package, nothing to enable.

Input chain (IPv6)

Mirrors the IPv4 input chain with two differences: icmpv6 replaces icmp and is mandatory not just useful (Neighbor Discovery, MLD, PMTUD all travel over ICMPv6 — block it and IPv6 stops working), and the loopback address is ::1 not 127.0.0.1.

ipv6-input.rsc v6 12 lines · 594 bytes
# IPv6 input chain (RouterOS v6 — separate ipv6 package)
/ipv6 firewall filter
add chain=input action=accept connection-state=established,related,untracked \
  comment="mtkf6: accept established/related/untracked"
add chain=input action=drop connection-state=invalid \
  comment="mtkf6: drop invalid"
add chain=input action=accept protocol=icmpv6 \
  comment="mtkf6: accept icmpv6 (RFC 4890 mandatory types)"
add chain=input action=accept dst-address=::1 \
  comment="mtkf6: accept loopback"
add chain=input action=drop in-interface-list=!LAN \
  comment="mtkf6: drop everything not from LAN"
ipv6-input.rsc v7 12 lines · 583 bytes
# IPv6 input chain (RouterOS v7 — integrated)
/ipv6/firewall/filter
add chain=input action=accept connection-state=established,related,untracked \
  comment="mtkf6: accept established/related/untracked"
add chain=input action=drop connection-state=invalid \
  comment="mtkf6: drop invalid"
add chain=input action=accept protocol=icmpv6 \
  comment="mtkf6: accept icmpv6 (RFC 4890 mandatory types)"
add chain=input action=accept dst-address=::1 \
  comment="mtkf6: accept loopback"
add chain=input action=drop in-interface-list=!LAN \
  comment="mtkf6: drop everything not from LAN"

Forward chain (IPv6)

Simpler than the IPv4 forward chain because there's no connection-nat-state to track — you don't NAT IPv6 (and if you do, you're solving a different problem with worse tools). The catch-all "drop new from WAN" replaces the dst-nat dance.

ipv6-forward.rsc v6 10 lines · 459 bytes
# IPv6 forward chain (RouterOS v6)
/ipv6 firewall filter
add chain=forward action=accept connection-state=established,related,untracked \
  comment="mtkf6: accept established/related/untracked"
add chain=forward action=drop connection-state=invalid \
  comment="mtkf6: drop invalid"
add chain=forward action=accept protocol=icmpv6 \
  comment="mtkf6: accept icmpv6"
add chain=forward action=drop in-interface-list=WAN \
  comment="mtkf6: drop new WAN-ingress"
ipv6-forward.rsc v7 10 lines · 459 bytes
# IPv6 forward chain (RouterOS v7)
/ipv6/firewall/filter
add chain=forward action=accept connection-state=established,related,untracked \
  comment="mtkf6: accept established/related/untracked"
add chain=forward action=drop connection-state=invalid \
  comment="mtkf6: drop invalid"
add chain=forward action=accept protocol=icmpv6 \
  comment="mtkf6: accept icmpv6"
add chain=forward action=drop in-interface-list=WAN \
  comment="mtkf6: drop new WAN-ingress"

Why ICMPv6 must stay open

Unlike IPv4 ICMP, ICMPv6 carries control-plane traffic that IPv6 cannot function without. Specifically:

  • Neighbor Discovery (types 135–136) — IPv6's replacement for ARP. Block it and hosts can't find their next-hop MAC.
  • Router Advertisements (type 134) — how clients learn the prefix and default route on a SLAAC network.
  • Path MTU Discovery (type 2 "Packet too big") — IPv6 routers don't fragment, so PMTUD is the only way to negotiate MTU.

If you want to be more surgical than the blanket protocol=icmpv6 accept, RFC 4890 lists which types are required and which are safe to drop on the public-facing interface. We use the blanket form because the per-type rule list is long and the operational risk of getting it wrong outweighs the marginal security gain.

When you'd want to deviate

  • Server with public IPv6 services. Add specific action=accept rules for the listening ports (e.g. protocol=tcp dst-port=443) above the catch-all drop. Don't drop the catch-all — keep it.
  • IPv6 ULA-only network. If you're routing fd00::/8 internally and not delegating a public prefix to LAN clients, the WAN-drop rule is still appropriate (you may have a WAN with a public prefix even if clients don't use it).

References

  • RFC 4890 — Recommendations for Filtering ICMPv6 Messages in Firewalls
  • MikroTik wiki: Manual:IPv6/Firewall
  • MikroTik forum thread on hAP ax² IPv6 hardware-offload status (FastTrack on IPv6 is v7-only and board-dependent)