As a background, I wanted to use l3hw routing, and needed to be able to provide failover between my two routers. Unfortunately vrrp-based default gateway is not going to work with l3hw, so the regular IP addresses on VLAN interfaces had to be used. I established vrrp relation on a dedicated VLAN just to detect the events and used onmaster/onbackup scripts to create the gateway IP addresses on the master router and destroy them on the backup router. Then, whenever the gateway address is moved between the routers, sending a gratuitous arp is supposed to reduce the time for the network devices to start using the new gateway.
Here is an example of the onmaster script sending the garp after bringing up the gateways:
Code: Select all
/ip/address/add address=10.255.202.1/24 interface=vlan1202
/ip/address/add address=192.168.222.1/24 interface=vlan222
:global sendgarp
:if ([:typeof $sendgarp]="nothing") do={ /system/script/run sendgarp };
$sendgarp "192.168.222.1/24";
$sendgarp "10.255.202.1/24";
This is the sendgarp script implementing this functionality
sendgarp
Code: Select all
:global split;
:if ([:typeof $split]="nothing") do={ /system script run myfunctions };
:global sendgarp do={
# Send garp out of interface with the IP in $1 (should be in "IP/MASK" format, so that find address finds it)
:local routerip $1;
:if ([:find $routerip "/"]) do={} else={
:log error [:put "sendgarp: expecting parameter to be in <ipaddress>/<mask> format, got: $routerip"];
:return nil;
}
:if ([:len [/ip address find address=$routerip]] = 0) do={
:log error [:put "sendgarp: Cannot find interface with address $routerip"]
:return nil;
};
:local routeriface [/ip address get [find address=$routerip] interface];
# :put "routeriface: $routeriface";
:global lowercase
:global join;
:global split;
:global map;
:global byte2hex;
:local routermac [$join "" [$split ":" [$lowercase [/interface get [find name=$routeriface] mac-address]]]];
# :put "Router MAC: $routermac";
:local destmac "ffffffffffff"
:local ipheader "08060001080006040001"
:local padding "000000000000000000000000000000000000"
:local routeriphex [$join "" [$split "." [:pick $routerip 0 [:find $routerip "/"]] $byte2hex]]
# :put "routeriphex: $routeriphex";
:local content "$destmac$routermac$ipheader$routermac$routeriphex$destmac$routeriphex$padding"
/tool traffic-generator inject interface=$routeriface data=$content
:log info [:put "sendgarp: sent to $routerip on $routeriface: $content"]
};
And those are the functions (including my versions of split, join and map operations) I created to help with the implementation and that I use with other scripts
myfunctions
Code: Select all
:global split do={
# Split string in $2 by delimiter $1. Return array (list of strings)
# As optimization if $3 is provided it is used as a lambda to process each token before it's put into the array.
:local str $2;
:local out [:toarray ""];
:local lambda $3;
:if ([:typeof $lambda] = "nothing") do={
:set lambda do={:return $1;}
};
:if ($1 = "") do={
# Special case - empty delimiter - split by every character
:for i from=0 to=([:len $str] - 1) do={
:set ($out->[:len $out]) [$lambda [:pick $str $i]]
};
:return $out;
};
do {
:local pos [:find $str $1];
:if ($pos=[]) do {
:set ($out->[:len $out]) [$lambda $str];
:return $out;
}
:set ($out->[:len $out]) [$lambda [:pick $str 0 $pos]]
:set str [:pick $str ($pos+1) [:len $str]];
} while (true)
:return $out;
}
:global join do={
# Join array in $2 by delimiter in $1
:local ret "";
:local delim $1;
:local arr $2;
:foreach i in=$arr do={
:if ($ret="") do={
:set ret $i;
} else={
:set ret ($ret.$delim.$i);
};
};
:return $ret;
}
:global map do={
# Execute function $1 on each item in array $2. Return the result array
# If $2 is a string, execute $1 on each character of the string, return the resulting string
:local out [:toarray ""];
:local action $1;
:local in $2;
:if ([:typeof $in]="str") do={
:local result "";
:for i from=0 to=([:len $in] - 1) do={
:set result ($result . [$action [:pick $in $i]]);
};
:return $result;
};
:foreach i in=$arr do={
:set ($out->[:len $out]) [$action $i]
};
:return $out;
}
:global "_lower" "0123456789abcdefghijklmnopqrstuvwxyz";
:global "_upper" "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
:global byte2hex do={
:if ($1 > 255) do={:return "--"};
:global "_lower";
:return ( ([:pick $"_lower" ($1/16)]) . ([:pick $"_lower" ($1-($1/16)*16)]) );
}
:global upperchar do={
:global "_lower";
:global "_upper";
:local pos [:find $"_lower" $1];
:if ($pos > -1) do={:return [:pick $"_upper" $pos];};
:return $1;
};
:global lowerchar do={
:global "_lower"
:global "_upper"
:local pos [:find $"_upper" $1];
:if ($pos > -1) do={:return [:pick $"_lower" $pos];};
:return $1;
};
:global uppercase do={
:global map
:global upperchar
:return [$map $upperchar $1]
};
:global lowercase do={
:global map
:global lowerchar
:return [$map $lowerchar $1]
};
I developed it on RouterOS v7, but tried (and adjusted the syntax a bit) on v6.48 and it seems to work there.
Hope somebody may find it useful