Last active
December 14, 2025 09:28
-
-
Save zajdee/cf91124c8d8a4f05573b943024562fd0 to your computer and use it in GitHub Desktop.
Mikrotik DS-Lite integration script and guide
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 1. Create an `aftr` option in the DHCPv6 Client->Client options | |
| # - Comment: aftr | |
| # - Name: aftr | |
| # - Code: 64 | |
| # - Value: (empty) | |
| # 2. Add the new `aftr` option to the DHCPv6 Client->interface configuration | |
| # 3. Add this script to the DHCPv6 Client Script | |
| # TODO: Create a firewall rule | |
| # /ipv6 firewall filter | |
| # add action=accept chain=input comment=dslite protocol=ipencap src-address=<aftr-ip>/128 | |
| # or a simplified (but slightly insecure) version: | |
| # add action=accept chain=input comment=dslite protocol=ipencap | |
| # 4. Renew the DHCP lease, the AFTR should be set as your IPIP6 Tunnel device's Remote address | |
| # - The script will create a `dslite` interface, add an IPv4 to it, and set a (default) route | |
| # Change the tunnel name and the WAN interface name here if you need to | |
| :local tunName "dslite" | |
| :local wanIf "wan" | |
| # This route will be added via the DS-Lite tunnel. | |
| # Set to 0.0.0.0/0 if you want to route all traffic that way | |
| # Set to any other route if you want to | |
| :local dstNet "0.0.0.0/0" | |
| :global DecodeAFTR do={ | |
| :local string $1 | |
| :local reencoded | |
| :local length | |
| :local extract | |
| :local i 0 | |
| :local strlength [:len $string] | |
| # last character is a null character | |
| :while (i < (strlength - 1)) do={ | |
| :set length [:convert [:pick $string $i] to=num] | |
| :set extract [:pick $string ($i + 1) ($i + 1 + $length)]; | |
| :set i ($i + 1 + $length) | |
| :set reencoded ($reencoded . $extract . ".") | |
| } | |
| if ([:len $reencoded] > 0) do={ | |
| # Remove the last dot at the end of the name. RouterOS doesn't like it. | |
| :return [:pick $reencoded 0 ([:len $reencoded] - 1)] | |
| } else={ | |
| # no data | |
| :return $reencoded | |
| } | |
| } | |
| :local naValid $"na-valid" | |
| :local pdValid $"pd-valid" | |
| # DS-Lite interface IP - do not change | |
| :local ipCidr "192.0.0.2/29" | |
| # only launch the script if we have received IA_NA or IA_PD | |
| :if (($naValid = "1") || ($pdValid = "1")) do={ | |
| :local aftr [$DecodeAFTR ($options->"64")] | |
| :if ([:typeof $aftr] = "nothing" || [:len $aftr] = 0) do={ | |
| :log warning "dhcpv6-aftr: AFTR option 64 not present in DHCPv6 reply" | |
| } else={ | |
| :log debug "dhcpv6-aftr: AFTR: $aftr" | |
| /interface ipipv6 | |
| :local tunId [find where name=$tunName] | |
| :if ([:len $tunId] = 0) do={ | |
| :log error "dhcpv6-aftr: IPIP6 interface $tunName not found, adding" | |
| /interface/ipipv6/add name=$tunName remote-address="3ffe::1" !keepalive comment="managed by dhcpv6-aftr" | |
| :set tunId [find where name=$tunName] | |
| } | |
| :if ([:len [/ip/address/find where interface=$tunName and address=$ipCidr]] = 0) do={ | |
| :log warning "dhcpv6-aftr: Adding IP $ipCidr to interface $tunName" | |
| /ip/address/add interface=$tunName address=$ipCidr comment="managed by dhcpv6-aftr" | |
| } else={ | |
| :log info "dhcpv6-aftr: IP $ipCidr already set on interface $tunName" | |
| } | |
| :local wanMtu [/interface/get [find where name=$wanIf] mtu] | |
| :local wantDsliteMtu ($wanMtu - 40) | |
| :local dsliteMtu [/interface/get [find where name=$tunName] mtu] | |
| :if ($dsliteMtu != $wantDsliteMtu) do={ | |
| /interface/set [find where name=$tunName] mtu=$wantDsliteMtu | |
| :log warning "dhcpv6-aftr: Changing MTU on $tunName from $dsliteMtu to $wantDsliteMtu" | |
| } else={ | |
| :log info "dhcpv6-aftr: MTU on $tunName already set to expected $wantDsliteMtu" | |
| } | |
| :if ([:len [/ip/route/find where dst-address=$dstNet and gateway=("%".$tunName)]] = 0) do={ | |
| /ip/route/add dst-address=$dstNet gateway=("%".$tunName) comment="managed by dhcpv6-aftr" | |
| :log warning "dhcpv6-aftr: Setting route to $dstNet via %$tunName" | |
| } else={ | |
| :log info "dhcpv6-aftr: Route to $dstNet via %$tunName already exists" | |
| } | |
| :local curRemote [get $tunId remote-address] | |
| :log debug "tunName: $tunName, curRemote: $curRemote" | |
| :if ($curRemote != $aftr) do={ | |
| :log warning "dhcpv6-aftr: updating $tunName remote-address from $curRemote to $aftr on device $tunName" | |
| set $tunId remote-address=$aftr | |
| } else={ | |
| :log info "dhcpv6-aftr: AFTR unchanged ($aftr)" | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment