Ubus is an RPC tool of OpenWRT, a micro system bus architecture. The aim of Ubus is to provide system-level Inter-process Communication(IPC) for various background processes and applications. Ubus is basically consistent with D-Bus in the design conception, providing system-level bus function. In order to be compatible with constrained environment says embedded systems, ubus reduced memory footprint.
Inter-process Communication refers to mechanism allowing processes to manage shared data. There are different approaches to IPC tailored to different software requirements, says performance, modularity, network bandwidth and latency.
Examples of IPC method:
Most of IPC methods use client-server model to share data between processes.
While there are m servers, and each server has
Ubus uses the broker pattern as its architecture. There are three components to perform IPC through ubus.
In this architecture, the number of connections is the number of processes, including both service "clients" and "servers", which is much less than IPC implementations under client/server model.
There are different roles in Ubus IPC processes.
Ubus uses JSON as data format to call object, method and respond to request.
https://www.json.org/json-en.html
Here is an status example in JSON format. We call the method status
of object network.interface.wan3
with null message '{}'
.
root@ugwcpe:/# ubus call network.interface.wan3 status
{
"up": true,
"pending": false,
"available": true,
"autostart": true,
"dynamic": false,
"uptime": 5,
"l3_device": "eth1_wan3",
"proto": "dhcp",
"device": "eth1_wan3",
"updated": [
"addresses",
"routes",
"data"
],
"metric": 0,
"dns_metric": 0,
"delegation": true,
"ipv4-address": [
{
"address": "192.168.121.101",
"mask": 24
}
],
"ipv6-address": [
],
"ipv6-prefix": [
],
"ipv6-prefix-assignment": [
],
"route": [
{
"target": "0.0.0.0",
"mask": 0,
"nexthop": "192.168.121.1",
"source": "192.168.121.101/32"
}
],
"dns-server": [
"192.168.121.1",
"8.8.8.8"
],
"dns-search": [
"gemteks.com"
],
"inactive": {
"ipv4-address": [
],
"ipv6-address": [
],
"route": [
],
"dns-server": [
],
"dns-search": [
]
},
"data": {
"leasetime": 86400
}
}
There are three delivery schemes to implement IPC in Ubus.
One-to-one
One-to-many(group by object)
One-to-many(group by event)
As mentioned, There are three delivery schemes to implement IPC in Ubus
Data flow of invoke.
Syntax:
–-> :Client to Ubusd
– ->:Ubusd to Client
Directions:
-> ubusd to object
<- object to ubusd
Dir. Obj.ID Ubus.ID Message Type Message
-> 2d0a3716 #2d0a3716 hello: {}
<- 2d0a3716 #00000000 add_object: {"objpath":"gserver.host","signature":{"gserver_post":{"id":5,"data":5,"msg":3},"gserver_stop":{}}}
-> 47dfa5f0 #00000000 invoke: {"objid":-921749017,"method":"ubus.object.add","data":{"id":-862368938,"path":"gserver.host"}}
-> 2d0a3716 #00000000 data: {"objid":-862368938,"objtype":619811862}
-> 2d0a3716 #00000000 status: {"status":0}
<- 47dfa5f0 #00000000 status: {"status":0,"objid":-921749017}
-> 41a666fd #41a666fd hello: {}
<- 41a666fd #00000000 lookup: {"objpath":"gserver.host"}
-> 41a666fd #00000000 data: {"objpath":"gserver.host","objid":-862368938,"objtype":619811862,"signature":{"gserver_post":{"id":5,"data":5,"msg":3},"gserver_stop":{}}}
-> 41a666fd #00000000 status: {"status":0}
<- 41a666fd #cc994b56 invoke: {"objid":-862368938,"method":"gserver_post","data":{"id":123456,"data":987654321,"msg":"Hi!"}}
-> 2d0a3716 #41a666fd invoke: {"objid":-862368938,"method":"gserver_post","data":{"id":123456,"data":987654321,"msg":"Hi!"},"user":"root","group":"root"}
<- 2d0a3716 #41a666fd data: {"objid":-862368938,"data":{"Gserver reply":"Request is being proceeded!"}}
-> 41a666fd #cc994b56 data: {"objid":-862368938,"data":{"Gserver reply":"Request is being proceeded!"}}
<- 2d0a3716 #41a666fd status: {"status":0,"objid":-862368938}
-> 41a666fd #cc994b56 status: {"status":0,"objid":-862368938}
-> b5d12db5 #b5d12db5 hello: {}
<- b5d12db5 #00000000 lookup: {"objpath":"gserver.host"}
-> b5d12db5 #00000000 data: {"objpath":"gserver.host","objid":-862368938,"objtype":619811862,"signature":{"gserver_post":{"id":5,"data":5,"msg":3},"gserver_stop":{}}}
-> b5d12db5 #00000000 status: {"status":0}
<- b5d12db5 #cc994b56 invoke: {"objid":-862368938,"method":"gserver_stop","data":{}}
-> 2d0a3716 #b5d12db5 invoke: {"objid":-862368938,"method":"gserver_stop","data":{},"user":"root","group":"root"}
<- 2d0a3716 #00000000 remove_object: {"objid":-862368938}
-> 2d0a3716 #00000000 data: {"objid":-862368938,"objtype":619811862}
-> 2d0a3716 #00000000 status: {"status":0}
<- 2d0a3716 #b5d12db5 status: {"status":0,"objid":-862368938}
-> 2d0a3716 #2d0a3716 hello: {}
<- 2d0a3716 #00000000 add_object: {"objpath":"gserver.host","signature":{"gserver_post":{"id":5,"data":5,"msg":3},"gserver_stop":{}}}
-> 47dfa5f0 #00000000 invoke: {"objid":-921749017,"method":"ubus.object.add","data":{"id":-862368938,"path":"gserver.host"}}
-> 2d0a3716 #00000000 data: {"objid":-862368938,"objtype":619811862}
-> 2d0a3716 #00000000 status: {"status":0}
<- 47dfa5f0 #00000000 status: {"status":0,"objid":-921749017}
-> 41a666fd #41a666fd hello: {}
<- 41a666fd #00000000 lookup: {"objpath":"gserver.host"}
-> 41a666fd #00000000 data: {"objpath":"gserver.host","objid":-862368938,"objtype":619811862,"signature":{"gserver_post":{"id":5,"data":5,"msg":3},"gserver_stop":{}}}
-> 41a666fd #00000000 status: {"status":0}
<- 41a666fd #cc994b56 invoke: {"objid":-862368938,"method":"gserver_post","data":{"id":123456,"data":987654321,"msg":"Hi!"}}
-> 2d0a3716 #41a666fd invoke: {"objid":-862368938,"method":"gserver_post","data":{"id":123456,"data":987654321,"msg":"Hi!"},"user":"root","group":"root"}
<- 2d0a3716 #41a666fd data: {"objid":-862368938,"data":{"Gserver reply":"Request is being proceeded!"}}
-> 41a666fd #cc994b56 data: {"objid":-862368938,"data":{"Gserver reply":"Request is being proceeded!"}}
<- 2d0a3716 #41a666fd status: {"status":0,"objid":-862368938}
-> 41a666fd #cc994b56 status: {"status":0,"objid":-862368938}
<- 2d0a3716 #00000000 remove_object: {"objid":-862368938}
-> 2d0a3716 #00000000 data: {"objid":-862368938,"objtype":619811862}
-> 2d0a3716 #00000000 status: {"status":0}
<- 2d0a3716 #b5d12db5 status: {"status":0,"objid":-862368938}
Data flow of Subscribe/notify.
Dir. Obj.ID Ubus.ID Message Type Message
-> 74f42091 #74f42091 hello: {}
<- 74f42091 #00000000 add_object: {"objpath":"gserver.host","signature":{"gserver_post":{"id":5,"data":5,"msg":3},"gserver_stop":{}}}
-> ea7bde21 #00000000 invoke: {"objid":-804320694,"method":"ubus.object.add","data":{"id":629913760,"path":"gserver.host"}}
-> 74f42091 #00000000 data: {"objid":629913760,"objtype":-429408539}
-> 74f42091 #00000000 status: {"status":0}
<- ea7bde21 #00000000 status: {"status":0,"objid":-804320694}
-> 478023e4 #478023e4 hello: {}
<- 478023e4 #00000000 add_object: {}
-> 478023e4 #00000000 data: {"objid":-1244318547}
-> 478023e4 #00000000 status: {"status":0}
<- 478023e4 #00000000 lookup: {"objpath":"gserver.host"}
-> 478023e4 #00000000 data: {"objpath":"gserver.host","objid":629913760,"objtype":-429408539,"signature":{"gserver_post":{"id":5,"data":5,"msg":3},"gserver_stop":{}}}
-> 478023e4 #00000000 status: {"status":0}
<- 478023e4 #00000000 subscribe: {"objid":-1244318547}
-> 74f42091 #00000000 notify: {"objid":629913760,"active":true}
-> 478023e4 #00000000 status: {"status":0}
~~~~~~Notification Trigger~~~~~~
<- 74f42091 #258bb8a0 notify: {"objid":629913760,"method":"gserver_post","data":{"id":123,"data":321,"msg":"abcdef"},"no_reply":true}
-> 478023e4 #74f42091 invoke: {"objid":-1244318547,"method":"gserver_post","data":{"id":123,"data":321,"msg":"abcdef"},"no_reply":true,"user":"root","group":"root"}
<- 478023e4 #00000000 unsubscribe: {"objid":-1244318547}
-> 74f42091 #00000000 notify: {"objid":629913760,"active":false}
<- 478023e4 #00000000 subscribe: {"objid":-1244318547}
-> 74f42091 #00000000 notify: {"objid":629913760,"active":true}
-> 478023e4 #00000000 status: {"status":0}
<- 74f42091 #258bb8a0 notify: {"objid":629913760,"method":"gserver_post","data":{"id":123,"data":321,"msg":"abcdef"},"no_reply":true}
-> 478023e4 #74f42091 invoke: {"objid":-1244318547,"method":"gserver_post","data":{"id":123,"data":321,"msg":"abcdef"},"no_reply":true,"user":"root","group":"root"}
<- 478023e4 #00000000 unsubscribe: {"objid":-1244318547}
-> 74f42091 #00000000 notify: {"objid":629913760,"active":false}
Data flow of Event Boardcast.
Dir. Obj.ID Ubus.ID Message Type Message
-> 55484d34 #55484d34 hello: {}
<- 55484d34 #00000000 add_object: {}
-> 55484d34 #00000000 data: {"objid":-964689289}
-> 55484d34 #00000000 status: {"status":0}
<- 55484d34 #00000001 invoke: {"objid":1,"method":"register","data":{"object":-964689289,"pattern":"g_server"}}
-> 55484d34 #00000001 status: {"status":0}
-> b79aea68 #b79aea68 hello: {}
<- b79aea68 #00000001 invoke: {"objid":1,"method":"send","data":{"id":"g_server","data":{"str":"gemtek"}}}
-> 55484d34 #00000000 invoke: {"objid":-964689289,"method":"g_server","data":{"str":"gemtek"}}
-> b79aea68 #00000001 status: {"status":0}
<- 55484d34 #00000000 status: {"status":0,"objid":-964689289}
<- 55484d34 #00000001 invoke: {"objid":1,"method":"register","data":{"object":-964689289,"pattern":"g_server"}}
-> 55484d34 #00000001 status: {"status":0}
<- b79aea68 #00000001 invoke: {"objid":1,"method":"send","data":{"id":"g_server","data":{"str":"gemtek"}}}
-> 55484d34 #00000000 invoke: {"objid":-964689289,"method":"g_server","data":{"str":"gemtek"}}
OpenWrt provides four tools to access ubus
The command "ubus" allows user to interact with the ubusd server. Services registered to ubusd server can be accessed by this command tool.
root@ugwcpe:/# ubus
Usage: ubus [<options>] <command> [arguments...]
Options:
-s <socket>: Set the unix domain socket to connect to
-t <timeout>: Set the timeout (in seconds) for a command to complete
-S: Use simplified output (for scripts)
-v: More verbose output
-m <type>: (for monitor): include a specific message type
(can be used more than once)
-M <r|t> (for monitor): only capture received or transmitted traffic
Commands:
- list [<path>] List objects
- call <path> <method> [<message>] Call an object method
- listen [<path>...] Listen for events
- send <type> [<message>] Send an event
- wait_for <object> [<object>...] Wait for multiple objects to appear on ubus
- monitor Monitor ubus traffic
To find out services currently running on the bus, just simply use the ubus list
command. A complete list of all object registed with namespace will be shown.
root@ugwcpe:/# ubus list
block
csd
devmd
dhcp
diagnosticsd
dwpald
firewalld
log
network
network.device
network.interface
network.interface.iface_eth0_1
network.interface.iface_eth0_2
network.interface.iface_eth0_3
network.interface.iface_eth0_4
network.interface.iface_eth1
network.interface.lan
network.interface.loopback
network.interface.wan3
network.wireless
polld
servd
service
system
uci
wsd
To find out methods and the argument signatures provided by specific service/object, we can type the service namespace, system
, after the command list
and use the option -v
.
root@ugwcpe:/# ubus -v list system
'system' @d1165900
"board":{}
"info":{}
"reboot":{}
"upgrade":{}
"done":{}
"watchdog":{"frequency":"Integer","timeout":"Integer","magicclose":"Boolean","stop":"Boolean"}
"signal":{"pid":"Integer","signum":"Integer"}
"sysupgrade":{"path":"String","prefix":"String","command":"String"}
"factoryreset":{"value":"String"}
"hostname":{"value":"String"}
To call method of specific object, we can use the call
command. Here is an example of call
command. As there is no required data in method status
, we just need to type info
after object system
.
The method being called takes actions, says return message, or turn on/off other services, depends on its callback function. In this case, the method info
of system
returns a message with system information to the callee.
root@ugwcpe:/# ubus call system info
{
"localtime": 1610460661,
"uptime": 82303,
"load": [
196608,
196608,
196608
],
"memory": {
"total": 1021562880,
"free": 884396032,
"shared": 1159168,
"buffered": 9117696
},
"swap": {
"total": 0,
"free": 0
}
}
To send message to an event with specific event pattern, we can use the send
command. The following example is sending data {"str":"gemtek"}
to event event_a
.
root@ugwcpe:/# ubus send event_a '{"str":"gemtek"}'
Command listen
is for listening to events. The following example is listening to event event_a
. The console prints data of related event update.
root@ugwcpe:/# ubus listen event_a
{ "event_a": {"str":"gemtek"} }
Command wait_for
returns when object waited is registered.
root@(none):/# ubus wait_for gserver.host
root@(none):/#
Command monitor
is for monitoring ubus traffic.
root@ugwcpe:/# ubus monitor
-> d17cae6a #00000003 status: {"status":0}
-> 23f12bf1 #23f12bf1 hello: {}
<- 23f12bf1 #00000001 invoke: {"objid":1,"method":"send","data":{"id":"event_a","data":{"str":"gemtek"}}}
-> 716e9a5f #00000000 invoke: {"objid":-2003009711,"method":"event_a","data":{"str":"gemtek"}}
-> 23f12bf1 #00000001 status: {"status":0}
<- 716e9a5f #00000000 status: {"status":0,"objid":-2003009711}
Function | Description |
---|---|
struct ubus_context *ubus_connect(const char *path) |
Connect the specified path, create and return the ubus context represented by the path. |
inline void ubus_add_uloop(struct ubus_context *ctx) |
Activate UBUS; this tells U-Loop to check for ubus events (listens to ubus). It is blocking function, meaning that once we call uloop_run, it is waiting for ubus to get something. |
int ubus_lookup_id(struct ubus_context *ctx, const char *path, uint32_t *id) |
This routine gets id from path. If it is successful, it returns UBUS_STATUS_OK, otherwise UBUS_STATUS_* error-code. |
void ubus_free(struct ubus_context *ctx) |
It is identical to ubus_shutdown() + freeing the context. |
int ubus_add_object(struct ubus_context *ctx, struct ubus_object *obj) |
Add a UBUS object into the list of objects to be queried. We must call this routine before doing ubus_lookup_id. |
int ubus_remove_object(struct ubus_context *ctx, struct ubus_object *obj) |
The opposite of ubus_add_object; called when we need to cleanup the pending request. |
Function | Description |
---|---|
int ubus_invoke(struct ubus_context *ctx, uint32_t obj, const char *method, struct blob_attr *msg, ubus_data_handler_t cb, void *priv, int timeout) |
Invoke RPC indicated in passed parameter method. cb is the callback object for server object responding with data. |
int ubus_send_reply(struct ubus_context *ctx, struct ubus_request *req, struct blob_attr *msg) |
Send reply to the incoming object call, says invoke . |
Function | Description |
---|---|
int ubus_register_subscriber(struct ubus_context *ctx, struct ubus_subscriber *obj) |
Register a callback object for receiving notifications from publisher. |
int ubus_subscribe(struct ubus_context *ctx, struct ubus_subscriber *obj, uint32_t id) |
Subscribe to an object with a registed callback object. |
int ubus_unsubscribe(struct ubus_context *ctx, struct ubus_subscriber *obj, uint32_t id) |
Unsubscribe from the object. |
Function | Description |
---|---|
int ubus_notify(struct ubus_context *ctx, struct ubus_object *obj, const char *type, struct blob_attr *msg, int timeout) |
Send notification to ubus, ubus invokes subscribers with data carried by the notification. |
int ubus_register_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *pattern) |
Register event handler ev to the event on ubus. |
int ubus_unregister_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev) |
Deregister event handler ev on ubus. |
int ubus_send_event(struct ubus_context *ctx, const char *id, struct blob_attr *data) |
Send data as an event with event pattern id to ubus. Ubus call related event handlers with the data. |
#include <stdio.h>
#include <stdint.h>
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>
/* Ubus method_call_functions */
static int gserver_post(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
static int gserver_stop(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
/* Enum for GSERVER policy order */
enum {
GSERVER_ID,
GSERVER_DATA,
GSERVER_MSG,
__GSERVER_MAX,
};
/* Ubus method policy */
static const struct blobmsg_policy gserver_policy[] =
{
[GSERVER_ID] = { .name="id", .type=BLOBMSG_TYPE_INT32},
[GSERVER_DATA] = { .name="data", .type=BLOBMSG_TYPE_INT32 },
[GSERVER_MSG] = { .name="msg", .type=BLOBMSG_TYPE_STRING },
};
static const struct blobmsg_policy gserver_stop_policy[] =
{
};
/* Ubus object methods */
static const struct ubus_method gserver_methods[] =
{
/* UBUS_METHOD(method_name, method_call_function, method_policy) */
UBUS_METHOD("gserver_post", gserver_post, gserver_policy),
UBUS_METHOD("gserver_stop", gserver_stop, gserver_stop_policy)
};
/* Ubus object type */
static struct ubus_object_type gserver_obj_type =
UBUS_OBJECT_TYPE("gserver_uobj", gserver_methods);
/* Ubus object */
static struct ubus_object gserver_object=
{
.name = "gserver.host", //objpath
.type = &gserver_obj_type,
.methods = gserver_methods,
.n_methods = ARRAY_SIZE(gserver_methods),
};
/* Ubus method_call_functions */
static int gserver_post(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg) {
/* do something */
return 0;
}
static int gserver_stop(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg) {
/* do something */
return 0;
}
void server(void) {
/* 1. create an epoll instatnce descriptor poll_fd */
uloop_init();
/* 2. connect to ubusd and get ctx(context) */
struct ubus_context *ctx = ubus_connect(NULL); // Use default UNIX sock path
/* 3. registger epoll events to uloop, start socket listening */
ubus_add_uloop(ctx);
/* 4. register a ubus_object to ubusd */
ubus_add_object(ctx, gserver_object);
/* 5. uloop routine: events monitoring and callback provoking */
uloop_run();
/* 6. terminate uloop */
uloop_done();
return;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>
static struct ubus_context *ctx;
static struct ubus_request_data req_data;
static struct blob_buf b_buf; //message carried in invoke
static uint32_t obj_id;
/* Ubus object */
static struct ubus_object client_object = {
};
/* callback */
static int callback (struct ubus_request *req,
int type, struct blob_attr *msg) {
/* do something */
return 0;
}
void client(void) {
/* 1. create an epoll instatnce descriptor poll_fd */
uloop_init();
/* 2. connect to ubusd and get ctx(context) */
struct ubus_context *ctx = ubus_connect(NULL); // Use default UNIX sock path
/* 3. registger epoll events to uloop, start socket listening */
ubus_add_uloop(ctx);
/* 4. register a ubus_object to ubusd */
ubus_add_object(ctx, &client_object);
/* 5. lookup object_id of service object */
ubus_lookup_id(ctx, "gserver.host", &obj_id);
/* 6. send request by ubus_invoke to service object */
ubus_invoke(ctx, obj_id, "gserver_post", b_buf, callback, 0, 3000);
/* 7. uloop routine: events monitoring and callback provoking */
uloop_run();
/* 8. terminate uloop */
uloop_done();
return;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>
static struct ubus_context *ctx;
static uint32_t obj_id;
static int notif_handler(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg) {
/* do something */
return 0;
}
void subscriber_object(void) {
/* 1. create an epoll instatnce descriptor poll_fd */
uloop_init();
/* 2. connect to ubusd and get ctx(context) */
struct ubus_context *ctx = ubus_connect(NULL); // Use default UNIX sock path
/* 3. registger epoll events to uloop, start socket listening */
ubus_add_uloop(ctx);
/* 4. register a ubus_object to ubusd */
ubus_add_object(ctx, &subscriber_object);
/* 5. lookup object_id of service object */
ubus_lookup_id(ctx, "gserver.host", &obj_id);
/* 6. subscribe service object */
ubus_subscribe(ctx, ¬if_handler, obj_id);
/* 7. uloop routine: events monitoring and callback provoking */
uloop_run();
/* 8. terminate uloop */
uloop_done();
return;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>
static struct ubus_context *ctx;
static struct ubus_request_data req_data;
static struct blob_buf b_buf;
static uint32_t obj_id;
static void receive_event(struct ubus_context *ctx, struct ubus_event_handler *ev,
const char *method, struct blob_attr *msg) {
printf("Event received\n");
/* do something */
return;
}
int event_example () {
/* 1. create an epoll instatnce descriptor poll_fd */
uloop_init();
/* 2. connect to ubusd and get ctx(context) */
struct ubus_context *ctx = ubus_connect(NULL); // Use default UNIX sock path
/* 3. registger epoll events to uloop, start socket listening */
ubus_add_uloop(ctx);
/* 4. register event on ubus */
const char *event = "gevent";
struct ubus_event_handler ev = {
.cb = receive_event,
};
ubus_register_event_handler(ctx, &ev, event);
/* 5. uloop routine: events monitoring */
uloop_run();
/* 6. terminate uloop */
uloop_done();
return;
}
https://openwrt.org/docs/techref/ubus
https://openwrt.org/docs/techref/libubox
https://www.programmersought.com/article/32835484946/
https://www.programmersought.com/article/26351479004/
https://www.programmersought.com/article/48733667380/