Manual:API: Difference between revisions

From MikroTik Wiki
Jump to navigation Jump to search
No edit summary
(API intro)
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
This document describes the operation of MikroTik RouterOS API for RouterOS3.  
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 ==
== Enabling and Connecting ==


* Default API port is 8728. By default API is disabled
* Default API port is 8728. By default API is disabled
* Enable API '''''/ip service enable api'''''
* Enable API
/ip service enable api
* Now you can initiate TCP connection to the API port of the router
* Now you can initiate TCP connection to the API port of the router


Line 13: Line 15:
* Words are grouped into sentences. End of sentence is terminated by zero length word.
* Words are grouped into sentences. End of sentence is terminated by zero length word.
* Lenght is encoded as follows:
* Lenght is encoded as follows:
{| border="1"
{| border="1"
!Value of length !! Number of bytes !! Encoding
!Value of length !! Number of bytes !! Encoding
Line 19: Line 20:
| 0 <= len <= 0x7F
| 0 <= len <= 0x7F
| 1
| 1
| <pre>len, lowest byte</pre>
| len, lowest byte
|-   
|-   
| 0x80 <= len <= 0x3FFF
| 0x80 <= len <= 0x3FFF
| 2
| 2
| <pre>len | 0x8000, two lower bytes</pre>
| <nowiki>len | 0x8000, two lower bytes</nowiki>
|-
|-
| 0x4000 <= len <= 0x1FFFFF
| 0x4000 <= len <= 0x1FFFFF
| 3
| 3
| <pre>len | 0xC00000, three lower bytes</pre>
| <nowiki>len | 0xC00000, three lower bytes</nowiki>
|-
|-
| 0x200000 <= len <= 0xFFFFFFF
| 0x200000 <= len <= 0xFFFFFFF
| 4
| 4
| <pre>len | 0xE0000000</pre>
| <nowiki>len | 0xE0000000</nowiki>
|-
|-
| len >= 0x10000000
| len >= 0x10000000
| 5
| 5
| <pre>0xF0 and len as four bytes</pre>
| 0xF0 and len as four bytes
|-
|-
|}
|}
* Although this scheme allows encoding of length up to '''0x7FFFFFFFF''', only four byte length is supported.
* 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).
* 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.
* 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.




Line 51: Line 52:
* Commands
* Commands
** First word is name of command. Examples:
** First word is name of command. Examples:
<pre>
  /login
  /login
  /ip/address/getall
  /ip/address/getall
Line 56: Line 58:
  /interface/vlan/remove
  /interface/vlan/remove
  /system/reboot
  /system/reboot
** Names of commands closely follow console, with spaces replaced by '/'. There are commands that are specific to API, like 'getall' or 'login'.
</pre>
** Names of commands closely follow console, with spaces replaced by '/'. There are commands that are specific to API, like <tt>getall</tt> or <tt>login</tt>.
** Name of command should begin with '/'.
** Name of command should begin with '/'.
** Next, command arguments can be specified. Examples:
** Next, command arguments can be specified. Examples:
Line 66: Line 69:
** First word of reply begins with '!'.
** First word of reply begins with '!'.
** Each command generates at least one reply (if connection does not get terminated).
** 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'.
** Last reply for every command is reply that has first word <tt>!done</tt>.
** Errors and exceptional conditions begin with '!trap'.
** Errors and exceptional conditions begin with <tt>!trap</tt>.
** Data replies begin with '!re'
** Data replies begin with <tt>!re</tt>




== Initial login ==
== Initial login ==


<tt>
{| style="width: 500px"
{| style="width: 500px"
|- style="background:#CC99CC; color:white"
{{apic|/login}}
| /login
{{apic|}}
|- style="background:#CCFFCC; color:#000"
{{apis|!done}}
| !done
{{apis|1==ret=ebddd18303a54111e2dea05a92ab46b4}}
|- style="background:#CCFFCC; color:#000"
{{apis|}}
| =ret=ebddd18303a54111e2dea05a92ab46b4
{{apic|/login}}
|- style="background:#CC99CC; color:white"
{{apic|1==name=admin}}
| /login
{{apic|1==response=001ea726ed53ae38520c8334f82d44c9f2}}
|- style="background:#CC99CC; color:white"
{{apic|}}
| =name=admin
{{apis|!done}}
|- style="background:#CC99CC; color:white"
{{apis|}}
| =response=001ea726ed53ae38520c8334f82d44c9f2
|- style="background:#CCFFCC; color:#000"
| !done
|}
|}
</tt>


# First, clients sends '''/login''' command.
* First, clients sends <tt>/login</tt> command.
# Reply contains '''=ret=challenge''' argument.
* Note that each command and response ends with an empty word.
# Client sends second '''/login''' command, with '''=name=username''' and '''=response=response'''.
* Reply contains <tt>=ret=''challenge''</tt> argument.
# In case of error, reply contains '''=ret=error message'''.
* Client sends second <tt>/login</tt> command, with <tt>=name=''username''</tt> and <tt>=response=''response''</tt>.
# In case of successful login client can start to issue commands.
* In case of error, reply contains <tt>=ret=''error message''</tt>.
* In case of successful login client can start to issue commands.


== Command examples ==
== Command examples ==


# '''/system/package/getall'''
=== <tt>/system/package/getall</tt> ===
# 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.
* Note that replies contain <tt>=.id=''Item internal number''</tt> property, that is not available in console. Names of properties that are API specific begin with dot.
* <tt>getall</tt> command is avaliable where console print command is available
 
{{bapi}}
{{apic|/system/package/getall}}
{{apic|}}
{{apis|!re}}
{{apis|1==.id=*5802}}
{{apis|1==disabled=no}}
{{apis|1==name=routeros-x86}}
{{apis|1==version=3.0beta2}}
{{apis|1==build-time=oct/18/2006 16:24:41}}
{{apis|1==scheduled=}}
{{apis|}}
{{apis|!re}}
{{apis|1==.id=*5805}}
{{apis|1==disabled=no}}
{{apis|1==name=system}}
{{apis|1==version=3.0beta2}}
{{apis|1==build-time=oct/18/2006 17:20:46}}
{{apis|1==scheduled=}}
{{apis|}}
|-
| ... more !re sentences ...
|-
{{apis|!re}}
{{apis|1==.id=*5902}}
{{apis|1==disabled=no}}
{{apis|1==name=advanced-tools}}
{{apis|1==version=3.0beta2}}
{{apis|1==build-time=oct/18/2006 17:20:49}}
{{apis|1==scheduled=}}
{{apis|}}
{{apis|!done}}
{{apis|}}
{{eapi}}
 
=== <tt>/user/active/listen</tt> ===
* <tt>listen</tt> command is avaliable where console print command is available, but it does not have expected effect everywhere (i.e. may not work)
* <tt>!re</tt> sentences are generated as something changes in particular item list
* this command does not terminate


<pre>
{{bapi}}
/system/package/getall
{{apic|/user/active/listen}}
{{apic|}}
{{apis|!re}}
{{apis|1==.id=*68}}
{{apis|1==radius=no}}
{{apis|1==when=oct/24/2006 08:40:42}}
{{apis|1==name=admin}}
{{apis|1==address=0.0.0.0}}
{{apis|1==via=console}}
{{apis|}}
{{apis|!re}}
{{apis|1==.id=*68}}
{{apis|1==.dead=yes}}
{{apis|}}
|-
| ... more !re sentences ...
|-
{{eapi}}


!re
== Example client ==
=.id=*5802
=disabled=no
=name=routeros-x86
=version=3.0beta2
=build-time=oct/18/2006 16:24:41
=scheduled=


!re
* this is simple API client in Python
=.id=*5805
* usage: api.py ''ip-address'' ''username'' ''password''
=disabled=no
* after that type words from keyboard, terminating them with newline
=name=system
=version=3.0beta2
=build-time=oct/18/2006 17:20:46
=scheduled=


... more !re sentences ...
<pre>
#!/usr/bin/python
import sys, posix, time, md5, binascii, socket


!re
class ApiRos:
=.id=*5902
    "Routeros api"
=disabled=no
    def __init__(self, sk):
=name=advanced-tools
        self.sk = sk
=version=3.0beta2
    def login(self, username, pwd):
=build-time=oct/18/2006 17:20:49
        for repl, attrs in self.say(["/login"]):
=scheduled=
            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())])


!done
    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()                         
</pre>
</pre>

Revision as of 11:18, 19 February 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()