perl API client

[New version uploaded 3/26/2008. Fixed a bug which caused command output to hang sometimes, and added simple command line client]

Attached should be a ZIP with three files:

Mtik.pm - a simple perl client for the Mtik API. Pretty much a perl port of the python client from the Wiki, with an extra routine or two for formatting the returned data.

mtik_api_example.pl - an example of how to use the API. Provides some useful wireless ACL control routines. I’ve included enough comments so you should be able to work out how it all hangs together.

mtik_tty.pl - a simple command line client for testing API commands with. Use -h switch for usage info.

This code is very much a first cut. I intend to make the Mtik.pm stuff object oriented, so it can support more than one open Mtik connection at a time, but for now it is purely function driven. I needed it in a hurry!

Please feel free to feed back comments, suggestions, bug reports or light bulb jokes. Also feel free to modify, redistribute and generally do what you will with the code.

Share And Enjoy.

– hugh

edit:
some other user added SSL functionality to this code. You can see it here (on this thread):
http://forum.mikrotik.com/viewtopic.php?p=457584#p457584
mtik_api2.zip (4.75 KB)

I think I’ve tracked down the reason it hangs occasionally on the socket recv. A misunderstanding on my part on how recv works with regards to specified read lengths.

I’m testing the new version, I’ll post it when I’m sure it’s fixed.

– hugh

New version uploaded. Bug described in previous post is fixed.

– hugh

Hello

Thank you for doing this wrap for perl, however i cannot get it to run it gives me no socket: Connection refused :confused:

Please help

Michal

ok i solved i had to enable api service on MT

B/R
Michal

Much appreciated. I’ll have some additional contributions to this soon.

Glad it helps you out.

I’ve now built a fairly comprehensive provisioning system for our Mtik based wireless networks using it, and so far haven’t had to update the main mtik_api.pl core.

If you feel adventurous, feel free to turn it into an actual package … :slight_smile:

– hugh

Hugh,

Thanks for making this available: it has been a big help.

One mistake I made that other people who want to use it should avoid is the assumption that you can use the “talk” function just like you would a telnet connection. E.g., I tried something like:

@cmd = ("/category/command", "keyword1=value1", "keyword2=value2");
($rc, @response) = Mtik::talk(\@cmd);

Mtik::talk works fine for commands with no operands, but, if you’re sending a command with keyword-based operands, you need to use the “mtik_cmd” function, and supply the operands in a hash, like the main loop in the example does. I think this is because mtik_cmd is doing the special formatting (like adding a leading “=” to operands) that’s different between the terminal interface and the API.

The problem I’m having right now, though, is that I’m using the API to fiddle the firewall tables to provide temporary access to certain servers for users who’ve logged in through a web interface. I can add entries okay, now that I’m using mtik_cmd. But, when I try to remove entries with:

    my @cmd = ("/ip/firewall/filter/remove", "$rowno");
    my($retval,@results) = Mtik::talk(\@cmd);

The debug output says that the code is sending the command and operand, and I get a “!done” response. But the command doesn’t remove the entry.

I’d kinda like to find out why this doesn’t work, for future reference. Has anyone worked out a general-purpose method for sending commands with positional parameters?

Meanwhile, I found a hint in another posting that enabled me to make it work. It was about another command that takes a “row number” as a positional parameter in the telnet interface. In the API, the row is identified using the “.id” value passed back when listing the table (notthe row number that you’d use in the telnet interface), and a keyword of “numbers”.

I have a function that uses the “talk” function to build an array of hashes containing the current firewall table, and another that will find the entry to be deleted. So the code to remove an entry looks like this:

# Remove a camera/IPA pair from the firewall list.
# Returns 0 if it was removed, or a negative number if it wasn't.
# Inputs:
#		The camera resource name (which is also used as the name of
#			its chain in the firewall)
#		The source (i.e.,  user's) IP address
sub mtik_access_remove
{
    my($chain) = shift;
    my($ipa) = shift;

		# First see whether it's already in the list
    my($rc, @firewall) = mtik_get_firewall;
	if ($rc) {
		return $rc;
		}
	my($rowno) = mtik_find_entry_in_firewall(\@firewall, $chain, $ipa);
	if ($rowno < 0) {
		print "IPA $ipa not found in chain $chain\n";
		return -1;
		}
    	# It's there, so try to remove it
#	my @cmd = ("/ip", "firewall", "filter", "remove", "$rowno");
    my %operands;
	$operands{'numbers'} = $firewall[$rowno]{".id"};
	my($retval,@results) = Mtik::mtik_cmd("/ip/firewall/filter/remove", \%operands);
	if ($retval != 1) {
		print "removal of IPA $ipa from $chain failed. RC = $retval\n$Mtik::error_msg\n";
		return $retval;
		}

	return 0;
}
[code]

Note the way I test $retval for a value of 1 (instead of 0) for "success".  I translate it to 0 for code calling my functions,  since I'm used to the convention of "0 for success,  non-zero for failure".

Thanks again for sharing.  I hope others find this record of my mistakes useful in avoiding repeating them.

Ran

use .id to remove entries, in this case, do print and get a list of rules with .id numbers, then parse the result and get what rules you want to remove.

also when you add a rule, you get the .id number of the rule, so if in the same session you want to remove the rule you can use returned .id number of the rule to remove it in the end

To cheesegrits: If you will add this info to the wiki, I will give you a free RouterOS license!

is possible to get a queue simple rate ?

Thank you very much for this scripts! Btw can somebody make php class for MT api?

Where should i put the pm file?
I’m running a kubuntu 7.10 distro.

yes try to filter out a table of 1000 firewall rules, another 1000 of queues and another one of mangles, 1st your MT must be really fast, same as the machine you’re running this script. It takes ages otherwise.

Also i checked, that id that you get returned when you add a rule, persist the same even after reboot, it only changes when you of course remove it :wink:. So if your doing some script for handling qos etc. It is good to keep those id’s on some database such as Mysql then you just refer to id’s you need, instead of parsing of hundreds of lines. besides that even if you have fast machines, such huge comunication (printing 5000 rules, and all ACKs in comunication) takes a lot of bandwidth. I’m doing such a database, and i always have at my hand script that when something goes wrong just does it in oldstyle way, removes all rules and add them again.

BTW. I saw that in ROUTEROS 3.13 something has changed becuase, usually my script worked but in this version the communication seems to be unregular, it looses some rules (in debug it shows “traped” instead of done). It runs smoothly on 3.10. There had to be some change. This what makes me always nervous at RouterOS, unpredictable changes… that are not presented in changelog.

B/R

Michal

BTW thx a lot for this perl wrap!!!

would be nice if you wrote what exactly is failing, and how large outputs you are getting.

okay i’ve made an effort and debugged it, it seems that api dosen’t accept src-port=445-65535, in fact it doesn’t accept any range of ports for dst-port and src-port, this happens in ROS 3.11 and above… if I hashout directives with port-ranges my script works fine…

now it’s your turn to fix or guide me how to overcome this :wink:

B/R
Michal

okay i have to give you back your honor, the problem was that MT got smarter in newer versions and can accept port ranges only for tcp and udp as far as i recall, and not giving parameter of protocol=tcp was the cause… and as far as i recall you mentioned this new behavior in changelog, soo I APOLOGIZE.

you never know how much frustating administrating can be … :wink:

merry christmas

NO, unfortunately i was assuming things too quickly, i was right it itsn’t accepting port ranges correctly:

root@ns1:/etc/lms# ./mtik_tty.pl -m 10.255.0.2 -u admin -p xxxxx
<<< /ip/firewall/mangle/add
<<< =chain=forward
<<< =protocol=tcp
<<< =src-port=445-460
<<<

!trap
=category=1
=message=invalid value for argument range

like i said logging into 3.10 this works flawlessly, and in my script i have protocol=tcp directive so everything is fine from my side

B/R
Michal

again it’s not surprise to not get an answer here

shall i forward this to support?

Thanks for the code - but I would like to write that the read_len() function in mtapi.pl is completely wrong. For one byte lengths it works (but only by accident - the initial IF statement is not correct).

  • the bitwise negation operator is not “!” but “~”. If you use ! all nonzero values are repleced by zero and zero is replaced by 1
  • bitwise AND has lower precedence than comparation (==). In original code two constants were compared and the result (mostly zero) was anded with $len
  • you have to read the subsequent bytes of block length using recv() not recursivelly calling read_len() again

This is the code which works for one and two byte lengths. I tried to correct others too but no checks/tests were made for them.

sub readbyte {
my $line;
$sock->recv($line,1);
if ($debug > 4)
{
printf “readbyte:received: %x\n”,ord($line);
}
return ord($line);
};

sub read_len {
if ($debug > 4)
{
print “start read_len\n”;
}
my $line;
my $len = readbyte();
if (($len & 0x80) == 0x00)
{
return $len;
}
elsif (($len & 0xC0) == 0x80)
{
$len &= ~0xC0;
$len <<= 8;
$len += readbyte();
}
elsif (($len & 0xE0) == 0xC0)
{
$len &= ~0xE0;
$len <<= 8;
$len += readbyte();
$len <<=8;
$len += readbyte();
}
elsif (($len & 0xF0) == 0xE0)
{
$len &= ~0xF0;
$len <<= 8;
$len += readbyte();
$len <<=8;
$len += readbyte();
$len <<=8;
$len += readbyte();
}
elsif (($len & 0xF8) == 0xF0)
{
$len = readbyte();
$len <<= 8;
$len += readbyte();
$len <<=8;
$len += readbyte();
$len <<=8;
$len += readbyte();
}
if ($debug > 4)
{
print “read_len got $len\n”;
}
return $len;
}

Regards
Dalibor Toman