DHCPv6 Relay in ROS 7.23.1: Missing route to client causing connectivity issues

Hi everyone,

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:

server:

/ipv6 address
add address=fe80::2:0:0:133 advertise=no auto-link-local=no interface=to_relay
add address=fd00:0:0:2::133 advertise=no interface=to_relay

/ipv6 nd
set [ find default=yes ] advertise-dns=yes disabled=yes
add interface=to_relay managed-address-configuration=yes other-configuration=yes
/ipv6 nd prefix
add autonomous=no interface=to_relay preferred-lifetime=5h30m prefix=fd00:0:0:2::/64 valid-lifetime=6h

/ipv6 pool
add name=p_vminternal prefix=fd00:0:0:2::/64 prefix-length=128

/ipv6 dhcp-server
add address-lists=al_dc_vm address-pool=p_vminternal allow-dual-stack-queue=no dhcp-option=dns_vminternal interface=to_relay lease-time=1h name=vm_internal_dhcp use-reconfigure=yes

relay:

/ipv6 address
add address=fd00:0:0:2::233 advertise=no disabled=yes interface=to_server
add address=fe80::2:0:0:233 advertise=no auto-link-local=no interface=to_server

add address=fd00:0:0:c::233 advertise=no disabled=yes interface=to_client
add address=fe80::c:0:0:233 advertise=no auto-link-local=no interface=to_client
###add address=fd00:0:0:2::333 advertise=no interface=to_client

/ipv6 nd
set [ find default=yes ] advertise-dns=yes disabled=yes
add advertise-dns=self interface=to_client managed-address-configuration=yes other-configuration=yes

/ipv6 dhcp-relay
add dhcp-options="" dhcp-server=fe80::2:0:0:133%to_server interface=to_client name=relay1

/ipv6 route
add comment="Default route" dst-address=::/0 gateway=fe80::2:0:0:133%to_server routing-table=main

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

Any insights would be greatly appreciated!

I cannot reproduce the issue, here is the full content of /ipv6 export on the test device configured as relay where everything works:

/ipv6 dhcp-relay
add dhcp-server=fe80::215:5dff:fe02:1e22%ether1 interface=ether2 name=relay1 store-relayed-bindings=yes
/ipv6 nd
set [ find default=yes ] interface=ether2 managed-address-configuration=yes other-configuration=yes
/ipv6 nd prefix
add interface=ether2 prefix=none
/ipv6 settings
set accept-router-advertisements=yes accept-router-advertisements-on=WAN

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.

Thanks for the reply. I'll look into your configuration and see if I made a mistake somewhere.

I noticed this item in the 7.24beta2 changelog:

Did you create the relay with WinBox? If yes, maybe try to recreate it with the CLI?

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.

@CGGXANNX
By the way, I have two quick questions:

  1. Does the DHCPv6 relay only apply to stateful DHCP, or can it not relay SLAAC?
  2. Can /ipv6 nd proxy be used to solve the issue of relaying SLAAC?

Thank you.

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:

    /ipv6 nd
    set [ find default=yes ] disabled=yes
    add interface=ether1 ra-lifetime=none
    
  • The router gets the prefix from RA and installs a dynamic EUI64 address for itself on ether1 with the prefix from upstream router:

    image

  • DHCPv6 server instance on ether2 (the LAN interface), that at first serves ULA addresses:

    /ipv6 dhcp-server option
    add code=23 name=no-dns
    
    /ipv6 pool
    add name=dhcpv6-ula prefix=fd9c:50fc:a2fa:0:2080::/80 prefix-length=128
    
    /ipv6 dhcp-server
    add address-pool=dhcpv6-ula allow-dual-stack-queue=no dhcp-option=no-dns \
        interface=ether2 lease-time=10m name=dhcpv6-lan use-reconfigure=yes
    
  • Make necessary configuration for sending RA on ether2 (the LAN interface) with no SLAAC prefix but with the management flags:

    /ipv6 nd
    add advertise-dns=self interface=ether2 \
        managed-address-configuration=yes other-configuration=yes
    
    /ipv6 nd prefix
    add interface=ether2 prefix=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.

    So after adding a bunch of static binding, we'll have the content that looks like this:

    /ipv6 dhcp-server binding
    add address=a:b:c:d::1000/128 comment="dynamic prefix \"ether1\"" \
        duid=0x0001xxxx ia-type=na iaid=123456 life-time=10m server=dhcpv6-lan
    add address=a:b:c:d::1001/128 comment="dynamic prefix \"ether1\"" \
        duid=0x0001xxxx ia-type=na iaid=234567 life-time=10m server=dhcpv6-lan
    add address=a:b:c:d::1002/128 comment="dynamic prefix \"ether1\"" \
        duid=0x0001xxxx ia-type=na iaid=345678 life-time=10m server=dhcpv6-lan
    # ...
    
  • We now add matching /ipv6 nd proxy entries for those addresses (still with bogus prefix a:b:c:d) with the interface set to ether1 (the WAN interface):

    /ipv6 nd proxy
    add address=a:b:c:d::1000 comment="dynamic prefix \"ether1\"" interface=ether1
    add address=a:b:c:d::1001 comment="dynamic prefix \"ether1\"" interface=ether1
    add address=a:b:c:d::1002 comment="dynamic prefix \"ether1\"" interface=ether1
    # ...
    
  • 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)

    :local commenttag "dynamic prefix";
    
    /ipv6 nd proxy;
    :foreach pxy in=[find comment~"$commenttag" !disabled] do={
        :local addr [get number=$pxy address];
        :local iface [get number=$pxy interface];
        disable number=$pxy;
        :log info "Refreshing ND proxy on \"$iface\" $addr...";
        enable number=$pxy;
    };
    

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.

That's a very detailed explanation. I'll definitely look into it. Thanks so much!