Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
docs:techref:ubus [2018/10/29 20:02] – [Full Example] stokitodocs:techref:ubus [2023/12/22 11:56] (current) – Add required 'Content-type: application/json' to curl examples. mikma
Line 1: Line 1:
 ====== ubus (OpenWrt micro bus architecture) ====== ====== ubus (OpenWrt micro bus architecture) ======
 +See also:
 +[[https://hackmd.io/@rYMqzC-9Rxy0Isn3zClURg/H1BY98bRw|What is UBUS?]]
  
-To provide [[wp>Inter-process_communication|Inter-process communication]] between various daemons and applications in OpenWrt a project called ''ubus'' has been developed. It consists of several parts including daemon, library and some extra helpers.+To provide [[wp>Inter-process_communication|Inter-process communication]] between various daemons and applications in OpenWrt a project called ''ubus'' has been developed. 
 +It consists of several parts including daemon, library and some extra helpers.
  
- +The heart of this project is the ''ubusd'' daemon. 
-The heart of this project is the ''ubusd'' daemon. It provides an interface for other daemons to register themselves as well as sending messages. For those curious, this interface is implemented using Unix sockets and it uses [[wp>Type-length-value|TLV (type-length-value)]] messages.+It provides an interface for other daemons to register themselves as well as sending messages. 
 +For those curious, this interface is implemented using Unix sockets and it uses [[wp>Type-length-value|TLV (type-length-value)]] messages.
  
 To simplify development of software using ''ubus'' (connecting to it) a library called ''libubus'' has been created. To simplify development of software using ''ubus'' (connecting to it) a library called ''libubus'' has been created.
  
-Every daemon registers a set of paths under a specific namespace. Every path can provide multiple procedures with any number of arguments. Procedures can reply with a message.+Every daemon registers a set of paths under a specific namespace. 
 +Every path can provide multiple procedures with any number of arguments. 
 +Procedures can reply with a message.
  
 The code is published under [[wp>GNU_Lesser_General_Public_License|LGPL 2.1 license]] and can be found via git at [[https://git.openwrt.org/project/ubus.git]]. The code is published under [[wp>GNU_Lesser_General_Public_License|LGPL 2.1 license]] and can be found via git at [[https://git.openwrt.org/project/ubus.git]].
  
 ===== Command-line ubus tool ===== ===== Command-line ubus tool =====
 +The ''ubus'' command line tool allows to interact with the ''ubusd'' server (with all currently registered services).
 +It's useful for investigating/debugging registered namespaces as well as writing shell scripts.
 +For calling procedures with parameters and returning responses it uses the user-friendly JSON format.
 +Below is an explanation of its commands.
  
-The ''ubus'' command line tool allows to interact with the ''ubusd'' server (with all currently registered services). It's useful for investigating/debugging registered namespaces as well as writing shell scripts. For calling procedures with parameters and returning responses it uses the user-friendly JSON format. Below is an explanation of its commands. +==== Help output ====
- +
-==== Help Output ==== +
-As of March, 2018+
 <code> <code>
 +# ubus
 Usage: ubus [<options>] <command> [arguments...] Usage: ubus [<options>] <command> [arguments...]
 Options: Options:
Line 37: Line 45:
  - monitor Monitor ubus traffic  - monitor Monitor ubus traffic
 </code> </code>
- 
  
 ==== list ==== ==== list ====
 +To find out which services are currently running on the bus simply use the ''ubus list'' command.
 +This will show a complete list of all namespaces registered with the RPC server:
  
-By default, list all namespaces registered with the RPC server: +<code> 
- +# ubus list 
-<code>root@OpenWrt:~# ubus list+dhcp 
 +dnsmasq 
 +file 
 +iwinfo 
 +log 
 +luci 
 +luci-rpc
 network network
 network.device network.device
 +network.interface
 network.interface.lan network.interface.lan
 network.interface.loopback network.interface.loopback
 network.interface.wan network.interface.wan
-root@OpenWrt:~#</code>+network.interface.wan6 
 +network.rrdns 
 +network.wireless 
 +service 
 +session 
 +system 
 +uci 
 +</code>
  
-If invoked with ''-v'', the procedures and their argument signatures are dumped in addition to the namespace path:+You can filter the list by specifying a service path:
  
-<code>root@OpenWrt:~# ubus -v list network.interface.lan+<code> 
 +# ubus list network.interface.* 
 +network.interface.lan 
 +network.interface.loopback 
 +network.interface.wan 
 +network.interface.wan6 
 +</code> 
 + 
 +To find out which procedures/methods and their argument signatures a specific service provides add ''-v'' in addition to the namespace path: 
 + 
 +<code> 
 +# ubus -v list network.interface.lan
 'network.interface.lan' @099f0c8b 'network.interface.lan' @099f0c8b
  "up": {  }  "up": {  }
Line 64: Line 98:
  "remove": {  }  "remove": {  }
  "set_data": {  }  "set_data": {  }
-root@OpenWrt:~#</code>+</code>
  
 ==== call ==== ==== call ====
- 
 Calls a given procedure within a given namespace and optionally pass arguments to it: Calls a given procedure within a given namespace and optionally pass arguments to it:
  
-<code>root@OpenWrt:~# ubus call network.interface.wan status+<code bash> 
 +# ubus call network.interface.wan status
 { {
  "up": true,  "up": true,
Line 96: Line 130:
  }  }
 } }
-root@OpenWrt:~#</code>+</code>
  
 The arguments must be a valid JSON string, with keys and values set according to the function signature: The arguments must be a valid JSON string, with keys and values set according to the function signature:
  
-<code>root@OpenWrt:~# ubus call network.device status '{ "name": "eth0" }'+<code bash> 
 +# ubus call network.device status '{ "name": "eth0" }'
 { {
  "type": "Network device",  "type": "Network device",
Line 134: Line 169:
  }  }
 } }
-root@OpenWrt:~#</code>+</code>
  
 ==== listen ==== ==== listen ====
 +Set up a listening socket and observe incoming events:
  
-Setup a listening socket and observe incoming events: +<code bash> 
- +# ubus listen & 
-<code>root@OpenWrt:~# ubus listen & +# ubus call network.interface.wan down
-root@OpenWrt:~# ubus call network.interface.wan down+
 { "network.interface": { "action": "ifdown", "interface": "wan" } } { "network.interface": { "action": "ifdown", "interface": "wan" } }
-root@OpenWrt:~# ubus call network.interface.wan up+# ubus call network.interface.wan up
 { "network.interface": { "action": "ifup", "interface": "wan" } } { "network.interface": { "action": "ifup", "interface": "wan" } }
 { "network.interface": { "action": "ifdown", "interface": "he" } } { "network.interface": { "action": "ifdown", "interface": "he" } }
Line 149: Line 184:
 { "network.interface": { "action": "ifup", "interface": "he" } } { "network.interface": { "action": "ifup", "interface": "he" } }
 { "network.interface": { "action": "ifup", "interface": "v6" } } { "network.interface": { "action": "ifup", "interface": "v6" } }
-root@OpenWrt:~# </code>+</code>
  
 ==== send ==== ==== send ====
- 
 Send an event notification: Send an event notification:
  
-<code>root@OpenWrt:~# ubus listen & +<code bash> 
-root@OpenWrt:~# ubus send foo '{ "bar": "baz" }'+# ubus listen & 
 +# ubus send foo '{ "bar": "baz" }'
 { "foo": { "bar": "baz" } } { "foo": { "bar": "baz" } }
-root@OpenWrt:~# </code>+</code>
  
 ===== Access to ubus over HTTP ===== ===== Access to ubus over HTTP =====
 +There is an ''uhttpd'' plugin called ''uhttpd-mod-ubus'' that allows ''ubus'' calls using HTTP protocol.
 +For example this is used in [[docs:techref:luci2|LuCI2]].
 +Requests have to be sent to the ''/ubus'' URL (unless changed by ''ubus_prefix'' option) using the ''POST'' method.
 +This interface uses [[https://www.jsonrpc.org/specification|jsonrpc v2.0]]  There are a few steps that you will need to understand.
  
-There is an ''uhttpd'' plugin called ''uhttpd-mod-ubus'' that allows ''ubus'' calls using HTTP protocol. For example this is used in [[docs:techref:luci2|LuCI2]]. Requests have to be send to the ''/ubus'' URL (unless changed by ''ubus_prefix'' option) using ''POST'' method. This interface uses [[https://www.jsonrpc.org/specification|jsonrpc v2.0]]  There are a few steps that you will need to understand.+If uhttpd-mod-ubus is installed, uhttpd automatically configures it and enables it, by default at /ubus, but you can [[docs:guide-user:services:webserver:uhttpd|configure this]]  
 +Also if you want to use the ''/ubus'' directly from a web client you need to specify ''ubus_cors=1'' option.
  
-To enable ubus via HTTP you need to [[docs:guide-user:services:webserver:uhttpd|configure uhttpd]] and specify ubus endpoint path prefix (''ubus_prefix''): +If you are using Nginx then you may try [[https://github.com/Ansuel/nginx-ubus-module|nginx-ubus-module]]. 
-<code> +For other web servers like Lighttpd you may use the [[https://github.com/yurt-page/cgi-ubus|ubus as CGI]]. 
-# uci set uhttpd.main.ubus_prefix=/ubus + 
-/etc/init.d/uhttpd restart +See the [[https://documenter.getpostman.com/view/5972215/TVzNHeej#154b5d84-4065-4887-a5a8-c1f3b51fe9a6|Postman collection]] with some examples of calling UBUS over HTTP
-</code> +
-Now try to open the configured url in browser http://localhost:8081/ubus and if you get ''400 Bad request'' message then it works and you can proceed but if ''404 Not found'' then it wasn't configured correctly or doesn't supported by your version of uhttpd. +
-Also if you want to use the ''/ubus'' directly from web client you need to specify ''ubus_cors=1'' option.+
  
-You can use the [[https://documenter.getpostman.com/view/2741510/RzZ1qhkK|UBUS Postman Collection]] with basic examples. 
 ==== ACLs ==== ==== ACLs ====
 +While logged in via ssh, you have direct, full access to ubus.
 +When you're accessing the ''/ubus'' url in uhttpd however, uhttpd first queries whether your call is allowed, and whoever is providing the ubus ''session.*'' namespace is in charge of implementing the access control:
  
-While logged into via ssh, you have direct, full access to ubus. When you're accessing the ''/ubus'' url in uhttpd however, uhttpd first queries whether your call is allowed, and whoever is providing the ubus ''session.*'' namespace is in charge of implementing the access control: +<code bash>
-<code>+
 ubus call session access '{ "ubus_rpc_session": "xxxxx", "object": "requested-object", "function": "requested-method" }' ubus call session access '{ "ubus_rpc_session": "xxxxx", "object": "requested-object", "function": "requested-method" }'
 </code> </code>
  
 +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 implementation.
 +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 log in, but you still wouldn't be able to do anything.
  
-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 implementation.  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 javascript>
-<code json>+
 { {
         "unauthenticated": {         "unauthenticated": {
Line 197: Line 237:
 An example of a complicated ACL, allowing quite fine grained access to different ubus modules and methods is [[https://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 complicated ACL, allowing quite fine grained access to different ubus modules and methods is [[https://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).+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. Placing this file in ''/usr/share/rpcd/acl.d/superuser.json'' will help you move forward to the next steps.
  
-<code json>+<code javascript>
 { {
         "superuser": {         "superuser": {
Line 209: Line 250:
                                 "*": [ "*" ]                                 "*": [ "*" ]
                         },                         },
-                        "uci": [ "*" ]+                        "uci": [ "*" ]
 +                        "file":
 +                                "*": ["*"
 +                        }
                 },                 },
                 "write": {                 "write": {
Line 215: Line 259:
                                 "*": [ "*" ]                                 "*": [ "*" ]
                         },                         },
-                        "uci": [ "*" ]+                        "uci": [ "*" ], 
 +                        "file":
 +                                "*": ["*"
 +                        }, 
 +                        "cgi-io": ["*"]
                 }                 }
         }         }
Line 222: Line 270:
  
 Below is an example of an ACL definition that only allows access to some specific ubus modules, rather than unrestricted access to everything. Below is an example of an ACL definition that only allows access to some specific ubus modules, rather than unrestricted access to everything.
-<code json>+ 
 +<code javascript>
 { {
         "lesssuperuser": {         "lesssuperuser": {
Line 244: Line 293:
 </code> </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 [[docs:techref:uci|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, but not specify that it is allowed to e.g. modify ''/etc/config/system'' but not ''/etc/config/network''. If your application/ACL doesn't need UCI access, you can just leave out the UCI section altogether.+**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 [[docs:techref:uci|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, but not specify that it is allowed to e.g. modify ''/etc/config/system'' but not ''/etc/config/network''. 
 +If your application/ACL doesn't need UCI access, you can just leave out the UCI section altogether.
  
-==== Full Example ====+==== 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''
  
-=== User Mapping === +<code bash>
-In this example we map username ''root'' to ''fhem_acl'' for [[docs:guide-user:services:automation:fhem|FHEM]] server. +
-Edit ''/etc/config/rpcd'': +
-<code - /etc/config/rpcd>+
 config login config login
  option username 'root'  option username 'root'
Line 259: Line 309:
  
 config login config login
- option username 'fhem+        option username 'blaer
-        +        option password '$p$blaer
-        # '$p$<username> => reference to user passowrd on /etc/shadow +        list read lesssuperuser 
-        # '$1$<hash>' => crypt() hash, using SHA1. generate via 'uhttpd -m fhem' +        list write lesssuperuser
-        # +
-        # password: fhem +
-        # +
- option password '$1$$xEODf2fNSQ0ArfkJu4L2i1' +
-+
-        # map username to rpcd acl name "fhem_acl" +
-        # +
-        list read 'fhem_acl' +
- list write '' +
-</code> +
-Then reload rpcd config: +
-<code> +
-# uci commit rpcd+
 </code> </code>
  
-=== User ACL ===+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. 
 +So ''list read *'' means the read credential of any group listed in the merged ACLs. 
 +Write implies read.
  
-All files under ''/usr/share/rpcd/acl.d/'' will be merge by ''rpcd'' to one config file!! +You can also use ''$1$<hash>'' which is a [[wp>Crypt_(Unix)|crypt]] hash, using SHA1, exactly as used in ''/etc/shadow''
- +You can generate these with ''uhttpd -m secret''.
-<code> +
-# ubus call hostapd.wlan0 get_clients +
-# ubus call hostapd.wlan1 get_clients +
-</code> +
- +
-Example permissions to run ubus calls remote by fhem over http/json. +
- +
-<code javascript /usr/share/rpcd/acl.d/fhem.json> +
-+
-        "fhem_acl":+
-                "description": "FHEM PRESENCE Module User.. https://github.com/janLo/OpenWRT-Wifi-Clients-POC", +
-                "read":+
-                        "ubus":+
-                                "hostapd.wlan0": [ "get_clients" ], +
-                                "hostapd.wlan1": [ "get_clients"+
-                        } +
-                } +
-        } +
-+
-</code> +
- +
-FHEM Server - show all connected wireless clients: +
-<code> +
-fhem-server$ python wifi_clients.py +
-Usage: wifi_clients.py.orig <OpenWrt_Host> <User> <Pass> <WiFi_1> [<WiFi2> ...] +
-fhem-server$ +
-fhem-server$ +
-fhem-server$ python wifi_clients.py 192.168.1.71 fhem fhem wlan0 wlan1 +
-c8:f6:50:e1:a9:10, 2c:f0:a2:e2:c0:15, 8c:a9:82:f1:9c:2a, 7c:c3:a1:b8:d2:1e, 64:9a:be:6a:6c:13, 00:1d:63:a3:8f:4a +
-fhem-server$ +
-</code>+
  
-=== Authentication === +==== Session management ==== 
-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'' +To login and receive a session id:
-<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 [[wp>Crypt_(Unix)|crypt]] hash, using SHA1, exactly as used in ''/etc/shadow''. You can generate these with ''uhttpd -m secret''+<code bash
- +$ curl -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "00000000000000000000000000000000", "session", "login", { "username": "root", "password": "secret"  } ] }'  http://your.server.ip/ubus
-=== Session management === +
- +
-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"}}]} {"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> </code>
  
-The session id ''00000000000000000000000000000000'' (32 zeros) is a special null-session which only has the rights from the ''unauthenticated'' access group, only enabling the ''session.login'' ubus call. A session has a timeout that can be specified when you login, otherwise it defaults to 5 minutes.+The session id ''00000000000000000000000000000000'' (32 zeros) is a special null-session which only has the rights from the ''unauthenticated'' access group, only enabling the ''session.login'' ubus call. 
 +A session has a timeout that can be specified when you login, otherwise it defaults to 5 minutes.
  
 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 more rights than just being allowed to attempt logins). 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 more rights than just being allowed to attempt logins).
Line 342: Line 339:
 To list all active sessions, try ''ubus call session list'' To list all active sessions, try ''ubus call session list''
  
-The session timeout is automatically reset 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.+The session timeout is automatically reset 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 
  
-=== Actually making calls === +<code javascript>
- +
-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' @ff0a2c92 'file' @ff0a2c92
  "read":{"path":"String","base64":"Boolean"}  "read":{"path":"String","base64":"Boolean"}
Line 371: Line 369:
 } }
 </code> </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. Its type is either a string or a number, so it can be an UUID, sha1 hash, md5 sum, sequence counter, unix timestamp, etc.+ 
 +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. 
 +Its type is either a string or a number, so it can be an UUID, sha1 hash, md5 sum, sequence counter, unix timestamp, etc.
  
 An example request to read a file ''/etc/board.json'' which contains device info would be:  An example request to read a file ''/etc/board.json'' which contains device info would be: 
-<code> + 
-$ curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "c1ed6c7b025d0caca723a816fa61b668", "file", "read", { "path": "/etc/board.json" } ] }'  http://your.server.ip/ubus+<code bash
 +$ curl -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "c1ed6c7b025d0caca723a816fa61b668", "file", "read", { "path": "/etc/board.json" } ] }'  http://your.server.ip/ubus
 {"jsonrpc":"2.0","id":1,"result":[0,{"data":"{\n\t\"model\": {\n\t\t\"id\": \"innotek-gmbh-virtualbox\",\n\t\t\"name\": \"innotek GmbH VirtualBox\"\n\t},\n\t\"network\": {\n\t\t\"lan\": {\n\t\t\t\"ifname\": \"eth0\",\n\t\t\t\"protocol\": \"static\"\n\t\t}\n\t}\n}\n"}]} {"jsonrpc":"2.0","id":1,"result":[0,{"data":"{\n\t\"model\": {\n\t\t\"id\": \"innotek-gmbh-virtualbox\",\n\t\t\"name\": \"innotek GmbH VirtualBox\"\n\t},\n\t\"network\": {\n\t\t\"lan\": {\n\t\t\t\"ifname\": \"eth0\",\n\t\t\t\"protocol\": \"static\"\n\t\t}\n\t}\n}\n"}]}
 </code> </code>
Line 384: Line 385:
 To beautify output you can use [[https://stedolan.github.io/jq/|jq]] utility: To beautify output you can use [[https://stedolan.github.io/jq/|jq]] utility:
    
-<code> +<code bash
-curl -s -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "c1ed6c7b025d0caca723a816fa61b668", "file", "read", { "path": "/etc/board.json" } ] }'  http://your.server.ip/ubus | jq+curl -s -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "c1ed6c7b025d0caca723a816fa61b668", "file", "read", { "path": "/etc/board.json" } ] }'  http://your.server.ip/ubus | jq
 { {
   "jsonrpc": "2.0",   "jsonrpc": "2.0",
Line 398: Line 399:
 </code> </code>
  
-In the response JSON document in the ''result[1].data'' field contains escaped JSON with the ''model'' and ''network'' objects. To fetch the concrete model name you can parse the response with the [[https://stedolan.github.io/jq/|jq]] program:+In the response JSON document in the ''result[1].data'' field contains escaped JSON with the ''model'' and ''network'' objects. 
 +To fetch the concrete model name you can parse the response with the [[https://stedolan.github.io/jq/|jq]] program:
 <code> <code>
-curl -s -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "c1ed6c7b025d0caca723a816fa61b668", "file", "read", { "path": "/etc/board.json" } ] }'  http://your.server.ip/ubus | jq -r '.result[1].data' | jq .model.name+curl -s -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "c1ed6c7b025d0caca723a816fa61b668", "file", "read", { "path": "/etc/board.json" } ] }'  http://your.server.ip/ubus | jq -r '.result[1].data' | jq .model.name
 "innotek GmbH VirtualBox" "innotek GmbH VirtualBox"
 </code> </code>
  
 As you can see in the example the router model is in fact just a OpenWrt runned on [[docs:guide-user:virtualization:virtualbox-vm|virtual machine in VirtualBox]]. As you can see in the example the router model is in fact just a OpenWrt runned on [[docs:guide-user:virtualization:virtualbox-vm|virtual machine in VirtualBox]].
- 
  
 ===== Lua module for ubus ===== ===== Lua module for ubus =====
- +This is even possible to use ''ubus'' in ''lua'' scripts. 
-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).+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).
  
 <code lua> <code lua>
Line 450: Line 452:
  
 ===== Namespaces & Procedures ===== ===== 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.
  
-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 namespacespaths and procedures they provide.+**Path only contains the first contexte.g. network for network.interface.wan**
  
-==== netifd ====+^ path ^ Description ^ Package ^ 
 +| [[docs:guide-developer:ubus:dhcp]] | dhcp server | odhcpd | 
 +| [[docs:guide-developer:ubus:file]] | file | rpcd | 
 +| [[docs:guide-developer:ubus:hostapd]] | acesspoints | wpad/hostapd | 
 +| [[docs:guide-developer:ubus:iwinfo]] | wireless informations | rpcd iwinfo | 
 +| [[docs:guide-developer:ubus:log]] | logging | procd | 
 +| [[docs:guide-developer:ubus:mdns]] | mdns avahi replacement | mdnsd | 
 +| [[docs:guide-developer:ubus:network]] | network | netifd 
 +| [[docs:guide-developer:ubus:service]] | init/service | procd | 
 +| [[docs:guide-developer:ubus:session]] | Session management | rpcd | 
 +| [[docs:guide-developer:ubus:system]] | system misc | procd | 
 +| [[docs:guide-developer:ubus:uci]] | Unified Configuration Interface | rpcd |
  
-[[https://git.openwrt.org/?p=project/netifd.git;a=blob;f=DESIGN;hb=HEAD|DESIGN document at repo of netifd]]+==== procd ==== 
 +Project [[docs:techref:procd|procd: Procd system init and daemon management]]
  
-^ Path ^ Procedure ^ Signature ^ Description ^ +{{page>docs:guide-developer:ubus:system&nofooter}} 
-| ''network'' | ''restart'' | ''}'' | Restart the network, reconfigures all interfaces | +{{page>docs:guide-developer:ubus:service&nofooter}} 
-| ''network'' | ''reload'' | ''}'' | Reload the network, reconfigure as needed | +{{page>docs:guide-developer:ubus:log&nofooter}}
-| ''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 ====+==== netifd ==== 
 +Project [[docs:techref:netifd|netifd: Network Interface Daemon]]
  
-Project [[docs:techref:rpcd|rpcdOpenWrt ubus RPC daemon for backend server]] is a 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. rpcd and the desired plugins must be available or installed via opkg. After installing remember to enable and start the service via ''/etc/init.d/rpcd enable'' and ''/etc/init.d/rpcd start'' .+{{page>docs:guide-developer:ubus:network&nofooter}}
  
-With plugin ''rpcd-mod-file'' enabled:+==== rpcd ==== 
 +Project [[docs:techref:rpcd|rpcd: OpenWrt ubus RPC daemon for backend server]] is a 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. 
 +rpcd and the desired plugins must be available or installed via opkg. 
 +After installing remember to enable and start the service via ''service rpcd enable'' and ''service rpcd start'' .
  
-^ Path ^ Procedure ^ Signature ^ Description ^ +{{page>docs:guide-developer:ubus:file&nofooter}} 
-| ''file'' | ''read'' | ''"path": "//String//", "base64": //Boolean// }'' | Read a file contents. The file path is encoded in Base64 if the ''//base64//'' param set to "true"+{{page>docs:guide-developer:ubus:iwinfo&nofooter}}
-| ''file'' | ''write'' | ''"path""//String//", "data""//String//", +
-  "append"//Boolean//, "mode": //Integer//, "base64": //Boolean// }'' | Write a ''//data//'' to a file by ''//path//''. The file path is encoded in Base64 if the ''//base64//'' param set to "true". If the ''//append//'' param is "true" then file is not overwritten but the new content is added to the end of the file. The ''//mode//'' param if specified represent file permission mode. | +
-| ''file'' | ''list'' | ''{ "path": "//String//" }'' | ? | +
-| ''file'' | ''stat'' | ''{ "path": "//String//" }'' | ? | +
-| ''file'' | ''md5'' | ''"path": "//String//" }'' | ? | +
-| ''file'' | ''exec'' | ''"command""//String//", "params": "//Array//", "env": "//Table//" }'' | ? | +
- +
-With plugin ''rpcd-mod-iwinfo'' enabled: +
- +
-^ Path ^ Procedure ^ Signature ^ Description ^ +
-| ''iwinfo'' | ''devices'' | ''{ }'' | ? | +
-| ''iwinfo'' | ''info'' | ''{ "device""//device//" }'' | ? | +
-| ''iwinfo'' | ''scan'' | ''{ "device": "//device//" }'' | ? | +
-| ''iwinfo'' | ''assoclist'' | ''{ "device": "//device//", "mac": "//mac//" }'' | ? | +
-| ''iwinfo'' | ''freqlist'' | ''{ "device": "//device//" }'' | ? | +
-| ''iwinfo'' | ''txpowerlist'' | ''{ "device": "//device//" }'' | ? | +
-| ''iwinfo'' | ''countrylist'' | ''{ "device": "//device//" }'' | ? | +
-| ''iwinfo'' | ''phyname'' | ''{ "section": "//device//" }'' | ? |+
  
 Always included in ''rpcd'': Always included in ''rpcd'':
  
-(When using ubus over HTTP, setting ''ubus_rpc_session'' isn't allowed, it's automatically set to the calling session)+{{page>docs:guide-developer:ubus:session&nofooter}} 
 +{{page>docs:guide-developer:ubus:uci&nofooter}}
  
-^ Path ^ Procedure ^ Signature ^ Description ^ +===== What'the difference between ubus vs dbus? ===== 
-''session'' | ''create'' | ''{ "timeout": //timeout// }'' | Create a new session and return its ID, set the session timeout to ''//timeout//'' | +[[wp>D-Bus|D-Bus]] is bloatedits C API is very annoying to use and requires writing large amounts of boilerplate code
-| ''session'' | ''list'' | ''{ "ubus_rpc_session": "//sid//" }'' | Dump session specified by ''//sid//'', if no ID is given, dump all sessions | +In fact, the pure C API is so annoying that its own API documentation states: "If you use this low-level API directlyyou're signing up for some pain."
-| ''session'' | ''grant'' | ''{ "ubus_rpc_session": "//sid//", "scope": "//scope//", +
-  "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'' | ''{ "ubus_rpc_session": "//sid//", "scope": "//scope//", +
-  "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 unsetrevoke all access | +
-| ''session'' | ''access'' | ''{ "ubus_rpc_session": "//sid//", "scope": "//scope//", +
-  "object": "//path//", "function": "//function//" }'' | Query whether access to the specified ''//function//'' in the namespace ''//path//'' is allowed | +
-| ''session'' | ''set'' | ''{ "ubus_rpc_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'' | ''{ "ubus_rpc_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'' | ''{ "ubus_rpc_session": "//sid//", +
-  "keys": [ "//key//", ... ] }'' | Within the session identified by ''//sid//'' unset all keys listed in the ''//keys//'' arrayIf the key list is unset, clear all keys | +
-| ''session'' | ''destroy'' | ''"ubus_rpc_session": "//sid//" }'' | Terminate the session identified by the given ID ''//sid//''+
-| ''session'' | ''login'' | ''{ "username": "//username//", +
-  "password": "//password//", +
-  "timeout": //timeout// }'' | Authenticate with rpcd and create a new session with access rights as specified in the ACLs |+
  
-^ Path ^ Procedure ^ Signature ^ Description ^ +''ubus'' is tiny and has the advantage of being easy to use from regular C codeas well as automatically making all exported API functionality also available to shell scripts with no extra effort.
-''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: +===== Examples ===== 
-  - ''{ "packages": [ "package1", ... ] }'' +==== FHEM ==== 
-  - ''{ "//package//": { +=== User mapping === 
-  "sname1":+In this example we map username ''root'' to ''fhem_acl'' for [[docs:guide-user:services:automation:fhem|FHEM]] server
-    ".type":   "type1", +Edit ''/etc/config/rpcd'':
-    "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//'' setadd 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: +<code bash
-  - ''{ "section": "sectionname" }'' +/etc/config/rpcd 
-</WRAP| +config login 
-| ''uci'' | ''delete'' | ''{ "package": "//package//", + option username 'root' 
-  "section": "//sname//", + option password '$p$root
-  "type":    "//type//", + list read '*
-  "option":  "//oname//" }'' | <WRAP>Delete the given value(s) or section(s), the option and type arguments are optional. + list write '*'
-  - 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: +config login 
-  - If no section of type ''//type//'' was found: ''UBUS_STATUS_NOT_FOUND'' else: ''UBUS_STATUS_OK'' + option username 'fhem
-  - 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'' +        # '$p$<username> => reference to user passowrd on /etc/shadow 
-  - If the option named ''//oname//'' within named section ''//sname//'' was not found: ''UBUS_STATUS_NOT_FOUND'' else: ''UBUS_STATUS_OK'' +        # '$1$<hash>=> crypt() hash, using SHA1. generate via 'uhttpd -m fhem
-</WRAP|+        # 
 +        # passwordfhem 
 +        # 
 + option password '$1$$xEODf2fNSQ0ArfkJu4L2i1' 
 +
 +        # map username to rpcd acl name "fhem_acl" 
 +        # 
 +        list read 'fhem_acl' 
 + list write '' 
 +</code>
  
-===== Example code snippets ===== +Then reload rpcd config:
- +
-==== Check if Link is up using devstatus and Json ====+
  
 <code bash> <code bash>
-#!/bin/sh+uci commit rpcd 
 +</code>
  
-/usr/share/libubox/jshn.sh+=== User ACL === 
 +All files under ''/usr/share/rpcd/acl.d/'' will be merge by ''rpcd'' to one config file!!
  
-WANDEV="$(uci get network.wan.ifname)"+<code bash> 
 +ubus call hostapd.wlan0 get_clients 
 +ubus call hostapd.wlan1 get_clients 
 +</code>
  
-json_load "$(devstatus $WANDEV)"+Example permissions to run ubus calls remote by fhem over http/json.
  
-json_get_var var1 speed +<code javascript /usr/share/rpcd/acl.d/fhem.json> 
-json_get_var var2 link+
 +        "fhem_acl":
 +                "description": "FHEM PRESENCE Module User.. https://github.com/janLo/OpenWRT-Wifi-Clients-POC", 
 +                "read":
 +                        "ubus":
 +                                "hostapd.wlan0": [ "get_clients" ], 
 +                                "hostapd.wlan1": [ "get_clients"
 +                        } 
 +                } 
 +        } 
 +
 +</code>
  
-echo "Speed: $var1" +FHEM Server - show all connected wireless clients: 
-echo "Link: $var2"+ 
 +<code bash> 
 +python wifi_clients.py 
 +Usagewifi_clients.py.orig <OpenWrt_Host> <User> <Pass> <WiFi_1> [<WiFi2> ...] 
 +python wifi_clients.py 192.168.1.71 fhem fhem wlan0 wlan1 
 +c8:f6:50:e1:a9:10, 2c:f0:a2:e2:c0:15, 8c:a9:82:f1:9c:2a, 7c:c3:a1:b8:d2:1e, 64:9a:be:6a:6c:13, 00:1d:63:a3:8f:4a
 </code> </code>
  
 +==== Getting firmware version ====
 +Get firmware version with [[https://github.com/openwrt/luci/wiki/JsonRpcHowTo|JSON RPC]].
  
-===== What'the difference between ubus vs dbus? =====+<code bash> 
 +luci_rpc() { 
 +local LIB="${1}" 
 +local DATA="${2}" 
 +local URL="https://${HOST}/
 +cgi-bin/luci/rpc/${LIB}?auth=${TOKEN}" 
 +curl --k -d "${DATA}" "${URL}"
 +| jsonfilter -e "$['result']" 
 +
 +HOST="localhost" 
 +USER="root" 
 +PASS="" 
 +DATA="{ \"id\": 1, \"method\": \"login\", 
 +\"params\": [ \"${USER}\", \"${PASS}\" ] }" 
 +TOKEN="$(luci_rpc auth "${DATA}")" 
 +DATA="{ \"id\": 2, \"method\": \"exec\", 
 +\"params\": [ \"ubus call system board\" ] }" 
 +VERSION="$(luci_rpc sys "${DATA}"
 +| jsonfilter -e "$['release']['version']")" 
 +echo "${VERSION}" 
 +</code>
  
-[[wp>D-Bus|D-Bus]] is bloated, its C API is very annoying to use and requires writing large amounts of boilerplate codeIn fact, the pure C API is so annoying that its own API documentation states: "If you use this low-level API directly, you're signing up for some pain."+Get firmware version with ubus RPC.
  
-''ubus'' is tiny and has the advantage of being easy to use from regular C codeas well as automatically making all exported API functionality also available to shell scripts with no extra effort.+<code bash> 
 +ubus_rpc() { 
 +local DATA="${1}" 
 +local URL="https://${HOST}/ubus" 
 +curl -s -k -d "${DATA}" "${URL}"
 +| jsonfilter -e "$['result']" 
 +
 +HOST="localhost" 
 +USER="root" 
 +PASS="" 
 +DATA="'jsonrpc': '2.0', 'id': 1, 'method': 'call', 
 +'params': [ '00000000000000000000000000000000', 'session', 
 +'login', { 'username': '${USER}', 'password': '${PASS}' } ] }" 
 +SESSION="$(ubus_rpc "${DATA}"
 +| jsonfilter -e "$[*]['ubus_rpc_session']")" 
 +DATA="{ 'jsonrpc': '2.0', 'id': 2, 'method': 'call', 
 +'params': [ '${SESSION}', 'system', 'board', {} ] }" 
 +VERSION="$(ubus_rpc "${DATA}"
 +| jsonfilter -e "$[*]['release']['version']")" 
 +echo "${VERSION}" 
 +</code>
  
  • Last modified: 2018/10/29 20:02
  • by stokito