From ea4a3f28dcc91cd70c883484446eb9157bac4aa3 Mon Sep 17 00:00:00 2001 From: Nick Hainke Date: Thu, 17 Dec 2020 12:41:32 +0100 Subject: [PATCH] babeld: add ubus bindings A better integration of babeld with OpenWrt is to connect the daemon to the IPC. So far, we can only communicate via a websocket. With ubus we can send and receive commands in json format. The commit adds a ubus interface to babeld with following functions: - get_info - get_neighbours - get_xroutes - get_routes All output is divided into IPv4 and IPv6. Ubus has to be enabled by setting "config general" option 'ubus_bindings' 'true' Example: root@OpenWrt:~# ubus call babeld get_info { "babeld-version": "babeld-1.9.2", "my-id": "32:xx:xx:xx:xx:xx:xx:xx", "host": "OpenWrt" } root@OpenWrt:~# ubus call babeld get_neighbours { "IPv4": { }, "IPv6": { "fe80::xx:xx:xx:xxx": { "dev": "br-lan", "hello-reach": 65408, "uhello-reach": 0, "rxcost": 96, "txcost": 96, "rtt": 4338271, "channel": -2, "if_up": true } } } root@OpenWrt:~# ubus call babeld get_xroutes { "IPv4": { "10.0.0.3/32": { "src-prefix": "0.0.0.0/0", "metric": 0 }, "10.0.0.0/24": { "src-prefix": "0.0.0.0/0", "metric": 0 } }, "IPv6": { "fdfa:xx:xx::1/128": { "src-prefix": "::/0", "metric": 0 } } } root@OpenWrt:~# ubus call babeld get_routes { "IPv4": { "10.2.0.1/32": { "src-prefix": "0.0.0.0/0", "route_metric": 96, "route_smoothed_metric": 96, "refmetric": 0, "id": "62:xx:xx:xx:xx:xx:xx:xx", "seqno": 41381, "channels": "", "age": 17, "via": "fe80::xx:xxxx:xxxx:xxxx", "nexthop": " nexthop ", "installed": true, "feasible": true }, "IPv6": { } } Additional IPC functionality will follow. Further, we changed the version to $version-ubus-mod. Signed-off-by: Nick Hainke --- babeld/Makefile | 5 +- babeld/files/babeld.config | 1 + babeld/patches/600-add-ubus.patch | 93 ++++++++ babeld/src/ubus.c | 384 ++++++++++++++++++++++++++++++ babeld/src/ubus.h | 45 ++++ 5 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 babeld/patches/600-add-ubus.patch create mode 100644 babeld/src/ubus.c create mode 100644 babeld/src/ubus.h diff --git a/babeld/Makefile b/babeld/Makefile index 5318889..43d9133 100644 --- a/babeld/Makefile +++ b/babeld/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=babeld PKG_VERSION:=1.9.2 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://www.irif.fr/~jch/software/files/ @@ -28,7 +28,7 @@ define Package/babeld SUBMENU:=Routing and Redirection TITLE:=A loop-avoiding distance-vector routing protocol URL:=https://www.irif.fr/~jch/software/babel/ - DEPENDS:=@IPV6 + DEPENDS:=@IPV6 +libubus +libubox endef define Package/babeld/description @@ -47,6 +47,7 @@ endef MAKE_FLAGS+= \ CFLAGS="$(TARGET_CFLAGS)" \ LDLIBS="" \ + LDLIBS+="-lubus -lubox" define Package/babeld/install $(INSTALL_DIR) $(1)/usr/sbin diff --git a/babeld/files/babeld.config b/babeld/files/babeld.config index a02f202..1e9367e 100644 --- a/babeld/files/babeld.config +++ b/babeld/files/babeld.config @@ -24,6 +24,7 @@ config general ## See comment at the top of this file for more details. # option 'conf_file' '/etc/babeld.conf' # option 'conf_dir' '/tmp/babel.d/' + # option 'ubus_bindings' 'false' config interface ## Remove this line to enable babeld on this interface diff --git a/babeld/patches/600-add-ubus.patch b/babeld/patches/600-add-ubus.patch new file mode 100644 index 0000000..7ffa47e --- /dev/null +++ b/babeld/patches/600-add-ubus.patch @@ -0,0 +1,93 @@ +--- a/babeld.c ++++ b/babeld.c +@@ -55,6 +55,8 @@ THE SOFTWARE. + #include "rule.h" + #include "version.h" + ++#include "ubus.h" ++ + struct timeval now; + + unsigned char myid[8]; +@@ -536,6 +538,9 @@ main(int argc, char **argv) + } + } + ++ if(ubus_bindings) ++ babeld_add_ubus(); ++ + init_signals(); + rc = resize_receive_buffer(1500); + if(rc < 0) +@@ -635,6 +640,8 @@ main(int argc, char **argv) + FD_SET(local_sockets[i].fd, &readfds); + maxfd = MAX(maxfd, local_sockets[i].fd); + } ++ if(ubus_bindings) ++ maxfd = babeld_ubus_add_read_sock(&readfds, maxfd); + rc = select(maxfd + 1, &readfds, NULL, NULL, &tv); + if(rc < 0) { + if(errno != EINTR) { +@@ -703,6 +710,9 @@ main(int argc, char **argv) + i++; + } + ++ if(ubus_bindings) ++ babeld_ubus_receive(&readfds); ++ + if(reopening) { + kernel_dump_time = now.tv_sec; + check_neighbours_timeout = now; +--- a/Makefile ++++ b/Makefile +@@ -11,11 +11,11 @@ LDLIBS = -lrt + + SRCS = babeld.c net.c kernel.c util.c interface.c source.c neighbour.c \ + route.c xroute.c message.c resend.c configuration.c local.c \ +- disambiguation.c rule.c ++ disambiguation.c rule.c ubus.c + + OBJS = babeld.o net.o kernel.o util.o interface.o source.o neighbour.o \ + route.o xroute.o message.o resend.o configuration.o local.o \ +- disambiguation.o rule.o ++ disambiguation.o rule.o ubus.o + + babeld: $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) -o babeld $(OBJS) $(LDLIBS) +--- a/generate-version.sh ++++ b/generate-version.sh +@@ -10,4 +10,4 @@ else + version="unknown" + fi + +-echo "#define BABELD_VERSION \"$version\"" ++echo "#define BABELD_VERSION \"$version-ubus-mod\"" +--- a/configuration.c ++++ b/configuration.c +@@ -41,6 +41,7 @@ THE SOFTWARE. + #include "kernel.h" + #include "configuration.h" + #include "rule.h" ++#include "ubus.h" + + static struct filter *input_filters = NULL; + static struct filter *output_filters = NULL; +@@ -850,7 +851,8 @@ parse_option(int c, gnc_t gnc, void *clo + strcmp(token, "daemonise") == 0 || + strcmp(token, "skip-kernel-setup") == 0 || + strcmp(token, "ipv6-subtrees") == 0 || +- strcmp(token, "reflect-kernel-metric") == 0) { ++ strcmp(token, "reflect-kernel-metric") == 0 || ++ strcmp(token, "ubus-bindings") == 0) { + int b; + c = getbool(c, &b, gnc, closure); + if(c < -1) +@@ -868,6 +870,8 @@ parse_option(int c, gnc_t gnc, void *clo + has_ipv6_subtrees = b; + else if(strcmp(token, "reflect-kernel-metric") == 0) + reflect_kernel_metric = b; ++ else if(strcmp(token, "ubus-bindings") == 0) ++ ubus_bindings = b; + else + abort(); + } else if(strcmp(token, "protocol-group") == 0) { diff --git a/babeld/src/ubus.c b/babeld/src/ubus.c new file mode 100644 index 0000000..5bec96f --- /dev/null +++ b/babeld/src/ubus.c @@ -0,0 +1,384 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "babeld.h" +#include "configuration.h" +#include "interface.h" +#include "kernel.h" +#include "local.h" +#include "message.h" +#include "neighbour.h" +#include "net.h" +#include "resend.h" +#include "route.h" +#include "rule.h" +#include "source.h" +#include "util.h" +#include "version.h" +#include "xroute.h" + +#include "ubus.h" + +// Definition of header variable whether to enable ubus bindings. +int ubus_bindings = 0; + +// Shared state maintained throughout calls to handle ubus messages. +static struct ubus_context *shared_ctx; + +// List of exported routes (to be used with ubox's list helpers). +struct xroute_list_entry { + struct list_head list; + struct xroute *xroute; +}; + +// List of received routes (to be used with ubox's list helpers). +struct route_list_entry { + struct list_head list; + struct route_stream *route; +}; + +// List of neighbours (to be used with ubox's list helpers). +struct neighbour_list_entry { + struct list_head list; + struct neighbour *neighbour; +}; + +// Sends a babel info message on ubus socket. +static void babeld_ubus_babeld_info(struct ubus_context *ctx_local, + struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, struct blob_attr *msg) { + struct blob_buf b = {0}; + void *prefix; + char host[64]; + int ret; + + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "babeld-version", BABELD_VERSION); + blobmsg_add_string(&b, "my-id", format_eui64(myid)); + if (!gethostname(host, sizeof(host))) + blobmsg_add_string(&b, "host", host); + + ret = ubus_send_reply(ctx_local, req, b.head); + if (ret) + fprintf(stderr, "Failed to send reply: %s\n", ubus_strerror(ret)); +} + +// Appends an exported route message entry to the buffer. +static void babeld_add_xroute_buf(struct xroute *xroute, struct blob_buf *b) { + void *prefix; + + prefix = blobmsg_open_table(b, format_prefix(xroute->prefix, xroute->plen)); + + blobmsg_add_string(b, "src-prefix", + format_prefix(xroute->src_prefix, xroute->src_plen)); + blobmsg_add_u32(b, "metric", xroute->metric); + blobmsg_close_table(b, prefix); +} + +// Sends an exported routes message on ubus socket, splitting apart IPv4 and IPv6 routes. +static void babeld_ubus_get_xroutes(struct ubus_context *ctx_local, + struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, struct blob_attr *msg) { + struct blob_buf b = {0}; + struct xroute_stream *xroutes; + struct xroute_list_entry *cur, *tmp; + void *ipv4, *ipv6; + int ret; + LIST_HEAD(xroute_ipv4_list); + LIST_HEAD(xroute_ipv6_list); + + xroutes = xroute_stream(); + if (xroutes) { + while (1) { + struct xroute *xroute = xroute_stream_next(xroutes); + if (xroute == NULL) + break; + + struct xroute_list_entry *xr = + calloc(1, sizeof(struct xroute_list_entry)); + xr->xroute = xroute; + + if (v4mapped(xroute->prefix)) { + list_add(&xr->list, &xroute_ipv4_list); + } else { + list_add(&xr->list, &xroute_ipv6_list); + } + } + xroute_stream_done(xroutes); + } + + blob_buf_init(&b, 0); + ipv4 = blobmsg_open_table(&b, "IPv4"); + list_for_each_entry_safe(cur, tmp, &xroute_ipv4_list, list) { + babeld_add_xroute_buf(cur->xroute, &b); + list_del(&cur->list); + free(cur); + } + blobmsg_close_table(&b, ipv4); + + ipv6 = blobmsg_open_table(&b, "IPv6"); + list_for_each_entry_safe(cur, tmp, &xroute_ipv6_list, list) { + babeld_add_xroute_buf(cur->xroute, &b); + list_del(&cur->list); + free(cur); + } + blobmsg_close_table(&b, ipv6); + + ret = ubus_send_reply(ctx_local, req, b.head); + if (ret) + fprintf(stderr, "Failed to send reply: %s\n", ubus_strerror(ret)); +} + +// Appends an route message entry to the buffer. +static void babeld_add_route_buf(struct babel_route *route, + struct blob_buf *b) { + void *prefix; + char channels[100]; + + if (route->channels_len == 0) { + channels[0] = '\0'; + } else { + int i, j = 0; + snprintf(channels, sizeof(channels), " chan ("); + j = strlen(channels); + for (i = 0; i < route->channels_len; i++) { + if (i > 0) + channels[j++] = ','; + snprintf(channels + j, sizeof(channels) - j, "%u", + (unsigned)route->channels[i]); + j = strlen(channels); + } + snprintf(channels + j, sizeof(channels) - j, ")"); + } + + prefix = blobmsg_open_table( + b, format_prefix(route->src->prefix, route->src->plen)); + + blobmsg_add_string( + b, "src-prefix", + format_prefix(route->src->src_prefix, route->src->src_plen)); + blobmsg_add_u32(b, "route_metric", route_metric(route)); + blobmsg_add_u32(b, "route_smoothed_metric", route_smoothed_metric(route)); + blobmsg_add_u32(b, "refmetric", route->refmetric); + blobmsg_add_string(b, "id", format_eui64(route->src->id)); + blobmsg_add_u32(b, "seqno", (uint32_t)route->seqno); + blobmsg_add_string(b, "channels", channels); + blobmsg_add_u32(b, "age", (int)(now.tv_sec - route->time)); + blobmsg_add_string(b, "via", format_address(route->neigh->address)); + if (memcmp(route->nexthop, route->neigh->address, 16) != 0) + blobmsg_add_string(b, "nexthop", format_address(route->nexthop)); + + blobmsg_add_u8(b, "installed", route->installed); + blobmsg_add_u8(b, "feasible", route_feasible(route)); + + blobmsg_close_table(b, prefix); +} + +// Sends received routes message on ubus socket, splitting apart IPv4 and IPv6 routes. +static void babeld_ubus_get_routes(struct ubus_context *ctx_local, + struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, struct blob_attr *msg) { + struct blob_buf b = {0}; + struct route_stream *routes; + struct route_list_entry *cur, *tmp; + void *prefix, *ipv4, *ipv6; + int ret; + LIST_HEAD(route_ipv4_list); + LIST_HEAD(route_ipv6_list); + + blob_buf_init(&b, 0); + + routes = route_stream(ROUTE_ALL); + if (routes) { + while (1) { + struct babel_route *route = route_stream_next(routes); + if (route == NULL) + break; + struct route_list_entry *r = calloc(1, sizeof(struct route_list_entry)); + r->route = route; + + if (v4mapped(route->src->prefix)) { + list_add(&r->list, &route_ipv4_list); + } else { + list_add(&r->list, &route_ipv6_list); + } + } + route_stream_done(routes); + } + + blob_buf_init(&b, 0); + ipv4 = blobmsg_open_table(&b, "IPv4"); + list_for_each_entry_safe(cur, tmp, &route_ipv4_list, list) { + babeld_add_route_buf(cur->route, &b); + list_del(&cur->list); + free(cur); + } + blobmsg_close_table(&b, ipv4); + + ipv6 = blobmsg_open_table(&b, "IPv6"); + list_for_each_entry_safe(cur, tmp, &route_ipv6_list, list) { + babeld_add_route_buf(cur->route, &b); + list_del(&cur->list); + free(cur); + } + blobmsg_close_table(&b, ipv6); + + ret = ubus_send_reply(ctx_local, req, b.head); + if (ret) + fprintf(stderr, "Failed to send reply: %s\n", ubus_strerror(ret)); +} + +// Appends an neighbour entry to the buffer. +static void babeld_add_neighbour_buf(struct neighbour *neigh, + struct blob_buf *b) { + void *neighbour; + + neighbour = blobmsg_open_table(b, format_address(neigh->address)); + blobmsg_add_string(b, "dev", neigh->ifp->name); + blobmsg_add_u32(b, "hello-reach", neigh->hello.reach); + blobmsg_add_u32(b, "uhello-reach", neigh->uhello.reach); + blobmsg_add_u32(b, "rxcost", neighbour_rxcost(neigh)); + blobmsg_add_u32(b, "txcost", neigh->txcost); + blobmsg_add_u32(b, "rtt", format_thousands(neigh->rtt)); + blobmsg_add_u32(b, "channel", neigh->ifp->channel); + blobmsg_add_u8(b, "if_up", if_up(neigh->ifp)); + blobmsg_close_table(b, neighbour); +} + +// Sends neighbours message on ubus socket, splitting apart IPv4 and IPv6 neighbours. +static void babeld_ubus_get_neighbours(struct ubus_context *ctx_local, + struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, + struct blob_attr *msg) { + struct blob_buf b = {0}; + struct neighbour *neigh; + struct neighbour_list_entry *cur, *tmp; + void *ipv4, *ipv6; + int ret; + LIST_HEAD(neighbour_ipv4_list); + LIST_HEAD(neighbour_ipv6_list); + + blob_buf_init(&b, 0); + + FOR_ALL_NEIGHBOURS(neigh) { + struct neighbour_list_entry *n = + calloc(1, sizeof(struct neighbour_list_entry)); + n->neighbour = neigh; + if (v4mapped(neigh->address)) { + list_add(&n->list, &neighbour_ipv4_list); + } else { + list_add(&n->list, &neighbour_ipv6_list); + } + } + + blob_buf_init(&b, 0); + + ipv4 = blobmsg_open_table(&b, "IPv4"); + list_for_each_entry_safe(cur, tmp, &neighbour_ipv4_list, list) { + babeld_add_neighbour_buf(cur->neighbour, &b); + list_del(&cur->list); + free(cur); + } + blobmsg_close_table(&b, ipv4); + + ipv6 = blobmsg_open_table(&b, "IPv6"); + list_for_each_entry_safe(cur, tmp, &neighbour_ipv6_list, list) { + babeld_add_neighbour_buf(cur->neighbour, &b); + list_del(&cur->list); + free(cur); + } + blobmsg_close_table(&b, ipv6); + + ret = ubus_send_reply(ctx_local, req, b.head); + if (ret) + fprintf(stderr, "Failed to send reply: %s\n", ubus_strerror(ret)); +} + +// List of functions we expose via the ubus bus. +static const struct ubus_method babeld_methods[] = { + UBUS_METHOD_NOARG("get_info", babeld_ubus_babeld_info), + UBUS_METHOD_NOARG("get_xroutes", babeld_ubus_get_xroutes), + UBUS_METHOD_NOARG("get_routes", babeld_ubus_get_routes), + UBUS_METHOD_NOARG("get_neighbours", babeld_ubus_get_neighbours), +}; + +// Definition of the ubus object type. +static struct ubus_object_type babeld_object_type = + UBUS_OBJECT_TYPE("babeld", babeld_methods); + +// Object we announce via the ubus bus. +static struct ubus_object babeld_object = { + .name = "babeld", + .type = &babeld_object_type, + .methods = babeld_methods, + .n_methods = ARRAY_SIZE(babeld_methods), +}; + +// Registers handlers for babel methods in the global ubus context. +static bool ubus_init_object() { + int ret; + + ret = ubus_add_object(shared_ctx, &babeld_object); + if (ret) { + fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret)); + return false; + } + + return true; +} + +// Initializes the global ubus context, connecting to the bus to be able to receive and send messages. +static bool babeld_ubus_init(void) { + if (shared_ctx) + return true; + + shared_ctx = ubus_connect(NULL); + if (!shared_ctx) + return false; + + return true; +} + +void babeld_ubus_receive(fd_set *readfds) { + if (!shared_ctx) + return; + if (FD_ISSET(shared_ctx->sock.fd, readfds)) + ubus_handle_event(shared_ctx); +} + +int babeld_ubus_add_read_sock(fd_set *readfds, int maxfd) { + if (!shared_ctx) + return maxfd; + + FD_SET(shared_ctx->sock.fd, readfds); + return MAX(maxfd, shared_ctx->sock.fd); +} + +bool babeld_add_ubus() { + if (!babeld_ubus_init()) { + fprintf(stderr, "Failed to initialize ubus!\n"); + return false; + } + + if (!ubus_init_object()) { + fprintf(stderr, "Failed to add objects to ubus!\n"); + return false; + } + + return true; +} diff --git a/babeld/src/ubus.h b/babeld/src/ubus.h new file mode 100644 index 0000000..84be97f --- /dev/null +++ b/babeld/src/ubus.h @@ -0,0 +1,45 @@ +/* + IPC integration of babeld with OpenWrt. + + The ubus interface offers following functions: + - get_info + - get_neighbours + - get_xroutes + - get_routes + + All output is divided into IPv4 and IPv6. +*/ + +#include + +// Whether to enable ubus bindings (boolean option). +extern int ubus_bindings; + +/** + * Initialize ubus interface. + * + * Connect to the ubus daemon and expose the ubus functions. + * + * @return if initializing ubus was successful + */ +bool babeld_add_ubus(); + +/** + * Add ubus socket to given filedescriptor set. + * + * We need to check repeatedly if the ubus socket has something to read. + * The functions allows to add the ubus socket to the normal while(1)-loop of babeld. + * + * @param readfs: the filedescriptor set + * @param maxfd: the current maximum file descriptor + * @return the maximum file descriptor + */ +int babeld_ubus_add_read_sock(fd_set* readfds, int maxfd); + + +/** + * Check and process ubus socket. + * + * If the ubus-socket signals that data is available, the ubus_handle_event is called. + */ +void babeld_ubus_receive(fd_set* readfds); -- 2.30.2