Getting started with OpenWrt Micro Bus - ubus

OpenWrt is an opensource Linux distribution which enables several Routers and Access Points. It is designed to be a lean router stack, but with a scaleable architecture where,it can be customized and extended with more features. It is possible that we can develop our own custom component and add it to the router stack conveniently.

This is the first part of a mini series dedicated to introduce openwrt bus architecture and adding a shell based custom component to Openwrt.

OpenWrt Micro Bus (UBus)

  • OpenWrt micro bus (ubus) provides system-level Inter-process Communication(IPC) for the services running in OpenWrt. UBus allows services to perform remote procedure call.

  • The heart of ubus is ubusd daemon, which allows services to register to a specific namespace and allow other components to call the procedures using the registered namespace.

  • Client applications can connect to the Ubus, ask for specific objects on the bus and call methods of these objects or they can create new objects and methods itself.

  • UBUS calls uses JSON format for the parameters passing and response messages.

UBUS Architecture

/static/images/ubus-daemon-service.png

UBUS Client To Explore

  • Openwrt provides different tools to access ubus. One is command-line ubus tools

  • The ubus command line tool allows to interact with the ubusd server (with all currently registered services).

  • To list out the existing objects in ubus we can use the ubus list command as mentioned below

root@OpenWrt:/usr/libexec/rpcd# ubus list
dhcp
dnsmasq
iwinfo
network.device
uci
..
...
  • To get information on the methods registered with the object , tryout the below command.

root@OpenWrt:/usr/libexec/rpcd# ubus -v list system
'system' @64d9bfba
        "board":{}
        "info":{}
        "reboot":{}
        "watchdog":{"frequency":"Integer","timeout":"Integer","magicclose":"Boolean","stop":"Boolean"}
        "signal":{"pid":"Integer","signum":"Integer"}
        "validate_firmware_image":{"path":"String"}
        "sysupgrade":{"path":"String","force":"Boolean","backup":"String","prefix":"String","command":"String","options":"Table"}
  • To invoke the methods registered in ubus, call command can be used. For example, we can get the board info using ubus itself.

root@OpenWrt:/usr/libexec/rpcd# ubus call system board
{
        "kernel": "6.6.74",
        "hostname": "OpenWrt",
        "system": "ARMv8 Processor rev 4",
        "board_name": "linux,dummy-virt",
        "rootfs_type": "squashfs",
        "release": {
                "distribution": "OpenWrt",
                "version": "SNAPSHOT",
                "revision": "r28724-807074309d",
                "target": "armsr/armv8",
                "description": "OpenWrt SNAPSHOT r28724-807074309d",
                "builddate": "1738094344"
        }
}
  • Other options also available in ubus command line, please checkout help options for more info.

Remote Procedure Call Daemon (RPCD)

  • Rpcd is an OpenWrt daemon that uses the ubus micro bus architecture to provide RPC services for small utils which doesn’t need to run as a background service.

  • It is possible to expose shell script functionality over ubus by using rpcd plugin executables functionality.

  • Executables stored in /usr/libexec/rpcd/ directory will be run by rpcd and registers them as UBUS procedures, to be called from other clients.

RPCD Architecture

/static/images/rpcd-ubus-interaction.png

RPCD Plugin Format

  • List and Call functions - A RPCD plugin must support call and list functions to be attached to the ubus

  • Input/Output

    • All input/output of ubus shell scripts are in the form of JSON format.

    • This format provides compatibility between all ubus services

    • A JSON object contains zero, one, or more key-value pairs, also called properties. The object is surrounded by curly braces {}. Every key-value pair is separated by a comma.

    • A key-value pair consists of a key and a value, separated by a colon (:). The key is a string, which identifies the key-value pair

    • Example

JSON
 '{"key1":"value1","key2":"value2","key3":"value3"}'

Creating RPCD Plugin

  • Lets create a ubus object to fetch the current date and time of the openwrt system.

  • current.date and current.time are the objects registered to the sys_time method.

root@OpenWrt:/usr/libexec/rpcd# cat sys_time
#!/bin/sh
case "$1" in
        list)
                echo '{ "current.time" : { }, "current.date" : { }}'
        ;;
        call)
                case "$2" in
                        current.time)
                                current_time=$(uptime -s | awk '{print $2}')
                                echo '{ "current_time" : "'$current_time '" }'
                        ;;
                        current.date)
                                current_date=$(uptime -s | awk '{print $1}')
                                echo '{ "current_date" : "'$current_date '" }'
                esac
        ;;
esac

root@OpenWrt:/usr/libexec/rpcd# chmod a+x sys_time
  • Once script is placed inside the rpcd folder, rpcd service has to be restarted

root@OpenWrt:/usr/libexec/rpcd# /etc/init.d/rpcd reload
  • This will expose the script to the ubus daemon.

root@OpenWrt:/usr/libexec/rpcd# ubus -v list sys_time
'sys_time' @9186a0cf
        "current.time":{}
        "current.date":{}
  • To invoke the ubus call for sys_time daemon with two methods, current.time and current.date

root@OpenWrt:/usr/libexec/rpcd# ubus call sys_time current.time
{
        "current_time": "21:56:44 "
}
root@OpenWrt:/usr/libexec/rpcd# ubus call sys_time current.date
{
        "current_date": "2025-02-27 "
}

UBUS Call Introspection

/static/images/rpc-uml-interaction.png
  • To monitoring the ubus traffic , ubus monitor command can be used. This command gives the insight of messages hitting ubus daemon.

root@OpenWrt:/usr/libexec/rpcd# ubus monitor &
  • Connection Establishment and Method registration between RPCD and UBUSD can be seen as,

root@OpenWrt:/usr/libexec/rpcd# /etc/init.d/rpcd reload

-> d384ecad #00000003         status: {"status":0}
-> a2afb3d0 #a2afb3d0          hello: {}
<- a2afb3d0 #00000000         lookup: {"objpath":"service"}
-> a2afb3d0 #00000000           data: {"objpath":"service","objid":315447600,"objtype":118647020,"signature":{"set":{"name":3,"script":3,"instances":2,"triggers":1,"validate":1,"autostart":7,"data":2},"add":{"name":3,"script":3,"instances":2,"triggers":1,"validate":1,"autostart":7,"data":2},"list":{"name":3,"verbose":7},"delete":{"name":3,"instance":3},"signal":{"name":3,"instance":3,"signal":5},"update_start":{"name":3},"update_complete":{"name":3},"event":{"type":3,"data":2},"validate":{"package":3,"type":3,"service":3},"get_data":{"name":3,"instance":3,"type":3},"state":{"spawn":7,"name":3},"watchdog":{"mode":5,"timeout":5,"name":3,"instance":3}}}
-> a2afb3d0 #00000000         status: {"status":0}
<- a2afb3d0 #12cd5930         invoke: {"objid":315447600,"method":"signal","data":{"name":"rpcd"}}
-> da4e6639 #a2afb3d0         invoke: {"objid":315447600,"method":"signal","data":{"name":"rpcd"},"user":"root","group":"root"}
-> fa317069 #fa317069          hello: {}
<- fa317069 #00000000     add_object: {"objpath":"session","signature":{"create":{"timeout":5},"list":{"ubus_rpc_session":3},"grant":{"ubus_rpc_session":3,"scope":3,"objects":1},"revoke":{"ubus_rpc_session":3,"scope":3,"objects":1},"access":{"ubus_rpc_session":3,"scope":3,"object":3,"function":3},"set":{"ubus_rpc_session":3,"values":2},"get":{"ubus_rpc_session":3,"keys":1},"unset":{"ubus_rpc_session":3,"keys":1},"destroy":{"ubus_rpc_session":3},"login":{"username":3,"password":3,"timeout":5}}}
-> da4e6639 #00000000         invoke: {"objid":-1425863935,"method":"ubus.object.add","data":{"id":-137075399,"path":"session"}}
-> da4e6639 #00000000         invoke: {"objid":-1425863935,"method":"ubus.object.add","data":{"id":875435327,"path":"uci"}}
-> fa317069 #00000000           data: {"objid":-1174288804,"objtype":1304846968}
-> fa317069 #00000000         status: {"status":0}
<- fa317069 #00000000     add_object: {"objpath":"sys_time","signature":{"current.time":{},"current.date":{}}}
  • Invoking the data from ubus from ubusd is given below,

root@OpenWrt:/usr/libexec/rpcd# ubus call sys_time current.time
-> bf475fc1 #bf475fc1          hello: {}
<- bf475fc1 #00000000         lookup: {"objpath":"sys_time"}
-> bf475fc1 #00000000           data: {"objpath":"sys_time","objid":-849228508,"objtype":-155009503,"signature":{"current.time":{},"current.date":{}}}
-> bf475fc1 #00000000         status: {"status":0}
<- bf475fc1 #cd61cd24         invoke: {"objid":-849228508,"method":"current.time","data":{}}
-> 45563b02 #bf475fc1         invoke: {"objid":-849228508,"method":"current.time","data":{},"user":"root","group":"root"}
<- 45563b02 #00000000         lookup: {}
-> 45563b02 #00000000           data: {"objpath":"file","objid":1221431004,"objtype":-790314395,"signature":{"read":{"path":3,"base64":7,"ubus_rpc_session":3},"write":{"path":3,"data":3,"append":7,"mode":5,"base64":7,"ubus_rpc_session":3},"list":{"path":3,"ubus_rpc_session":3},"stat":{"path":3,"ubus_rpc_session":3},"md5":{"path":3,"ubus_rpc_session":3},"remove":{"path":3,"ubus_rpc_session":3},"exec":{"command":3,"params":1,"env":2,"ubus_rpc_session":3}}}
-> 45563b02 #00000000           data: {"objpath":"log","objid":-922528967,"objtype":953641379,"signature":{"read":{"lines":5,"stream":7,"oneshot":7},"write":{"event":3}}}
-> 45563b02 #00000000           data: {"objpath":"sys_time","objid":-849228508,"objtype":-155009503,"signature":{"current.time":{},"current.date":{}}}
<- 45563b02 #bf475fc1           data: {"objid":-849228508,"data":{"current_time":"21:56:44 "}}
<- 45563b02 #bf475fc1         status: {"status":0,"objid":-849228508}
-> bf475fc1 #cd61cd24         status: {"status":0,"objid":-849228508}

Conclusion

This exploratory article covered the introduction to OpenWrt bus architecture and the steps to create a custom component using RFC Daemon. The upcoming article will uncover the jshn library to replace the current shell script approach.