ROS API 中文说明


前言

ROS 是 一个比较流行的软路由系统,它的强大在于它的高度定制性,它提供了应用程序编程使用的API接口,是它应用更加灵活,我们可以自己开发软件或WEB程序来操控ROS,比较实用的例子就是当用ROS管理小区网络时,我们用自己写的软件来管理上网账号,安全又方便,总之好处多多,本文基于官方API文档: HYPERLINK “http://wiki.mikrotik.com/wiki/API" http://wiki.mikrotik.com/wiki/API 翻译而成,结合了作者的理解,水平有限,难免有错漏的地方,望大家批评指正,由nohacks.cn原创,转载请注明出处!

目录

1 简介
2 通讯协议
2.1 API语句
2.1.1 命令词
2.1.2 属性词
2.1.3 API 属性词
2.1.4 查询语句
2.1.5 回复语句
2.2 API 特定命令说明
3 初始登录
4 标签(.Tags )
5 API命令说明
5.1 查询词说明
5.2 OID
6 API 命令例子
6.1 /system/package/getall
6.2 /user/active/listen
6.3 /cancel, simultaneous commands
7 客户端程序例子
8 参考
8.1 API examples in the Wiki
8.2 API examples on the MikroTik Forum
8.3 API exmaples elsewhere

正文

1.简介

应用程序编程接口(API),允许用户创建定制的软件解决方案与RouterOS的沟通,收集信息,调整配置和管理路由器。 API紧随命令行界面(CLI)的语法。它可以用来创建转换或自定义的配置工具,以帮助管理使用RouterOS的路由器。使用API需要RouterOS版本3.x或更高的版本。默认情况下,API使用端口8728,默认服务是禁用的。通讯服务的名称是API,请在IP-SERVER里开启,服务管理的详细信息,请参阅相应的手册部分。

2.通讯协议:

应用程序与路由器的通信是通过发送和接收路由器的一个或多个编码的句子来完成的。一个句子是以零字符结尾的单词序列。词是句子以某种方式编码 -(编码长度是数据的一部分),路由器发送和接收回复并发送这些句子。每个句子发送到路由器使用API没有特定的顺序,每个命令字是以零字符标记结束的。当路由器接收到完整的句子(命令字,或多个属性的话,零字符结束),它就开始执行命令,并将结果返回给应用程序。

2.1 API语句

词是句子的一部分。每个词长都用某种方式编码 - 词长编码跟随词的内容就是一个句子。词的长度应为将要发送的字节计数(不包括词长编码)。
词长编码如下:

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

对应的10进制:

长度 字节数 词长编码
0 <= 长度<=127 1 长度低位
128 <= 长度<= 16383 2 位或(长度 ,32768)取低2位
16384<= 长度<= 2097151 3 位或(长度 ,12582912)取低3位
2097152 <= 长度 <= 268435455 4 位或(长度 ,3758096384)
长度 >= 268435456 5 {240} + 到字节集(长度)

每个词的编码长度,然后紧接着许多字节的词内容(词长编码 + 词内容);
字组合成句子,以零字符结束;
最高长度可以达到0x7FFFFFFFFF,最高占用4字节;
词长编码字节(Len)总是在最前面(网络顺序);
如果单词的第一个字节是> =0xF8,那么它是一个保留的控制字节。未知的控制字节API客户端接收后无法继续,因为它不知道如何解释以下字节;
目前,控制字节不使用;
句子一般情况是这样的:<词长编码><词的内容>,主要有5种类型:命令语句,属性语句,API属性语句,查询语句,回复语句;

2.1.1 命令语句

在句子的第一个字是由名字(属性)和零长度的词终止字的命令。命令字的名称应以’/‘开始。命令中的名字,与命令行界面输入的一样,要注意的API中的命令不能有空格,需要用以’/‘替换,比如查看网卡信息 “/int print” 在API里就必须这样”/int/print”,不然无法识别;

注意:发送的命令必须严格按照这样的顺序:

编码长度
内容前缀"/"
命令行的转换命令(空格用"/"替换)

API特定的命令:

getall 
login 
cancel

命令连接例子:

/login
/ip/address/getall
/user/active/listen
/interface/vlan/remove
/system/reboot

2.1.2 属性语句(Attribute word)

 每个命令都有其自己的属性列表,命令内容决定属性。
 属性结构由5部分组成,顺序如下:
编码长度
内容前缀( ! - = )
属性名称
    分离符号( ! - = )
    属性值(可以被忽略,说明这个属性没有值)

注意:为了编码方便,一个命令里的多个属性赋值可以在一句里完成 属性值可以为空
没有编码的长度前缀的例子:

=address=10.0.0.1        
=name=iu=c3Eeg        
=disable-running-check=yes

注意:属性词和API参数的顺序并不重要,不应依赖;

2.1.3 API属性语句

API属性语句的结构必须严格按照下面的顺序:

编码长度
内容与名称前缀"=."
属性名称
名称后缀符"="
属性值

system/resource/print
=.proplist=uptime,cpu-load,uptime.oid,cpu-load.oid

目前只有这样的API属性的标签。

注意:如果句子包含了属性语句标签,返回的每一个句子和从路由器标记句子将标记相同的标签,关于标签后面的章节有单独的介绍.

2.1.4 查询语句

查询语句支持对参数进行一定范围内的模糊查询,在下面的章节中有单独介绍;
例如句子使用查询词的属性:

/interface/print
?type=ether
?type=vlan
?#|!

查询语句以符号”?”开始,目前查询语句只支持”print”命令;
警告:查询语句始终是在最前面;

2.1.5 回复语句

回复语句只能由路由器发送,它仅发送完整的句子,由客户端发送响应。

回复语句的第一个字是以”!”开始的;
发送的每一句话产生至少一个答复(如果连接没有得到终止);
每一句的最后答复是答复的第一个字!done
错误和异常情况以!trap开始;
开始数据回复以!re开始;
如果连接被关闭,RouterOS发送!fatal作为致命的原因进行答复并且关闭连接;

2.2 API语句

API语句是使用API通信的主要对象

空的句子被忽略;
句子是以字符”0”作为结束标志的;
客户端登陆后发送句子有数量和大小的限制;
属性语句没有顺序区别,比如.proplist属性语句的顺序和计数就是多变的;
句子结构如下:

第一句话应该包含命令字;
应包含结束标志字符{0};
可以包含0个或多个属性词,没有特定的顺序,不管什么属性词必须在句子里发送,属性词的顺序并不重要;
可以包含没有一个或几个查询词,查询词在句子的顺序是很重要的;

注:零长度的词(字节’0’)终止了一句,如果没有提供,路由器将无法测试句字的有效性,只能把收到的句子当做句子的一部分;

3. 初始登录

/login

 !done
=ret=ebddd18303a54111e2dea05a92ab46b4

/login
=name=admin
=response=001ea726ed53ae38520c8334f82d44c9f2

 !done

注意:每个命令和响应结束都有一句空的语句;
首先,客户端发送”/login”命令
路由器的回复包含”=ret=需要的参数”
客户端发送第二个”/login”命令,接着是用户名(“=name=username”)和密码(“=response=response”)验证命令;
在错误的情况下,答复包含= RET =错误消息。
在成功登录客户端的情况下,就可以开始发出命令。

4. 标签(.tag)

它是可以同时运行多个命令,而不必等待前一个完成。如果API的客户端是这样做的,需要区分命令的反应,它可以使用在命令句子’.tag’API的参数。
如果你有“.tag”命令句与非空值的参数,然后’.tag’参数完全相同的值将包含在该命令生成的所有答复。
如果不包括’.tag’参数,或它的值是空的,那么这个命令所有的反应将不会有“.tag”参数。

5. API命令说明

  • cancle(取消)
    可选参数:=tag=tag ,取消所有正在运行的命令;
    不能取消本身
    所有取消的命令都是中断操作,并且在通常情况下会产生’!trap’ 和’!done’ 的回复;
    请注意,”/cancel “是单独的命令,可以有它自己独特的’.tag’ 参数,它是不相关’=.tag’ 这个命令的参数;

  • listen (监听)
    listen是在控制台print 命令可用的情况下使用,它没有预期中的效果(即可能无法正常工作);
    !re 数据回复句子会产生特定的项目列表中的一些变化;
    当项目被删除或以其他任何方式清除,数据回复句子( ‘!re’)的属性值会包含’=.dead=yes’ ;
    此命令不会终止。终止使用取消命令”/cancle”

  • getall (获取)
    getall命令是在控制台print命令可用的情况下使用,自3.21版本以后的getall命令是print命令的别名。
    回复包含= .id =项目内部编号属性

  • print (显示)
    API的print命令和控制台的print命令的不同主要有以下几个方面:
    虽然参数不支持,但可以使用查询词(见下文)筛选项目。
    传回的项目可能有额外的属性。
    返回的属性的顺序是没有定义的。
    如果列表中包含重复的条目,这些条目的处理没有被定义。
    如果属性格式目前是在.proplist里,但项目里没有这个属性,该项目没有这个属性的值,(?名称将评估该项目为假)
    如果没有设置.proplist参数,将打印所有的属性,甚至那些比较耗时的项目(如文件内容性能记数),因此推荐使用.proplist参数,设置=detail= argument,虽然可能会有遗漏,但是换回的却是高性能。
    查询
    print命令接受限制返回的句子设置的查询词。此功能是自RouterOS的3.21开始的。

5.1 查询词说明

查询词以符号”?’开始。
查询词的顺序是在最前面的,从第一个字开始模糊查询。
对查询列表中每个项目进行评估,如果查询成功,项目被处理,如果查询失败,项目将被忽略。
查询评估在堆栈使用布尔逻辑值。最初,堆栈包含无限量的“真”值。在评估结束时,如果堆栈包含至少一个“假”值,查询失败。
查询词按照下列规定操作:

查询词 描叙
?name 如果项目属性名称的值不为空,堆栈压入真,否则压入假
?-name 如果项目属性名称的值为空,堆栈压入真,否则压入假
?name=x 或 ?=name=x 如果项目属性名称的值=x,堆栈压入真,否则压入假
?<name=x 如果项目属性名称的值<x ,堆栈压入真,否则压入假
?>name=x 如果项目属性名称的值>x ,堆栈压入真,否则压入假

?# 操作符适用于操作在堆栈的值。
操作字符串是从左向右计算的。
任何其他的字符或单词的末尾的十进制数字序列被解释为一个堆栈指数。最高值指数0。
后跟一个字符的索引,压入该指数值的副本
指数是由单词的末尾替换所有值与该指数的值。
!操作符是反义字符,替换堆顶值为反义值;
& 操作符是逻辑”与”操作符,从堆栈弹出2个值,进行逻辑与操作并将结果压于入堆栈
| 操作符是逻辑”或”操作符, 从堆栈弹出2个值,进行逻辑“或”操作并将结果压于入堆栈
. 索引后什么都不做
. 另一个字符后压入副本值到堆顶。

例子:
取得所有的以太网和VLAN接口:

/interface/print
?type=ether
?type=vlan
?#|

获取所有有备注的路由:

/ip/route/print
?>comment=

设置
set命令必须包含查询属性:”.id”, ,顺序并不重要,查询词可以是num,name或者Comment等可以区分对象的属性值,支持模糊查询,如果有多个对象的属性值包含查询词,只会修改找到的第一个对象,不会全部修改。

一个修改PPP用户的例子:
增加一个用户名为nohacks,密码为test,备注为comment的PPPoE服务的用户账户。

 /ppp/secret/add  
=name=nohacks 
=service=pppoe
=password=test
=Comment=comment
!done
=ret=*1

第一种方法:通过具有唯一性的num属性进行修改

   /ppp/secret/set
=.id=*1
=name=nohacks
=password=test  
    !done  

第二种方法:通过具有唯一性的name属性进行修改

 /ppp/secret/set
=.id=nohacks
=name=nohacks
=password=test    
!done   

.id属性支持模糊查询,例如
=.id=nohac
也可以成功修改nohacks账号
但要注意的是如果有多个对象包含查询词,比如还有一个账户anohac,这个命令只会修改序号靠前的账户。

第三种方法:通过comment属性进行修改

    /ppp/secret/set
=.id=comment
=name=nohack
=password=test    
    !done

5.2 OID

print命令可以返回属性是在SNMP OID值。此功能出现在3.23版本以后。
在控制台,OID值可以运行’print oid’命令看出。在API这些属性有”.OID”结束的名称,并可以加入他们的名字的值用”.proplist”检索。一个例子:

/system/resource/print  
=.proplist=uptime,cpu-load,uptime.oid,cpu-load.oid   

 !re  
=uptime=01:22:53  
=cpu-load=0  
=uptime.oid=.1.3.6.1.2.1.1.3.0  
=cpu-load.oid=.1.3.6.1.2.1.25.3.3.1.2.1  
 !done  

6. API 命令例子

6.1 /system/package/getall

/system/package/getall 
/system/package/getall

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

 !re
=.id=*5805
=disabled=no
=name=system
=version=3.0beta2
=build-time=oct/18/2006 17:20:46
=scheduled=

… 更多 !re 回复句子 …

 !re
=.id=*5902
=disabled=no
=name=advanced-tools
=version=3.0beta2
=build-time=oct/18/2006 17:20:49
=scheduled=

 !done

6.2 /user/active/listen

/user/active/listen 
/user/active/listen

 !re
=.id=*68
=radius=no
=when=oct/24/2006 08:40:42
=name=admin
=address=0.0.0.0
=via=console

 !re
=.id=*68
=.dead=yes

6.3 /cancel, simultaneous commands

/login

 !done
=ret=856780b7411eefd3abadee2058c149a3

/login
=name=admin
=response=005062f7a5ef124d34675bf3e81f56c556

 !done

-- first start listening for interface changes (tag is 2)
/interface/listen
.tag=2

-- disable interface (tag is 3)
/interface/set
=disabled=yes
=.id=ether1
.tag=3

-- this is done for disable command (tag 3)
 !done
.tag=3

-- enable interface (tag is 4)
/interface/set
=disabled=no
=.id=ether1
.tag=4

-- this update is generated by change made by first set command (tag 3)
 !re
=.id=*1
=disabled=yes
=dynamic=no
=running=no
=name=ether1
=mtu=1500
=type=ether
.tag=2

-- this is done for enable command (tag 4)
 !done
.tag=4

-- get interface list (tag is 5)
/interface/getall
.tag=5

-- this update is generated by change made by second set command (tag 4)
 !re
=.id=*1
=disabled=no
=dynamic=no
=running=yes
=name=ether1
=mtu=1500
=type=ether
.tag=2

-- these are replies to getall command (tag 5)
 !re
=.id=*1
=disabled=no
=dynamic=no
=running=yes
=name=ether1
=mtu=1500
=type=ether
.tag=5

 !re
=.id=*2
=disabled=no
=dynamic=no
=running=yes
=name=ether2
=mtu=1500
=type=ether
.tag=5

-- here interface getall ends (tag 5)
 !done
.tag=5

-- stop listening - request to cancel command with tag 2, cancel itself uses tag 7
/cancel
=tag=2
.tag=7

-- listen command is interrupted (tag 2)
 !trap
=category=2
=message=interrupted
.tag=2

-- cancel command is finished (tag 7)
 !done
.tag=7

-- listen command is finished (tag 2)
 !done
.tag=2

7. 客户端例子

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
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"]):
            chal = binascii.unhexlify(attrs['=ret'])
        md = md5.new()
        md.update('\x00')
        md.update(pwd)
        md.update(chal)
        self.talk(["/login", "=name=" + username,
                   "=response=00" + binascii.hexlify(md.digest())])
    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 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]);

    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__':
    main()
例子运行实例
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
>>> 

8.参考资料

HYPERLINK “http://wiki.mikrotik.com/wiki/API_command_notes"API command notes

8.1 API examples in the Wiki

HYPERLINK “http://wiki.mikrotik.com/wiki/API_PHP_class"in PHP#1
HYPERLINK “http://wiki.mikrotik.com/wiki/API_PHP_package"in PHP using PEAR2#2
HYPERLINK “http://wiki.mikrotik.com/wiki/API_Delphi"in Delphi#1
HYPERLINK “http://wiki.mikrotik.com/wiki/API_Delphi_Client"in Delphi#2
HYPERLINK “http://wiki.mikrotik.com/wiki/API_in_C"in C
HYPERLINK “http://wiki.mikrotik.com/wiki/API_in_C_using_winsock"in C using winsock
HYPERLINK “http://wiki.mikrotik.com/wiki/API_In_CPP"API in C++
HYPERLINK “http://wiki.mikrotik.com/wiki/API_in_C_Sharp"in C#
HYPERLINK “http://wiki.mikrotik.com/wiki/API_ActionScript_3_class"in Flash Actionscript 3
HYPERLINK “http://wiki.mikrotik.com/wiki/API_Ruby_class"in Ruby on rails
HYPERLINK “http://wiki.mikrotik.com/wiki/API_in_VB_dot_NET"in VB .NET
HYPERLINK “http://wiki.mikrotik.com/wiki/API_in_Java"in java
HYPERLINK “http://wiki.mikrotik.com/wiki/MikroNode"in NodeJS
HYPERLINK “http://wiki.mikrotik.com/wiki/Manual%3AAPI_Python3"Python3

8.2 API examples on the MikroTik Forum

HYPERLINK “http://forum.mikrotik.com/viewtopic.php?f=9&t=22744"in Perlby Hugh
HYPERLINK “http://forum.mikrotik.com/viewtopic.php?f=9&t=28821"in Delphiby Rodolfo
HYPERLINK “http://forum.mikrotik.com/viewtopic.php?f=9&t=31555"in Delphi #2by Chupaka
HYPERLINK “http://forum.mikrotik.com/viewtopic.php?f=9&t=51861"in NodeJSby Trakkasure
HYPERLINK “http://forum.mikrotik.com/viewtopic.php?f=2&t=51584"in VBby lucho512
HYPERLINK “http://forum.mikrotik.com/viewtopic.php?f=9&t=56869"on PHP for sparks frameworkby vthinkteam

8.3 API exmaples elsewhere

HYPERLINK “http://code.google.com/p/mikrotik4net/"in .NET (C#) high-level api solutionby danikf
HYPERLINK “https://sourceforge.net/projects/netrouteros/"in PHPby boen_robot
Retrieved from “HYPERLINK “http://wiki.mikrotik.com/wiki/Manual:API"http://wiki.mikrotik.com/wiki/Manual:API"


文章作者: nohacks
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 nohacks !
  目录