Monday, March 20, 2017

OpenWRT Modules (UBOX, UBUS, etc.)

OpenWRT system relies on these projects to bind and integrate userspace applications with centralized configuration.  So, basically OpenWRT = GNU/Linux + network apps + OpenWRT libraries (U- modules)
  • U-BOX
  • U-BUS
  • UCI
  • J-UCI

The foundation is the U-BOX framework. U-BUS depends on it.  While UCI depends on U-BUS, and J-UCI depends on UCI.
The dependency:

J-UCI
 |
 |
 +---> UCI
        |
        |
        +---> UBUS
               |
               |
               +---> UBOX



UBOX


UBox (Micro Box) is a collection of utilities intensively used by OpenWrt project.  It provides facilities, such as polling, event handling, socket helper functions, logging, MD5 hashing, generating/parsing tagged binary data, AVL binary-tree  balancing, JSON handling, task queueing/completion tracking, and others. The RPC module for inter-process communication is called ubus (http://git.openwrt.org/project/ubus.git).






Modules:
  • ULoop
  • UBus



  • BLOB (Binary Large Object)
  • BLOBMSG (based on BLOB)


  • GIT repos: 


    ULoop

    Most used functions in this subsystem are:
    int uloop_init(void);
    void uloop_run(void);
    void uloop_done(void);

    Routine
    Description
    int uloop_init(void)
    Must be called before we can use uloop_run()
    void uloop_run(void)
    This routine does the main loop:
    • Execute scheduled one-shot tasks, if any
    • Execute queued tasks, if any
    • Execute signal handlers, if any
    • Execute event handlers, if any
    The routine runs continuously until we set the global variable uloop_cancelled to true.
    void uloop_done(void)
    Finally, we can this routine before quit to cleanup uloop's internal spaces used before.

    Compilation

    Basically, we just need to tell GCC the path to libubox.so and the headers needed.
    For example:

    Basic Compilation Command
    gcc -o myprog -I. -I/usr/local/include myprog.c -L/usr/local/lib -lubox

    One-shot Timer Task

    A one-time scheduled task is added via uloop_timout_add().
    Routine Name
    Description
    int uloop_timeout_add(struct uloop_timeout *timeout)Add our timeout task into queue
    int uloop_timeout_cancel(struct uloop_timeout *timeout)Delete our timeout task from queue
    int uloop_timeout_set(struct uloop_timeout *timeout, int msec)Set milliseconds from now (e.g, to be executed later after mSecs elapsed)
    int uloop_timeout_remaining(struct uloop_timeout *timeout)How many mSecs remained for the task to be executed
    int uloop_clear_timeouts(void)Empty the one-shot queue completely

    To add an one-time shot event timer, we call uloop_timeout_set(struct uloop_timeout uto*, msecs) (as defined in libubox).   In uloop_timeout, we set a member variable cb for callback, for example:
    Event Callback
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static struct uloop_timeout my_event_timer;
    static void my_event_handler(struct uloop_timeout *timeout)
    {
        // if we want to make a repeatable event, set the timer again by calling uloop_timeout(timeout, <timeout val in msec)
    }
    void set_event(void)
    {
        my_event_timer.cb = my_event_handler;
        uloop_timeout_set(&my_event_timer, 1000); // timeout in 1 sec
    }
    int main(void)
    {
        uloop_init();
        set_event();
        uloop_run();
        uloop_done();
        return 0;
    }

    Another example:
    Delayed single-shot Task
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdint.h>
    #include <time.h>
    #include "uloop.h"
    void oneshot_cbhandler(struct uloop_timeout *t)
    {
        printf("Executed!\n");
    }
    struct uloop_timeout timeout = {
        .cb = oneshot_cbhandler
    };
    void add_oneshot_timer(struct uloop_timeout *t, unsigned int delay)
    {
        uloop_timeout_set(t, delay);
        uloop_timeout_add(t);
    }
    // set the delay to 1 Second
    add_oneshot_timer(&timeout, 1000);
    // oneshot_cbhandler will be executed 1 second later by uloop_run()

    To make a periodic task, in the callback we re-add our task:

    Periodic Task
    void oneshot_cbhandler(struct uloop_timeout *t)
    {
        printf("Executed!\n");
        add_oneshot_timer(t, 1000);
    }

    The following example shows how to create a periodic task but only executed for limited time, then the uloop is finished:
    Periodic Task For a limited Time
    /*
     * timer.c
     * Lutfi Muhammad
     */
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdint.h>
    #include "uloop.h"
    // 0.25 secs
    TIME_INTERVAL       250
    //forward declaration
    void oneshot_timer(struct uloop_timeout *t);
    static unsigned long ticks = 0;
    struct uloop_timeout timeout = { .cb = oneshot_timer };
    void add_oneshot_timer(struct uloop_timeout *t, unsigned int delay)
    {
        uloop_timeout_set(t, delay);
        uloop_timeout_add(t);
    }
    void oneshot_timer(struct uloop_timeout *t)
    {
        ticks++;
        printf("%s: ticks=%lu\n", __FUNCTION__, ticks);
        if (ticks == 50)
            uloop_cancelled = true;
        else
            add_oneshot_timer(t, TIME_INTERVAL);
    }
    int main(int argc, char **argv)
    {
        uloop_init();
        add_oneshot_timer(&timeout, TIME_INTERVAL);
        uloop_run();
        uloop_done();
        return 0;
    }

    External Processes

    ULoop supports to queue up external processes to be executed in later time.  The execution is one-time (once the process has been executed, it is not repeated unless we re-enter the entry into process queue).
    To queue a process to be executed by uloop_run above, we need to call uloop_process_add() as shows below.  Subsequent calls to uloop_process_add() will queue the processes and would be executed FIFO way.  Once the process/task has been called, it is removed from the queue.

    Routine Name
    Description
    int uloop_process_add(struct uloop_process *p)Insert our task into process queue
    int uloop_process_delete(struct uloop_process *p)Delete our task from process queue

    Example:
    Adding process into queue
    char *init[] = { "/bin/sh""/etc/preinit", NULL };
    struct uloop_process preinit_proc;
    spawn_procd(struct uloop_process *proc, int ret)
    {
        char *argv[] = {"/sbin/procd", NULL};
        execvp(argv[0], argv);
    }
    preinit_proc.cb = spawn_procd;
    preinit_proc.pid = fork();
    if (preinit_proc.pid == 0)
    {
        // we're a child already, so simply execute "init" directly
        execvp(init[0], init);
        exit(-1);
    }
    // add to queue of processes to be executed
    uloop_process_add(&preinit_proc);
    //.... sometime later,spawn_procd() would be called by uloop_run() ....


    uloop_init() is used to do main loop. A typical construct is as follow:

    Server using U-Loop
    const char *port = "8080";
    void server_cb(int param)
    {
        // yadi..yada
    }
    struct uloop_fd server = {
        .cb = server_cb,
        .fd = usock(USOCK_TCP | USOCK_SERVER | USOCK_IPV4ONLY | USOCK_NUMERIC, "127.0.0.1", port);
    }'
    int main()
    {
        uloop_init();
         
        uloop_fd_add(&server, ULOOP_READ);  // add server_cb as the TCP listener at port 8080
        uloop_run();  // this will run forever
    }


    Generic Example of ULoop app
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    int main(int argc, char **argv)
    {
        const char *ubus_socket = NULL;
        bool client = false;
        int ch;
        while ((ch = getopt(argc, argv, "cs:")) != -1) {
            switch (ch) {
            case 's':
                ubus_socket = optarg;
                break;
            case 'c':
                client = true;
                break;
            default:
                break;
            }
        }
        argc -= optind;
        argv += optind;
        uloop_init();
        ctx = ubus_connect(ubus_socket);
        if (!ctx) {
            fprintf(stderr, "Failed to connect to ubus\n");
            return -1;
        }
        ubus_add_uloop(ctx);
        if (client)
            client_main();
        else
            server_main();
        ubus_free(ctx);
        uloop_done();
        return 0;
    }

    Client version  (when client_main() routine above is executed):
    ULoop Client
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static void client_main(void)
    {
        uint32_t id;
        int ret;
        ret = ubus_add_object(ctx, &test_client_object);
        if (ret) {
            fprintf(stderr, "Failed to add_object object: %s\n", ubus_strerror(ret));
            return;
        }
        if (ubus_lookup_id(ctx, test_object.name, &id)) {
            fprintf(stderr, "Failed to look up test object\n");
            return;
        }
        blob_buf_init(&b, 0);
        blobmsg_add_u32(&b, "id", test_client_object.id);
        ubus_invoke(ctx, id, "watch", b.head, NULL, 0, 3000);
        uloop_run();
    }
    All routines prepended with "ubus_" provided by ubus  library.
    To prepare ubus for RPC communication, first we create a context by calling: ctx = ubus_connect(path), where path is the path to pipe (e.g: "/tmp/compipe").  Once we have the context, we call ubus_add_uloop() to let uloop to handle ubus-related default events.

    Task Queue

    As described in libubox/runqueue.h:
    Task and Process Queue APIs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    struct runqueue;
    struct runqueue_task;
    struct runqueue_task_type;
    struct runqueue {
        struct safe_list tasks_active;
        struct safe_list tasks_inactive;
        struct uloop_timeout timeout;
        int running_tasks;
        int max_running_tasks;
        bool stopped;
        bool empty;
        /* called when the runqueue is emptied */
        void (*empty_cb)(struct runqueue *q);
    };
    struct runqueue_task_type {
        const char *name;
        /*
         * called when a task is requested to run
         *
         * The task is removed from the list before this callback is run. It
         * can re-arm itself using runqueue_task_add.
         */
        void (*run)(struct runqueue *q, struct runqueue_task *t);
        /*
         * called to request cancelling a task
         *
         * int type is used as an optional hint for the method to be used when
         * cancelling the task, e.g. a signal number for processes. Calls
         * runqueue_task_complete when done.
         */
        void (*cancel)(struct runqueue *q, struct runqueue_task *t, int type);
        /*
         * called to kill a task. must not make any calls to runqueue_task_complete,
         * it has already been removed from the list.
         */
        void (*kill)(struct runqueue *q, struct runqueue_task *t);
    };
    struct runqueue_task {
        struct safe_list list;
        const struct runqueue_task_type *type;
        struct runqueue *q;
        void (*complete)(struct runqueue *q, struct runqueue_task *t);
        struct uloop_timeout timeout;
        int run_timeout;
        int cancel_timeout;
        int cancel_type;
        bool queued;
        bool running;
        bool cancelled;
    };
    struct runqueue_process {
        struct runqueue_task task;
        struct uloop_process proc;
    };
    #define RUNQUEUE_INIT(_name, _max_running) { \
            .tasks_active = SAFE_LIST_INIT(_name.tasks_active), \
            .tasks_inactive = SAFE_LIST_INIT(_name.tasks_inactive), \
            .max_running_tasks = _max_running \
        }
    #define RUNQUEUE(_name, _max_running) \
        struct runqueue _name = RUNQUEUE_INIT(_name, _max_running)
    void runqueue_init(struct runqueue *q);
    void runqueue_cancel(struct runqueue *q);
    void runqueue_cancel_active(struct runqueue *q);
    void runqueue_cancel_pending(struct runqueue *q);
    void runqueue_kill(struct runqueue *q);
    void runqueue_stop(struct runqueue *q);
    void runqueue_resume(struct runqueue *q);
    void runqueue_task_add(struct runqueue *q, struct runqueue_task *t, bool running);
    void runqueue_task_add_first(struct runqueue *q, struct runqueue_task *t, bool running);
    void runqueue_task_complete(struct runqueue_task *t);
    void runqueue_task_cancel(struct runqueue_task *t, int type);
    void runqueue_task_kill(struct runqueue_task *t);
    void runqueue_process_add(struct runqueue *q, struct runqueue_process *p, pid_t pid);
    /* to be used only from runqueue_process callbacks */
    void runqueue_process_cancel_cb(struct runqueue *q, struct runqueue_task *t, int type);
    void runqueue_process_kill_cb(struct runqueue *q, struct runqueue_task *t);

    Normally, we first call runqueue_init first.  Later we can add a task or process to the queue (which will be executed by uloop_run).  To add a task, we call runqueue_task_add() and to add a process to the queue is runqueue_process_add().  The difference between task and process in this context is that process is an external job/program to be executed (similar to at or cron), while task is simply a routine to be called later.
    For Example:
    Task Queue Example
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    static struct runqueue my_queue;
    ...
    runqueue_init(&my_queue);
    ...
    static void handle_run_script(struct job *j, struct blob_attr *exec, struct blob_attr *env)
    {
        pid_t pid;
        pid = fork();
        if (pid < 0)
           return;
        if (pid > 0) {
            runqueue_process_add(&q, &j->proc, pid);
            return;
        }
        // after this line, it applies only to a child process
         // ---- DO SOMETHING HERE -----
    }
    static struct cmd handlers[] = {
        {
            .name = "run_script",
            .handler = handle_run_script,
        },
    };
    static void rule_handle_command(struct trigger *t, const char *name, struct blob_attr *exec, struct blob_attr *vars)
    {
        struct trigger *t = container_of(ctx, struct trigger);
        int i;
        if (t->pending)
            return;
        for (i = 0; i < ARRAY_SIZE(handlers); i++) {
            if (!strcmp(handlers[i].name, name)) {
                add_job(t, &handlers[i], exec, vars);
                break;
            }
        }
    }
    (&my_queue)

    BLOB (Binary Large OBject)

    BLOB submodule is used for message formatting, packaging and handling.  There are several supported datatypes, and it creates blobs that could be sent over any socket, as endianess is taken care in the library itself.
    The method is to create a type-value chained data, supporting nested data. This part basically covers putting and getting datatypes. Further functionality is implemented in blobmsg.h.
    Blobmsg sits on top of blob.h, providing tables and arrays, providing it's own but parallel datatypes.

    Some important routines:
    BLOB API
    bool              blob_attr_equal(const struct blob_attr *a1, const struct blob_attr *a2);
    int               blob_buf_init(struct blob_buf *buf, int id);
    void              blob_buf_free(struct blob_buf *buf);
    bool              blob_buf_grow(struct blob_buf *buf, int required);
    void              blob_fill_pad(struct blob_attr *attr);
    struct blob_attr* blob_new(struct blob_buf *buf, int id, int payload);
    void*             blob_nest_start(struct blob_buf *buf, int id);
    void              blob_nest_end(struct blob_buf *buf, void *cookie);
    struct blob_attr* blob_put(struct blob_buf *buf, int id, const void *ptr, unsigned int len);
    struct blob_attr* blob_put_raw(struct blob_buf *buf, const void *ptr, unsigned int len);
    bool              blob_check_type(const void *ptr, unsigned int len, int type);
    int               blob_parse(struct blob_attr *attr, struct blob_attr **data, const struct blob_attr_info *info, int max);
    struct blob_attr* blob_memdup(struct blob_attr *attr);
    void              blob_set_raw_len(struct blob_attr *attr, unsigned int len);
    static <type> inline blob_get_<type>(blob_attr *);
    static <type> inline blob_put_<type>(blob_attr *);

    Example:
    BLOB Example
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include "blob.h"
    #define BUF_ID    0
    enum {
         BUF_U8,
         BUF_U16,
         BUF_U32,
         BUF_U64,
         BUF_STR
    };
    int main()
    {
        static struct blob_buf buf;
        // initiate
        blob_buf_init(&buf, BUF_ID);
        // put some values in sequence
        blob_put_string(&buf, BUF_STR, Hello, World!");
        uint32_t val = 300;
        blob_put_U32(&buf, BUF_U32, (uint32_t)val);
         
        // free it
        blob_buf_free(&buf);
    }

    BLOBMSG

    Function Name
    Description
    int blobmsg_hdrlen(unsigned int namelen)

    void blobmsg_clear_name(struct blob_attr *attr)

    const char *blobmsg_name(const struct blob_attr *attr)

    int blobmsg_type(const struct blob_attr *attr)

    void *blobmsg_data(const struct blob_attr *attr)

    int blobmsg_data_len(const struct blob_attr *attr)

    bool blobmsg_check_attr(const struct blob_attr *attr, bool name)

    bool blobmsg_check_attr_list(const struct blob_attr *attr, int type)

    int blobmsg_check_array(const struct blob_attr *attr, int type)
    int blobmsg_parse(const struct blobmsg_policy *policy, int policy_len,
    struct blob_attr **tb, void *data, unsigned int len)

    int blobmsg_parse_array(const struct blobmsg_policy *policy, int policy_len,
    struct blob_attr **tb, void *data, unsigned int len)

    int blobmsg_add_field(struct blob_buf *buf, int type, const char *name,
    const void *data, unsigned int len)

    int blobmsg_add_u8(struct blob_buf *buf, const char *name, uint8_t val)

    int blobmsg_add_u16(struct blob_buf *buf, const char *name, uint16_t val)

    int blobmsg_add_u32(struct blob_buf *buf, const char *name, uint32_t val)

    int blobmsg_add_u64(struct blob_buf *buf, const char *name, uint64_t val)

    int blobmsg_add_string(struct blob_buf *buf, const char *name, const char *string)

    int blobmsg_add_blob(struct blob_buf *buf, struct blob_attr *attr)

    void *blobmsg_open_nested(struct blob_buf *buf, const char *name, bool array);

    void *blobmsg_open_array(struct blob_buf *buf, const char *name)

    void *blobmsg_open_table(struct blob_buf *buf, const char *name)

    void
    blobmsg_close_array(struct blob_buf *buf, void *cookie)

    void
    blobmsg_close_table(struct blob_buf *buf, void *cookie)

    int blobmsg_buf_init(struct blob_buf *buf)

    uint8_t blobmsg_get_u8(struct blob_attr *attr)





    ULOG

    The APIs provided are:
    ULOG API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    enum {
        ULOG_KMSG   = (1 << 0),
        ULOG_SYSLOG = (1 << 1),
        ULOG_STDIO  = (1 << 2)
    }; // for channels
    /* as defined in /usr/include/sys/syslog.h, facility can be one of these:
    LOG_KERN
    LOG_USER
    LOG_MAIL
    LOG_DAEMON
    LOG_AUTH
    LOG_SYSLOG
    LOG_LPR
    LOG_NEWS
    LOG_UUCP
    LOG_CRON
    LOG_AUTHPRIV
    LOG_FTP
    LOG_LOCAL[0-7]
    ident: a string to identify our string, so the log will be prepended with "<ident>: "
    */
    void ulog_open(int channels, int facility, const char *ident);
    void ulog_close(void);
    void ulog_threshold(int threshold);
    void ulog(int priority, const char *fmt, ...);
    #define ULOG_INFO(fmt, ...) ulog(LOG_INFO, fmt, ## __VA_ARGS__)
    #define ULOG_NOTE(fmt, ...) ulog(LOG_NOTICE, fmt, ## __VA_ARGS__)
    #define ULOG_WARN(fmt, ...) ulog(LOG_WARNING, fmt, ## __VA_ARGS__)
    #define ULOG_ERR(fmt, ...) ulog(LOG_ERR, fmt, ## __VA_ARGS__)
    void ulog_threshold(int threshold);
    void ulog(int priority, const char *fmt, ...);
    #define ULOG_INFO(fmt, ...) ulog(LOG_INFO, fmt, ## __VA_ARGS__)
    #define ULOG_NOTE(fmt, ...) ulog(LOG_NOTICE, fmt, ## __VA_ARGS__)
    #define ULOG_WARN(fmt, ...) ulog(LOG_WARNING, fmt, ## __VA_ARGS__)
    #define ULOG_ERR(fmt, ...) ulog(LOG_ERR, fmt, ## __VA_ARGS__)

    (...to be continued...)

    2 comments:

    1. A good summary of uloop, ubus and blob. Thanks a lot.

      ReplyDelete
    2. Hi,
      Thanks for the detailed explanations.
      I am trying to run multiple periodic tasks within a same uloop_run().
      But somehow its not working.
      If possible could you add the example as above.

      ReplyDelete