Manual:API: Difference between revisions

From MikroTik Wiki
Jump to navigation Jump to search
(API intro)
 
(java API example is slighly incorrect and is not working)
(2 intermediate revisions by the same user not shown)
(No difference)

Revision as of 07:35, 7 March 2007

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

Template:Apic

Template:Apic

Template:Apis

Template:Apis

Template:Apis

Template:Apic

Template:Apic

Template:Apic

Template:Apic

Template:Apis

Template:Apis

  • 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()