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

  1. Inter-Process Communication (IPC): Enables processes to exchange messages or invoke methods.
  2. Publish-Subscribe Model: Supports asynchronous event notifications through subscriptions.
  3. Remote Procedure Calls (RPC): Allows processes to call functions in other processes and receive JSON responses.
  4. Lightweight and Efficient: Optimized for low resource usage, suitable for embedded systems.
  5. Extensible: Developers can register custom objects and methods to extend functionality.
  6. 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:

  1. 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.
  2. UBUS Server Object:

    • Typically a daemon or interface (e.g., netifd registering a network object).
    • Registers with ubusd, exposing callable methods.
  3. 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
      
  4. UBUS Library (libubus):

    • A C library providing APIs for interacting with ubusd.
    • Supports connecting to the bus, registering objects, and handling messages.
  5. UBUS CLI Tool:

    • A command-line interface for debugging and scripting with ubusd.
  6. 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

  1. Object: A process registered with ubusd, acting as a server (providing methods) or client (calling methods).
  2. Method: A procedure exposed by a server object, callable with JSON arguments.
  3. Data: JSON-formatted information in requests or responses.
  4. Subscriber: A client subscribed to a server object, receiving notifications.
  5. Event: Identified by an event pattern (string), used for broadcasting data.
  6. Event Registrant: A process registered to receive events matching a specific pattern.

Data Flow Schemes

UBUS supports three communication patterns:

  1. 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
      
  2. 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
      
  3. 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:
      ubus list
      Output:
      block
      dhcp
      firewalld
      network
      network.interface.wan3
      system
      uci
    • Verbose example:
      ubus -v list system
      Output:
      'system' @d1165900
        "board":{}
        "info":{}
        "reboot":{}
        "watchdog":{"frequency":"Integer","timeout":"Integer"}
  • call []:

    • Calls a method on an object.
    • Example:
      ubus call system info
      Output:
      {
        "localtime": 1610460661,
        "uptime": 82303,
        "memory": {
      	"total": 1021562880,
      	"free": 884396032
        }
      }
  • listen […]:

    • Listens for events.
    • Example:
      ubus listen event_a
      Output:
      { "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"}}}