babeld: add ubus bindings 634/head
authorNick Hainke <vincent@systemli.org>
Thu, 17 Dec 2020 11:41:32 +0000 (12:41 +0100)
committerGabriel Kerneis <gabriel@kerneis.info>
Fri, 15 Jan 2021 00:13:26 +0000 (01:13 +0100)
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 <vincent@systemli.org>
babeld/Makefile
babeld/files/babeld.config
babeld/patches/600-add-ubus.patch [new file with mode: 0644]
babeld/src/ubus.c [new file with mode: 0644]
babeld/src/ubus.h [new file with mode: 0644]

index 5318889e8f07649014014d020d01925965edac1f..43d9133e4865dfc3f2b73830541ea6fd094018d0 100644 (file)
@@ -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
index a02f2023bdd0a12ac82507696dbcf157fe3a6750..1e9367e7cfa7af0dd2bf1749b14f27b4ecc4a1ce 100644 (file)
@@ -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 (file)
index 0000000..7ffa47e
--- /dev/null
@@ -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 (file)
index 0000000..5bec96f
--- /dev/null
@@ -0,0 +1,384 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/select.h>
+
+#include <libubox/blob.h>
+#include <libubox/blobmsg.h>
+#include <libubox/list.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#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 (file)
index 0000000..84be97f
--- /dev/null
@@ -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 <libubus.h>
+
+// 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);