UBUS: Inter-Process Messaging in OpenWRT
UBUS – IPC Framework in OpenWRT
UBUS (Micro Bus) is OpenWrt’s lightweight Inter-Process Communication (IPC) framework, designed for resource-constrained embedded systems such as routers. It serves as a system-level message/object bus, enabling seamless communication between daemons, scripts, and applications. Conceptually similar to D-Bus, UBUS is optimized for minimal memory footprint and high efficiency, making it ideal for OpenWrt’s embedded environment.
UBUS is integral to several OpenWrt components:
- procd: Manages services and system state changes.
- netifd: Handles network configuration and management.
- rpcd: Facilitates remote procedure calls, enabling web-based interactions via LuCI.
- Other system components for inter-process coordination.
Key Features
- Inter-Process Communication (IPC): Enables processes to exchange messages or invoke methods.
- Publish-Subscribe Model: Supports asynchronous event notifications through subscriptions.
- Remote Procedure Calls (RPC): Allows processes to call functions in other processes and receive JSON responses.
- Lightweight and Efficient: Optimized for low resource usage, suitable for embedded systems.
- Extensible: Developers can register custom objects and methods to extend functionality.
- Access Control: Integrates with OpenWrt’s access control lists (ACLs) via rpcd for secure method call permissions.
UBUS Architecture
UBUS employs a broker pattern with the following components:
UBUS Daemon (ubusd):
- Central message broker managing communication between processes.
- Handles object registration, method calls, event broadcasts, and subscriptions.
- Forwards messages between server and client objects.
UBUS Server Object:
- Typically a daemon or interface (e.g., netifd registering a
network
object). - Registers with ubusd, exposing callable methods.
- Typically a daemon or interface (e.g., netifd registering a
UBUS Client Object:
- Calls methods on server objects or subscribes to events.
- Can look up registered objects and their methods.
flowchart TD %% Ubus model subgraph "Ubus model" direction TB Ubus["Ubusd"] P1["Process 1<br>Server Object"] P2["Process 2<br>Server Object"] P3["Process 3<br>Client Object"] P4["Process 4<br>Client Object"] P5["Process 5<br>Client Object"] P6["Process 6<br>Client Object"] P1 --> Ubus Ubus --> P1 P2 --> Ubus Ubus --> P2 P3 --> Ubus Ubus --> P3 P4 --> Ubus Ubus --> P4 P5 --> Ubus Ubus --> P5 P6 --> Ubus Ubus --> P6 end
UBUS Library (libubus):
- A C library providing APIs for interacting with ubusd.
- Supports connecting to the bus, registering objects, and handling messages.
UBUS CLI Tool:
- A command-line interface for debugging and scripting with ubusd.
UBUS Lua Module:
- Enables Lua scripts (e.g., in LuCI) to interact with ubus.
Data Format
UBUS uses JSON for structured data exchange. Example of calling status
on network.interface.wan3
:
{
"up": true,
"pending": false,
"available": true,
"autostart": true,
"dynamic": false,
"uptime": 5,
"l3_device": "eth1_wan3",
"proto": "dhcp",
"device": "eth1_wan3",
"ipv4-address": [
{
"address": "192.168.121.101",
"mask": 24
}
],
"dns-server": [
"192.168.121.1",
"8.8.8.8"
],
"data": {
"leasetime": 86400
}
}
Comparison with Client-Server Model
Unlike traditional client-server models with ( m ) servers and ( n_k ) clients requiring ( \sum_{k=1}^{m}n_k ) connections, UBUS uses a broker model where each process connects only to ubusd, reducing the number of connections to the number of processes.
flowchart LR %% Cluster 1: Client-server Model (1:1) subgraph "Client-server Model (1:1)" direction LR A[Process 1<br>Server] B[Process 2<br>Client] B -->|Request| A A -->|Response| B end %% Cluster 2: Client-server Model (m:n) subgraph "Client-server Model (m:n)" direction LR S1[Process 1] S2[Process 2] D[Process 3] E[Process 4] F[Process 5] G[Process 6] S1 --> D D --> S1 E --> S1 S1 --> E F --> S1 S1 --> F E --> S2 S2 --> E F --> S2 S2 --> F S2 --> G G --> S2 S1 --> S2 S2 --> S1 D --> E E --> D F --> D D --> F end
UBUS Roles and Concepts
- Object: A process registered with ubusd, acting as a server (providing methods) or client (calling methods).
- Method: A procedure exposed by a server object, callable with JSON arguments.
- Data: JSON-formatted information in requests or responses.
- Subscriber: A client subscribed to a server object, receiving notifications.
- Event: Identified by an event pattern (string), used for broadcasting data.
- Event Registrant: A process registered to receive events matching a specific pattern.
Data Flow Schemes
UBUS supports three communication patterns:
Invoke (One-to-One):
- Direct method calls to a specific object.
- Example:
ubus call network.interface.wan3 status
.sequenceDiagram participant C1 as UBUS CLIENT 1<br>PROCESS 1 participant D as UBUSD participant C2 as UBUS CLIENT 2<br>PROCESS 2 C1->>D: Invoke D->>C2: Invoke C2-->>D: Reply D-->>C1: Reply
Subscribe/Notify (One-to-Many):
- Notifications sent to multiple subscribers of an object.
- Example: A network daemon notifies subscribers of an interface status change.
flowchart LR C1["UBUS CLIENT 1<br>PROCESS 1"] U["UBUSD"] C2["UBUS CLIENT 2<br>PROCESS 2<br>(SUBSCRIBER)"] C3["UBUS CLIENT 3<br>PROCESS 3<br>(SUBSCRIBER)"] C4["UBUS CLIENT 4<br>PROCESS 4<br>(SUBSCRIBER)"] C1 -->|Notify| U U -->|Invoke| C2 U -->|Invoke| C3 U -->|Invoke| C4
Event Broadcast (One-to-Many):
- Data broadcast to all listeners of a specific event pattern.
- Example: Broadcasting an
interface.up
event.flowchart LR C0["UBUS CLIENT 0<br>PROCESS 0"] C1["UBUS CLIENT 1<br>PROCESS 1"] U["UBUSD"] C2["UBUS CLIENT 2<br>PROCESS 2<br>(Event_1 LISTENER)"] C3["UBUS CLIENT 3<br>PROCESS 3<br>(Event_1 LISTENER)"] C4["UBUS CLIENT 4<br>PROCESS 4<br>(Event_1 LISTENER)"] C0 -->|Send Event_1| U C1 -->|Send Event_1| U U -->|Invoke| C2 U -->|Invoke| C3 U -->|Invoke| C4
UBUS Tools
OpenWrt provides multiple tools for interacting with UBUS:
1. Command-Line UBUS Tool
The ubus
CLI tool is used for debugging and scripting.
Usage
ubus [<options>] <command> [arguments...]
Options
-s <socket>
: Specify the UNIX domain socket.-t <timeout>
: Set command timeout (seconds).-S
: Simplified output for scripts.-v
: Verbose output.-m <type>
: Filter message types for monitoring.-M <r|t>
: Monitor received (r
) or transmitted (t
) traffic.
Commands
list []:
- Lists registered objects or methods.
- Example:Output:
ubus list
block dhcp firewalld network network.interface.wan3 system uci
- Verbose example:Output:
ubus -v list system
'system' @d1165900 "board":{} "info":{} "reboot":{} "watchdog":{"frequency":"Integer","timeout":"Integer"}
call []:
- Calls a method on an object.
- Example:Output:
ubus call system info
{ "localtime": 1610460661, "uptime": 82303, "memory": { "total": 1021562880, "free": 884396032 } }
listen […]:
- Listens for events.
- Example:Output:
ubus listen event_a
{ "event_a": {"str":"gemtek"} }
send []:
- Sends an event.
- Example:
ubus send event_a '{"str":"gemtek"}'
wait_for []:
- Waits for objects to register.
- Example:
ubus wait_for gserver.host
monitor:
- Monitors UBUS traffic.
- Example:
ubus monitor
2. C Library (libubus)
The libubus
library provides APIs for programmatic interaction with ubusd.
Key Functions
Connection:
ubus_connect(const char *path)
: Connects to ubusd.ubus_add_uloop(struct ubus_context *ctx)
: Integrates with uloop.ubus_free(struct ubus_context *ctx)
: Frees the context.
Object Management:
ubus_add_object(struct ubus_context *ctx, struct ubus_object *obj)
: Registers an object.ubus_remove_object(struct ubus_context *ctx, struct ubus_object *obj)
: Deregisters an object.ubus_lookup_id(struct ubus_context *ctx, const char *path, uint32_t *id)
: Retrieves object ID.
Method Invocation:
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)
: Calls a method.ubus_send_reply(struct ubus_context *ctx, struct ubus_request *req, struct blob_attr *msg)
: Sends a response.
Subscription:
ubus_register_subscriber(struct ubus_context *ctx, struct ubus_subscriber *obj)
: Registers a subscriber.ubus_subscribe(struct ubus_context *ctx, struct ubus_subscriber *obj, uint32_t id)
: Subscribes to an object.ubus_unsubscribe(struct ubus_context *ctx, struct ubus_subscriber *obj, uint32_t id)
: Unsubscribes.
Events:
ubus_notify(struct ubus_context *ctx, struct ubus_object *obj, const char *type, struct blob_attr *msg, int timeout)
: Sends a notification.ubus_register_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *pattern)
: Registers an event handler.ubus_send_event(struct ubus_context *ctx, const char *id, struct blob_attr *data)
: Sends an event.
Example: Server Object
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>
enum {
GSERVER_ID,
GSERVER_DATA,
GSERVER_MSG,
__GSERVER_MAX
};
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[] = {};
static int gserver_post(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg) {
struct blob_buf b = {};
blob_buf_init(&b, 0);
blobmsg_add_string(&b, "Gserver reply", "Request is being proceeded!");
ubus_send_reply(ctx, req, b.head);
blob_buf_free(&b);
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) {
return 0;
}
static const struct ubus_method gserver_methods[] = {
UBUS_METHOD("gserver_post", gserver_post, gserver_policy),
UBUS_METHOD("gserver_stop", gserver_stop, gserver_stop_policy)
};
static struct ubus_object_type gserver_obj_type =
UBUS_OBJECT_TYPE("gserver_uobj", gserver_methods);
static struct ubus_object gserver_object = {
.name = "gserver.host",
.type = &gserver_obj_type,
.methods = gserver_methods,
.n_methods = ARRAY_SIZE(gserver_methods)
};
int main(void) {
uloop_init();
struct ubus_context *ctx = ubus_connect(NULL);
if (!ctx) return -1;
ubus_add_uloop(ctx);
ubus_add_object(ctx, &gserver_object);
uloop_run();
ubus_free(ctx);
uloop_done();
return 0;
}
Example: Client Object
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>
static struct ubus_context *ctx;
static uint32_t obj_id;
static void callback(struct ubus_request *req, int type, struct blob_attr *msg) {
char *str = blobmsg_format_json(msg, true);
printf("Response: %s\n", str);
free(str);
}
int main(void) {
uloop_init();
ctx = ubus_connect(NULL);
if (!ctx) return -1;
ubus_add_uloop(ctx);
if (ubus_lookup_id(ctx, "gserver.host", &obj_id)) {
ubus_free(ctx);
uloop_done();
return -1;
}
struct blob_buf b = {};
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "id", 123456);
blobmsg_add_u32(&b, "data", 987654321);
blobmsg_add_string(&b, "msg", "Hi!");
ubus_invoke(ctx, obj_id, "gserver_post", b.head, callback, NULL, 3000);
blob_buf_free(&b);
uloop_run();
ubus_free(ctx);
uloop_done();
return 0;
}
Example: Subscriber
#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) {
char *str = blobmsg_format_json(msg, true);
printf("Notification: %s\n", str);
free(str);
return 0;
}
int main(void) {
uloop_init();
ctx = ubus_connect(NULL);
if (!ctx) return -1;
ubus_add_uloop(ctx);
struct ubus_subscriber sub = { .cb = notif_handler };
ubus_register_subscriber(ctx, &sub);
if (ubus_lookup_id(ctx, "gserver.host", &obj_id)) {
ubus_free(ctx);
uloop_done();
return -1;
}
ubus_subscribe(ctx, &sub, obj_id);
uloop_run();
ubus_free(ctx);
uloop_done();
return 0;
}
Example: Event Listener
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>
static struct ubus_context *ctx;
static void receive_event(struct ubus_context *ctx, struct ubus_event_handler *ev,
const char *method, struct blob_attr *msg) {
char *str = blobmsg_format_json(msg, true);
printf("Event received: %s\n", str);
free(str);
}
int main(void) {
uloop_init();
ctx = ubus_connect(NULL);
if (!ctx) return -1;
ubus_add_uloop(ctx);
struct ubus_event_handler ev = { .cb = receive_event };
ubus_register_event_handler(ctx, &ev, "g_server");
uloop_run();
ubus_free(ctx);
uloop_done();
return 0;
}
3. UBUS Lua Module
The Lua module allows LuCI scripts to interact with ubus, providing similar functionality to libubus for web-based applications.
Integration with OpenWrt Components
- Procd: Uses ubus to manage service lifecycles and communicate system state changes.
- Rpcd: Exposes ubus methods over HTTP/JSON-RPC, enabling LuCI interactions and enforcing ACLs. See a simple example here.
- UCI (Unified Configuration Interface): UBUS integrates with UCI to apply configuration changes, e.g.,
ubus call uci apply
for network settings.
UBUS Communication Flow
Invoke (One-to-One)
sequenceDiagram participant UBUSD participant Client1 as UBUS Client 1 (Process 1) participant Client2 as UBUS Client 2 (Process 2) UBUSD->>UBUSD: 0: create socket, bind, listen Client1->>UBUSD: 1.1: Connect UBUSD-->>Client1: 1.2: Accept UBUSD-->>Client1: 1.3: Hello Client1->>UBUSD: 2.1: Register object, method UBUSD->>UBUSD: 2.2: Update AVL-tree Note right of UBUSD: Assign and add objpath,\nobjid, objtype,\nand method to AVL-tree. Client2->>UBUSD: 3.1: Connect UBUSD-->>Client2: 3.2: Accept UBUSD-->>Client2: 3.3: Hello Client2->>UBUSD: 4.1: Lookup {objpath} UBUSD-->>Client2: 4.2: Reply {objpath, objid, objtype, signature} Note left of Client2: Signature includes methods\nand required parameters\nfor each method. Client2->>UBUSD: 5.1: Invoke {objid, method, msg} UBUSD-->>Client1: 5.2: Invoke {objid, method, msg} Client1-->>UBUSD: 5.3: Reply {objid, msg} UBUSD-->>Client2: 5.4: Reply {objid, msg}
Example Traffic
-> 2d0a3716 #2d0a3716 hello: {}
<- 2d0a3716 #00000000 add_object: {"objpath":"gserver.host","signature":{"gserver_post":{"id":5,"data":5,"msg":3}}}
<- 41a666fd #cc994b56 invoke: {"objid":-862368938,"method":"gserver_post","data":{"id":123456,"data":987654321,"msg":"Hi!"}}
-> 2d0a3716 #41a666fd data: {"objid":-862368938,"data":{"Gserver reply":"Request is being proceeded!"}}
Subscribe/Notify (One-to-Many)
sequenceDiagram participant UBUSD participant P1 as UBUS CLIENT 1: Process 1 participant P3 as UBUS CLIENT 3: Process 3 UBUSD->>UBUSD: 0: Create socket, bind, listen P1->>UBUSD: 2.1: Register object, method UBUSD->>UBUSD: 2.2: Update AVL tree P3->>UBUSD: 3.1: Connect UBUSD-->>P3: 3.2: Accept UBUSD-->>P3: 3.3: Hello P3->>UBUSD: 4.1: Lookup: {objpath} UBUSD-->>P3: 4.2: Reply: {objpath, objid, objtype, signature} P3->>UBUSD: 5.1: Subscribe: {objid} UBUSD->>UBUSD: 5.2: Update Subscription tree UBUSD-->>P1: 5.3: Notify: {subscriber objid,\nactive: true} P1->>P1: 6.0: Trigger Notification P1->>UBUSD: 6.1: Notify: {objid, msg} UBUSD->>UBUSD: 6.2: Lookup Subscription tree UBUSD-->>P3: 6.3: Invoke: {objid, msg} P3->>UBUSD: 7.1: Unsubscribe: {objid} UBUSD->>UBUSD: 7.2: Update Subscription tree UBUSD-->>P1: 7.3: Notify: {subscriber objid,\nactive: false}
Example Traffic
<- 478023e4 #00000000 subscribe: {"objid":-1244318547}
-> 74f42091 #00000000 notify: {"objid":629913760,"active":true}
<- 74f42091 #258bb8a0 notify: {"objid":629913760,"method":"gserver_post","data":{"id":123,"data":321,"msg":"abcdef"}}
Event Broadcast (One-to-Many)
sequenceDiagram participant UBUSD participant P1 as UBUS CLIENT 1: Process 1 participant P2 as UBUS CLIENT 2: Process 2 UBUSD->>UBUSD: 0: Create socket, bind, listen P1->>UBUSD: 1.1: Connect UBUSD-->>P1: 1.2: Accept UBUSD-->>P1: 1.3: Hello P2->>UBUSD: 1.4: Connect UBUSD-->>P2: 1.5: Accept UBUSD-->>P2: 1.6: Hello P1->>UBUSD: 2.1: Invoke: {objid, method: "register",\ndata: {object, pattern}} UBUSD->>UBUSD: 2.2: Update Event Registration Tree P2->>UBUSD: 3.1: Invoke: {objid, method: "send",\ndata: {pattern, data}} UBUSD->>UBUSD: 3.2: Lookup Event Registration Tree UBUSD-->>P1: 3.3: Invoke: {objid, method: pattern, data}
Example Traffic
<- 55484d34 #00000001 invoke: {"objid":1,"method":"register","data":{"object":-964689289,"pattern":"g_server"}}
<- b79aea68 #00000001 invoke: {"objid":1,"method":"send","data":{"id":"g_server","data":{"str":"gemtek"}}}