EDIT: Note that the script code in this post was last updated on 23 February 2023
Sorry for the thread bump, but this seemed the best place to put this.
Inspired by previous postings here, I have taken a swing at implementing RFC6204/RFC7084/RFC9096 via scripting. But this time, in such a way that you can still use v6 prefix pools. You should be able to drop this script in place on a router otherwise configured as you would expect (DHCPv6 client obtaining delegated prefix from ISP, and then assigning longer prefixes out of the dynamic pool that’s generated to various interfaces), and it will “just work”. In my limited testing so far, it seems to be pretty robust. Note, though, that so far I have only tested under 6.49.x. Feedback appreciated if you give it a try.
I almost despaired at being able to get this to work, since the dynamic pool created by the DHCPv6 client (and thus the Used Prefixes assigned from that pool) is instantly deleted prior to the on-lease-change script running, so it was impossible to grab a list of the used prefixes that needed to be invalidated from ‘/ipv6 pool used’. Fortunately, despite that, the dynamically-generated neighbor discovery prefixes still exist 1-2 seconds after that…which is weird (and I’m not sure we can count on this to always be the case in future RouterOS versions), but for now I’ll take it. The only way to resolve those back to a specific DHCPv6 client instance is by checking to see if a particular ND prefix is covered by the freshly-revoked delegated prefix; if it is, then it is included in the list of prefixes that the script generates static ND prefixes for with 0 lifetimes.
Sob mentioned in the commentary for his initial version that his script would sometimes abort if he tried to add a static ND prefix that overlapped with a dynamic one that hadn’t been cleared out yet, which is why he abandoned trying to support prefix pools. I account for that by wrapping the add within a ‘do on-error={}’ block, and attempting the add/remove cycle four times. If you get re-assigned the same prefix by ISP/upstream router, no harm done. If you don’t, dynamic prefixes will (in my experience) clear out in time for at least a couple of the adds to work.
One weird glitch I ran into was that, in the event you do get re-assigned the same prefix, occasionally the static ND prefix add would successfully happen, and then the DHCP client would re-add the overlapping dynamic prefixes before the script was able to remove the static ones. When this happened, RouterOS would stop transmitting unsolicited router advertisements for those prefixes. So as a workaround, upon successfully obtaining a lease, this script will disable and re-enable the associated addresses, which effectively re-generates the dynamic ND prefixes, fixing the problem.
To implement, simply copy-and-paste the script into the Advanced (tab) > Script block for your DHCPv6 client instance. I would also recommend that you set ‘/ipv6 nd set [find] ra-delay=0’. Also, you should set ‘multicast-helper=full’ on any MikroTik WiFi access point interfaces, in order to increase the chances that all of your wireless clients will actually receive any of these router advertisements (though if you run non-MikroTik APs that do not support doing multicast-to-unicast for IPv6 ND/RA traffic, hopefully the fact that the script adds the retired prefixes many times in a row within a few seconds will mean that at least one gets through to everybody on your LAN). Hope this proves helpful to someone.
Quite frankly, at this point in the second half of 2022 with RouterOS 7.4.1 being out, it’s simply embarrassing that MikroTik still does not implement this as a first-party feature and that we have to resort to such hackery as this…at the same time, though, I’m once again thankful that ROS is so flexible that sometimes it is possible to work around shortcomings like this with the native scripting.
`
:global dhcpv6rafix
:set dhcpv6rafix ($dhcpv6rafix + 1)
:local invalid6prefixes [/ipv6 nd prefix print as-value where prefix in $"pd-prefix"]
:global ra6gettemp do={
:return [/ipv6 nd prefix find where !dynamic and interface=($1->"interface") and prefix=($1->"prefix") and preferred-lifetime~"^0s\$" and valid-lifetime~"^0s\$"]
}
:global ra6removetemp do={
:global ra6gettemp
:foreach x in=$1 do={
/ipv6 nd prefix remove [$ra6gettemp $x]
}
}
:local ra6invalidate do={
:global ra6removetemp
:if ([:len $1] < 1) do={
:return 0
}
[$ra6removetemp $1]
:delay 1
:foreach x in=$1 do={
:do { /ipv6 nd prefix add interface=($x->"interface") prefix=($x->"prefix") preferred-lifetime=0 valid-lifetime=0 } on-error={ }
}
}
:local checkstucktempra do={
:global ra6gettemp
:global ra6removetemp
:local foundstuck 0
:foreach x in=$2 do={
:if ([:len [$ra6gettemp $x]] > 0) do={
:set foundstuck 1
}
}
:if ($foundstuck != 0) do={
:delay 10
:local pooladdrs [/ipv6 address find where address in $1 and from-pool~".+"]
[$ra6removetemp $2]
/ipv6 address disable $pooladdrs
/ipv6 address enable $pooladdrs
}
}
:if (![:tobool [:tonum ($"pd-valid")]]) do={
:delay 2
:for i from=0 to=2 do={
[$ra6invalidate $invalid6prefixes]
:delay 1
}
} else={
[$checkstucktempra $"pd-prefix" $invalid6prefixes]
}
:if ($dhcpv6rafix < 2) do={
:set ra6removetemp
:set ra6gettemp
:set dhcpv6rafix
} else={
:set dhcpv6rafix ($dhcpv6rafix - 1)
}