Manual:API: Difference between revisions
→elsewhere: finally normal java implementation |
|||
(26 intermediate revisions by 6 users not shown) | |||
Line 5: | Line 5: | ||
To use API RouterOS version 3.x or newer is required. | To use API RouterOS version 3.x or newer is required. | ||
By default API uses port #''8728'' and service is | By default API uses port #''8728'' and service is enabled. More on service management see in corresponding [[Manual:IP/Services | manual section]]. Corresponding service name is '''api''' | ||
== Protocol == | == Protocol == | ||
Line 63: | Line 63: | ||
API specific commands: | API specific commands: | ||
login | login | ||
cancel | cancel | ||
Command word | Command word content examples: | ||
/login | /login | ||
/user/active/listen | /user/active/listen | ||
Line 79: | Line 76: | ||
====Attribute word==== | ====Attribute word==== | ||
Each ''command word'' | Each ''command word'' has its own list of ''attribute words'' depending on content. | ||
''Atribute word'' structure consists of 5 parts in this order: | ''Atribute word'' structure consists of 5 parts in this order: | ||
* encoded length | * encoded length | ||
* content prefix equals | * content prefix equals sign - ''='' | ||
* attribute name | * attribute name | ||
* separating equals sign - ''='' | * separating equals sign - ''='' | ||
Line 159: | Line 156: | ||
== Initial login == | == Initial login == | ||
Login method pre-v6.43: | |||
<tt> | <tt> | ||
{| style="width: 500px" | {| style="width: 500px" | ||
Line 174: | Line 172: | ||
|} | |} | ||
</tt> | </tt> | ||
{{Warning| this login method is deprecated and no longer supported in versions above 6.45.1.}} | |||
{{Note| that each command and response ends with an empty word.}} | {{Note| that each command and response ends with an empty word.}} | ||
Line 181: | Line 181: | ||
* Client sends second <tt>/login</tt> command, with <tt>=name=''username''</tt> and <tt>=response=''response''</tt>. | * Client sends second <tt>/login</tt> command, with <tt>=name=''username''</tt> and <tt>=response=''response''</tt>. | ||
* In case of error, reply contains <tt>=ret=''error message''</tt>. | * In case of error, reply contains <tt>=ret=''error message''</tt>. | ||
* In case of successful login client can start to issue commands. | |||
Login method post-v6.43: | |||
<tt> | |||
{| style="width: 500px" | |||
{{apic|/login}} | |||
{{apic|1==name=admin}} | |||
{{apic|1==password=}} | |||
{{apic|}} | |||
{{apis|!done}} | |||
{{apis|}} | |||
|} | |||
</tt> | |||
* Now client sends username and password in first message. | |||
* Password is sent in plain text. | |||
* in case of error, reply contains <tt>=message=''error message''</tt>. | |||
* In case of successful login client can start to issue commands. | * In case of successful login client can start to issue commands. | ||
Line 256: | Line 274: | ||
* '''<tt>.</tt>''' after another character pushes copy of top value. | * '''<tt>.</tt>''' after another character pushes copy of top value. | ||
|} | |} | ||
{{Warning | Regular expressions are not supported in API, so do not try to send query with '''~''' symbol}} | |||
Examples: | Examples: | ||
Line 496: | Line 517: | ||
#!/usr/bin/python | #!/usr/bin/python | ||
import sys, posix, time, md5, binascii, socket, select | import sys, posix, time, md5, binascii, socket, select, ssl | ||
class ApiRos: | class ApiRos: | ||
Line 503: | Line 524: | ||
self.sk = sk | self.sk = sk | ||
self.currenttag = 0 | self.currenttag = 0 | ||
def login(self, username, pwd): | def login(self, username, pwd): | ||
for repl, attrs in self.talk(["/login"]): | |||
for repl, attrs in self.talk(["/login", "=name=" + username, | |||
"=password=" + pwd]): | |||
if repl == '!trap': | |||
return False | |||
elif '=ret' in attrs.keys(): | |||
#for repl, attrs in self.talk(["/login"]): | |||
chal = binascii.unhexlify(attrs['=ret']) | chal = binascii.unhexlify(attrs['=ret']) | ||
md = md5.new() | |||
md.update('\x00') | |||
md.update(pwd) | |||
md.update(chal) | |||
for repl2, attrs2 in self.talk(["/login", "=name=" + username, | |||
"=response=00" + binascii.hexlify(md.digest())]) | "=response=00" + binascii.hexlify(md.digest())]): | ||
if repl2 == '!trap': | |||
return False | |||
return True | |||
def talk(self, words): | def talk(self, words): | ||
Line 545: | Line 575: | ||
if w == '': return r | if w == '': return r | ||
r.append(w) | r.append(w) | ||
def writeWord(self, w): | def writeWord(self, w): | ||
print "<<< " + w | print "<<< " + w | ||
Line 568: | Line 598: | ||
self.writeStr(chr((l >> 8) & 0xFF)) | self.writeStr(chr((l >> 8) & 0xFF)) | ||
self.writeStr(chr(l & 0xFF)) | self.writeStr(chr(l & 0xFF)) | ||
elif l < 0x10000000: | elif l < 0x10000000: | ||
l |= 0xE0000000 | l |= 0xE0000000 | ||
self.writeStr(chr((l >> 24) & 0xFF)) | self.writeStr(chr((l >> 24) & 0xFF)) | ||
self.writeStr(chr((l >> 16) & 0xFF)) | self.writeStr(chr((l >> 16) & 0xFF)) | ||
self.writeStr(chr((l >> 8) & 0xFF)) | self.writeStr(chr((l >> 8) & 0xFF)) | ||
self.writeStr(chr(l & 0xFF)) | self.writeStr(chr(l & 0xFF)) | ||
else: | else: | ||
self.writeStr(chr(0xF0)) | self.writeStr(chr(0xF0)) | ||
self.writeStr(chr((l >> 24) & 0xFF)) | self.writeStr(chr((l >> 24) & 0xFF)) | ||
Line 581: | Line 611: | ||
self.writeStr(chr(l & 0xFF)) | self.writeStr(chr(l & 0xFF)) | ||
def readLen(self): | def readLen(self): | ||
c = ord(self.readStr(1)) | c = ord(self.readStr(1)) | ||
if (c & 0x80) == 0x00: | if (c & 0x80) == 0x00: | ||
pass | pass | ||
elif (c & 0xC0) == 0x80: | elif (c & 0xC0) == 0x80: | ||
c &= ~0xC0 | c &= ~0xC0 | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
elif (c & 0xE0) == 0xC0: | elif (c & 0xE0) == 0xC0: | ||
c &= ~0xE0 | c &= ~0xE0 | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
elif (c & 0xF0) == 0xE0: | elif (c & 0xF0) == 0xE0: | ||
c &= ~0xF0 | c &= ~0xF0 | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
elif (c & 0xF8) == 0xF0: | elif (c & 0xF8) == 0xF0: | ||
c = ord(self.readStr(1)) | c = ord(self.readStr(1)) | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
c <<= 8 | c <<= 8 | ||
c += ord(self.readStr(1)) | c += ord(self.readStr(1)) | ||
return c | return c | ||
def writeStr(self, str): | def writeStr(self, str): | ||
n = 0; | n = 0; | ||
while n < len(str): | while n < len(str): | ||
r = self.sk.send(str[n:]) | r = self.sk.send(str[n:]) | ||
if r == 0: raise RuntimeError, "connection closed by remote end" | if r == 0: raise RuntimeError, "connection closed by remote end" | ||
n += r | n += r | ||
def readStr(self, length): | def readStr(self, length): | ||
ret = '' | ret = '' | ||
while len(ret) < length: | while len(ret) < length: | ||
s = self.sk.recv(length - len(ret)) | s = self.sk.recv(length - len(ret)) | ||
if s == '': raise RuntimeError, "connection closed by remote end" | if s == '': raise RuntimeError, "connection closed by remote end" | ||
ret += s | ret += s | ||
return ret | return ret | ||
def open_socket(dst, port, secure=False): | |||
s = None | |||
res = socket.getaddrinfo(dst, port, socket.AF_UNSPEC, socket.SOCK_STREAM) | |||
af, socktype, proto, canonname, sockaddr = res[0] | |||
skt = socket.socket(af, socktype, proto) | |||
if secure: | |||
s = ssl.wrap_socket(skt, ssl_version=ssl.PROTOCOL_TLSv1_2, ciphers="ADH-AES128-SHA256") #ADH-AES128-SHA256 | |||
else: | |||
s = skt | |||
s.connect(sockaddr) | |||
return s | |||
def main(): | def main(): | ||
s = | s = None | ||
dst = sys.argv[1] | |||
apiros = ApiRos(s); | user = "admin" | ||
apiros.login( | passw = "" | ||
secure = False | |||
port = 0 | |||
# use default username and pasword if not specified | |||
if len(sys.argv) == 4: | |||
user = sys.argv[2] | |||
passw = sys.argv[3] | |||
elif len(sys.argv) == 3: | |||
user = sys.argv[2] | |||
if (port==0): | |||
port = 8729 if secure else 8728 | |||
s = open_socket(dst, port, secure) | |||
if s is None: | |||
print ('could not open socket') | |||
sys.exit(1) | |||
apiros = ApiRos(s); | |||
if not apiros.login(user, passw): | |||
return | |||
inputsentence = [] | inputsentence = [] | ||
Line 656: | Line 720: | ||
if __name__ == '__main__': | if __name__ == '__main__': | ||
if len(sys.argv) == 1: | |||
print "Usage: %s IP [user] [pass] [--secure]" % str(sys.argv[0]) | |||
else: | |||
main() | main() | ||
</pre> | </pre> | ||
Line 701: | Line 770: | ||
=====in the Wiki ===== | =====in the Wiki ===== | ||
*[[API_PHP_package|in PHP]] | *[[API_PHP_package|in PHP]] | ||
*[[API Delphi|in Delphi]] #1 | *[[API Delphi|in Delphi]] #1 | ||
*[[API_Delphi_Client|in Delphi]] #2 | *[[API_Delphi_Client|in Delphi]] #2 | ||
* [[API_in_Swift|Swift]] | |||
*[[API_in_C| in C]] #1 | *[[API_in_C| in C]] #1 | ||
*[[Librouteros| in C]] #2 | *[[Librouteros| in C]] #2 | ||
Line 716: | Line 785: | ||
*[[API_Ruby_class|in Ruby on rails]] | *[[API_Ruby_class|in Ruby on rails]] | ||
*[[API_in_VB_dot_NET|in VB .NET]] | *[[API_in_VB_dot_NET|in VB .NET]] | ||
*[[API_in_Java|in java]] | *[[API_in_Java|in java]] see java client on github from Gideon LeGrange | ||
*[[MikroNode|in NodeJS]] | *[[MikroNode|in NodeJS]] | ||
*[[Manual:API_Python3|Python3]] | *[[Manual:API_Python3|Python3]] | ||
*[[API_rust| in RUST]] GPL v3 | |||
*[[API_in_Go| in GO]] by andredossantos | |||
===== on the MikroTik Forum ===== | ===== on the MikroTik Forum ===== | ||
Line 728: | Line 799: | ||
* [http://forum.mikrotik.com/viewtopic.php?f=2&t=51584 in VB] by lucho512 | * [http://forum.mikrotik.com/viewtopic.php?f=2&t=51584 in VB] by lucho512 | ||
* [http://forum.mikrotik.com/viewtopic.php?f=9&t=56869 on PHP for sparks framework] by vthinkteam | * [http://forum.mikrotik.com/viewtopic.php?f=9&t=56869 on PHP for sparks framework] by vthinkteam | ||
* [http://forum.mikrotik.com/viewtopic.php?f=9&t=108989#p540866 for Windows PowerShell] by navidrasi | |||
===== | ===== External sources ===== | ||
*[ | *[https://github.com/danikf/tik4net in .NET (C#) high-level api solution] [http://forum.mikrotik.com/viewtopic.php?f=9&t=99954 forum thread] [https://github.com/danikf/tik4net/wiki additional info] by danikf | ||
*[https://sourceforge.net/projects/netrouteros/ in PHP] by boen_robot | *[https://sourceforge.net/projects/netrouteros/ in PHP] by boen_robot | ||
*[https://github.com/haakonnessjoen/librouteros-api in C] by Håkon Nessjøen | *[https://github.com/haakonnessjoen/librouteros-api in C] by Håkon Nessjøen | ||
*[https://github.com/GideonLeGrange/mikrotik-java in Java] by Gideon LeGrange | *[https://github.com/GideonLeGrange/mikrotik-java in Java] by Gideon LeGrange | ||
*[https://github.com/comtihon/erotik in Erlang] by Valery Comtihon | |||
*[https://github.com/go-routeros/routeros in GO] by André Luiz dos Santos | |||
*[https://github.com/LaiArturs/RouterOS_API in Python3] by Arturs Laizans | |||
*[https://github.com/aymanalqadhi/tikpp in C++17] by Ayman Al-Qadhi | |||
[[Category:API|A]] | [[Category:API|A]] |
Latest revision as of 09:01, 14 October 2020
Summary
Application Programmable Interface (API) allows users to create custom software solutions to communicate with RouterOS to gather information, adjust configuration and manage router. API closely follows syntax from command line interface (CLI). It can be used to create translated or custom configuration tools to aid ease of use running and managing routers with RouterOS.
To use API RouterOS version 3.x or newer is required.
By default API uses port #8728 and service is enabled. More on service management see in corresponding manual section. Corresponding service name is api
Protocol
Communication with router is done by sending sentences to the router and receiving one or more sentences in return. Sentence is sequence of words terminated by zero length word. Word is part of sentence encoded in certain way - encoded length and data. Communication happen by sending sentences to the router and receiving replies to sent sentences. Each sentence sent to router using API should contain command as a first word followed by words in no particular order, end of sentence is marked by zero length word. When router receives full sentence (command word, no or more attribute words and zero length word) it is evaluated and executed, then reply is formed and returned.
API words
Words are part of sentence. Each word has to be encoded in certain way - length of the word followed by word content. Length of the word should be given as count of bytes that are going to be sent.
Length of the word is encoded as follows:
Value of length | # of bytes | Encoding |
---|---|---|
0 <= len <= 0x7F | 1 | len, lowest byte |
0x80 <= len <= 0x3FFF | 2 | len | 0x8000, two lower bytes |
0x4000 <= len <= 0x1FFFFF | 3 | len | 0xC00000, three lower bytes |
0x200000 <= len <= 0xFFFFFFF | 4 | len | 0xE0000000 |
len >= 0x10000000 | 5 | 0xF0 and len as four bytes |
- Each word is encoded as length, followed by that many bytes of content;
- Words are grouped into sentences. End of sentence is terminated by zero length word;
- Scheme allows encoding of length up to 0x7FFFFFFFFF, only four byte length is supported;
- Bytes of len are sent most significant first (network order);
- If first byte of word is >= 0xF8, then it is a reserved control byte. After receiving unknown control byte API client cannot proceed, because it cannot know how to interpret following bytes;
- Currently control bytes are not used;
In general words can be described like this <<encoded word length><word content>>. Word content can be separated in 5 parts: command word, attribute word, API attribute word. query word and reply word
Command word
First word in sentence has to be command followed by attribute words and zero length word or terminating word. Name of command word should begin with '/'. Names of commands closely follow CLI, with spaces replaced with '/'. There are commands that are specific to API;
Command word structure in strict order:
- encoded length
- content prefix /
- CLI converted command
API specific commands:
login cancel
Command word content examples:
/login
/user/active/listen
/interface/vlan/remove
/system/reboot
Attribute word
Each command word has its own list of attribute words depending on content.
Atribute word structure consists of 5 parts in this order:
- encoded length
- content prefix equals sign - =
- attribute name
- separating equals sign - =
- value of attribute if there is one. It is possible that attribute does not have a value
Note: Value can hold multiple equal signs in the value of attribute word since the way word is encoded
Note: Value can be empty
Examples without encoded length prefix:
=address=10.0.0.1
=name=iu=c3Eeg
=disable-running-check=yes
Warning: Order of attribute words and API parameters is not important and should not be relied on
API attribute word
API attribute word structure is in strict order:
- encoded length
- content prefix with dot .
- attribute name
- name postfixed with equals =sign
- attribute value
Currently the only such API attribute is tag.
Note: If sentence contain API attribute word tag then each returned sentence in reply from router to that tagged sentence will be tagged with same tag.
Query word
Senteces can have additional query paramteres that restrict their scope. They are explained in detail in separate section.
Example of sentence using query word attributes:
/interface/print ?type=ether ?type=vlan ?#|!
- Query words begin with '?'.
- Currently only print command handles query words.
Warning: Order of query words is significant
Reply word
It is sent only by the router. It is only sent in response to full sentence send by the client.
- First word of reply begins with '!';
- Each sentence sent generates at least one reply (if connection does not get terminated);
- Last reply for every sentence is reply that has first word !done;
- Errors and exceptional conditions begin with !trap;
- Data replies begin with !re
- If API connection is closed, RouterOS sends !fatal with reason as reply and then closes the connection;
API sentences
API sentence is main object of communication using API.
- Empty sentences are ignored.
- Sentence is processed after receiving zero length word.
- There is a limit on number and size of sentences client can send before it has logged in.
- Order of attribute words should not be relied on. As order and count is changeable by .proplist attribute.
- Sentence structure is as follows:
- First word should contain command word;
- Should contain zero length word to terminate the sentence;
- Can contain none or several attribute words. There is no particular order at what attribute words has to be sent in the sentence, order is not important for attribute words;
- Can contain none or several query words. Order of query words in the sentence is important.
Note: Zero length word terminates the sentence. If it is not provided router will not start to evaluate sent words and will consider all the input as part of the same sentence.
Initial login
Login method pre-v6.43:
Warning: this login method is deprecated and no longer supported in versions above 6.45.1.
Note: that each command and response ends with an empty word.
- First, clients sends /login command.
- Reply contains =ret=challenge argument.
- Client sends second /login command, with =name=username and =response=response.
- In case of error, reply contains =ret=error message.
- In case of successful login client can start to issue commands.
Login method post-v6.43:
- Now client sends username and password in first message.
- Password is sent in plain text.
- in case of error, reply contains =message=error message.
- In case of successful login client can start to issue commands.
Tags
- It is possible to run several commands simultaneously, without waiting for previous one to complete. If API client is doing this and needs to differentiate command responses, it can use 'tag' API parameter in command sentences.
- If you include 'tag' parameter with non-empty value in command sentence, then 'tag' parameter with exactly the same value will be included in all responses generated by this command.
- If you do not include 'tag' parameter or it's value is empty, then all responses for this command will not have 'tag' parameter.
Command description
- /cancel
- optional argument: =tag=tag of command to cancel, without it cancels all running commands
- does not cancel itself
- all canceled commands are interruped and in the usual case generate '!trap' and '!done' responses
- please note that /cancel is separate command and can have it's own unique '.tag' parameter, that is not related to '=tag' argument of this command
- listen
- listen command is avaliable where console print command is available, but it does not have expected effect everywhere (i.e. may not work)
- !re sentences are generated as something changes in particular item list
- when item is deleted or dissapears in any other way, the '!re' sentence includes value '=.dead=yes'
- This command does not terminate. To terminate it use /cancel command.
- getall
- getall command is available where console print command is available. Since version 3.21 getall is an alias for print.
- replies contain =.id=Item internal number property.
- print
- API print command differs from the console counterpart in the following ways:
- where argument is not supported. Items can be filtered using query words (see below).
- .proplist argument is a comma separated list of property names that should be included for the returned items.
- returned items may have additional properties.
- order of returned properties is not defined.
- if list contains duplicate entries, handling of such entries is not defined.
- if propery is present in .proplist, but absent from the item, then that item does not have this property value (?name will evaluate to false for that item).
- if .proplist is absent, all properties are included as requested by print command, even those that have slow access time (such as file contents and perfomance counters). Thus use of .proplist is encouraged. Omission of .proplist may have high perfomance penalty if =detail= argument is set.
- API print command differs from the console counterpart in the following ways:
Queries
print command accepts query words that limit set of returned sentences. This feature is available since RouterOS 3.21.
- Query words begin with '?'.
- Order of query words is significant. Query is evaluated starting from the first word.
- Query is evaluated for each item in the list. If query succeeds, item is processed, if query fails, item is ignored.
- Query is evaluated using a stack of boolean values. Initially stack contains infinite amount of 'true' values. At the end of evaluation, if stack contains at least one 'false' value, query fails.
- Query words operate according to the following rules:
Query | Desciption |
---|---|
?name | pushes 'true' if item has value of property name, 'false' if it does not. |
?-name | pushes 'true' if item does not have value of property name, 'false' otherwise. |
?name=x ?=name=x |
pushes 'true' if property name has value equal to x, 'false' otherwise. |
?<name=x | pushes 'true' if property name has value less than x, 'false' otherwise. |
?>name=x | pushes 'true' if property name has value greater than x, 'false' otherwise. |
?#operations | applies operations to the values in the stack.
|
Warning: Regular expressions are not supported in API, so do not try to send query with ~ symbol
Examples:
- Get all ethernet and VLAN interfaces:
/interface/print ?type=ether ?type=vlan ?#|
- Get all routes that have non-empty comment:
/ip/route/print ?>comment=
OID
print command can return OID values for properties that are available in SNMP. This feature appeared in 3.23 version.
In console, OID values can be seen by running 'print oid' command. In API, these properties have name that ends with ".oid", and can be retrieved by adding their name to the value of '.proplist'. An example:
!trap
When for some reason API sentence fails trap is sent in return accompanied with message attribute and on some occasions category argument.
message
When API sentence fails some generic message or message from used internal process is return to give more details about failure
<<< /ip/address/add <<< =address=192.168.88.1 <<< =interface=asdf <<< >>> !trap >>> =category=1 >>> =message=input does not match any value of interface
category
if it is a general error, it is categorized and error category is returned. possible values for this attribute are
- 0 - missing item or command
- 1 - argument value failure
- 2 - execution of command interrupted
- 3 - scripting related failure
- 4 - general failure
- 5 - API related failure
- 6 - TTY related failure
- 7 - value generated with :return command
Command examples
/system/package/getall
Template:Bapi Template:Apic Template:Apic Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis |- | ... more !re sentences ... |- Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Eapi
/user/active/listen
Template:Bapi Template:Apic Template:Apic Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis |- | ... more !re sentences ... |- Template:Eapi
/cancel, simultaneous commands
Template:Bapi Template:Apic Template:Apic Template:Apis Template:Apis Template:Apis Template:Apic Template:Apic Template:Apic Template:Apic Template:Apis Template:Apis Template:Apih Template:Apic Template:Apic Template:Apic Template:Apih Template:Apic Template:Apic Template:Apic Template:Apic Template:Apic Template:Apih Template:Apis Template:Apis Template:Apis Template:Apih Template:Apic Template:Apic Template:Apic Template:Apic Template:Apic Template:Apih Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Apih Template:Apic Template:Apic Template:Apic Template:Apih Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Apih Template:Apic Template:Apic Template:Apic Template:Apic Template:Apih Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Eapi
Example client
- this is simple API client in Python2
- example for Python3
- usage: api.py ip-address username password
- after that type words from keyboard, terminating them with newline
- Since empty word terminates sentence, you should press enter twice after last word before sentence will be sent to router.
#!/usr/bin/python import sys, posix, time, md5, binascii, socket, select, ssl class ApiRos: "Routeros api" def __init__(self, sk): self.sk = sk self.currenttag = 0 def login(self, username, pwd): for repl, attrs in self.talk(["/login", "=name=" + username, "=password=" + pwd]): if repl == '!trap': return False elif '=ret' in attrs.keys(): #for repl, attrs in self.talk(["/login"]): chal = binascii.unhexlify(attrs['=ret']) md = md5.new() md.update('\x00') md.update(pwd) md.update(chal) for repl2, attrs2 in self.talk(["/login", "=name=" + username, "=response=00" + binascii.hexlify(md.digest())]): if repl2 == '!trap': return False return True def talk(self, words): if self.writeSentence(words) == 0: return r = [] while 1: i = self.readSentence(); if len(i) == 0: continue reply = i[0] attrs = {} for w in i[1:]: j = w.find('=', 1) if (j == -1): attrs[w] = '' else: attrs[w[:j]] = w[j+1:] r.append((reply, attrs)) if reply == '!done': return r def writeSentence(self, words): ret = 0 for w in words: self.writeWord(w) ret += 1 self.writeWord('') return ret def readSentence(self): r = [] while 1: w = self.readWord() if w == '': return r r.append(w) def writeWord(self, w): print "<<< " + w self.writeLen(len(w)) self.writeStr(w) def readWord(self): ret = self.readStr(self.readLen()) print ">>> " + ret return ret def writeLen(self, l): if l < 0x80: self.writeStr(chr(l)) elif l < 0x4000: l |= 0x8000 self.writeStr(chr((l >> 8) & 0xFF)) self.writeStr(chr(l & 0xFF)) elif l < 0x200000: l |= 0xC00000 self.writeStr(chr((l >> 16) & 0xFF)) self.writeStr(chr((l >> 8) & 0xFF)) self.writeStr(chr(l & 0xFF)) elif l < 0x10000000: l |= 0xE0000000 self.writeStr(chr((l >> 24) & 0xFF)) self.writeStr(chr((l >> 16) & 0xFF)) self.writeStr(chr((l >> 8) & 0xFF)) self.writeStr(chr(l & 0xFF)) else: self.writeStr(chr(0xF0)) self.writeStr(chr((l >> 24) & 0xFF)) self.writeStr(chr((l >> 16) & 0xFF)) self.writeStr(chr((l >> 8) & 0xFF)) self.writeStr(chr(l & 0xFF)) def readLen(self): c = ord(self.readStr(1)) if (c & 0x80) == 0x00: pass elif (c & 0xC0) == 0x80: c &= ~0xC0 c <<= 8 c += ord(self.readStr(1)) elif (c & 0xE0) == 0xC0: c &= ~0xE0 c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) elif (c & 0xF0) == 0xE0: c &= ~0xF0 c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) elif (c & 0xF8) == 0xF0: c = ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) return c def writeStr(self, str): n = 0; while n < len(str): r = self.sk.send(str[n:]) if r == 0: raise RuntimeError, "connection closed by remote end" n += r def readStr(self, length): ret = '' while len(ret) < length: s = self.sk.recv(length - len(ret)) if s == '': raise RuntimeError, "connection closed by remote end" ret += s return ret def open_socket(dst, port, secure=False): s = None res = socket.getaddrinfo(dst, port, socket.AF_UNSPEC, socket.SOCK_STREAM) af, socktype, proto, canonname, sockaddr = res[0] skt = socket.socket(af, socktype, proto) if secure: s = ssl.wrap_socket(skt, ssl_version=ssl.PROTOCOL_TLSv1_2, ciphers="ADH-AES128-SHA256") #ADH-AES128-SHA256 else: s = skt s.connect(sockaddr) return s def main(): s = None dst = sys.argv[1] user = "admin" passw = "" secure = False port = 0 # use default username and pasword if not specified if len(sys.argv) == 4: user = sys.argv[2] passw = sys.argv[3] elif len(sys.argv) == 3: user = sys.argv[2] if (port==0): port = 8729 if secure else 8728 s = open_socket(dst, port, secure) if s is None: print ('could not open socket') sys.exit(1) apiros = ApiRos(s); if not apiros.login(user, passw): return inputsentence = [] while 1: r = select.select([s, sys.stdin], [], [], None) if s in r[0]: # something to read in socket, read sentence x = apiros.readSentence() if sys.stdin in r[0]: # read line from input and strip off newline l = sys.stdin.readline() l = l[:-1] # if empty line, send sentence and start with new # otherwise append to input sentence if l == '': apiros.writeSentence(inputsentence) inputsentence = [] else: inputsentence.append(l) if __name__ == '__main__': if len(sys.argv) == 1: print "Usage: %s IP [user] [pass] [--secure]" % str(sys.argv[0]) else: main()
Example run:
debian@localhost:~/api-test$ ./api.py 10.0.0.1 admin '' <<< /login <<< >>> !done >>> =ret=93b438ec9b80057c06dd9fe67d56aa9a >>> <<< /login <<< =name=admin <<< =response=00e134102a9d330dd7b1849fedfea3cb57 <<< >>> !done >>> /user/getall <<< /user/getall <<< >>> !re >>> =.id=*1 >>> =disabled=no >>> =name=admin >>> =group=full >>> =address=0.0.0.0/0 >>> =netmask=0.0.0.0 >>> >>> !done >>>
See also
API examples
API implementations in different languages, provided by different sources. They are not ordered in any particular order.
in the Wiki
- in Flash Actionscript 3
- in Ruby on rails
- in VB .NET
- in java see java client on github from Gideon LeGrange
- in NodeJS
- Python3
- in RUST GPL v3
- in GO by andredossantos
on the MikroTik Forum
- in Perl by Hugh
- in Delphi by Rodolfo
- in Delphi #2 by Chupaka
- in NodeJS by Trakkasure
- in VB by lucho512
- on PHP for sparks framework by vthinkteam
- for Windows PowerShell by navidrasi
External sources
- in .NET (C#) high-level api solution forum thread additional info by danikf
- in PHP by boen_robot
- in C by Håkon Nessjøen
- in Java by Gideon LeGrange
- in Erlang by Valery Comtihon
- in GO by André Luiz dos Santos
- in Python3 by Arturs Laizans
- in C++17 by Ayman Al-Qadhi