Community discussions

MikroTik App
 
mfedotov
just joined
Topic Author
Posts: 18
Joined: Mon Oct 25, 2021 3:32 am

My gratuitous arp script

Mon May 16, 2022 5:34 pm

I wanted to share my gratuitous arp script. It is based on the packet format from https://wiki.mikrotik.com/wiki/Manual:I ... uitous_ARP

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:
/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
: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
: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
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11968
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: My gratuitous arp script

Mon May 16, 2022 5:56 pm

If can help... a shorter and simpler way for some functions....
:global ip2hex do={
  :local number  [:tonum [:toip $1]]
  :local hexadec "0"
  :local remainder 0
  :local hexChars "0123456789abcdef"
  :if ($number > 0) do={:set hexadec ""}
  :while ( $number > 0 ) do={
        :set remainder ($number % 16)
        :set number (($number-$remainder) / 16)
        :set hexadec ([:pick $hexChars $remainder].$hexadec)
  } 
  :set hexadec "00000000$hexadec"
  :set hexadec [:pick $hexadec ([:len $hexadec] - 8) [:len $hexadec]]
  :return $hexadec
}


:global lmac do={
  :local string    [:tostr $1]
  :local result    ""
  :local fromChars "0123456789ABCDEFabcdef:"
  :local toChars   "0123456789abcdefabcdef"
  :local len       [:len $string]
  :local pos       0
  do {
      :local char [:pick $string $pos ($pos + 1)]
      :local char [:pick $toChars [:find $fromChars $char -1]]
      :if ([:typeof $char] != "nil") do={:set result "$result$char"}
      :set pos ($pos + 1)
  } while=($pos < 17)
  :return $result
}

:global sendgarp do={
    :global ip2hex
    :global lmac
    :local routerip    $1
    :local routeriphex [$ip2hex [:pick $routerip 0 [:find $routerip "/"]]]
    :local routeriface [/ip address get [find where address=$routerip] interface]
    :local routermac   [$lmac [/interface get [find where name=$routeriface] mac-address] ]
    :local destmac     "ffffffffffff"
    :local ipheader    "08060001080006040001"
    :local padding     "000000000000000000000000000000000000"

    :local content "$destmac$routermac$ipheader$routermac$routeriphex$destmac$routeriphex$padding"
    /tool traffic-generator inject interface=$routeriface data=$content
}
Last edited by rextended on Tue May 17, 2022 12:58 am, edited 2 times in total.
Reason: Fixed thanks to @mfedotov
 
mfedotov
just joined
Topic Author
Posts: 18
Joined: Mon Oct 25, 2021 3:32 am

Re: My gratuitous arp script

Mon May 16, 2022 11:57 pm

If can help... a shorter and simpler way for some functions....

Sure, don't want to say my implementation is the best. But I guess your implementation was theoretical, right?
I see that ip2hex does not provide the correct result. I had to put
[:tonum [:toip$1]]
to make it work (thank, btw, did not know about this trick).

And the sendgarp function needs
:global lmac;
before the invocation to make it work (I hit this issue myself during my development cycle)

Still split/join implementation makes it easier to understand (to its developer :D ). I wish Mikrotik had their own implementation built in the scripting language...

Regards
Mike
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11968
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: My gratuitous arp script

Tue May 17, 2022 1:00 am

[...] I guess your implementation was theoretical, right? [...]
Really... not... why theoretical? (tested on on 6.48.6 terminal):
the [:toip ] is present on sendgarp before I move on ip2hex, but I forgot to update the first part of the script on forum.
I do not know why you need to put :global xxx inside last part, because on terminal work,
probably because I test the script on terminal and not on scheduler or script, but is easy fixable.

Still split/join implementation makes it easier to understand (to its developer :D )
Yes, but the single function $lmac with a single cycle inside is more simple and easy understandable,
compared with 3 functions with one "nested" with another, and each with multiple "if" and "cycle" used inside...

However I prefer to have everything in the same script, changing something in the general functions in other scripts to be loaded previously, to adapt them to the needs, often breaks the original functioning and the previous scripts that called the function may no longer work correctly...

Thanks for sharing your works, and I hope to suggest something for you.
 
User avatar
Sertik
Member
Member
Posts: 425
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: My gratuitous arp script

Tue Sep 06, 2022 2:31 pm

: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;
}
An error has been made in the function. The $arr variable is not defined. We need to replace it with $2.
 
jaclaz
Long time Member
Long time Member
Posts: 557
Joined: Tue Oct 03, 2023 4:21 pm

Re: My gratuitous arp script

Wed Dec 13, 2023 4:57 pm

Maybe useful, maybe not, I am attaching a batch script (tested on Windows XP and later) to create the "data" to be sent with Traffic-Generator tool according to:
https://wiki.mikrotik.com/wiki/Manual:I ... uitous_ARP

It contains some explanation of what the packet is composed of and lists possibly needed on some systems alternative packets (different Target MAC and reply instead of request).

The extension of the file needs to be changed from .txt to .cmd to become executable..

I am also attaching a small spreadsheet in .xls format to the same effect and a screenshot.
You do not have the required permissions to view the files attached to this post.
 
jaclaz
Long time Member
Long time Member
Posts: 557
Joined: Tue Oct 03, 2023 4:21 pm

Re: My gratuitous arp script

Tue Jan 09, 2024 1:03 pm

Only as a side note, possibly useful to some other member, I did some experiments with sending Gratuitous Arp via script.

It wasn't working from script, while working fine from command line.

I thought that the issue was since I was using it from Netwatch the need to add the dont-require-permissions=yes, but in itself didn't make any difference.

But after a quite few tests, in order to actually run properly the script (for *whatever* reason) needs the "sniff" permission (in addition to the normal "policy=write, read, test, reboot").

And of course, if run from Netwatch also the dont-require-permissions=yes.

I understand now that the "dont-require-permissions=" is related to the permissions of the "user" (netwatch in this case) whilst the "policy=" is related to the permissions of the actual "script" (of the commands in it), something that I had completely missed.

Still the "sniff" policy is defined in the Mikrotik documentation I read as "sniff - policy that grants rights to use packet sniffer tool", it doesn't look as connected to "/tool traffic-generator inject " command :? .

Who is online

Users browsing this forum: No registered users and 23 guests