Ended up using the loopback interface to force RouterOS to reserve a subnet from the delegated prefix. DHCPv6 Client's script is used to update the firewall rules as necessary. It's a somewhat smart because it makes sure that allocated subnet has the same prefix as the newly delegated one. Should help if script gets called before changes are propagated throughout RouterOS.
/system/script/add name=nptv6
# argLoopbackInt: name of the loopback interface
# argWanPool: name of the WAN pool
# argUlaPool: name of the ULA pool
# argManagedID: regex-escaped unique ID of the managed objects
:global argLoopbackInt
:global argWanPool
:global argUlaPool
:global argManagedID
/ipv6/pool
:local varWanPrefix [get value-name=prefix $argWanPool]
:local varUlaPrefix [get value-name=prefix $argUlaPool]
:global WaitAddress do={
/ipv6/address
:local varAddress
:retry command={
:set varAddress [get value-name=address [find interface=$1 (address in $2) comment~"$3\$"]]
} delay=1 max=5
:return $varAddress
}
:do {
/ipv6/address
:local varOldGuaPrefix [get value-name=address [find comment~"$argManagedID\$"]]
:local varNewGuaPrefix [$WaitAddress $argLoopbackInt $varWanPrefix $argManagedID]
:if ($varOldGuaPrefix != $varNewGuaPrefix) do={
:log info "Set $varNewGuaPrefix <-> $varUlaPrefix"
/ipv6/firewall/mangle
set dst-prefix=$varNewGuaPrefix [find action=snpt comment~"$argManagedID\$"]
set dst-address=$varNewGuaPrefix src-prefix=$varNewGuaPrefix [find action=dnpt comment~"$argManagedID\$"]
}
} on-error={
/ipv6/address
remove [find comment~"$argManagedID\$"]
add interface=$argLoopbackInt advertise=no from-pool=$argWanPool comment="Managed: NPTv6 / $argManagedID"
:local varGuaPrefix
:do {
:set varGuaPrefix [$WaitAddress $argLoopbackInt $varWanPrefix $argManagedID]
} on-error={
remove [find comment~"$argManagedID\$"]
:log error "Unable to allocate prefix from $varWanPrefix on $argLoopbackInt"
:error ""
}
:log info "Add $varGuaPrefix <-> $varUlaPrefix"
/ipv6/firewall/mangle
remove [find comment~"$argManagedID\$"]
add chain=postrouting action=snpt src-address=$varUlaPrefix src-prefix=$varUlaPrefix dst-prefix=$varGuaPrefix comment="Managed: NPTv6 / $argManagedID"
add chain=prerouting action=dnpt dst-address=$varGuaPrefix src-prefix=$varGuaPrefix dst-prefix=$varUlaPrefix comment="Managed: NPTv6 / $argManagedID"
}
/ipv6/dhpc-client/edit value-name=script
:if ($"pd-valid" = 1) do={
:global argLoopbackInt "loopback"
:global argWanPool <Pool added by DHCPv6 Client>
:global argUlaPool <Pool reserved for ULA addresses>
:global argManagedID "some-random-string"
/system/script/run nptv6
}
Where loopback is just "/interface/bridge/add name=loopback"