Show pageOld revisionsBacklinksBack to top This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== ubus (OpenWrt micro bus 架构) ====== 为了在OpenWrt中提供守护进程和应用程序间的通讯,开发了ubus项目工程。它包含了守护进程、库以及一些额外的帮助程序。 核心部分是ubusd守护进程,它提供了其他守护进程将自己注册以及发送消息的接口。因为这个,接口通过使用Unix socket来实现,并使用TLV(type-length-value)消息。 为了简化软件的开发,可以使用已有的libubus库来使用ubus(连接ubus)。 每个守护进程在自己的名称空间中注册自有的路径。每个路径可以提供多个带有不定数量参数的方法,方法可以通过消息回复调用。 代码在LGPL 2.1授权方法下发布,你可以通过git在[[git://nbd.name/luci2/ubus.git]]或通过http在[[http://nbd.name/gitweb.cgi?p=luci2/ubus.git;a=summary]]获取。 ubus从[[https://dev.openwrt.org/changeset/28499|r28499]]起被包含在OpenWrt中。 ===== ubus命令行工具 ===== ''ubus''可以和''ubusd''服务器交互(和当前所有已经注册的服务). 它对研究和调试注册的命名空间以及编写脚本非常有用。对于调用带参数和返回信息的方法,它使用友好的JSON格式。下面是它的命令说明。 ==== list ==== 缺省列出所有通过RPC服务器注册的命名空间: <code>root@uplink:~# ubus list network network.device network.interface.lan network.interface.loopback network.interface.wan root@uplink:~#</code> 如果调用时包含参数''-v'',将会显示指定命名空间更多方法参数等信息: <code>root@uplink:~# ubus -v list network.interface.lan 'network.interface.lan' @099f0c8b "up": { } "down": { } "status": { } "prepare": { } "add_device": { "name": "String" } "remove_device": { "name": "String" } "notify_proto": { } "remove": { } "set_data": { } root@uplink:~#</code> ==== call ==== 调用指定命名空间中指定的方法,并且通过消息传递给它: <code>root@uplink:~# ubus call network.interface.wan status { "up": true, "pending": false, "available": true, "autostart": true, "uptime": 86017, "l3_device": "eth1", "device": "eth1", "address": [ { "address": "178.25.65.236", "mask": 21 } ], "route": [ { "target": "0.0.0.0", "mask": 0, "nexthop": "178.25.71.254" } ], "data": { } } root@uplink:~#</code> 消息参数必须是有效的JSON字符串,并且携带函数所要求的键及值: <code>root@uplink:~# ubus call network.device status '{ "name": "eth0" }' { "type": "Network device", "up": true, "link": true, "mtu": 1500, "macaddr": "c6:3d:c7:90:aa:da", "txqueuelen": 1000, "statistics": { "collisions": 0, "rx_frame_errors": 0, "tx_compressed": 0, "multicast": 0, "rx_length_errors": 0, "tx_dropped": 0, "rx_bytes": 0, "rx_missed_errors": 0, "tx_errors": 0, "rx_compressed": 0, "rx_over_errors": 0, "tx_fifo_errors": 0, "rx_crc_errors": 0, "rx_packets": 0, "tx_heartbeat_errors": 0, "rx_dropped": 0, "tx_aborted_errors": 0, "tx_packets": 184546, "rx_errors": 0, "tx_bytes": 17409452, "tx_window_errors": 0, "rx_fifo_errors": 0, "tx_carrier_errors": 0 } } root@uplink:~#</code> ==== listen ==== 设置一个监听socket并观察进入的事件: <code>root@uplink:~# ubus listen & root@uplink:~# ubus call network.interface.wan down { "network.interface": { "action": "ifdown", "interface": "wan" } } root@uplink:~# ubus call network.interface.wan up { "network.interface": { "action": "ifup", "interface": "wan" } } { "network.interface": { "action": "ifdown", "interface": "he" } } { "network.interface": { "action": "ifdown", "interface": "v6" } } { "network.interface": { "action": "ifup", "interface": "he" } } { "network.interface": { "action": "ifup", "interface": "v6" } } root@uplink:~# </code> ==== send ==== 发送一个事件提醒: <code>root@uplink:~# ubus listen & root@uplink:~# ubus send foo '{ "bar": "baz" }' { "foo": { "bar": "baz" } } root@uplink:~# </code> ===== 通过HTTP访问ubus ===== 这里有个叫做''uhttpd-mod-ubus''的''uhttpd''插件允许通过HTTP协议来调用ubus,请求必须通过POST方法发送''/ubus''(除非有修改)URL请求。这个接口使用了[[http://www.jsonrpc.org/specification|jsonrpc v2.0]]这里有几个步骤需要你去了解。 (Documentation written while using BarrierBreaker r40831, ymmv) ==== ACL访问控制列表 ==== 通过ssh登陆后,你可直接访问,并且拥有ubus的所有权限。但是无论何时,当你在uhttpd通过''/ubus''访问ubus时,uhttpd runs "ubus call session access '{ ubus-rpc-session, requested-object, requested-method }' and whoever is providing the ubus session.* namespace is in charge of implementing the ACL. This happens to be ''rpcd'' at the moment, with the http-json interface, for friendly operation with browser code, but this is just one possible implmementation. Because we're using rpcd to implement the ACLs at this time, this allows/requires (depending on your point of view) ACLs to be configured in ''/usr/share/rpcd/acl.d/*.json''. The __names__ of the files in ''/usr/share/rpcd/acl.d/*.json'' don't matter, but the top level keys define roles. The default acl, listed below, __only__ defines the login methods, so you can login, but you still wouldn't be able to do anything. <code> { "unauthenticated": { "description": "Access controls for unauthenticated requests", "read": { "ubus": { "session": [ "access", "login" ] } } } } </code> An example of a complicated ACL, allowing quite fine grained access to different ubus modules and methods is [[http://git.openwrt.org/?p=project/luci2/ui.git;a=blob;f=luci2/share/acl.d/luci2.json|available in the Luci2 project]] An example of a "security is for suckers" config, where a "superuser" ACL group is defined, allowing unrestricted access to everything, is shown below. (This illustrates the usage of '*' definitions in the ACLs, but keep reading for better examples) Placing this file in ''/usr/share/rpcd/acl.d/superuser.json'' will help you move forward to the next steps. <code> { "superuser": { "description": "Super user access role", "read": { "ubus": { "*": [ "*" ] }, "uci": [ "*" ] }, "write": { "ubus": { "*": [ "*" ] }, "uci": [ "*" ] } } } </code> Below is an example of an ACL definition that only allows access to some specific ubus modules, rather than unrestricted access to everything. <code> { "lesssuperuser": { "description": "not quite as super user", "read": { "ubus": { "file": [ "*" ], "log": [ "*" ], "service": [ "*" ], }, }, "write": { "ubus": { "file": [ "*" ], "log": [ "*" ], "service": [ "*" ], }, } } } </code> Note: Before we leave this section, you may have noticed that there's both a "ubus" and a "uci" section, even though ubus has a uci method. The uci: scope is used for the uci api provided by rpcd to allow defining per-file permissions because using the ubus scope you can only say "uci set" is allowed or not allowed but not specify that it is allowed to e.g. modify /e/c/system but not /e/c/network If your application/ACL doesn't need UCI access, you can just leave out the UCI section altogether. ==== Authentication ==== Now that we have an ACL that allows operations beyond just logging in, we can actually try this out. As mentioned, ''rpcd'' is handling this, so you need an entry in ''/etc/config/rpcd'' <code> config login option username 'root' option password '$p$root' list read '*' list write '*' </code> The ''$p'' magic means to look in ''/etc/shadow'' and the ''$root'' part means to use the password for the root user in that file. The list of read and write sections, those map acl roles to user accounts. You can also use ''$1$<hash>''which is a "crypt()" hash, using SHA1, exactly as used in /etc/shadow. You can generate these with, eg, "uhhtpd -m secret" To login and receive a session id: <code> $ curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "00000000000000000000000000000000", "session", "login", { "username": "root", "password": "secret" } ] }' http://your.server.ip/ubus {"jsonrpc":"2.0","id":1,"result":[0,{"ubus_rpc_session":"c1ed6c7b025d0caca723a816fa61b668","timeout":300,"expires":299,"acls":{"access-group":{"superuser":["read","write"],"unauthenticated":["read"]},"ubus":{"*":["*"],"session":["access","login"]},"uci":{"*":["read","write"]}},"data":{"username":"root"}}]} </code> The sessionid "00000000000000000000000000000000" (32 zeros) is a special null-session which just has enough access rights for the session.login ubus call. A session has a timeout, that is specified when you login, but has a default. You can request a longer timeout in your initial login call, with a "timeout" key in the login parameters section. If you ever receive a response like, ''{"jsonrpc":"2.0","id":1,"result":[6]}'' That is a valid jsonrpc response, 6 is the ubus code for UBUS_STATUS_PERMISSION_DENIED (you'll get this if you try and login before setting up the "superuser" file, or any file that gives you anymore rights than just being allowed to attempt logins. To list all active sessions, try ''ubus call session list'' ==== Session management ==== A session is automatically renewned on every use. There are plans to use these sessions even for luci1, but at present, if you use this interface in a luci1 environment, you'll need to manage sessions yourself. ==== Actually making calls ==== Now that you have a ''ubus_rpc_session'' you can make calls, based on your ACLs and the available ubus services. ''ubus -v list'' is your primary documentation on what can be done, but see the rest of this page for more information. For example, ''ubus -v list file'' returns <code> 'file' @24a6bd4a "read":{"path":"String","data":"String"} "write":{"path":"String","data":"String"} "list":{"path":"String","data":"String"} "stat":{"path":"String","data":"String"} "exec":{"command":"String","params":"Array","env":"Table"} </code> The json container format is: <code> { "jsonrpc": "2.0", "id": <unique-id-to-identify-request>, "method": "call", "params": [ <ubus_rpc_session>, <ubus_object>, <ubus_method>, { <ubus_arguments> } ] } </code> The "id" key is merely echo'ed by the server, so it needs not be strictly unique, it's mainly intended for client software to easily correlate responses to previously made requests. It's type is either a string or a number, so it can be an sha1 hash, md5 sum, sequence counter, unix timestamp, .... An example request to read a file would be: <code> $ curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "7cba69a942c0e9db1eb7982cd91f3a48", "file", "read", { "path": "/tmp/hello.karl" } ] }' http://eg-134867.local/ubus {"jsonrpc":"2.0","id":1,"result":[0,{"data":"this is the contents of a file\n"}]} </code> ===== Lua module for ubus ===== This is even possible to use ''ubus'' in ''lua'' scripts. Of course it's not possible to use native libraries directly in ''lua'', so an extra module has been created. It's simply called ''ubus'' and is a simple interface between ''lua'' scripts and the ''ubus'' (it uses ''libubus'' internally). ==== Load module ==== <code>require "ubus"</code> ==== Establish connection ==== <code>local conn = ubus.connect() if not conn then error("Failed to connect to ubusd") end</code> ==== Iterate all namespaces and procedures ==== <code>local namespaces = conn:objects() for i, n in ipairs(namespaces) do print("namespace=" .. n) local signatures = conn:signatures(n) for p, s in pairs(signatures) do print("\tprocedure=" .. p) for k, v in pairs(s) do print("\t\tattribute=" .. k .. " type=" .. v) end end end</code> ==== Call a procedure ==== <code>local status = conn:call("network.device", "status", { name = "eth0" }) for k, v in pairs(status) do print("key=" .. k .. " value=" .. tostring(v)) end</code> ==== Close connection ==== <code>conn:close()</code> ===== Namespaces & Procedures ===== As explained earlier, there can be many different daemons (services) registered in ''ubus''. Below you will find a list of the most common projects with namespaces, paths and procedures they provide. ==== netifd ==== [[http://nbd.name/gitweb.cgi?p=luci2/netifd.git;a=blob;f=DESIGN|Design of netifd]] ^ Path ^ Procedure ^ Signature ^ Description ^ | ''network'' | ''restart'' | ''{ }'' | Restart the network, reconfigures all interfaces | | ''network'' | ''reload'' | ''{ }'' | Reload the network, reconfigure as needed | | ''network.device'' | ''status'' | ''{ "name": "//ifname//" }'' | Dump status of given network device ''//ifname//'' | | ''network.device'' | ''set_state'' | ''{ "name": "//ifname//", "defer": //deferred// }'' | Defer or ready the given network device ''//ifname//'', depending on the boolean value //deferred// | | ''network.interface.//name//'' | ''up'' | ''{ }'' | Bring interface ''//name//'' up | | ''network.interface.//name//'' | ''down'' | ''{ }'' | Bring interface ''//name//'' down | | ''network.interface.//name//'' | ''status'' | ''{ }'' | Dump status of interface ''//name//'' | | ''network.interface.//name//'' | ''prepare'' | ''{ }'' | Prepare setup of interface ''//name//'' | | ''network.interface.//name//'' | ''add_device'' | ''{ "name": "//ifname//" }'' | Add network device ''//ifname//'' to interface ''//name//'' (e.g. for bridges: ''brctl addif br-//name// //ifname//'') | | ''network.interface.//name//'' | ''remove_device'' | ''{ "name": "//ifname//" }'' | Remove network device ''//ifname//'' from interface ''//name//'' (e.g. for bridges: ''brctl delif br-//name// //ifname//'') | | ''network.interface.//name//'' | ''remove'' | ''{ }'' | Remove interface ''//name//'' (?) | ==== rpcd ==== Project ''rpcd'' is set of small plugins providing sets of ''ubus'' procedures in separated namespaces. These plugins are not strictly related to any particular software (like ''netifd'' or ''dhcp'') so it wasn't worth it to implement them as separated projects. ^ Path ^ Procedure ^ Signature ^ Description ^ | ''file'' | ''read'' | ? | ? | | ''file'' | ''write'' | ? | ? | | ''file'' | ''list'' | ? | ? | | ''file'' | ''stat'' | ? | ? | | ''file'' | ''exec'' | ? | ? | \\ ^ Path ^ Procedure ^ Signature ^ Description ^ | ''iwinfo'' | ''info'' | ? | ? | | ''iwinfo'' | ''scan'' | ? | ? | | ''iwinfo'' | ''assoclist'' | ? | ? | | ''iwinfo'' | ''freqlist'' | ? | ? | | ''iwinfo'' | ''txpowerlist'' | ? | ? | | ''iwinfo'' | ''countrylist'' | ? | ? | ^ Path ^ Procedure ^ Signature ^ Description ^ | ''session'' | ''list'' | ''{ "session": "//sid//" }'' | Dump session specified by ''//sid//'', if no ID is given, dump all sessions | | ''session'' | ''create'' | ''{ "timeout": //timeout// }'' | Create a new session and return its ID, set the session timeout to ''//timeout//'' | | ''session'' | ''grant'' | ''{ "session": "//sid//", "objects": [ [ "path", "func" ], ... ] }'' | Within the session identified by ''//sid//'' grant access to all specified procedures ''//func//'' in the namespace ''//path//'' listed in the ''//objects//'' array | | ''session'' | ''revoke'' | ''{ "session": "//sid//", "objects": [ [ "path", "func" ], ... ] }'' | Within the session identified by ''//sid//'' revoke access to all specified procedures ''//func//'' in the namespace ''//path//'' listed in the ''//objects//'' array. If ''//objects//'' is unset, revoke all access | | ''session'' | ''access'' | ? | ? | | ''session'' | ''set'' | ''{ "session": "//sid//", "values": { "//key//": //value//, ... } }'' | Within the session identified by ''//sid//'' store the given arbitrary values under their corresponding keys specified in the ''//values//'' object | | ''session'' | ''get'' | ''{ "session": "//sid//", "keys": [ "//key//", ... ] }'' | Within the session identified by ''//sid//'' retrieve all values associated with the given keys listed in the ''//keys//'' array. If the key array is unset, dump all key/value pairs | | ''session'' | ''unset'' | ''{ "session": "//sid//", "keys": [ "//key//", ... ] }'' | Within the session identified by ''//sid//'' unset all keys listed in the ''//keys//'' array. If the key list is unset, clear all keys | | ''session'' | ''destroy'' | ''{ "session": "//sid//" }'' | Terminate the session identified by the given ID ''//sid//'' | | ''session'' | ''login'' | ? | ? | ^ Path ^ Procedure ^ Signature ^ Description ^ | ''uci'' | ''get'' | ''{ "package": "//package//", "section": "//sname//", "type": "//type//", "option": "//oname//" }'' | <WRAP>Return the requested uci value(s), all arguments are optional. - When called without argument or with empty object: return an array of package names in the ''packages'' field - When called with ''//package//'' set: return an object containing all sections containing all options in a field named after the package - When called with ''//package//'' and ''//type//'' set: return an object containing all sections of type ''//type//'' containing all options in a field named after the package - When called with ''//package//'' and ''//sname//'' set: return an object containing all options of the section in a field named after the section - When called with ''//package//'' and ''//type//'' and ''//oname//'' set: return an object containing the value of each option named ''//oname//'' within a section of type ''//type//'' in a field named after the matched section - When called with ''//package//'' and ''//sname//'' and ''//oname//'' set: return the result string in a field named ''//oname//'' in case of options or an array of result strings in a field named ''//oname//'' in case of list options Return messages: - ''{ "packages": [ "package1", ... ] }'' - ''{ "//package//": { "sname1": { ".type": "type1", "option1": "value1", "option2": [ "value2.1", ... ], ... }, ... } }'' - ''{ "//package//": { "sname1": { ".type": "//type//", "option1": "value1", "option2": [ "value2.1", ... ], ... }, ... } }'' - ''{ "//sname//": { ".type": "type", "option1": "value1", "option2": [ "value2.1", ... ], ... } }'' - ''{ "sectionname1": "value1", "sectionname2": [ "value2.1", ... ], ... }'' - - ''{ "//oname//": "value1" }'' - ''{ "//oname//": [ "value1.1", ... ] }'' </WRAP> | | ''uci'' | ''set'' | ''{ "package": "//package//", "section": "//sname//", "option": "//oname//", "value": "//value//" }'' | <WRAP>Set the given value(s), the option argument is optional. - When called with ''//package//'' and ''//sname//'' and ''//value//'' set: add a new section ''//sname//'' in ''//package//'' and set it to the type given in ''//value//'' - When called with ''//package//'' and ''//sname//'', ''//oname//'' and ''//value//'' set: - If ''//value//'' is of type array: set strings in the ''value'' array as list option ''//oname//'' - If ''//value//'' is of type string: set ''//value//'' as normal option ''//oname//'' The call does not produce any data, instead it returns with the following status codes: - If there already is a section called ''//sname//'': ''UBUS_STATUS_INVALID_ARGUMENT'' else: ''UBUS_STATUS_OK'' - If there is no section ''//sname//'' or if ''//value//'' is neither a string nor an array: ''UBUS_STATUS_INVALID_ARGUMENT'' else: ''UBUS_STATUS_OK'' </WRAP> | | ''uci'' | ''add'' | ''{ "package": "//package//", "type": "//type//" }'' | <WRAP>Add new anonymous section of given type. - When called with ''//package//'' and ''//type//'' set: Add a new anonymous section of type ''//type//''. Return message: - ''{ "section": "sectionname" }'' </WRAP> | | ''uci'' | ''delete'' | ''{ "package": "//package//", "section": "//sname//", "type": "//type//", "option": "//oname//" }'' | <WRAP>Delete the given value(s) or section(s), the option and type arguments are optional. - When called with ''//package//'' and ''//type//'' set: delete all sections of type ''//type//'' in ''//package//'' - When called with ''//package//'' and ''//sname//'' set: delete the section named ''//sname//'' in ''//package//'' - When called with ''//package//'', ''//type//'' and ''//oname//'' set: delete the option named ''//oname//'' within each section of type ''//type//'' in ''//package//'' - When called with ''//package//'', ''//sname//'' and ''//oname//'' set: delete the option named ''//oname//'' in section ''//sname//'' of ''//package//'' The call does not result in any data, instead it returns the following status codes: - If no section of type ''//type//'' was found: ''UBUS_STATUS_NOT_FOUND'' else: ''UBUS_STATUS_OK'' - If no section named ''//sname//'' was found: ''UBUS_STATUS_NOT_FOUND'' else: ''UBUS_STATUS_OK'' - If no options named ''//oname//'' within sections of type ''//type//'' where found: ''UBUS_STATUS_NOT_FOUND'' else: ''UBUS_STATUS_OK'' - If the option named ''//oname//'' within named section ''//sname//'' was not found: ''UBUS_STATUS_NOT_FOUND'' else: ''UBUS_STATUS_OK'' </WRAP> | ===== 代码片段 ===== ==== Check if Link is up using devstatus and Json ==== <code> #!/bin/sh . /usr/share/libubox/jshn.sh WANDEV="$(uci get network.wan.ifname)" json_load "$(devstatus $WANDEV)" json_get_var var1 speed json_get_var var2 link echo "Speed: $var1" echo "Link: $var2" </code> Last modified: 2018/10/24 20:29by stokito