Manual:API
This document describes the operation of MikroTik RouterOS API for RouterOS3. The API (application programming interface) is a way to create your own versions of Winbox. This guide will help you make simplified, or translated conrol applications for RouterOS v3.
Enabling and Connecting
- Default API port is 8728. By default API is disabled
- Enable API
/ip service enable api
- Now you can initiate TCP connection to the API port of the router
Protocol
- Protocol stream is formatted as a sequence of words.
- Each word is encoded as lenght, followed by that many bytes of content.
- Words are grouped into sentences. End of sentence is terminated by zero length word.
- Lenght is encoded as follows:
Value of length | Number 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 |
- Although this scheme allows encoding of length up to 0x7FFFFFFFF, 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.
Short description of API sentences
- Empty sentences are ignored.
- Sentences are processed after receiving terminating zero length word.
- There is a limit on number and size of sentences client can send before it has logged in.
- Commands
- First word is name of command. Examples:
/login /ip/address/getall /user/active/listen /interface/vlan/remove /system/reboot
- Names of commands closely follow console, with spaces replaced by '/'. There are commands that are specific to API, like getall or login.
- Name of command should begin with '/'.
- Next, command arguments can be specified. Examples:
=address=10.0.0.1 =name=iu=c3Eeg =disable-running-check=yes
- Command argument should begin with '=' followed by name of argument, followed by another '=', followed by value of argument.
- Agrument value can be empty and can contain '='.
- First word of reply begins with '!'.
- Each command generates at least one reply (if connection does not get terminated).
- Last reply for every command is reply that has first word !done.
- Errors and exceptional conditions begin with !trap.
- Data replies begin with !re
Initial login
- First, clients sends /login command.
- Note that each command and response ends with an empty word.
- 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.
Command examples
/system/package/getall
- Note that replies contain =.id=Item internal number property, that is not available in console. Names of properties that are API specific begin with dot.
- getall command is avaliable where console print command is available
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
- 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
- this command does not terminate
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
Example client
- this is simple API client in Python
- usage: api.py ip-address username password
- after that type words from keyboard, terminating them with newline
#!/usr/bin/python import sys, posix, time, md5, binascii, socket class ApiRos: "Routeros api" def __init__(self, sk): self.sk = sk def login(self, username, pwd): for repl, attrs in self.say(["/login"]): chal = binascii.unhexlify(attrs['=ret']) md = md5.new() md.update('\x00') md.update(pwd) md.update(chal) None in self.say(["/login", "=name=" + username, "=response=00" + binascii.hexlify(md.digest())]) def say(self, words): if self.writeWords(words) == 0: return while 1: i = self.readWords() repl = i.next() attrs = {} for w in i: j = w.find('=', 1) if (j == -1): attrs[w] = '' else: attrs[w[:j]] = w[j+1:] yield (repl, attrs) if repl == '!done': return def writeWords(self, words): ret = 0 for w in words: self.writeWord(w) ret += 1 self.writeWord('') return ret def readWords(self): while 1: w = self.readWord() if (w != ''): break while 1: yield w w = self.readWord() if w == '': return 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 getParagraph(): for l in iter(sys.stdin.readline, '\n'): yield l[:-1] def main(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((sys.argv[1], 8728)) apiros = ApiRos(s); apiros.login(sys.argv[2], sys.argv[3]); while 1: for repl, attrs in apiros.say(getParagraph()): print repl print attrs print '' if __name__ == '__main__': main()