I've been testing the DHCPv6 relay in RouterOS v7.23.1. I noticed that while clients can successfully obtain an IPv6 address, the entire link becomes unreachable because the relay doesn't automatically create a route to the client's address.
Could someone help me figure out if this is a misconfiguration on my end, or if this feature is simply not supported yet?
Here is my basic topology: client ---->(to_client)ROS 7.23.1 Relay Server(to_server)---->(to_relay)ROS 7.23.1 DHCPv6 Server
Here are the configs for the relay and the server:
Workaround:
If I manually add a route in the relay router, the client can communicate normally. For example (assuming the client obtained the address fd00:0:0:2::5): /ipv6 route add disabled=yes distance=1 dst-address=fd00:0:0:2::5/128 gateway=to_client pref-src="" routing-table=main scope=30 target-scope=10
With ether1 being the interface to the upstream router where DHCPv6 server is running (and member of the WAN list), and ether2 is the LAN interface.
No manual addresses are added (only autogenerated link-local EUI64 addresses), also no manual static routes. The relay router correctly adds /128 routes and the clients behind the relay router (on its ether2) have IPv6 connectivity.
I've found the issue. The key is to set store-relayed-bindings to yes. Of course, to see the effect immediately, you'll need to remove the distributed dynamic addresses on the server side, and then request a new address on the client side.
No, the DHCPv6 relay functionality cannot relay SLAAC.
As for /ipv6 nd proxy. It does "work" but it's very tedious, because the entries in that table are static entries with hard-coded addresses only. Which means you'll need scripting. And because the entries have hardcoded addresses, on the client side (the clients that should get IPv6 addresses), using SLAAC is not practical, because those clients will choose for them random Interface IDs (the 64bit suffix part), and if you want to use scripts to populate the /ipv6 nd proxy table, you'll probably have to periodically scan the neighbor table.
I have a test setup using /ipv6 nd proxy that chooses another way. The clients have to support DHCPv6 and will get /128 addresses assigned by DHCPv6, with ULA pool. The ULA binding are then made static, the prefix on them changed to the GUA prefix gotten from upstream via SLAAC. Then matching static /ipv6 nd proxy entries are added. There is a scheduled script that monitors the prefix change on ether1 and automatically updates the static DHCPv6 bindings and the static /ipv6 nd proxy entries with the new prefix.
In this test setup:
ether1 is the WAN port, that where Router Advertisement is accepted. No DHCPv6 client is involved.
/interface list
add name=WAN_RA
/interface list member
add interface=ether1 list=WAN_RA
/ipv6 settings
set accept-router-advertisements=yes accept-router-advertisements-on=WAN_RA
To prevent sending RA on ether1 (an issue starting from 7.23), the default entry with the all interface is disabled, and an entry created for ether1 with ra-lifetime=none:
At this point, we can plug our client devices (that can use DHCPv6, which means Android devices are not supported) to the LAN port and they'll get dynamic ULA bindings. We'll make them static, just to get the right DUID and IAID. While doing so, we clear the Prefix or Address Pool field, shorten the lifetime, and give them the comment dynamic prefix "ether1". We can also change the prefix part of the assigned addresses to a:b:c:d so that they stand out.
A script will be added next, that will update the DHCPv6 server bindings and ND proxy entries with the correct prefix from ether1. This is the script content, adapted from an older post from this forum:
:local commenttag "dynamic prefix";
:local PREFIX2IP6 do={
:return [:toip6 [:pick $1 0 [:find $1 "/" 0]]];
};
:local PREFIXLEN2MASK do={
:local mask [[:parse ":return $(~::)/$1"]];
:return [:toip6 [:pick $mask 0 [:find $mask "/" 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 dhcp-server binding
:foreach bding in=[find comment~"$commenttag"] do={
:local addr [get number=$bding address];
:local clientduid [get number=$bding duid];
:local ifname [$EXTRACTQUOTE [get number=$bding comment]];
:if (0=[:len $ifname]) do={
:log warning "dynamic_prefix_update: A DHCPv6 static binding is marked for update but does not name an interface.";
} else={
:local ifaddresses [/ipv6 address find global !deprecated interface=$ifname];
:if (1>[:len $ifaddresses]) do={
:log error "dynamic_prefix_update: Found $[:len $ifaddresses] global IPv6 addresses for interface \"$ifname\".";
} else={
:if (1!=[:len $ifaddresses]) do={
:log warning "dynamic_prefix_update: Found $[:len $ifaddresses] global IPv6 addresses for interface \"$ifname\".";
}
:local addrlen [:pick $addr ([:find $addr "/" 0]+1) [:len $addr]];
:local prefix [/ipv6 address get number=($ifaddresses->0) address];
:local prefixlen [:pick $prefix ([:find $prefix "/" 0]+1) [:len $prefix]];
:if ($addrlen < $prefixlen) do={
:set prefixlen $addrlen;
};
:local mask [$PREFIXLEN2MASK $prefixlen];
:local newaddr ((([$PREFIX2IP6 $addr] & ~$mask) | ([$PREFIX2IP6 $prefix] & $mask))."/".$addrlen);
:if ($addr!=$newaddr || ([get number=$bding prefix-pool]!="")) do={
:log info "dynamic_prefix_update: Updating \"$clientduid\" $addr -> $newaddr (prefix from \"$ifname\").";
set number=$bding address=$newaddr prefix-pool="";
};
};
};
};
/ipv6 nd proxy;
:foreach pxy in=[find comment~"$commenttag"] do={
:local addr [get number=$pxy address];
:local iface [get number=$pxy interface];
:local ifname [$EXTRACTQUOTE [get number=$pxy comment]];
:if (0=[:len $ifname]) do={
:log warning "dynamic_prefix_update: A ND proxy entry is marked for update but does not name an interface.";
} else={
:local ifaddresses [/ipv6 address find global !deprecated interface=$ifname];
:if (1>[:len $ifaddresses]) do={
:log error "dynamic_prefix_update: Found $[:len $ifaddresses] global IPv6 addresses for interface \"$ifname\".";
} else={
:if (1!=[:len $ifaddresses]) do={
:log warning "dynamic_prefix_update: Found $[:len $ifaddresses] global IPv6 addresses for interface \"$ifname\".";
}
:local prefix [/ipv6 address get number=($ifaddresses->0) address];
:local prefixlen [:pick $prefix ([:find $prefix "/" 0]+1) [:len $prefix]];
:local mask [$PREFIXLEN2MASK $prefixlen];
:local newaddr ((([$PREFIX2IP6 ($addr."/128")] & ~$mask) | ([$PREFIX2IP6 $prefix] & $mask)));
:if ($addr!=$newaddr) do={
:log info "dynamic_prefix_update: Updating ND proxy on \"$iface\" $addr -> $newaddr (prefix from \"$ifname\").";
set number=$pxy address=$newaddr;
};
};
};
};
If we run the script once, we'll see that all the previous a:b:c:d prefixes in the binding and ND proxy entries have been updated to have the correct prefix that was previously obtained with SLAAC.
When the connected DHCPv6 clients renew their binding, they'll get the new correct IPv6 addresses and have IPv6 connectivity.
The script above should be scheduled to run periodically (for example every 10 minutes) and also at boot (with maybe some delay to be sure that ether1 got the correct address from upstream first).
In my tests, I sometime experience that if the client devices have no IPv6 traffic for a long time, the ND proxy entries might become stale, and the router no longer answer NS messages for them anymore. So a script that disables and re-enables all the /ipv6 nd proxy entries should also be scheduled (to run evey hour for example)
With this setup, I am able to give IPv6 internet access to PC & VM clients behind a router that only gets an IPv6 address via SLAAC from upstream, without needing NAT66.
Also, if the SLAAC prefix on the WAN side never changes, then you don't need the script that reacts to prefix changes, just hardcode that prefix everywhere in the ND proxy and DHCPv6 binding entries.