Mikrotik RouterOS Script: Update the Firewall with Dynamic IPv6 Prefix

RouterOS has no built-in method for updating firewall rules in case of an IPv6 prefix change, and the firewall cannot match on just the local part of an IPv6 address. If your ISP hands out a new prefix and your network gets renumbered, the server gets a new IPv6 address (new prefix + old interface identifier) and you need to update the firewall rule which allows access to certain ports on that server.

This script enables the use of firewall rules with dynamic prefixes by updating firewall address list entries if a prefix changes. To do that, the script needs to be called regularly or after certain events. For example, you can call the script when a DHCPv6 lease is obtained by adding this code to a DHCPv6 client. This invocation assumes that the script is stored in /system/scripts under the name dynamic_prefix_update:

if (1=$"pd-valid") do={
    /delay 2
    /system script run dynamic_prefix_update;

Firewall address list entries that you want updated in case of a new prefix must have a comment containing dynamic prefix and in quotes the name of an interface which has a global IPv6 address. The script treats the first quoted string in the comment as the name of the interface. For example, if you want your address list entry to always have the prefix which is assigned to the vlan5-if interface, add this comment to the address list entry: dynamic prefix "vlan5-if"

The script uses log level "warning" for problems and log level "info" for successful updates. It is silent if nothing needs to be done.

Save the script or just copy and paste below.

If you like this, you can make my day by sending a small token of appreciation to this Bitcoin address: 1L3UMexLCfyJ68FhMR56uo1fPdsxEYdAgw

# This script updates IPv6 firewall address list entries with dynamic prefixes
# from interface addresses.
# It acts on address list entries that have a comment containing 'dynamic prefix'
# and (in quotes, "...") the name of an interface with a global IPv6 address.
# The update mask is taken from the interface IPv6 address. The mask length of
# the address list entry is not changed.
# scripting at n377 de

:local comment "dynamic prefix";

:local PREFIX2IP6 do={
    :return [:toip6 [:pick $1 0 [:find $1 "/" 0]]];

:local EXTRACTQUOTE do={
    :local start [:find $1 "\"" 0];
    :local end [:find $1 "\"" $start];
    :if ($start>=0 && $end>$start) do={
        :return [:pick $1 ($start+1) $end];
    :return "";

/ipv6 firewall address-list;
:foreach addrlist in=[find comment~$comment] do={
    :local addr [get number=$addrlist address];
    :local listname [get number=$addrlist list];
    :local ifname [$EXTRACTQUOTE [get number=$addrlist comment]];
    :if (0=[:len $ifname]) do={
        :log warning "dynamic_prefix_update: An address list entry is marked for update but does not name an interface.";
    } else={
        :local ifaddresses [/ipv6 address find global interface=$ifname];
        :if (1!=[:len $ifaddresses]) do={
            :log warning "dynamic_prefix_update: Found $[:len $ifaddresses] global IPv6 addresses for interface \"$ifname\".";
        } else={
            :local prefix [/ipv6 address get number=($ifaddresses->0) address];
            :local prefixlen [:pick $prefix ([:find $prefix "/" 0]+1) [:len $prefix]];
            :local mask [$PREFIX2IP6 [[:parse ":return $(~::)/$prefixlen"]]];
            :local newaddr ((([$PREFIX2IP6 $addr] & ~$mask) | ([$PREFIX2IP6 $prefix] & $mask)).[:pick $addr [:find $addr "/" 0] [:len $addr]]);
            :if ($addr!=$newaddr) do={
                :log info "dynamic_prefix_update: Updating \"$listname\" $addr -> $newaddr (prefix from \"$ifname\").";
                set number=$addrlist address=$newaddr;