Monday, March 20, 2017

OpenWRT Modules: U-BUS

The u-bus (micro version of D-Bus) is an interface that allows users to access and use services from the same place. Some services are built-in to OpenWRT and other services are executable files that we have created ourselves.
Each process or daemon can register set of its own paths/pipes.  The communication is made by marshalling data to TLV-format message and sent via Unix socket.


Components

The main component of UBUS framework is the ubus daemon.  It must be running before all other stuff can be called.  On some devices, it runs with default Unix socket path /var/run/ubus.sock.
Sometimes we need routines from U-Box too (uloop_* routines), such as ubus_add_uloop.

UBUS Structure




We connected to ubus via the ubus command line interface, ubus_cli on the figure. The ubus allowed us connect to onion service through the rcpd, which we will discuss in depth later on. Once the onion service executed the requested function, the ouput was relayed back to the ubus_cli via the ubus.
The ubus gives us a very powerful tool for accessing services both locally and remotely. 
ubus module in openWRT is located in folder $BASE/openwrt/build_dir/target-arm_cortex-a9_uClibc-0.9.33.2_eabi/ubus-2015-05-25/

ubus CLI
device-linux: ~ # ubus help
Usage: ubus [<options>] <command> [arguments...]
Options:
 -s <socket>:        Set the unix domain socket to connect to
 -t <timeout>:        Set the timeout (in seconds) for command to complete
 -S:            Use simplified output (for scripts)
 -v:            More verbose output
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

From OpenWRT shell, we can list what services are available:
Services
 device-linux: config # ubus list
/juci/dhcp
/juci/ethernet
/juci/firewall.dmz
/juci/juci
/juci/modems
/juci/network
/juci/network.lua
/juci/network.status
/juci/rtgraphs
/juci/swconfig
/juci/system
/juci/system.conf
/juci/system.process
/juci/system.service
/juci/system.time
/juci/system.upgrade
/juci/system.user
/juci/upnpd
/juci/usb
/juci/wireless
dhcp
juci.ui
log
network
network.device
network.interface
network.interface.cfg094d8f
network.interface.lan
network.interface.loopback
network.interface.wan
network.interface.wan_6
network.interface.wwan
network.wireless
service
session
system
tr069
uci
device-linux: config # ubus list -v tr069
'tr069' @5212a95a
        "notify":{}
        "inform":{"event":"String"}
        "command":{"name":"String"}

To talk to the service module and perform the supported service, we can type:
device-linux: config # ubus call tr069 "notify"

Services/Plugins

When we invoke "ubus list", it lists all the services available, either native to OpenWRT or custom made (by third parties).  To find which ones are custom services, do:
device-linux: config # ls /usr/libexec/rpcd
juci-ubus-core-cgi
The rpcd plugin allows users to expose their custom services in the form of executable shell scripts over the ubus. 
The module above is generated from code located in $BASE/openwrt/build_dir/target-arm_cortex-a9_uClibc-0.9.33.2_eabi/juci-<uuid>/backend/juci-core, where <uuid> is generated during first make.
On the target, some of the modules stored in /www/cgi-bin/ as webUI interfaces.

To see what functions available for a certain path, type "ubus list -v <path>", an to invoke the RPC function we type "ubus call <path/service> <function> '{<JSON parameters>}'
For example, to call tr069::notify(), we do:
RPC Invocation on UBUS
'tr069' @1c3f840d
        "notify":{}
        "inform":{"event":"String"}
        "command":{"name":"String"}
device-linux: ~ # ubus call tr069 notify
  
device-linux: ~ # ubus call tr069 "inform" '{"event":"inform"}'
  
device-linux: ~ # ubus call uci get '{"config":"version"}'                                                                                                    
{
        "values": {
                "config": {
                        ".anonymous"false,
                        ".type""version",
                        ".name""config",
                        ".index": 0,
                        "minor""0",
                        "major""1"
                }
        }
}

To monitor WAN Status
device-31F0-linux: config # ubus -v call network.interface.wan status | jsonfilter -e '$.up'
true


device-31F0-linux: config # ubus -v list network.interface.wan
'network.interface.wan' @9d315f0b
"up":{}
"down":{}
"status":{}
"prepare":{}
"dump":{}
"add_device":{"name":"String","link-ext":"Boolean"}
"remove_device":{"name":"String","link-ext":"Boolean"}
"notify_proto":{}
"remove":{}
"set_data":{}
device-31F0-linux: config # ubus -v call network.interface.wan down
device-31F0-linux: config # ubus -v call network.interface.wan up

Another example, to get the current firmware version:
To get firmware version
device-31F0-linux: config # ubus -v call system board | jsonfilter -e '$.release.version'
10.3.0.117


U-BUS C Function Calls/API


Function
Description
struct ubus_context *ubus_connect(const char *path)
UBUS initialization. Call this at very beginning before we use UBUS. The path is something like "network.interface".
To see the available path on device, type:
ubus list
int ubus_connect_ctx(struct ubus_context *ctx, const char *path)

void ubus_auto_connect(struct ubus_auto_conn *conn)Auto connect UBUS when the connection is lost. The connection is checked every 1000 msecs (1 Second)
int ubus_reconnect(struct ubus_context *ctx, const char *path)Try to reconnect existing context to path. This is usually useful when we've had ubus context, but for some reason the connection has severed/disconnected.
void ubus_free(struct ubus_context *ctx)It is identical to ubus_shutdown() + freeing the context.
void ubus_shutdown(struct ubus_context *ctx)
Shutdown UBUS: free blob buffer, close socket used and free message buffer, but it doesn't free context
void ubus_auto_shutdown(struct ubus_auto_conn *conn)

const char *ubus_strerror(int error)

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(struct ubus_context *ctx, const char *path, ubus_lookup_handler_t cb, void *priv)
ctx: ubus context (assigned by ubus_connect())
path: ubus path. For example: "network"
cb: callback routine to be called upon receiving data (currently, it is no effect/not-used)
priv: parameter to pass to remote (seems not used)
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
Example:

Example of ubus_lookup_id
ret = ubus_lookup_id(ctx, "network.interface.wan", &id);
if (ret == UBUS_STATUS_OK)
{
    // do something
}
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.
Add object
void handle_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj)
{
    fprintf(stderr, "Subscribers active: %d\n", obj->has_subscribers);
}
struct ubus_object my_client_object = {
    .subscribe_cb = handle_subscribe_cb,
};
ret = ubus_add_object(ubus_ctx, &my_client_object);
if (ret != UBUS_STATUS_OK) {
    fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
    return;
}
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
int ubus_register_subscriber(struct ubus_context *ctx, struct ubus_subscriber *obj)
int ubus_subscribe(struct ubus_context *ctx, struct ubus_subscriber *obj, uint32_t id)

int ubus_unsubscribe(struct ubus_context *ctx, struct ubus_subscriber *obj, uint32_t id)
int ubus_monitor_start(struct ubus_context *ctx)

int ubus_monitor_stop(struct ubus_context *ctx)

void ubus_complete_request_async(struct ubus_context *ctx, struct ubus_request *req)
void ubus_abort_request(struct ubus_context *ctx, struct ubus_request *req)
Abort/cancel previous not-yet-sent request pointed by req parameter
Cancel request
static struct ubus_request req_status = { .list = LIST_HEAD_INIT(req_status.list) };
ubus_abort_request(ubus_ctx, &req_status);
int ubus_invoke_async(struct ubus_context *ctx, uint32_t obj, const char *method, struct blob_attr *msg, struct ubus_request *req)
Invoke RPC asynchronously.
method: the name of method to remotely execute
msg: parameters (in the form of BLOB)
req: request object returned (contains sequence number, context, etc.)
Send RPC
// this routine handles wan status value returned from RPC call
static void hand_wan_status(_unused struct ubus_request *req, _unused int type, struct blob_attr *msg)
{
    // yadi..yada
}
ret = ubus_invoke_async(ubus, objid, "status", NULL, &req_status);
if (status == UBUS_STATUS_OK)
{
    req_status.data_cb = handle_wan_status;
    ret = ubus_complete_request_async(ubus_ctx, &req_status);
}
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. Unlike ubus_invoke_asyn(), the caller doesn't need to call ubus_complete_request() {actually it does internally}.
cbif not NULL, callback routine to be called once data is available.
This routine internally calls ubus_invoke_async()
Example: Get WAN Status
typedef void (*ubusRxMsgCb_t)(struct ubus_request *req, int type, struct blob_attr *msg);
     
int wanIsUp = 0;
void ifStatusCb(*ubusRxMsgCb_t)(struct ubus_request *req,
                int type, struct blob_attr *msg)
{
    char *str;
    bool *isUp = (bool *)req->priv;
    if ((!msg) return;
    // netifd returns in JSON format
    str = blobmsg_format_json_indent(msg, true, -1);
    char *status = strstr(str, "up");
    // skip up":
    status += 4;
    char *end = strchr(status, ',');
    *(end)='\0';
    if (strcmp(status, "true") == 0)
        *isUp = true;
    else
        *isUp = false;
    free(str);
}
struct blob_buf bb;
int ubusCallMethod(struct ubus_context *ctx, const char *path,
                   const char *methodName, ubusRxMsgCb_t cb, void *retval)
{
    int id;
    int ret;
    ret = blob_buf_init(&bb, 0);
    ret = ubus_lookup_id(ctx, path, &id);
     
    return ubus_invoke(ctx, id, methodName, bb.head, cb, retval, 1000);
}
bool wanIsUp(void)
{
    bool isUp = false;
    ubusCallMethod(ctx, "network.interface.wan", ifStatusCb, &isUp);
    return isUp;
}
int ubus_send_reply(struct ubus_context *ctx, struct ubus_request *req, struct blob_attr *msg)Send reply to the incoming object method call
int ubus_send_event(struct ubus_context *ctx, const char *id, struct blob_attr *data)
Send an event indicated in ev parameter with parameter data
int ubus_register_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *pattern)Register ev handler for event
int ubus_unregister_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev)

EXAMPLES 


A very basic client:
Client Side U-BUS
struct ubus_context *ctx;
static void client_main(void)
{
    //
}
int main()
{
    uloop_init();
    ctx = ubus_connect(ubus_socket);
    ubus_add_uloop(ctx);
    client_main();
    uloop_run();
    ubus_free(ctx);
    uloop_done();
    return 0;
}



(...to be continued....)

5 comments:

  1. How do you make a uci set configuration to update disabled option for wireless config by ubus with a curl?

    ReplyDelete
  2. curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "7cba69a942c0e9db1eb7982cd91f3a48", "file", "read", { "path": "/tmp/hello.karl" } ] }' http://eg-134867.local/ubus

    ReplyDelete
  3. curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "7cba69a942c0e9db1eb7982cd91f3a48", "file", "read", { "path": "/tmp/hello.karl" } ] }' http://eg-134867.local/ubus

    ReplyDelete
  4. Please give an example for UBUS Listen use in client side.

    ReplyDelete