By the way, I can open some ports for my server thoug… BUT… That’s for ALL my inner network devices, which isn’t a good practice. and I have noticed that The ipv6 firewall has a rule called “Src.mac”, So why we don’t have an option called “Dst.mac” ? I’m sure we can know the “destination ipv6 address” stands for which NIC as we have some thing called “ipv6 neighbor discovery”.
Otherwise I agree that RouterOS needs to support pre-matcher masks for the IPv6 firewall. There are other areas where it’s necessary, e.g. multicast scopes.
WOW you mean address list? makes my eyes brighten, I haven’t try this before. many thanks for The sample.
much agree with ipv6 mask support, Current implementation adds much more study cost
And it does the work. You add address list entries for your devices and the script takes care of updating the prefixes when the DHCPv6 client obtains new prefixes, by putting this in the Advanced tab of the DHCPv6 Client entry:
if (1=$"pd-valid") do={
/delay 2
/system script run dynamic_prefix_update;
};
I’ve extended the script from that thread, and now it also automatically updates static DNS entries and firewall netmap (or other kind of NAT) rules as well. With some modification you can also tell it to get the prefix from the named IPv6 pools instead of the interfaces (with comments like dynamic pool prefix “pool name”, beside dynamic prefix “interface name”). Here is what currently running on my routers:
:local commenttag "dynamic prefix";
:local commenttagpool "dynamic pool prefix";
:local PREFIX2IP6 do={
:return [:toip6 [:pick $1 0 [:find $1 "/" 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 firewall address-list;
:foreach addrlist in=[find comment~"$commenttag"] do={
:local addr [get number=$addrlist address];
:local listname [get number=$addrlist list];
:local ifname [$EXTRACTQUOTE [get number=$addrlist comment]];
:if (0=[:len $ifname]) do={
:log warning "dynamic_prefix_update: An address list entry is marked for update but does not name an interface.";
} else={
:local ifaddresses [/ipv6 address find global !dynamic 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 [$PREFIX2IP6 [[:parse ":return $(~::)/$prefixlen"]]];
:local newaddr ((([$PREFIX2IP6 $addr] & ~$mask) | ([$PREFIX2IP6 $prefix] & $mask))."/".$addrlen);
:if ($addr!=$newaddr) do={
:log info "dynamic_prefix_update: Updating \"$listname\" $addr -> $newaddr (prefix from \"$ifname\").";
set number=$addrlist address=$newaddr;
};
};
};
};
/ip dns static;
:foreach record in=[find comment~"$commenttag"] do={
:local addr [get number=$record address];
:local dnsname [get number=$record name];
:local ifname [$EXTRACTQUOTE [get number=$record comment]];
:if (0=[:len $ifname]) do={
:log warning "dynamic_prefix_update: A DNS record is marked for update but does not name an interface.";
} else={
:local ifaddresses [/ipv6 address find global !dynamic 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 [$PREFIX2IP6 [[:parse ":return $(~::)/$prefixlen"]]];
:local newaddr ((([$PREFIX2IP6 ($addr."/128")] & ~$mask) | ([$PREFIX2IP6 $prefix] & $mask)));
:if ($addr!=$newaddr) do={
:log info "dynamic_prefix_update: Updating \"$dnsname\" $addr -> $newaddr (prefix from \"$ifname\").";
set number=$record address=$newaddr;
};
};
};
};
/ipv6 firewall nat;
:foreach natrule in=[find comment~"$commenttag"] do={
:local addr [get number=$natrule to-address];
:local srcaddr [get number=$natrule src-address];
:local ifname [$EXTRACTQUOTE [get number=$natrule comment]];
:if (0=[:len $ifname]) do={
:log warning "dynamic_prefix_update: An firewall nat entry is marked for update but does not name an interface.";
} else={
:local ifaddresses [/ipv6 address find global !dynamic 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 [$PREFIX2IP6 [[:parse ":return $(~::)/$prefixlen"]]];
:local newaddr ((([$PREFIX2IP6 $addr] & ~$mask) | ([$PREFIX2IP6 $prefix] & $mask))."/".$addrlen);
:if ($addr!=$newaddr) do={
:log info "dynamic_prefix_update: Updating netmap \"$srcaddr\" $addr -> $newaddr (prefix from \"$ifname\").";
set number=$natrule to-address=$newaddr;
};
};
};
};
/ipv6 firewall nat;
:foreach natrule in=[find comment~"$commenttagpool"] do={
:local addr [get number=$natrule to-address];
:local srcaddr [get number=$natrule src-address];
:local poolname [$EXTRACTQUOTE [get number=$natrule comment]];
:if (0=[:len $poolname]) do={
:log warning "dynamic_prefix_update: An firewall nat entry is marked for update but does not name a pool.";
} else={
:local pools [/ipv6 pool find name=$poolname];
:if (1!=[:len $pools]) do={
:log error "dynamic_prefix_update: Found $[:len $pools] IPv6 pools with name \"$poolname\".";
} else={
:local addrlen [:pick $addr ([:find $addr "/" 0]+1) [:len $addr]];
:local prefix [/ipv6 pool get number=($pools->0) prefix];
:local prefixlen [:pick $prefix ([:find $prefix "/" 0]+1) [:len $prefix]];
:if ($addrlen < $prefixlen) do={
:set prefixlen $addrlen;
};
:local mask [$PREFIX2IP6 [[:parse ":return $(~::)/$prefixlen"]]];
:local newaddr ((([$PREFIX2IP6 $addr] & ~$mask) | ([$PREFIX2IP6 $prefix] & $mask))."/".$addrlen);
:if ($addr!=$newaddr) do={
:log info "dynamic_prefix_update: Updating netmap \"$srcaddr\" $addr -> $newaddr (prefix from pool \"$poolname\").";
set number=$natrule to-address=$newaddr;
};
};
};
};
/delay 2;
/ip cloud force-update;
Of course you can with a bit of scripting. And it consumes less resource than putting something like dst-mac-address in the firewall rules, because the Neighbors table lookup doesn't have to be performed on every incoming connection, but only periodically by the scheduler. You can let the scheduled script maintain the address lists with the SLAAC addresses of the devices. Here is my scheduled script:
:local macAddresses {{"TV";"04:4E:AF:XX:XX:XX"};{"TV";"84:C7:EA:XX:XX:XX"};{"XBOX";"B8:31:B5:XX:XX:XX"}};
:local defaultTimeout "2h";
:local cleanupTimeout "5m"
:local prefixLength "/128";
/ipv6/neighbor/remove [find dynamic !mac-address status="noarp"];
/ipv6/firewall/address-list
:foreach item in=$macAddresses do={
:local listName ($item->0);
:local macAddress ($item->1);
#:log info "$listName with MAC $macAddress";
:foreach neighbor in=[/ipv6/neighbor/find mac-address=$macAddress] do={
:if (!([/ipv6/neighbor/get $neighbor address] in fe80::/10) && ([/ipv6/neighbor/get $neighbor status as-string] != "failed")) do={
:local deviceIPv6 ([/ipv6/neighbor/get $neighbor address] . $prefixLength);
#:log info "Found address $deviceIPv6 for $macAddress";
# remove old address list entries
:local oldEntries [find list=$listName address=$deviceIPv6 timeout<$cleanupTimeout timeout>"0m"];
:local hasOldEntries ([:len $oldEntries] > 0);
:if ($hasOldEntries) do={
#:log info "Removing old address list $listName entries for $macAddress with IP $deviceIPv6";
remove numbers=$oldEntries;
};
# add entry if not already present
:local activeEntries [find list=$listName address=$deviceIPv6];
:if ([:len $activeEntries] = 0) do={
:if ($hasOldEntries) do={
#:log info "Refreshing address list $listName entry for $macAddress with IP $deviceIPv6";
} else={
:log info "Adding new address list $listName entry for $macAddress with IP $deviceIPv6";
};
add list=$listName address=$deviceIPv6 timeout=$defaultTimeout comment="Device $macAddress";
};
};
};
};
The scheduler interval just needs to be shorter than cleanupTimeout (I have it set to 4 minutes). $macAddresses has the MAC address -> address list name mapping.
The firewall rules just make use of the address lists.
In this case routeros already has the data, and taking into accout how much the scripting changes every time the new version is out, i’d rather not use any scripts in ros at all.
Imo this is just the devs postition to not evolve in ipv6 support, as well as dhcpv6-pd only support and general lack of services added to ipv6. tis sad but it is.
Actually, they have steadily improved the IPv6 support over the years. DHCPv6 IA_NA is now available, as well as ND Proxy, and in 7.21 you can also choose the subnet ID when getting prefixes from pools and interfaces where RA is accepted. Same with IPv6 fasttrack support that was added in 7.18.
With a bit of scripting RouterOS can now achieve all what I need for IPv6. That RouterOS is scriptable is also part of its strength.