diff --git a/net/babeld/Makefile b/net/babeld/Makefile new file mode 100644 index 00000000000000..a6832bd2f87885 --- /dev/null +++ b/net/babeld/Makefile @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2007-2021 OpenWrt.org +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=babeld +PKG_VERSION:=1.13.1 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://www.irif.fr/~jch/software/files/ +PKG_HASH:=15f24d26da0ccfc073abcdef0309f281e4684f2aa71126f826572c4c845e8dd9 + +PKG_MAINTAINER:=Gabriel Kerneis , \ + Baptiste Jonglez , \ + Nick Hainke +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=LICENCE + +include $(INCLUDE_DIR)/package.mk + +define Package/babeld + SECTION:=net + CATEGORY:=Network + SUBMENU:=Routing and Redirection + TITLE:=A loop-avoiding distance-vector routing protocol + URL:=https://www.irif.fr/~jch/software/babel/ + DEPENDS:=@IPV6 +libubus +libubox +endef + +define Package/babeld/description + Babel is a loop-avoiding distance-vector routing protocol for IPv6 and IPv4 + with fast convergence properties. It is based on the ideas in DSDV, AODV and + Cisco's EIGRP, but is designed to work well not only in wired networks but + also in wireless mesh networks, and has been extended with support for + overlay networks. Babel is an IETF standard protocol (RFC 8966). +endef + +define Package/babeld/conffiles +/etc/babeld.conf +/etc/config/babeld +endef + +MAKE_FLAGS+= \ + CFLAGS="$(TARGET_CFLAGS)" \ + LDLIBS="-lubus -lubox" + +define Package/babeld/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/babeld $(1)/usr/sbin/ + $(INSTALL_DIR) $(1)/etc + $(INSTALL_CONF) ./files/babeld.conf $(1)/etc/ + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/babeld.config $(1)/etc/config/babeld + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/babeld.init $(1)/etc/init.d/babeld +endef + +$(eval $(call BuildPackage,babeld)) diff --git a/net/babeld/files/babeld.conf b/net/babeld/files/babeld.conf new file mode 100644 index 00000000000000..746139d31780b3 --- /dev/null +++ b/net/babeld/files/babeld.conf @@ -0,0 +1,38 @@ +# babel config file +# +# This config file simply documents sample entries. +# "redistribute" means: redistribute routes from other routing protocols +# into babel. "local" means addresses assigned to local interfaces. +# +# You do not need to edit this file: you can use /etc/config/babeld +# instead (sections "interface" and "filter"). Both files can be used +# simultaneously (the rules of this file are executed first). + +# the default rules are: +# +## redistribute local +## redistribute deny +# +# this says, redistribute local addresses but no other routes + + +# redistribute IPv4 default route into babel +## redistribute local ip 0.0.0.0/0 le 0 metric 128 + +# same but for IPv6 +## redistribute local ip ::/0 le 0 metric 128 + + +# don't redistribute all local addresses, only selected ones +# after the first line, the "deny" rules kicks in. After the "deny" +# no redistribute local rules are going to match +## redistribute local ip 192.160.4.0/24 +## redistribute local deny + + +# Babel refuses to redistribute routes with a protocol number of "boot"; +# this is standard practice, and means that you cannot easily +# redistribute the default route installed by dhcp. It is however +# possible to redistribute such route by explicitly specifying "proto 3" +# on the redistribute line. +## redistribute ip 0.0.0.0/0 le 0 proto 3 metric 128 diff --git a/net/babeld/files/babeld.config b/net/babeld/files/babeld.config new file mode 100644 index 00000000000000..1e9367e7cfa7af --- /dev/null +++ b/net/babeld/files/babeld.config @@ -0,0 +1,103 @@ +package babeld + +# Detailed documentation: https://openwrt.org/docs/guide-user/services/babeld + +# Babeld reads options from the following files (the last one takes precedence +# if an option is defined in several places): +# - the file defined by the option conf_file (default: /etc/babeld.conf), +# - *.conf files in the directory defined by conf_dir (default: /tmp/babel.d/), +# - this UCI configuration file. + +# See "man babeld" for all available options ("Global options"). +# Important: remember to use '_' instead of '-' in option names. +config general + # option 'random_id' 'true' + # option 'debug' '1' + # option 'local_port' '33123' + # option 'log_file' '/var/log/babeld.log' + ## Enable ipv6-subtrees by default since OpenWrt should ship with a + ## recent enough kernel for it to work. + option 'ipv6_subtrees' 'true' + # list 'import_table' '42' + # list 'import_table' '100' + ## Alternative configuration file and directory. + ## 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 + option 'ignore' 'true' + ## You can use aliases (like lan, wlan) or real names (like eth0.0). + ## If you use an alias, it must be already defined when babeld starts. + ## Otherwise, the name is taken literally and the interface can be + ## brought up later (useful for tunnels for instance). + option 'ifname' 'wlan' + ## You can set options, see babeld man page ("Interface configuration") + # option 'rxcost' '256' + # option 'hello_interval' '1' + +config interface + option 'ignore' 'true' + ## Physical interface name + option 'ifname' 'tun-example' + ## Specify the type of interface: tunnels use the RTT-based metric. + option 'type' 'tunnel' + ## Other options that can be overriden. + # option 'max_rtt_penalty' '96' + +# A config interface without "option ifname" will set default options +# for all interfaces. Interface-specific configuration always overrides +# default configuration. +config interface + # option 'enable_timestamps' 'true' + # option 'update_interval' '30' + + +# A filter consists of a type ('in', 'out', 'redistribute' or 'install'), +# a set of selectors ('ip', 'eq', etc.) and a set of actions to perform +# ('allow', 'deny', 'metric xxx', 'src-prefix xxx', 'table xxx', 'pref-src xxx'). +# See babeld man page ("Filtering rules") for more details. + +# Below is a sample filter that redistributes the default route if its +# protocol number is "boot", e.g. when it is installed by dhcp (see +# /etc/iproute2/rt_protos). This filter is disabled thanks to the 'ignore' +# setting. +config filter + option 'ignore' 'true' + # Type of filter + option 'type' 'redistribute' + # Selectors: ip, eq, le, ge, src_ip, src_eq, src_le, src_ge, neigh, id, + # proto, local, if. + option 'ip' '0.0.0.0/0' + option 'eq' '0' + option 'proto' '3' + # Action, which can be any of: allow, deny, metric , src-prefix , + # table , pref-src . + # The action defaults to "allow" if not specified. Here, we specify a higher + # redistribution metric than the default (0). + option 'action' 'metric 128' + +# Another example filter: don't redistribute local addresses in a certain IP prefix. +# By default, babeld redistributes *all* local addresses. +config filter + option 'ignore' 'true' + option 'type' 'redistribute' + # Only apply to routes/addresses within this prefix. + option 'ip' '198.51.100.0/24' + # Notice that the 'local' selector is a boolean. + option 'local' 'true' + # Don't redistribute. + option 'action' 'deny' + +# Example install filter, to change or filter routes before they are inserted +# into the kernel. +config filter + option 'ignore' 'true' + option 'type' 'install' + # Optional: only apply to routes within 2001:db8:cafe::/48 + option 'ip' '2001:db8:cafe::/48' + # We specify the kernel routing table and the preferred source address to use for these routes. + # "Allow" is implicit. + option 'action' 'table 200 pref-src 2001:db8:ba:be1::42' diff --git a/net/babeld/files/babeld.init b/net/babeld/files/babeld.init new file mode 100755 index 00000000000000..378a8abcdb7e5e --- /dev/null +++ b/net/babeld/files/babeld.init @@ -0,0 +1,206 @@ +#!/bin/sh /etc/rc.common + +. $IPKG_INSTROOT/lib/functions/network.sh + +USE_PROCD=1 +START=70 + +CONFIGFILE='/var/etc/babeld.conf' +OTHERCONFIGFILE="/etc/babeld.conf" +OTHERCONFIGDIR="/tmp/babeld.d/" +EXTRA_COMMANDS="status" +EXTRA_HELP=" status Dump Babel's table to the log file." + +# Append a line to the configuration file +cfg_append() { + local value="$1" + echo "$value" >> "$CONFIGFILE" +} + +cfg_append_option() { + local section="$1" + local option="$2" + local value + config_get value "$section" "$option" + # babeld convention for options is '-', not '_' + [ -n "$value" ] && cfg_append "${option//_/-} $value" +} + +# Append to the "$buffer" variable +append_ifname() { + local section="$1" + local option="$2" + local switch="$3" + local _name + config_get _name "$section" "$option" + [ -z "$_name" ] && return 0 + local ifname=$(uci_get_state network "$_name" ifname "$_name") + append buffer "$switch $ifname" +} + +append_bool() { + local section="$1" + local option="$2" + local value="$3" + local _loctmp + config_get_bool _loctmp "$section" "$option" 0 + [ "$_loctmp" -gt 0 ] && append buffer "$value" +} + +append_parm() { + local section="$1" + local option="$2" + local switch="$3" + local _loctmp + config_get _loctmp "$section" "$option" + [ -z "$_loctmp" ] && return 0 + append buffer "$switch $_loctmp" +} + +babel_filter() { + local cfg="$1" + local _loctmp + + local _ignored + config_get_bool _ignored "$cfg" 'ignore' 0 + [ "$_ignored" -eq 1 ] && return 0 + + unset buffer + append_parm "$cfg" 'type' '' + + append_bool "$cfg" 'local' 'local' + + append_parm "$cfg" 'ip' 'ip' + append_parm "$cfg" 'eq' 'eq' + append_parm "$cfg" 'le' 'le' + append_parm "$cfg" 'ge' 'ge' + append_parm "$cfg" 'src_ip' 'src-ip' + append_parm "$cfg" 'src_eq' 'src-eq' + append_parm "$cfg" 'src_le' 'src-le' + append_parm "$cfg" 'src_ge' 'src-ge' + append_parm "$cfg" 'neigh' 'neigh' + append_parm "$cfg" 'id' 'id' + append_parm "$cfg" 'proto' 'proto' + + append_ifname "$cfg" 'if' 'if' + + append_parm "$cfg" 'action' '' + + cfg_append "$buffer" +} + +# Only one of babeld's options is allowed multiple times, "import-table". +# We just append it multiple times. +list_cb() { + option_cb "$@" +} + +babel_config_cb() { + local type="$1" + local section="$2" + case "$type" in + "general") + option_cb() { + local option="$1" + local value="$2" + # Ignore options that are not supposed to be given to babeld + [ "$option" = "conf_file" ] && return + [ "$option" = "conf_dir" ] && return + # Skip lists. They will be taken care of by list_cb + test "${option#*_ITEM}" != "$option" && return + test "${option#*_LENGTH}" != "$option" && return + cfg_append "${option//_/-} $value" + } + ;; + "interface") + local _ifname + config_get _ifname "$section" 'ifname' + # Try to resolve the logical interface name + unset interface + network_get_device interface "$_ifname" || interface="$_ifname" + option_cb() { + local option="$1" + local value="$2" + local _interface + # "option ifname" is a special option, don't actually + # generate configuration for it. + [ "$option" = "ifname" ] && return + [ -n "$interface" ] && _interface="interface $interface" || _interface="default" + cfg_append "$_interface ${option//_/-} $value" + } + # Handle ignore options. + local _ignored + # This works because we loaded the whole configuration + # beforehand (see config_load below). + config_get_bool _ignored "$section" 'ignore' 0 + if [ "$_ignored" -eq 1 ] + then + option_cb() { return; } + else + # Also include an empty "interface $interface" statement, + # so that babeld operates on this interface. + [ -n "$interface" ] && cfg_append "interface $interface" + fi + ;; + *) + # Don't use reset_cb, this would also reset config_cb + option_cb() { return; } + ;; + esac +} + +# Support for conf_file and conf_dir +babel_configpaths() { + local cfg="$1" + local conf_file + config_get conf_file "$cfg" "conf_file" + [ -n "$conf_file" ] && OTHERCONFIGFILE="$conf_file" + local conf_dir + config_get conf_dir "$cfg" "conf_dir" + [ -n "$conf_dir" ] && OTHERCONFIGDIR="$conf_dir" +} + +start_service() { + mkdir -p /var/lib + mkdir -p /var/etc + + # First load the whole config file, without callbacks, so that we are + # aware of all "ignore" options in the second pass. This also allows + # to load the configuration paths (conf_file and conf_dir). + config_load babeld + + # Configure alternative configuration file and directory + config_foreach babel_configpaths "general" + + # Start by emptying the generated config file + >"$CONFIGFILE" + # Import dynamic config files + mkdir -p "$OTHERCONFIGDIR" + for f in "$OTHERCONFIGDIR"/*.conf; do + [ -f "$f" ] && cat "$f" >> "$CONFIGFILE" + done + + # Parse general and interface sections thanks to the "config_cb()" + # callback. This allows to loop over all options without having to + # know their name in advance. + config_cb() { babel_config_cb "$@"; } + config_load babeld + # Parse filters separately, since we know which options we expect + config_foreach babel_filter filter + procd_open_instance + # Using multiple config files is supported since babeld 1.5.1 + procd_set_param command /usr/sbin/babeld -I "" -c "$OTHERCONFIGFILE" -c "$CONFIGFILE" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_set_param file "$OTHERCONFIGFILE" "$OTHERCONFIGDIR"/*.conf "$CONFIGFILE" + procd_set_param respawn + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger babeld +} + +status() { + kill -USR1 $(pgrep -P 1 babeld) +} diff --git a/net/babeld/patches/600-add-ubus.patch b/net/babeld/patches/600-add-ubus.patch new file mode 100644 index 00000000000000..93fb56a861d8bd --- /dev/null +++ b/net/babeld/patches/600-add-ubus.patch @@ -0,0 +1,139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nick Hainke +Date: Thu, 17 Dec 2020 12:41:32 +0100 +Subject: [PATCH] add ubus bindings + +--- + +--- a/babeld.c ++++ b/babeld.c +@@ -54,6 +54,8 @@ THE SOFTWARE. + #include "local.h" + #include "version.h" + ++#include "ubus.h" ++ + struct timeval now; + + unsigned char myid[8]; +@@ -505,6 +507,9 @@ main(int argc, char **argv) + } + } + ++ if(ubus_bindings) ++ babeld_add_ubus(); ++ + init_signals(); + rc = resize_receive_buffer(1500); + if(rc < 0) +@@ -597,6 +602,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) { +@@ -665,6 +672,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/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 +@@ -42,6 +42,8 @@ THE SOFTWARE. + #include "hmac.h" + #include "configuration.h" + ++#include "ubus.h" ++ + static struct filter *input_filters = NULL; + static struct filter *output_filters = NULL; + static struct filter *redistribute_filters = NULL; +@@ -1024,7 +1026,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) +@@ -1042,6 +1045,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) { +--- a/local.c ++++ b/local.c +@@ -42,6 +42,8 @@ THE SOFTWARE. + #include "local.h" + #include "version.h" + ++#include "ubus.h" ++ + int local_server_socket = -1; + struct local_socket local_sockets[MAX_LOCAL_SOCKETS]; + int num_local_sockets = 0; +@@ -191,6 +193,8 @@ local_notify_neighbour(struct neighbour + if(local_sockets[i].monitor) + local_notify_neighbour_1(&local_sockets[i], neigh, kind); + } ++ if(ubus_bindings) ++ ubus_notify_neighbour(neigh, kind); + } + + static void +@@ -228,6 +232,8 @@ local_notify_xroute(struct xroute *xrout + if(local_sockets[i].monitor) + local_notify_xroute_1(&local_sockets[i], xroute, kind); + } ++ if(ubus_bindings) ++ ubus_notify_xroute(xroute, kind); + } + + static void +@@ -273,6 +279,8 @@ local_notify_route(struct babel_route *r + if(local_sockets[i].monitor) + local_notify_route_1(&local_sockets[i], route, kind); + } ++ if(ubus_bindings) ++ ubus_notify_route(route, kind); + } + + static void +--- 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 \ +- hmac.c rfc6234/sha224-256.c BLAKE2/ref/blake2s-ref.c ++ hmac.c ubus.c rfc6234/sha224-256.c BLAKE2/ref/blake2s-ref.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 \ +- hmac.o rfc6234/sha224-256.o BLAKE2/ref/blake2s-ref.o ++ hmac.o ubus.o rfc6234/sha224-256.o BLAKE2/ref/blake2s-ref.o + + babeld: $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) -o babeld $(OBJS) $(LDLIBS) diff --git a/net/babeld/src/ubus.c b/net/babeld/src/ubus.c new file mode 100644 index 00000000000000..01a152c8f8533c --- /dev/null +++ b/net/babeld/src/ubus.c @@ -0,0 +1,539 @@ +#include +#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 "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 babel_route *route; +}; + +// List of neighbours (to be used with ubox's list helpers). +struct neighbour_list_entry { + struct list_head list; + struct neighbour *neighbour; +}; + +// Definition of interface function enums (to be used with ubox's blobmsg +// helpers). +enum { INTERFACE_IFNAME, __INTERFACE_MAX }; + +// Definition of interface parsing (to be used with ubox's blobmsg helpers). +static const struct blobmsg_policy interface_policy[__INTERFACE_MAX] = { + [INTERFACE_IFNAME] = {"ifname", BLOBMSG_TYPE_STRING}, +}; + +// Definition of filter function enums (to be used with ubox's blobmsg +// helpers). +enum { FILTER_IFNAME, FILTER_TYPE, FILTER_METRIC, __FILTER_MAX }; + +// Definition of filter parsing (to be used with ubox's blobmsg helpers). +static const struct blobmsg_policy filter_policy[__FILTER_MAX] = { + [FILTER_IFNAME] = {"ifname", BLOBMSG_TYPE_STRING}, + [FILTER_TYPE] = {"type", BLOBMSG_TYPE_INT32}, + [FILTER_METRIC] = {"metric", BLOBMSG_TYPE_INT32}, +}; + +// Adds a filter (ubus equivalent to "filter"-function). +static int babeld_ubus_add_filter(struct ubus_context *ctx_local, + struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, struct blob_attr *msg) { + struct blob_attr *tb[__FILTER_MAX]; + struct blob_buf b = {0}; + struct filter *filter = NULL; + char *ifname; + int metric = 0, type; + + blobmsg_parse(filter_policy, __FILTER_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[FILTER_IFNAME]) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (!tb[FILTER_TYPE]) + return UBUS_STATUS_INVALID_ARGUMENT; + + type = blobmsg_get_u32(tb[FILTER_TYPE]); + + if (tb[FILTER_METRIC]) + metric = blobmsg_get_u32(tb[FILTER_METRIC]); + + filter = calloc(1, sizeof(struct filter)); + if (filter == NULL) + return UBUS_STATUS_UNKNOWN_ERROR; + + filter->af = AF_INET6; + filter->proto = 0; + filter->plen_le = 128; + filter->src_plen_le = 128; + filter->action.add_metric = metric; + + ifname = blobmsg_get_string(tb[FILTER_IFNAME]); + filter->ifname = strdup(ifname); + filter->ifindex = if_nametoindex(filter->ifname); + + add_filter(filter, type); + + return UBUS_STATUS_OK; +} + +// Adds an inteface (ubus equivalent to "interface"-function). +static int babeld_ubus_add_interface(struct ubus_context *ctx_local, + struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, + struct blob_attr *msg) { + struct blob_attr *tb[__INTERFACE_MAX]; + struct blob_buf b = {0}; + struct interface *ifp = NULL; + char *ifname; + + blobmsg_parse(interface_policy, __INTERFACE_MAX, tb, blob_data(msg), + blob_len(msg)); + + if (!tb[INTERFACE_IFNAME]) + return UBUS_STATUS_INVALID_ARGUMENT; + + ifname = blobmsg_get_string(tb[INTERFACE_IFNAME]); + + ifp = add_interface(ifname, NULL); + if (ifp == NULL) + return UBUS_STATUS_UNKNOWN_ERROR; + + return UBUS_STATUS_OK; +} + +// Sends a babel info message on ubus socket. +static int 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)); + + blob_buf_free(&b); + + return 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, NULL); + blobmsg_add_string(b, "address", + 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 int 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); + + blob_buf_init(&b, 0); + + 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); + } + + ipv4 = blobmsg_open_array(&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_array(&b, ipv4); + + ipv6 = blobmsg_open_array(&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_array(&b, ipv6); + + ret = ubus_send_reply(ctx_local, req, b.head); + if (ret) + fprintf(stderr, "Failed to send reply: %s\n", ubus_strerror(ret)); + + blob_buf_free(&b); + + return 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; + + prefix = blobmsg_open_table( + b, NULL); + blobmsg_add_string( + b, "address", + 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_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 int 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(0); + 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); + } + + ipv4 = blobmsg_open_array(&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_array(&b, ipv4); + + ipv6 = blobmsg_open_array(&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_array(&b, ipv6); + + ret = ubus_send_reply(ctx_local, req, b.head); + if (ret) + fprintf(stderr, "Failed to send reply: %s\n", ubus_strerror(ret)); + + blob_buf_free(&b); + + return 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, NULL); + blobmsg_add_string(b, "address", 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_string(b, "rtt", format_thousands(neigh->rtt)); + 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 int 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); + } + } + + ipv4 = blobmsg_open_array(&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_array(&b, ipv4); + + ipv6 = blobmsg_open_array(&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_array(&b, ipv6); + + ret = ubus_send_reply(ctx_local, req, b.head); + if (ret) + fprintf(stderr, "Failed to send reply: %s\n", ubus_strerror(ret)); + + blob_buf_free(&b); + + return ret; +} + +// List of functions we expose via the ubus bus. +static const struct ubus_method babeld_methods[] = { + UBUS_METHOD("add_interface", babeld_ubus_add_interface, interface_policy), + UBUS_METHOD("add_filter", babeld_ubus_add_filter, filter_policy), + 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 ubus_notify_route(struct babel_route *route, int kind) { + struct blob_buf b = {0}; + char method[50]; // possible methods are route.change, route.add, route.flush + + if (!babeld_object.has_subscribers) + return; + + if (!route) + return; + + if (!shared_ctx) + return; + + blob_buf_init(&b, 0); + babeld_add_route_buf(route, &b); + snprintf(method, sizeof(method), "route.%s", local_kind(kind)); + ubus_notify(shared_ctx, &babeld_object, method, b.head, -1); + blob_buf_free(&b); +} + +void ubus_notify_xroute(struct xroute *xroute, int kind) { + struct blob_buf b = {0}; + char method[50]; // possible methods are xroute.change, xroute.add, + // xroute.flush + + if (!babeld_object.has_subscribers) + return; + + if (!xroute) + return; + + if (!shared_ctx) + return; + + blob_buf_init(&b, 0); + babeld_add_xroute_buf(xroute, &b); + snprintf(method, sizeof(method), "xroute.%s", local_kind(kind)); + ubus_notify(shared_ctx, &babeld_object, method, b.head, -1); + blob_buf_free(&b); +} + +void ubus_notify_neighbour(struct neighbour *neigh, int kind) { + struct blob_buf b = {0}; + char method[50]; // possible methods are neigh.change, neigh.add, neigh.flush + + if (!babeld_object.has_subscribers) + return; + + if (!neigh) + return; + + if (!shared_ctx) + return; + + blob_buf_init(&b, 0); + babeld_add_neighbour_buf(neigh, &b); + snprintf(method, sizeof(method), "neigh.%s", local_kind(kind)); + ubus_notify(shared_ctx, &babeld_object, method, b.head, -1); + blob_buf_free(&b); +} + +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/net/babeld/src/ubus.h b/net/babeld/src/ubus.h new file mode 100644 index 00000000000000..08aeb52a607b03 --- /dev/null +++ b/net/babeld/src/ubus.h @@ -0,0 +1,99 @@ +/* + IPC integration of babeld with OpenWrt. + + The ubus interface offers following functions: + - add_filter '{"ifname":"eth0", "type":0, "metric":5000}' + type: + 0: FILTER_TYPE_INPUT + 1: FILTER_TYPE_OUTPUT + 2: FILTER_TYPE_REDISTRIBUTE + 3: FILTER_TYPE_INSTALL + - add_interface '{"ifname":"eth0"}' + - get_info + - get_neighbours + - get_xroutes + - get_routes + + All output is divided into IPv4 and IPv6. + + Ubus notifications are sent if we receive updates for + - xroutes + - routes + - neighbours + + The format is: + - {route,xroute,neighbour}.add: Object was added + - {route,xroute,neighbour}.change: Object was changed + - {route,xroute,neighbour}.flush: Object was flushed + +*/ + +#include +#include + +struct babel_route; +struct neighbour; +struct xroute; + +// 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); + +/*** + * Notify the ubus bus that a new xroute is received. + * + * If a new xroute is received or changed, we will notify subscribers. + * + * @param xroute: xroute that experienced some change + * @param kind: kind that describes if we have a flush, add or change + */ +void ubus_notify_xroute(struct xroute *xroute, int kind); + +/*** + * Notify the ubus bus that a new route is received. + * + * If a new route is received or changed, we will notify subscribers. + * + * @param route: route that experienced some change + * @param kind: kind that describes if we have a flush, add or change + */ +void ubus_notify_route(struct babel_route *route, int kind); + +/*** + * Notify the ubus bus that a new neighbour is received. + * + * If a new neighbour is received or changed, we will notify subscribers. + * + * @param neigh: neighbour that experienced some change + * @param kind: kind that describes if we have a flush, add or change + */ +void ubus_notify_neighbour(struct neighbour *neigh, int kind);