API
本页介绍 RouterOS 的应用程序接口(API),用于与路由器进行安全通信,并说明语法规则、句子结构、编码方式以及 login、cancel 等受支持的命令。
API
**应用程序接口(API)允许用户创建自定义软件解决方案,与 RouterOS 通信以收集信息、调整配置和管理路由器。API 紧密遵循命令行界面(CLI)**的语法。您可以用它创建经过翻译或自定义的配置工具,让 RouterOS 路由器的运行和管理更加方便。
在尝试建立连接之前,必须先启用 API 服务,使用 /ip/service set api disabled=no 启用。默认情况下,API 使用 TCP 端口 8728 和 8729(安全)。
API-SSL 服务可以在有证书或无证书两种模式下运行。如果服务未配置证书,则只提供匿名 Diffie-Hellman 加密套件,当前 TLS 客户端不接受这些套件,因此连接会因握手错误而失败。如果服务配置了证书,客户端可以建立常规 TLS 会话。服务设置请参阅 ip/service CLI 参考。
协议
与路由器通信时,您发送句子并接收一个或多个句子作为返回。句子是由零长度字终止的字序列。字是句子的一部分,以特定方式编码:编码长度后跟数据。当路由器收到完整句子(命令字、一个或多个属性字以及零长度字)后,它会求值并执行该句子,然后形成并返回回复。
API 句子
句子是与 API 通信的主要对象。
- 空句子会被忽略。
- 收到零长度字后才会处理句子。
- 客户端在登录前可以发送的句子数量和大小有限制。
- 不要依赖属性字的顺序,因为
.proplist属性可能改变顺序和数量。
句子结构如下:
- 第一个字必须包含命令字。
- 句子必须包含零长度字作为终止。
- 句子可以包含零个或多个属性字。属性字的顺序无关紧要。
- 句子可以包含零个或多个查询字。查询字的顺序很重要。
危险
如果未提供零长度字,路由器不会开始求值已发送的字,并会把后续所有输入视为同一个句子的一部分。
API 字
- 字按句子分组。零长度字终止句子。
- 每个字编码为长度后跟相应字节数的内容。
- 编码支持最大 0xFFFFFFFF 的长度;五字节形式(
0xF0+ 四个字节)用于编码最大长度。 - len 字节按最高有效位在前(网络字节序)发送。
- 如果字的第一个字节 >= 0xF8,则它是保留控制字节。收到未知控制字节后,API 客户端无法继续,因为不知道如何解释后续字节。
- 当前未使用控制字节。
字的长度编码如下:
| 长度值 | 字节数 | 编码 |
|---|---|---|
0 <= len <= 0x7F | 1 | len,最低字节 |
0x80 <= len <= 0x3FFF | 2 | len | 0x8000,两个低字节 |
0x4000 <= len <= 0x1FFFFF | 3 | len | 0xC00000,三个低字节 |
0x200000 <= len <= 0xFFFFFFF | 4 | len | 0xE0000000 |
len >= 0x10000000 | 5 | 0xF0 以及作为四个字节的 len |
总的来说,字可以描述为 <encoded-word-length><word-content>。
字内容可以分为五部分:命令字、属性字、API 属性字、查询字 和 回复字。
命令字
句子中的第一个字必须是命令,其后是属性字以及零长度字或终止字。命令字的名称必须以斜杠(/)开头。命令名称紧密遵循 CLI。路径对象之间的空格不受支持,必须替换为斜杠(/)。
某些命令是 API 专用的:
login- 用于登录过程,提供登录凭据。cancel- 用于取消当前正在运行的命令。
命令字结构严格按以下顺序:
- 编码长度
- 内容前缀 /
- 转换后的 CLI 命令
命令字内容的几个示例:
/login
/user/active/listen
/interface/vlan/remove
/system/reboot
属性字
每个命令字根据内容都有其属性字列表。
属性字结构按以下顺序由五部分组成:
- 编码长度
- 内容以等号(
=)字符为前缀 - 属性名称
- 等号(
=)字符作为名称和值分隔符 - 属性值(如果存在)。
一些属性示例(不包含编码长度前缀):
=address=10.0.0.1
=disable-running-check=yes
值可以包含等号(=)符号:
=name=iu=c3Eeg
值可以为空:
=comment=
请记住,属性字和 API 参数的顺序无关紧要,不应依赖该顺序。
API 属性字
API 属性字结构严格按以下顺序:
- 编码长度
- 内容以点(
.)字符为前缀 - 属性名称
- 等号(
=)字符作为名称和值分隔符 - 属性值
目前唯一的此类 API 属性是 tag。
查询字
句子可以带有附加查询参数来限制其范围。详情请参阅查询部分。
- 查询字以
?开头。 - 目前只有
print命令处理查询字。
使用查询字属性的句子示例:
/interface/print
?type=ether
?type=vlan
?#|!
信息
查询字的顺序很重要
回复字
它只由路由器在响应客户端收到的完整句子时发送。
- 回复的第一个字以
!开头。 - 发送的每个句子至少生成一个回复(如果连接未被终止)。
- 每个句子的最后一个回复是第一个字为
!done的回复。 - 错误和异常情况以
!trap开头。 - 数据回复以
!re开头。 - 没有数据可回复的命令,其回复以
!empty开头。 - 如果必须关闭 API 连接,RouterOS 会发送带原因说明的
!fatal,然后关闭连接。
初始登录
login 命令支持两种身份验证方法。
明文密码 - 使用当前密码格式创建的帐户所采用的方法:
/login
=name=admin
=password=
如果身份验证成功,路由器回复 !done。密码以明文发送。登录成功后,客户端可以开始发出命令。
如果凭据错误或帐户无法使用 API,路由器会回复错误:
!trap
=message=invalid user name or password
!done
旧版质询-响应 - 使用旧密码格式创建的帐户所采用的方法:
当路由器对登录返回 =ret=<challenge> 而不是简单的 !done 时,登录使用旧版方法。不带密码的登录句子会得到质询,例如:
/login
!done
=ret=93b438ec9b80057c06dd9fe67d56aa9a
客户端在第二条消息中回答用户名和质询响应:两个字符 00,后跟 0x00 字节、密码和收到的质询的小写十六进制 MD5 摘要。
/login
=name=admin
=response=00e134102a9d330dd7b1849fedfea3cb57
!done
使用当前密码格式的帐户无法完成旧版交换;路由器会拒绝该响应。
关闭 TCP 连接会结束 API 会话。
标签
API 允许同时运行多个命令,而无需等待前一个命令完成。为了区分回复,请在命令句子中添加 API 属性字 .tag=<value>;这样该句子的每个回复都会带有相同的 .tag=<value> 字。
如果省略 .tag 字或将其值留空,则该命令的回复不带标签。
请注意标签与名为 tag 的参数之间的区别:标签是 API 属性字(.tag=),而 /cancel 的 =tag= 等命令参数是常规属性字(=name=value)。
命令说明
-
/cancel- 可选参数:
=tag=<要取消的命令标签>;不包含该参数时,所有正在运行的命令都会被取消。 - 不会取消自身。
- 所有被取消的命令都会被中断,通常会生成
!trap和!done响应。 - 请注意,
/cancel是独立命令,可以有自己的唯一.tag参数,该参数与命令的=tag参数无关。
- 可选参数:
-
listen- 只要 CLI
print命令可用,listen命令就可用,但它可能无法在所有位置正常工作。 - 当特定项目列表发生变化时,会生成
!re句子。 - 当项目被删除或消失时,
!re句子包含布尔值=.dead=true。 - 该命令不会自行终止;请使用
/cancel命令停止它。
- 只要 CLI
-
getall- 只要 CLI
print命令可用,getall命令就可用(getall是print的别名)。 - 回复包含
=.id=<项目内部编号>属性。
- 只要 CLI
-
print- API 的
print命令与 CLI 对应命令有以下区别:- 不支持
where参数;可以使用查询字筛选项目。 .proplist参数是以逗号分隔的属性名称列表,用于指定返回项目中包含的属性。- 返回的项目可能带有附加属性。
- 返回属性的顺序不重要,不能依赖。
- 如果列表包含重复条目,这些重复条目的处理方式未定义。
- 如果某个属性出现在
.proplist中但项目中不存在,则该项目的该属性值为空(对于该项目,?name求值为 false)。 - 如果缺少
.proplist,则会按print命令的要求包含所有属性,包括访问速度较慢的属性(例如文件内容和性能计数器)。因此建议使用.proplist。如果设置了=detail=参数,省略.proplist可能导致较高的性能开销。
- 不支持
- API 的
查询
print 和 getall 命令接受查询字,用于限制返回句子的集合。
- 查询字以
?开头。 - 查询字的顺序很重要。查询从第一个字开始求值。
- 查询会对列表中的每个项目求值。如果查询成功,则处理该项目;如果查询失败,则忽略该项目。
- 查询使用布尔值栈求值。初始时,栈中包含无限多个
true值。求值结束时,如果栈中至少有一个false值,则查询失败。 - 查询字按以下规则工作:
| 查询 | 说明 |
|---|---|
?name | 如果项目具有属性 name 的值,则压入 true,否则压入 false。 |
?-name | 如果项目不具有属性 name 的值,则压入 true,否则压入 false。 |
?name=x | 如果属性 name 的值等于 x,则压入 true,否则压入 false。 |
?<name=x | 如果属性 name 的值小于 x,则压入 true,否则压入 false。 |
?>name=x | 如果属性 name 的值大于 x,则压入 true,否则压入 false。 |
?#operations | 对栈中的值执行操作。操作字符串从左到右求值。十进制数字序列后跟任意其他字符或字结束符时,会被解释为栈索引。栈顶值的索引为 0。索引后跟字符时,压入该索引处值的副本。索引后跟字结束符时,用该索引处的值替换所有值。! 字符将栈顶值替换为其相反值。<code>&</code> 弹出两个值并压入逻辑 and 运算结果。| 弹出两个值并压入逻辑 or 运算结果。索引后的 . 不做任何操作。其他字符后的 . 压入栈顶值的副本。 |
信息
API 不支持正则表达式,因此不要尝试发送包含
~符号的查询 示例:
- 获取所有以太网和 VLAN 接口(等价于 CLI 命令
/interface/print where type=ether || type=vlan):
/interface/print
?type=ether
?type=vlan
?#|
- 获取所有注释非空的路由(等价于 CLI 命令
/ip/route/print where comment):
/ip/route/print
?>comment=
- 获取所有 distance 不大于 1 且 gateway 等于 172.16.1.1 的路由(等价于 CLI 命令
/ip/route/print where !(distance>1 && gateway=172.16.1.1)):
/ip/route/print
?>distance=1
?gateway=172.16.1.1
?#&!
OID
print 命令可以为 SNMP 中可用的属性返回 OID 值。
在 CLI 中,可以通过运行 print oid 命令查看 OID 值。在 API 中,具有 OID 的属性通过名称以 .oid 结尾的属性暴露;将这些名称添加到 .proplist 的值中即可检索它们。示例:
/system/resource/print
=.proplist=uptime,cpu-frequency,uptime.oid,cpu-frequency.oid
路由器发送回复:
!re
=uptime=16m44s
=cpu-frequency=864
=uptime.oid=.1.3.6.1.2.1.1.3.0
=cpu-frequency.oid=.1.3.6.1.4.1.14988.1.1.3.14.0
!done
并非每个 SNMP 属性都通过 API 暴露。具有 OID 的属性就是路由器通过 print oid 命令显示的属性;没有 OID 的属性(例如系统资源中的 cpu-load)没有对应的 .oid。
!trap
当 API 句子执行失败时,路由器返回 trap 句子。trap 带有 message 属性,包含失败详情;某些 trap 还带有 category 属性,表示错误类别:
- 0 - 缺少项目或命令
- 1 - 参数值错误
- 2 - 命令执行被中断
- 3 - 脚本相关错误
- 4 - 一般错误
- 5 - API 相关错误
- 6 - TTY 相关错误
- 7 - 由 :return 命令生成的值
例如,未知命令会返回带 =category=0 的 trap:
/foobar/print
路由器回复:
!trap
=category=0
=message=no such command or directory (foobar)
!trap
=message=no such command prefix
!done
错误的参数值会返回仅带 message 的 trap:
/ip/address/add
=address=192.168.88.1
=interface=asdf
路由器回复:
!trap
=message=input does not match any value of interface
!done
修改现有项目
与 CLI 对应命令一样,API 有 set 命令,接受项目 ID 和要设置的参数。.id 属性的值可以是内部编号(例如 *1),对于有名称的项目也可以是名称(例如 ether1)。
唯一的例外是 API 的 set 命令不直接接受查询。例如,以下 CLI 命令用于设置所有以太网接口的 MTU:
/interface set [find where type=ether] mtu=1500
要通过 API 实现相同效果,首先需要运行 print 查询获取 ID,然后才能执行 set 命令。
/interface/print
=.proplist=.id
?type=ether
路由器回复:
!re
=.id=*1
!re
=.id=*2
现在需要遍历返回的 ID,并为每个 ID 发送 set 命令:
/interface/set
=.id=*1
=mtu=1500
/interface/set
=.id=*2
=mtu=1500
set 成功时,路由器回复单个 !done(无数据);如果未找到项目,则回复 !trap。
命令示例
listen 通知
开始监听接口列表的变化:
/interface/listen
每当项目出现或发生变化时,路由器发送 !re 句子。项目被移除时,!re 句子带有布尔值 =.dead=true:
!re
=.id=*1
=name=ether1
=disabled=false
=running=true
!re
=.id=*1
=.dead=true
使用 /cancel 停止监听。
/cancel 与同时运行的命令
开始监听接口变化(tag 为 2):
/interface/listen
.tag=2
发送命令禁用接口(tag 为 3):
/interface/set
=disabled=true
=.id=ether1
.tag=3
路由器对 disable 命令(以 tag 3 执行)回复 !done;该变更还会为 listen 命令(tag 2)生成更新:
!re
=.id=*1
=disabled=true
=dynamic=false
=running=false
=name=ether1
=mtu=1500
=type=ether
.tag=2
!done
.tag=3
启用接口(tag 为 4):
/interface/set
=disabled=false
=.id=ether1
.tag=4
该变更会为 listen 命令生成另一次更新,随后是 enable 命令的 done:
!re
=.id=*1
=disabled=false
=dynamic=false
=running=true
=name=ether1
=mtu=1500
=type=ether
.tag=2
!done
.tag=4
发送获取接口列表的命令(tag 为 5):
/interface/getall
.tag=5
客户端收到 getall 命令(以 tag 5 执行)的回复:
!re
=.id=*1
=disabled=false
=dynamic=false
=running=true
=name=ether1
=mtu=1500
=type=ether
.tag=5
!re
=.id=*2
=disabled=false
=dynamic=false
=running=true
=name=ether2
=mtu=1500
=type=ether
.tag=5
!done
.tag=5
停止监听 - 请求取消 tag 2 的命令;cancel 自身使用 tag 7:
/cancel
=tag=2
.tag=7
listen 命令被中断(tag 2):
!trap
=category=2
=message=interrupted
.tag=2
cancel 命令完成(tag 7):
!done
.tag=7
listen 命令完成(tag 2):
!done
.tag=2
客户端示例
一个简单的 Python3 API 客户端,支持两种登录方法,并在终端打印整个交互过程。