ILD

openwrt ubus 学习
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2022-8-18 站点:Inside Linux Development

ubus是openwrt平台的RPC机制,它包含3个部分:libubus,ubus,ubusd。


ubus是中心服务型,ubusd负责客户端和服务端的消息转发。

    client <-> ubusd <-> server


ubus是命令行客户端。libubus是ubus的c库,可以实现客户端和服务端。


协议

ubus消息,有一个消息头,然后跟着payload。


struct ubus_msghdr {

        uint8_t version;

        uint8_t type;

        uint16_t seq;

        uint32_t peer;

} __packetdata;


type是消息的类型:

enum ubus_msg_type {

        /* initial server message */

        UBUS_MSG_HELLO,


        /* generic command response */

        UBUS_MSG_STATUS,


        /* data message response */

        UBUS_MSG_DATA,


        /* ping request */

        UBUS_MSG_PING,


        /* look up one or more objects */

        UBUS_MSG_LOOKUP,


        /* invoke a method on a single object */

        UBUS_MSG_INVOKE,


        UBUS_MSG_ADD_OBJECT,

        UBUS_MSG_REMOVE_OBJECT,


        /*

         * subscribe/unsubscribe to object notifications

         * The unsubscribe message is sent from ubusd when

         * the object disappears

         */

        UBUS_MSG_SUBSCRIBE,

        UBUS_MSG_UNSUBSCRIBE,


        /*

         * send a notification to all subscribers of an object.

         * when sent from the server, it indicates a subscription

         * status change

         */

        UBUS_MSG_NOTIFY,


        UBUS_MSG_MONITOR,


        /* must be last */

        __UBUS_MSG_LAST,

};


seq是消息的序号,每个endpoint都保存一个seq到 context,请求的时候,这个seq不停的递增,这样就可以标识每一个request。

peer表示对端是谁,如果是发给ubusd,比如add object消息,peer为0,如果是ubus call,则peer是object id。



libubus接口

1 连接

struct ubus_context {

        struct list_head requests;

        struct avl_tree objects;

        struct list_head pending;


        struct uloop_fd sock;

        struct uloop_timeout pending_timer;


        uint32_t local_id;

        uint16_t request_seq;

        bool cancel_poll;

        int stack_depth;


        void (*connection_lost)(struct ubus_context *ctx);

        void (*monitor_cb)(struct ubus_context *ctx, uint32_t seq, struct blob_attr *data);


        struct ubus_msghdr_buf msgbuf;

        uint32_t msgbuf_data_len;

        int msgbuf_reduction_counter;

};


connection_lost成员是一个回调函数,在连接丢失的时候执行。

缺省是 ubus_default_connection_lost

static void ubus_default_connection_lost(struct ubus_context *ctx)

{

        if (ctx->sock.registered)

                uloop_end();

}

会执行uloop_end()

static inline void uloop_end(void)

{

        uloop_cancelled = true;

}

结束uloop。


1 接口 ubus_connect()

struct ubus_context *ubus_connect(const char *path)

path是ubusd服务器的unix socket路径,传入NULL,使用默认的路径。

这个接口分配一个ubus_context 对象,然后调用ubus_connect_ctx()连接到ubusd。


其对应的释放接口是: void ubus_free(struct ubus_context *ctx)

调用 ubus_shutdown()。然后  free(ctx)


ubus_connect_ctx()

int ubus_connect_ctx(struct ubus_context *ctx, const char *path)

初始化 ctx。调用 ubus_reconnect(ctx, path) 连接到 ubusd。


其对应的释放接口为:void ubus_shutdown(struct ubus_context *ctx);


3 ubus_reconnect()

第一次连接,和重新连接都是调用这个接口。连接的实例id保存到 local_id。

注意:它会调用 ubus_refresh_state() 重新注册objects。


ubus_add_uloop()

static inline void ubus_add_uloop(struct ubus_context *ctx)

将ubus的socket加到uloop中运行。


客户端连接后,ubusd会回复一个hello消息。


2 自动连接

自动连接,是在连接断开的时候,自动重新连接。连接的失败的时候,会起一个定时器重新连接,直接到连接成功。


struct ubus_auto_conn {

        struct ubus_context ctx;

        struct uloop_timeout timer;

        const char *path;

        ubus_connect_handler_t cb;

};


自动连接将ubus_context嵌入到自己的结构体中,timer用来做失败和重连时的定时器。

cb用来在连接成功之后执行,通常用来添加objects。

连接成功后,会调用 ubus_add_uloop()。


3 object注册和解注册


struct ubus_method {

        const char *name;

        ubus_handler_t handler;


        unsigned long mask;

        unsigned long tags;

        const struct blobmsg_policy *policy;

        int n_policy;

};


struct ubus_object_type {

        const char *name;

        uint32_t id;


        const struct ubus_method *methods;

        int n_methods;

};


struct ubus_object {

        struct avl_node avl;


        const char *name;

        uint32_t id;


        const char *path;

        struct ubus_object_type *type;


        ubus_state_handler_t subscribe_cb;

        bool has_subscribers;


        const struct ubus_method *methods;

        int n_methods;

};


一个定义object的例子:


static const struct blobmsg_policy route_policy[__HR_MAX] = {

        [HR_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },

        [HR_V6] = { .name = "v6", .type = BLOBMSG_TYPE_BOOL },

        [HR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING },

};


static struct ubus_method main_object_methods[] = {

        { .name = "restart", .handler = netifd_handle_restart },

        { .name = "reload", .handler = netifd_handle_reload },

        UBUS_METHOD("add_host_route", netifd_add_host_route, route_policy),

        { .name = "get_proto_handlers", .handler = netifd_get_proto_handlers },

        UBUS_METHOD("add_dynamic", netifd_add_dynamic, dynamic_policy),

        UBUS_METHOD("netns_updown", netifd_netns_updown, netns_updown_policy),

};


static struct ubus_object_type main_object_type =

        UBUS_OBJECT_TYPE("netifd", main_object_methods);


static struct ubus_object main_object = {

        .name = "network",

        .type = &main_object_type,

        .methods = main_object_methods,

        .n_methods = ARRAY_SIZE(main_object_methods),

};


ubus_object的type是用来定义object有哪些method,method的policy有哪些。type会发给ubusd,ubusd用来来做数据合法性验证。

ubus_object的methods是给自己用的,不发给ubusd,当有消息来时,用来找到method,并执行相关的cb。

有些特殊的object,不需要type,比如 event和subscribe添加的object,他们只需要一个object id用来追踪事件等。


添加、删除object的接口,注意object需要long live,不能是一个临时变量,其作为一个指针保存在context的avl树中。

int ubus_add_object(struct ubus_context *ctx, struct ubus_object *obj);

int ubus_remove_object(struct ubus_context *ctx, struct ubus_object *obj);


查找一个或所有object:

int ubus_lookup(struct ubus_context *ctx, const char *path,

                ubus_lookup_handler_t cb, void *priv);


根据名字查找object id:

int ubus_lookup_id(struct ubus_context *ctx, const char *path, uint32_t *id);


4 invoke

ubus call 方式,就是调用的invoke接口,参数信息:

obj: obj的id

method:method的名字(字符串类型)

msg: blob格式的payload

cb: 回调函数

timeout: 超时时间


static inline 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)

{

        return ubus_invoke_fd(ctx, obj, method, msg, cb, priv, timeout, -1);

}


static inline int

ubus_invoke_async(struct ubus_context *ctx, uint32_t obj, const char *method,

                  struct blob_attr *msg, struct ubus_request *req)

{

        return ubus_invoke_async_fd(ctx, obj, method, msg, req, -1);

}


4 subscribe / notify


订阅一个object的事件


struct ubus_subscriber {

        struct ubus_object obj;


        ubus_handler_t cb;

        ubus_remove_handler_t remove_cb;

};


订阅者,也要添加一个object,不过这个object,由libubus的接口自己封装好了:

int ubus_register_subscriber(struct ubus_context *ctx, struct ubus_subscriber *obj);


static inline int

ubus_unregister_subscriber(struct ubus_context *ctx, struct ubus_subscriber *obj)

{

        return ubus_remove_object(ctx, &obj->obj);

}


上述接口ubus_register_subscriber注册一个订阅者后,还需要调用下面的接口,来订阅object。

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);

id是object的id。


通知者,使用下面的接口,来通知subscribe:

int ubus_notify(struct ubus_context *ctx, struct ubus_object *obj,

                const char *type, struct blob_attr *msg, int timeout);


5 event


typedef void (*ubus_event_handler_t)(struct ubus_context *ctx, struct ubus_event_handler *ev,

                                     const char *type, struct blob_attr *msg);


struct ubus_event_handler {

        struct ubus_object obj;


        ubus_event_handler_t cb;

};


obj不用自己去封装,由libubus的注册接口自己封装。

事件包括事件名、事件payload两部分,事件名是一个字符串,payload是一个json格式的数据。


发送事件:

int ubus_send_event(struct ubus_context *ctx, const char *id,

                    struct blob_attr *data);

id,是事件名,字符串类型。

data,是事件的payload,blob json格式。


注册事件接收:

int ubus_register_event_handler(struct ubus_context *ctx,

                                struct ubus_event_handler *ev,

                                const char *pattern);

pattern是要监听的事件名,* 表示所有事件。

这个接口可以调用多次,来同时监听多个事件。第一次注册时,会添加一个object。


static inline int ubus_unregister_event_handler(struct ubus_context *ctx,

                                                struct ubus_event_handler *ev)

{

    return ubus_remove_object(ctx, &ev->obj);

}


例子:


// server

void test_event_child()

{

        struct ubus_context * ctx;

        struct blob_buf b = {0};


        blob_buf_init(&b, 0);


        ctx = ubus_connect(NULL);

        if (!ctx) {

                fprintf(stderr, "child connect ubus failed\n");

                return;

        }


        ubus_send_event(ctx, "myevent", b.head);

}


// client

void my_event_handler(struct ubus_context *ctx,

                struct ubus_event_handler *ev,

                const char *type, struct blob_attr *msg)

{

        printf("recv event: %s\n", type);

}


struct ubus_event_handler ev_handler = {

        .cb = my_event_handler,

};


void test_event_parent()

{

        struct ubus_context * ctx;

        int ret;


        uloop_init();


        ctx = ubus_connect(NULL);

        if (!ctx) {

                fprintf(stderr, "parent connect ubus failed\n");

                return;

        }


        ret = ubus_register_event_handler(ctx, &ev_handler, "myevent");

        if (ret < 0) {

                fprintf(stderr, "register event handler failed: %s\n", ubus_strerror(ret));

                return;

        }


        ubus_add_uloop(ctx);

        uloop_run();

}


参考openwrt使用到ubus的包:

netifd/ubus.c

ubox/logd.c


https://e-mailky.github.io/2017-08-14-ubus2


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.