diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig index ae4239c16..e9fc267ec 100644 --- a/configs/aarch64_defconfig +++ b/configs/aarch64_defconfig @@ -123,6 +123,7 @@ BR2_PACKAGE_FINIT_PLUGIN_TTY=y BR2_PACKAGE_FINIT_PLUGIN_URANDOM=y BR2_PACKAGE_IFUPDOWN_NG=y BR2_PACKAGE_KLINFIX=y +BR2_PACKAGE_NET=y BR2_PACKAGE_P_NET=y BR2_PACKAGE_P_NET_MAX_SUBSLOTS=12 BR2_PACKAGE_P_NET_MAX_PHYSICAL_PORTS=10 diff --git a/configs/amd64_defconfig b/configs/amd64_defconfig index 650d1711e..ae94db730 100644 --- a/configs/amd64_defconfig +++ b/configs/amd64_defconfig @@ -128,6 +128,7 @@ BR2_PACKAGE_FINIT_PLUGIN_TTY=y BR2_PACKAGE_FINIT_PLUGIN_URANDOM=y BR2_PACKAGE_IFUPDOWN_NG=y BR2_PACKAGE_KLINFIX=y +BR2_PACKAGE_NET=y BR2_PACKAGE_P_NET=y BR2_PACKAGE_P_NET_MAX_SUBSLOTS=12 BR2_PACKAGE_P_NET_MAX_PHYSICAL_PORTS=10 diff --git a/package/Config.in b/package/Config.in index 8127126d1..2335bf86f 100644 --- a/package/Config.in +++ b/package/Config.in @@ -7,6 +7,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/klinfix/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/klish/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/klish-plugin-sysrepo/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/mdnsd/Config.in" +source "$BR2_EXTERNAL_INFIX_PATH/package/net/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/osal/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/p-net/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/profeth/Config.in" diff --git a/package/confd/Config.in b/package/confd/Config.in index cb551a7a7..170aeecce 100644 --- a/package/confd/Config.in +++ b/package/confd/Config.in @@ -1,9 +1,10 @@ config BR2_PACKAGE_CONFD bool "confd" - select BR2_PACKAGE_SYSREPO - select BR2_PACKAGE_NETOPEER2 select BR2_PACKAGE_AUGEAS select BR2_PACKAGE_JANSSON + select BR2_PACKAGE_LIBITE + select BR2_PACKAGE_NETOPEER2 + select BR2_PACKAGE_SYSREPO help A plugin to sysrepo that provides the core YANG models used to manage an Infix based system. Configuration can be done using diff --git a/package/confd/confd.mk b/package/confd/confd.mk index f707dfae1..5b4fd02a3 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -8,7 +8,7 @@ CONFD_VERSION = 1.0 CONFD_LICENSE = BSD-3-Clause CONFD_SITE_METHOD = local CONFD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/confd -CONFD_DEPENDENCIES = augeas sysrepo +CONFD_DEPENDENCIES = augeas jansson libite sysrepo CONFD_AUTORECONF = YES define CONFD_INSTALL_EXTRA diff --git a/package/net/Config.in b/package/net/Config.in new file mode 100644 index 000000000..f15df4e59 --- /dev/null +++ b/package/net/Config.in @@ -0,0 +1,6 @@ +config BR2_PACKAGE_NET + bool "net" + select BR2_PACKAGE_IPROUTE2 + select BR2_PACKAGE_LIBITE + help + Handles transitions between network states. diff --git a/package/net/net.mk b/package/net/net.mk new file mode 100644 index 000000000..d353a71ab --- /dev/null +++ b/package/net/net.mk @@ -0,0 +1,19 @@ +################################################################################ +# +# net +# +################################################################################ + +NET_VERSION = 1.0 +NET_LICENSE = ISC +NET_SITE_METHOD = local +NET_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/net +NET_DEPENDENCIES = libite +NET_AUTORECONF = YES + +define NET_INSTALL_EXTRA + chmod +x $(TARGET_DIR)/usr/share/net/*.sh +endef +NET_TARGET_FINALIZE_HOOKS += NET_INSTALL_EXTRA + +$(eval $(autotools-package)) diff --git a/src/net/.gitignore b/src/net/.gitignore index 5166df2d7..15b6c7ebe 100644 --- a/src/net/.gitignore +++ b/src/net/.gitignore @@ -1,2 +1,13 @@ *.o +*.log +*.trs net +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +aux/ +config.* +configure +src/.deps/ +stamp-h1 diff --git a/src/net/LICENSE b/src/net/LICENSE new file mode 100644 index 000000000..fe2233455 --- /dev/null +++ b/src/net/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 The KernelKit Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/net/Makefile b/src/net/Makefile deleted file mode 100644 index 5c14a66ea..000000000 --- a/src/net/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -LDLIBS = -lite -EXEC = net - -net: main.o - $(CC) -o $@ $^ $(LDLIBS) - -check: $(EXEC) - ./test.sh - -clean: - $(RM) net - -distclean: clean - $(RM) *.o *~ diff --git a/src/net/Makefile.am b/src/net/Makefile.am new file mode 100644 index 000000000..fd16c15f3 --- /dev/null +++ b/src/net/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = src test +DISTCLEANFILES = *~ *.d +ACLOCAL_AMFLAGS = -I m4 + diff --git a/src/net/README.md b/src/net/README.md index 55625148a..326213e16 100644 --- a/src/net/README.md +++ b/src/net/README.md @@ -11,7 +11,7 @@ Concept Overview ---------------- net applies a configuration from `/run/net/next`, where `next` is a file -that holds the number of the current generation. It is up to the user, +that holds the number of the next net generation. It is up to the user, i.e., sysrepo, to create the below tree structure with commands to run. The setup we start with and later move `eth4` from `lag0` to `br0`. @@ -25,29 +25,25 @@ The setup we start with and later move `eth4` from `lag0` to `br0`. eth4 eth5 Consider the case when `next` contains `0`, net reads all interfaces, in -dependency order, from `/run/net/0/` running all `ip-link` and `ip-addr` -commands. When done, it writes `0` to the file `/run/net/gen` and then -removes the `next` file to confirm the generation has been activated. +dependency order, from `/run/net/0/` running all `init.cmd` scripts. +When done, it writes `0` to the file `/run/net/gen` and then removes the +`next` file to confirm the generation has been activated. **Note:** it is currently up to the user to remove any old generation. /run/net/0/ - |-- lo/ - | |-- deps/ - | |-- ip-link.up - | `-- ip-addr.up |-- br0/ | |-- deps/ | | |-- eth1 -> ../../eth1 | | |-- eth2 -> ../../eth2 | | |-- eth3 -> ../../eth3 | | `-- lag0 -> ../../lag0 - | |-- ip-link.up - | `-- ip-addr.up + | |-- admin-state + | `-- init.ip |-- eth0/ | |-- deps/ - | |-- ip-link.up - | `-- ip-addr.up + | |-- admin-state + | `-- init.ip |-- ethX/ | |-- ... : : @@ -55,13 +51,13 @@ removes the `next` file to confirm the generation has been activated. | |-- deps/ | | |-- eth4 -> ../../eth4 | | `-- eth5 -> ../../eth5 - | |-- ip-link.up - | `-- ip-addr.up + | |-- admin-state + | `-- init.ip `-- vlan1/ |-- deps/ | `-- br0 -> ../../br0 - |-- ip-link.up - `-- ip-addr.up + |-- admin-state + `-- init.ip The `deps/` sub-directory for each of the interfaces contains symlinks to any interfaces this interface may depend on. I.e., those interfaces @@ -70,7 +66,7 @@ are evaluated first. Essentially, all leaves must be set up before their parents. Moving a leaf from one parent to another, e.g., from lag0 to br0, is tricky, it involves traversing the previous dependency order when removing leaves, -and traversing the next dependency order when addning, see next section +and traversing the next dependency order when adding, see next section for an example. @@ -83,52 +79,50 @@ and generate the `next` generation. However, as mentioned previously, when moving leaves between parents we must do so in the dependency order of the current generation. -So, the user (sysrepo) needs to add `.dn` scripts in the current tree, -and `.up` scripts in the next. +So, the user (sysrepo) needs to add `exit.cmd` scripts in the current +tree for every leaf that leaves a parent, and `init.cmd` scripts for +leaves that are new or added to parents in the next generation. In our example, interface `eth4` is moved from `lag0` to `br0`, so we -need to run `eth4/ip-link.dn` in the current generation first to remove -`eth4` from `lag0` before its `ip-link.up` script in the next generation +need to run `eth4/exit.ip` in the current generation first to remove +`eth4` from `lag0` before its `init.ip` script in the next generation sets `eth4` as a bridge member instead. -We traverse the current generation and execute all `.dn` scripts: +We traverse the current generation and execute all `exit.cmd` scripts: /run/net// - |-- lo/ - | `-- deps/ |-- br0/ | |-- deps/ | | |-- eth1 -> ../../eth1 | | |-- eth2 -> ../../eth2 | | |-- eth3 -> ../../eth3 | | `-- lag0 -> ../../lag0 - | `-- ip-link.up + | |-- admin-state + | `-- init.ip |-- eth0/ | |-- deps/ - | |-- ip-link.up - | `-- ip-addr.up + | |-- admin-state + | `-- init.ip |-- eth4/ | |-- deps/ - | |-- ip-link.dn - | `-- ip-link.up + | |-- admin-state + | |-- exit.ip + | `-- init.ip |-- lag0/ | |-- deps/ | | |-- eth4 -> ../../eth4 | | `-- eth5 -> ../../eth5 - | `-- ip-link.up + | |-- admin-state + | `-- init.ip `-- vlan1/ |-- deps/ | `-- br0 -> ../../br0 - |-- ip-link.up - `-- ip-addr.up + |-- admin-state + `-- init.ip -Now we can run all the `.up` scripts in the next generation: +Now we can run all the `init.cmd` scripts in the next generation: /run/net// - |-- lo/ - | |-- deps/ - | |-- ip-link.up - | `-- ip-addr.up |-- br0/ | |-- deps/ | | |-- eth1 -> ../../eth1 @@ -136,23 +130,38 @@ Now we can run all the `.up` scripts in the next generation: | | |-- eth3 -> ../../eth3 | | |-- eth4 -> ../../eth4 | | `-- lag0 -> ../../lag0 - | `-- ip-link.up + | |-- admin-state + | `-- init.ip |-- eth0/ | |-- deps/ - | |-- ip-link.up - | `-- ip-addr.up + | |-- admin-state + | `-- init.ip |-- eth4/ | |-- deps/ - | |-- ip-link.dn - | `-- ip-link.up + | |-- admin-state + | `-- init.ip |-- lag0/ | |-- deps/ | | `-- eth5 -> ../../eth5 - | `-- ip-link.up + | |-- admin-state + | `-- init.ip `-- vlan1/ |-- deps/ | `-- br0 -> ../../br0 - |-- ip-link.up - `-- ip-addr.up - - + |-- admin-state + `-- init.ip + +When there are no changes compared to the previous generation, the base +structure with deps need to remain intact, but there's no need for any +`init.cmd` scripts. However, for the purpose of supporting the commands +`net down` and `net up`, the user must have an `admin-state` file per +interface which must contain one of: + + - disabled + - up + - down + +This describes the state which the user's last `init.ip` or `exit.ip` +command, or the `net [up | down]` action, left the interface in. When +the `disabled` keyword is used it means the net tool is not allowed to +apply any `up` or `down` action on the interface. diff --git a/src/net/autogen.sh b/src/net/autogen.sh new file mode 100755 index 000000000..69ad0e189 --- /dev/null +++ b/src/net/autogen.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +autoreconf -W portability -vifm diff --git a/src/net/configure.ac b/src/net/configure.ac new file mode 100644 index 000000000..bf7350dff --- /dev/null +++ b/src/net/configure.ac @@ -0,0 +1,40 @@ +AC_PREREQ(2.61) +AC_INIT([net], [1.0], [https://github.com/kernelkit/infix/issues]) +AC_CONFIG_AUX_DIR(aux) +AM_INIT_AUTOMAKE([1.11 foreign]) +AM_SILENT_RULES([yes]) + +AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([Makefile src/Makefile test/Makefile]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_PROG_CC +AC_PROG_INSTALL + +# Check for pkg-config first, warn if it's not installed +PKG_PROG_PKG_CONFIG +PKG_CHECK_MODULES([libite], [libite >= 2.5.0]) + +AC_OUTPUT + +SYSCONFDIR=`eval echo $sysconfdir` +RUNSTATEDIR=`eval echo $runstatedir` +RUNSTATEDIR=`eval echo $RUNSTATEDIR` +cat < -#include -#include -#include -#include -#include - -#define _PATH_NET "/run/net" -#define DEBUG 0 -#define dbg(fmt, args...) if (DEBUG) warnx(fmt, ##args) - -static char **handled; -static int if_num; - - -static void if_alloc(int num) -{ - handled = calloc(num, sizeof(char *)); - if (!handled) - err(1, "calloc"); - if_num = num; -} - -static void if_free() -{ - for (int i = 0; i < if_num; i++) { - if (handled[i]) - free(handled[i]); - } - free(handled); -} - -static void if_done(char *ifname) -{ - for (int i = 0; i < if_num; i++) { - if (handled[i]) { - if (strcmp(handled[i], ifname)) - continue; - return; - } - handled[i] = strdup(ifname); - break; - } -} - -static int if_find(char *ifname) -{ - for (int i = 0; i < if_num; i++) { - if (handled[i] && !strcmp(handled[i], ifname)) - return 1; - } - return 0; -} - -static int deps(char *ipath, char *ifname, const char *action) -{ - char path[strlen(ipath) + 42]; - int num, rc = -1; - char **files; - char *cmd; - - snprintf(path, sizeof(path), "%s/deps", ipath); - num = dir(path, NULL, NULL, &files, 0); - for (int i = 0; i < num; i++) { - char dpath[sizeof(path) + strlen(files[i])]; - char *ifnm = files[i]; - char *rp; - - snprintf(dpath, sizeof(dpath), "%s/%s", path, ifnm); - rp = realpath(dpath, NULL); - if (!rp) - continue; - - deps(rp, ifnm, action); - free(ifnm); - free(rp); - } - - if (if_find(ifname)) - return 0; - - snprintf(path, sizeof(path), "%s/%s", ipath, action); - cmd = realpath(path, NULL); - if (!cmd || access(cmd, X_OK)) { - if (errno == ENOENT) - rc = 0; /* no action for this interface */ - goto done; - } - - rc = systemf("%s", cmd); -done: - free(cmd); - if_done(ifname); - - return rc; -} - -static int iter(char *path, size_t len, const char *action) -{ - char **files; - int rc = 0; - int num; - - num = dir(path, NULL, NULL, &files, 0); - if_alloc(num); - - for (int j = 0; j < num; j++) { - char *ifname = files[j]; - char ipath[len]; - - snprintf(ipath, sizeof(ipath), "%s/%s", path, ifname); - dbg("Calling deps(%s, %s, %s)", ipath, ifname, action); - rc += deps(ipath, ifname, action); - free(ifname); - } - - if_free(); - - return rc; -} - -static int deactivate(const char *net, char *gen) -{ - char path[strlen(net) + strlen(gen) + 5 + IFNAMSIZ]; - char *action[] = { - "ip-addr.dn", - "ip-link.dn", - }; - int rc = 0; - - snprintf(path, sizeof(path), "%s/%s", net, gen); - for (size_t i = 0; i < NELEMS(action); i++) - rc += iter(path, sizeof(path), action[i]); - - return rc; -} - -static int activate(const char *net, char *gen) -{ - char path[strlen(net) + strlen(gen) + 5 + IFNAMSIZ]; - char *action[] = { - "ip-link.up", - "ip-addr.up", - }; - int rc = 0; - - snprintf(path, sizeof(path), "%s/%s", net, gen); - for (size_t i = 0; i < NELEMS(action); i++) - rc += iter(path, sizeof(path), action[i]); - - return rc; -} - -static int load_gen(const char *net, char *gen, char *buf, size_t len) -{ - FILE *fp; - - fp = fopenf("r", "%s/%s", net, gen); - if (!fp) - return EX_OSFILE; - if (!fgets(buf, len, fp)) - return EX_IOERR; - fclose(fp); - chomp(buf); - - return 0; -} - -static int save_gen(const char *net, char *gen, char *buf) -{ - FILE *fp; - - fp = fopenf("w", "%s/%s", net, gen); - if (!fp) - return -1; - fprintf(fp, "%s\n", buf); - fclose(fp); - - return 0; -} - -int main(void) -{ - const char *net = _PATH_NET; - char curr[512], next[512]; - int rc; - - if (getenv("NET_DIR")) - net = getenv("NET_DIR"); - if (access(net, X_OK)) { - if (makedir(net, 0755)) - err(1, "makedir"); - } - - if ((rc = load_gen(net, "next", next, sizeof(next)))) { - if (rc == EX_IOERR) - warnx("missing next generation"); - exit(0); /* nothing to do */ - } - - if ((rc = load_gen(net, "gen", curr, sizeof(curr)))) { - if (rc == EX_IOERR) - errx(rc, "missing current generation"); - /* no current generation */ - } else { - rc = deactivate(net, curr); - if (rc) - err(1, "failed deactivating current generation"); - } - - rc = activate(net, next); - if (rc) - err(1, "failed activating next generation"); - - if (save_gen(net, "gen", next)) - err(1, "next generation applied, failed current"); - - if (fremove("%s/next", net)) - err(1, "failed removing %s/next", net); - - return 0; -} diff --git a/src/net/src/Makefile.am b/src/net/src/Makefile.am new file mode 100644 index 000000000..c9b18fa22 --- /dev/null +++ b/src/net/src/Makefile.am @@ -0,0 +1,5 @@ +sbin_PROGRAMS = net + +net_SOURCES = main.c +net_CFLAGS = $(libite_CFLAGS) -Wall -Wextra -Werror -Wno-unused-parameter +net_LDADD = $(libite_LIBS) diff --git a/src/net/src/main.c b/src/net/src/main.c new file mode 100644 index 000000000..8226341ce --- /dev/null +++ b/src/net/src/main.c @@ -0,0 +1,616 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define _PATH_NET "/run/net" +#define dbg(fmt, args...) if (debug) warnx(fmt, ##args) +#define log(fmt, args...) if (verbose) warnx(fmt, ##args) + +typedef enum { + INVAL = -1, + DO = 0, + UP = 1, + DOWN = 2, +} cmd_t; + +struct iface { + TAILQ_ENTRY(iface) link; + char ifname[IFNAMSIZ]; +}; + +static TAILQ_HEAD(iflist, iface) iface_list = TAILQ_HEAD_INITIALIZER(iface_list); + +static int debug; +static int verbose; +static int dep; + +static FILE *ip, *bridge; +static char *prognm; + +static void addif(char *ifname) +{ + struct iface *entry; + + entry = malloc(sizeof(*entry)); + if (!entry) + err(1, "malloc"); + + strlcpy(entry->ifname, ifname, sizeof(entry->ifname)); + TAILQ_INSERT_TAIL(&iface_list, entry, link); +} + +static int findif(char *ifname) +{ + struct iface *iface; + + TAILQ_FOREACH(iface, &iface_list, link) { + if (strcmp(iface->ifname, ifname)) + continue; + + return 1; + } + + return 0; +} + +static void freeifs(void) +{ + struct iface *iface, *tmp; + + TAILQ_FOREACH_SAFE(iface, &iface_list, link, tmp) { + TAILQ_REMOVE(&iface_list, iface, link); + free(iface); + } +} + +static void savedep(char *ipath) +{ + char line[20]; + char *ifname; + char *path; + FILE *fp; + + path = strdupa(ipath); + if (!path) + return; + + ifname = basename(path); + path = dirname(path); + + fp = fopenf("a+", "%s/deps", path); + if (!fp) + return; + + (void)fseek(fp, 0L, SEEK_SET); + while (fgets(line, sizeof(line), fp)) { + if (!strcmp(chomp(line), ifname)) + goto done; + } + + dep++; + fprintf(fp, "%s\n", ifname); +done: + fclose(fp); +} + +static int save_rdeps(const char *path, char *gen) +{ + return systemf("sed '1!G;h;$!d' < %s/%s/deps >%s/%s/rdeps", path, gen, path, gen); +} + +static FILE *pipep(const char *action) +{ + char *ptr; + + ptr = strrchr(action, '.'); + if (!ptr) { + warnx("missing action type, assuming shell script: '%s'", action); + return NULL; + } + + if (!strcmp(ptr, ".ip")) + return ip; + if (!strcmp(ptr, ".bridge")) + return bridge; + + return NULL; +} + +static int pipeit(FILE *pp, const char *action) +{ + char line[256]; + FILE *fp; + + fp = fopen(action, "r"); + if (!fp) + return 0; /* nop */ + + while (fgets(line, sizeof(line), fp)) { + chomp(line); + dbg(">>> %s", line); + fprintf(pp, "%s\n", line); + } + + return fclose(fp); +} + +static int run(const char *action) +{ + FILE *pp = pipep(action); + int rc; + + if (pp) { + log("running pipe action %s:", action); + return pipeit(pp, action); + } + + /* other actions are plain shell scripts (.sh) */ + log("running shell action %s:", action); + + rc = systemf("%s", action); + if (rc) + warn("failed %s, rc %d", action, rc); + return 0; +} + +static int dir_filter(const char *file) +{ + char *files[] = { + "deps", + "rdeps", + "admin-state", + }; + size_t i; + + for (i = 0; i < NELEMS(files); i++) { + if (!strcmp(file, files[i])) + return 0; + } + + return 1; +} + +static int deps(char *ipath, char *ifname, const char *action) +{ + char path[strlen(ipath) + 42]; + int num, rc = -1; + char **files; + char *cmd; + + snprintf(path, sizeof(path), "%s/deps", ipath); + num = dir(path, NULL, dir_filter, &files, 0); + for (int i = 0; i < num; i++) { + char dpath[sizeof(path) + strlen(files[i])]; + char *ifnm = files[i]; + char *rp; + + snprintf(dpath, sizeof(dpath), "%s/%s", path, ifnm); + rp = realpath(dpath, NULL); + if (!rp) + continue; + + deps(rp, ifnm, action); + free(ifnm); + free(rp); + } + + savedep(ipath); + if (findif(ifname)) + return 0; + + snprintf(path, sizeof(path), "%s/%s", ipath, action); + cmd = realpath(path, NULL); + if (!cmd || access(cmd, F_OK)) { + if (errno == ENOENT || errno == ENOTDIR) + rc = 0; /* no action for this interface */ + goto done; + } + + rc = run(cmd); +done: + free(cmd); + addif(ifname); + + return rc; +} + +static int iter(char *path, size_t len, const char *action) +{ + char **files; + int rc = 0; + FILE *pp; + int num; + + num = dir(path, NULL, dir_filter, &files, 0); + + for (int i = 0; i < num; i++) { + char *ifname = files[i]; + char ipath[len]; + + snprintf(ipath, sizeof(ipath), "%s/%s", path, ifname); + dbg("Calling deps(%s, %s, %s)", ipath, ifname, action); + rc += deps(ipath, ifname, action); + dbg("rc => %d", rc); + free(ifname); + } + + freeifs(); + + pp = pipep(action); + if (pp) { + log("closing pipe for %s", action); + pclose(pp); + } + + return rc; +} + +static int rdeps(char *path, size_t len, const char *action) +{ + char cmd[len + 20 + strlen(action)]; + char ifname[20]; + int rc = 0; + FILE *fp; + + fp = fopenf("r", "%s/rdeps", path); + if (!fp) + return 0; /* no deps in prev. generation */ + + while (fgets(ifname, sizeof(ifname), fp)) { + snprintf(cmd, sizeof(cmd), "%s/%s/%s", path, chomp(ifname), action); + if (access(cmd, F_OK)) { +// dbg("skipping %s, errno %d: %s", cmd, errno, strerror(errno)); + continue; + } + rc += run(cmd); + } + + return rc; +} + +static int deactivate(const char *net, char *gen) +{ + char path[strlen(net) + strlen(gen) + 5 + IFNAMSIZ]; + char *action[] = { + "exit.bridge", + "exit.ip", + "exit-ethtool.sh", + }; + int rc = 0; + + snprintf(path, sizeof(path), "%s/%s", net, gen); + for (size_t i = 0; i < NELEMS(action); i++) + rc += rdeps(path, sizeof(path), action[i]); + + return rc; +} + +static int activate(const char *net, char *gen) +{ + char path[strlen(net) + strlen(gen) + 5 + IFNAMSIZ]; + char *action[] = { + "init-ethtool.sh", + "init.ip", + "init.bridge", + }; + int rc = 0; + + snprintf(path, sizeof(path), "%s/%s", net, gen); + for (size_t i = 0; i < NELEMS(action); i++) + rc += iter(path, sizeof(path), action[i]); + + return rc; +} + +static int load_gen(const char *net, char *gen, char *buf, size_t len) +{ + FILE *fp; + + fp = fopenf("r", "%s/%s", net, gen); + if (!fp) + return EX_OSFILE; + if (!fgets(buf, len, fp)) + return EX_IOERR; + fclose(fp); + chomp(buf); + + return 0; +} + +static int save_gen(const char *net, char *gen, char *buf) +{ + FILE *fp; + + fp = fopenf("w", "%s/%s", net, gen); + if (!fp) + return -1; + fprintf(fp, "%s\n", buf); + fclose(fp); + + return 0; +} + +static void pipe_init(void) +{ + ip = popen("ip -batch -", "w"); + if (!ip) + err(1, "failed starting ip command pipe"); + + bridge = popen("bridge -batch -", "w"); + if (!bridge) + err(1, "failed starting bridge command pipe"); +} + +static const char *getnet(void) +{ + const char *net = _PATH_NET; + + if (getenv("NET_DIR")) + net = getenv("NET_DIR"); + dbg("net directory %s", net); + if (access(net, X_OK)) { + if (makedir(net, 0755)) + err(1, "makedir"); + } + + return net; +} + +static char *getpath(void) +{ + const char *net = getnet(); + char path[strlen(net) + 42]; + char buf[40]; + FILE *fp; + + snprintf(path, sizeof(path), "%s/gen", net); + fp = fopen(path, "r"); + if (!fp) + err(1, "cannot find %s", path); + if (!fgets(buf, sizeof(buf), fp)) + err(1, "failed reading %s", path); + fclose(fp); + + snprintf(path, sizeof(path), "%s/%d", net, atoi(buf)); + return strdup(path); +} + +/* build list from current generation */ +static void getifs(void) +{ + char *path = getpath(); + char **files; + int num; + + dbg("fetching available interfaces from %s", path); + num = dir(path, NULL, dir_filter, &files, 0); + for (int i = 0; i < num; i++) { + dbg("adding %s ...", files[i]); + addif(files[i]); + } +} + +static char *ifadmin(const char *ifname, char *buf, size_t len) +{ + char *path = getpath(); + + if (!path) + return NULL; + + snprintf(buf, len, "%s/%s/admin-state", path, ifname); + return buf; +} + +/* is the interface eligible for being taken up/down? */ +static int allowed(const char *ifname) +{ + char buf[128]; + int rc = 1; + FILE *fp; + + if (!ifadmin(ifname, buf, sizeof(buf))) + goto fail; + + fp = fopen(buf, "r"); + if (fp) { + if (fgets(buf, sizeof(buf), fp)) { + chomp(buf); + if (!strcmp(buf, "disabled")) + rc = 0; + } + fclose(fp); + } +fail: + return rc; +} + +static int ifupdown(int updown) +{ + const char *action = updown ? "up" : "down"; + struct iface *iface; + int rc = 0; + + dbg("preparing for if%s ...", action); + if (TAILQ_EMPTY(&iface_list)) + getifs(); + + TAILQ_FOREACH(iface, &iface_list, link) { + int result; + + if (!allowed(iface->ifname)) { +// dbg("skipping if%s %s", action, iface->ifname); + continue; + } + + dbg("if%s %s", action, iface->ifname); + result = systemf("ip link set %s %s", iface->ifname, action); + if (!result) { + char buf[128]; + FILE *fp; + + if (!ifadmin(iface->ifname, buf, sizeof(buf))) + continue; + + dbg("updating %s '%s'", buf, action); + fp = fopen(buf, "w"); + if (fp) { + fprintf(fp, "%s\n", action); + fclose(fp); + } + } + + rc += result; + } + + return rc; +} + +static int activate_next(void) +{ + const char *net = getnet(); + char curr[512], next[512]; + int rc; + + pipe_init(); + + if ((rc = load_gen(net, "next", next, sizeof(next)))) { + if (rc == EX_IOERR) + warnx("missing next generation"); + exit(0); /* nothing to do */ + } + + if ((rc = load_gen(net, "gen", curr, sizeof(curr)))) { + if (rc == EX_IOERR) + errx(rc, "missing current generation"); + /* no current generation */ + } else { + rc = deactivate(net, curr); + if (rc) + err(1, "failed deactivating current generation"); + } + + rc = activate(net, next); + if (rc) + err(1, "failed activating next generation"); + + if (dep) { + if (save_rdeps(net, next)) + err(1, "failed saving interface deps in %s", next); + } + + if (save_gen(net, "gen", next)) + err(1, "next generation applied, failed current"); + + if (fremove("%s/next", net)) + err(1, "failed removing %s/next", net); + + return 0; +} + +static int act(cmd_t cmd) +{ + dbg("cmd %d", cmd); + switch (cmd) { + case DO: + return activate_next(); + case UP: + return ifupdown(1); + case DOWN: + return ifupdown(0); + default: + break; + } + + freeifs(); + + return EX_USAGE; +} + +cmd_t transform(char *arg0) +{ + prognm = strrchr(arg0, '/'); + if (prognm) + prognm++; + else + prognm = arg0; + + if (!strcmp(prognm, "ifup")) + return UP; + if (!strcmp(prognm, "ifdown")) + return DOWN; + + return INVAL; +} + +static int usage(int code) +{ + printf("Usage: %s [-adhv] [do | (up | down [ifname ...])]\n" + "\n" + "Options:\n" + " -a Act on all interfaces, ignored, for compat only.\n" + " -d Debug\n" + " -h This help text\n" + " -v Verbose, show actions taken\n" + "\n" + "Commands:\n" + " do Activate next network generation\n" + " up Bring up one/many or all interfaces in the current generation\n" + " down Take down one/many or all interfaces in the current generation\n" + "\n" + "Args:\n" + " ifname Zero, one, or more interface names to act on.\n" + "\n", prognm); + + return code; +} + +int main(int argc, char *argv[]) +{ + cmd_t cmd; + int c; + + cmd = transform(argv[0]); + + while ((c = getopt(argc, argv, "adhv")) != EOF) { + switch (c) { + case 'a': + /* compat with ifup/ifdown */ + break; + case 'd': + debug = 1; + break; + case 'h': + return usage(0); + case 'v': + verbose = 1; + break; + default: + return usage(1); + } + } + + for (c = optind; c < argc; c++) { + if (cmd == INVAL) { + if (!strcmp("do", argv[c]) || !strcmp("apply", argv[c])) + cmd = DO; + if (!strcmp("up", argv[c])) + cmd = UP; + if (!strcmp("down", argv[c])) + cmd = DOWN; + + continue; + } + + addif(argv[c]); + } + + if (cmd == INVAL) + return usage(EX_USAGE); + + return act(cmd); +} diff --git a/src/net/test.sh b/src/net/test.sh index c008f527f..837f728be 100755 --- a/src/net/test.sh +++ b/src/net/test.sh @@ -8,15 +8,14 @@ export NET_DIR gensh() { cat <<-EOF >"$1" - #!/bin/sh - echo "Running \$0 ..." + EOF chmod +x "$1" } check() { - if ./net; then + if ./net -v do; then printf "\n[\033[1;32m OK \033[0m] Checking %s\n" "$1" return 0 fi @@ -25,77 +24,102 @@ check() return 1 } -echo "Verify that all leaves and dependencies are" -echo "evaluated first:" +echo "Verify leaves and dependencies are evaluated first:" +echo "" echo " vlan1" echo " ______/____" echo " [____br0____]" echo " / / \\ \\" -echo "lo eth0 eth1 eth2 eth3 lag0" +echo " eth0 eth1 eth2 eth3 lag0" echo " / \\" echo " eth4 eth5" -printf "_______________________________________________\n\n" +printf "___________________________________________________\n\n" mkdir -p $NET_DIR/0 echo 0 > $NET_DIR/next -mkdir -p $NET_DIR/0/lo/deps -gensh $NET_DIR/0/lo/ip-link.up - mkdir -p $NET_DIR/0/eth0/deps mkdir -p $NET_DIR/0/eth1/deps mkdir -p $NET_DIR/0/eth2/deps mkdir -p $NET_DIR/0/eth3/deps mkdir -p $NET_DIR/0/eth4/deps +mkdir -p $NET_DIR/0/eth5/deps -gensh $NET_DIR/0/eth0/ip-link.up -gensh $NET_DIR/0/eth1/ip-link.up -gensh $NET_DIR/0/eth2/ip-link.up -gensh $NET_DIR/0/eth3/ip-link.up -gensh $NET_DIR/0/eth4/ip-link.up +gensh $NET_DIR/0/eth0/init.ip +gensh $NET_DIR/0/eth1/init.ip +gensh $NET_DIR/0/eth2/init.ip +gensh $NET_DIR/0/eth3/init.ip +gensh $NET_DIR/0/eth4/init.ip +gensh $NET_DIR/0/eth5/init.ip mkdir -p $NET_DIR/0/lag0/deps -ln -sf ../../eth3 $NET_DIR/0/lag0/deps/ ln -sf ../../eth4 $NET_DIR/0/lag0/deps/ -gensh $NET_DIR/0/lag0/ip-link.up +ln -sf ../../eth5 $NET_DIR/0/lag0/deps/ +gensh $NET_DIR/0/lag0/init.ip mkdir -p $NET_DIR/0/br0/deps ln -sf ../../eth1 $NET_DIR/0/br0/deps/ ln -sf ../../eth2 $NET_DIR/0/br0/deps/ +ln -sf ../../eth3 $NET_DIR/0/br0/deps/ ln -sf ../../lag0 $NET_DIR/0/br0/deps/ -gensh $NET_DIR/0/br0/ip-link.up +gensh $NET_DIR/0/br0/init.bridge +gensh $NET_DIR/0/br0/init.ip mkdir -p $NET_DIR/0/vlan1/deps ln -sf ../../br0 $NET_DIR/0/vlan1/deps/ -gensh $NET_DIR/0/vlan1/ip-link.up +gensh $NET_DIR/0/vlan1/init.ip check "initial startup, gen 0" +cat $NET_DIR/0/rdeps -printf "_______________________________________________\n\n" +printf "___________________________________________________\n\n" mkdir -p $NET_DIR/1 -mkdir -p $NET_DIR/1/lo/deps mkdir -p $NET_DIR/1/eth0/deps mkdir -p $NET_DIR/1/eth1/deps mkdir -p $NET_DIR/1/eth2/deps mkdir -p $NET_DIR/1/eth3/deps mkdir -p $NET_DIR/1/eth4/deps +mkdir -p $NET_DIR/1/eth5/deps mkdir -p $NET_DIR/1/lag0/deps -ln -sf ../../eth4 $NET_DIR/1/lag0/deps/ +ln -sf ../../eth5 $NET_DIR/1/lag0/deps/ mkdir -p $NET_DIR/1/br0/deps ln -sf ../../eth1 $NET_DIR/1/br0/deps/ ln -sf ../../eth2 $NET_DIR/1/br0/deps/ ln -sf ../../eth3 $NET_DIR/1/br0/deps/ +ln -sf ../../eth4 $NET_DIR/1/br0/deps/ +ln -sf ../../lag0 $NET_DIR/1/br0/deps/ mkdir -p $NET_DIR/1/vlan1/deps ln -sf ../../br0 $NET_DIR/1/vlan1/deps/ -gensh $NET_DIR/0/eth3/ip-link.dn -gensh $NET_DIR/1/eth3/ip-link.up +gensh $NET_DIR/0/eth4/exit.ip +gensh $NET_DIR/1/eth4/init.ip echo 1 > $NET_DIR/next -check "move eth3 from lag0 to br0" +check "move eth4 from lag0 to br0" +cat $NET_DIR/1/rdeps + +printf "___________________________________________________\n\n" +mkdir -p $NET_DIR/2 + +mkdir -p $NET_DIR/2/eth0/deps +mkdir -p $NET_DIR/2/eth1/deps +mkdir -p $NET_DIR/2/eth2/deps +mkdir -p $NET_DIR/2/eth3/deps +mkdir -p $NET_DIR/2/eth4/deps +mkdir -p $NET_DIR/2/eth5/deps + +mkdir -p $NET_DIR/2/lag0/deps +ln -sf ../../eth5 $NET_DIR/2/lag0/deps/ + +gensh $NET_DIR/1/vlan1/exit.ip +gensh $NET_DIR/1/br0/exit.ip +echo 2 > $NET_DIR/next + +check "delete vlan1 and br0" +cat $NET_DIR/2/rdeps exit 0 diff --git a/src/net/test/Makefile.am b/src/net/test/Makefile.am new file mode 100644 index 000000000..914764533 --- /dev/null +++ b/src/net/test/Makefile.am @@ -0,0 +1,10 @@ +EXTRA_DIST = lib.sh $(TESTS) +pkgdata_DATA = $(EXTRA_DIST) + +TEST_EXTENSIONS = .sh +TESTS_ENVIRONMENT = unshare -mrun +TESTS = three-independent.sh +TESTS += bridge.sh +TESTS += bridge-lag.sh + +CLEANFILES = *~ *.trs *.log diff --git a/src/net/test/bridge-lag.sh b/src/net/test/bridge-lag.sh new file mode 100755 index 000000000..e86a6777f --- /dev/null +++ b/src/net/test/bridge-lag.sh @@ -0,0 +1,75 @@ +#!/bin/sh +# vlan1 +# ______/____ +# [____br0____] +# / / \ \ +# eth0 eth1 eth2 eth3 lag0 +# / \ +# eth4 eth5 +# +# - Bridge with five bridge ports, one of which is a lag, and VLAN interface +# - Move a port from lag to bridge +# - Remove VLAN interface from bridge and remove bridge +# + +TEST_DIR=$(dirname "$0") +. "$TEST_DIR/lib.sh" + +################################################ +say "Verify bringup of bridge with three ports and an upper VLAN interface" + +create_iface eth0 10.0.0.1/24 +create_iface eth1 +create_iface eth2 +create_iface eth3 +create_iface eth4 +create_iface eth5 + +create_lag lag0 "" eth4 eth5 + +create_bridge br0 "vlan_filtering 1" eth1 eth2 eth3 lag0 +bridge_init br0 br0 1 2 3 + +create_vlan_iface vlan1 br0 1 192.168.1.1/24 +netdo + +#ip -d -j link show eth4 |jq -r '.[].master' +#ip -d link show +#bridge -d link show +#bridge -d vlan show + +assert_bridge_ports br0 true eth1 eth2 eth3 lag0 +assert_iface br0 +assert_iface vlan1 192.168.1.1/24 +assert_lag_ports lag0 true eth4 eth5 + +################################################ +sep +say "Verify moving a port from lag0 to br0" + +del_lagport lag0 eth4 +init_next_gen eth0 eth1 eth2 eth3 eth4 eth5 lag0 br0 vlan1 +init_deps br0 eth1 eth2 eth3 lag0 +init_deps lag0 eth5 +add_brport br0 eth4 + +netdo + +assert_lag_ports lag0 true eth5 +assert_lag_ports lag0 false eth4 + +assert_bridge_ports br0 true eth1 eth2 eth3 eth4 lag0 +assert_iface br0 + +################################################ +sep +say "Verify removing VLAN interface (vlan1) and bridge (br0)" +remove_iface vlan1 +remove_iface br0 +init_next_gen eth0 eth1 eth2 eth3 eth4 eth5 lag0 + +netdo + +for iface in eth0 eth1 eth2 eth3 eth4 lag0 eth5; do + assert_iface $iface +done diff --git a/src/net/test/bridge.sh b/src/net/test/bridge.sh new file mode 100755 index 000000000..88cd306cc --- /dev/null +++ b/src/net/test/bridge.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# - Basic bridge with three bridge ports +# - Add another port +# - Remove a port + +TEST_DIR=$(dirname "$0") +. "$TEST_DIR/lib.sh" + +################################################ +say "Verify bringup of basic bridge with three ports" +create_iface eth0 +create_iface eth1 +create_iface eth2 +create_bridge br0 "" eth0 eth1 eth2 + +netdo + +assert_bridge_ports br0 true eth0 eth1 eth2 +assert_iface br0 + +################################################ +sep +say "Verify add another bridge port (eth3)" +init_next_gen eth0 eth1 eth2 br0 +init_deps br0 eth0 eth1 eth2 + +create_iface eth3 +add_brport br0 eth3 + +netdo + +assert_bridge_ports br0 true eth0 eth1 eth2 eth3 +assert_iface br0 + +################################################ +sep +say "Verify delete a bridge port (eth1)" +del_brport br0 eth1 + +init_next_gen eth0 eth2 eth3 br0 +init_deps br0 eth0 eth2 eth3 + +netdo + +assert_bridge_ports br0 true eth0 eth2 eth3 +assert_bridge_ports br0 false eth1 +assert_iface br0 diff --git a/src/net/test/lib.sh b/src/net/test/lib.sh new file mode 100755 index 000000000..40c2cc2db --- /dev/null +++ b/src/net/test/lib.sh @@ -0,0 +1,479 @@ +#!/bin/sh + +# Session set from Makefile before calling unshare -mrun +if [ -z "$SESSION" ]; then + SESSION=$(mktemp -d) + TMPSESS=1 +fi + +if [ -n "$DEBUG" ]; then + DEBUG="-v -d" +else + DEBUG="" +fi + +# Test name, used everywhere as /tmp/$NM/foo +NM=$(basename "$0" .sh) +NET_DIR="${SESSION}/${NM}" +export NET_DIR + +gen=-1 + +NET=$(command -v net) +if [ -n "$NET" ]; then + # Verify we didn't find Samba net command, our net live in sbin + if [ "$(dirname "$NET")" = "/usr/bin" ]; then + NET="" + fi +fi +[ -n "$NET" ] || NET=../src/net + +# Exit immediately on error, treat unset variables as error +set -eu + +color_reset='\e[0m' +fg_red='\e[1;31m' +fg_green='\e[1;32m' +fg_yellow='\e[1;33m' +log() +{ + test=$(basename "$0" ".sh") + printf "\e[2m[%s]\e[0m %b%b%b %s\n" "$test" "$1" "$2" "$color_reset" "$3" +} + +sep() +{ + printf "\e[2m――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\e[0m\n" +} + +say() +{ + log "$fg_yellow" "•" "$@" +} + +skip() +{ + log "$fg_yellow" − "$*" + exit 77 +} + +fail() +{ + log "$fg_red" ✘ "$*" + exit 99 +} + +assert() +{ + __assert_msg=$1 + shift + + if [ ! "$@" ]; then + log "$fg_red" ✘ "$__assert_msg ($*)" + return 1 + fi + + log "$fg_green" ✔ "$__assert_msg" + return 0 +} + +signal() +{ + echo + if [ "$1" != "EXIT" ]; then + print "Got signal, cleaning up" + fi + + rm -rf "${NET_DIR}" + if [ -n "$TMPSESS" ] && [ -d "$SESSION" ]; then + rm -rf "$SESSION" + fi +} + +# props to https://stackoverflow.com/a/2183063/1708249 +# shellcheck disable=SC2064 +trapit() +{ + func="$1" ; shift + for sig ; do + trap "$func $sig" "$sig" + done +} + +# shellcheck disable=SC2120 +init_next_gen() +{ + _=$((gen += 1)) + mkdir -p "$NET_DIR/$gen" + echo $gen > "$NET_DIR/next" + + # shellcheck disable=SC2068 + for iface in $@; do + create_iface_data "$iface" + done +} + +init_deps() +{ + parent=$1 + shift + + mkdir -p "$NET_DIR/$gen/$parent/deps" + #shellcheck disable=SC2068 + for iface in $@; do + ln -s "../../$iface" "$NET_DIR/$gen/$parent/deps/$iface" + done +} + +setup() +{ + say "Test start $(date)" + init_next_gen + + # Runs once when including lib.sh + mkdir -p "${NET_DIR}" + trapit signal INT TERM QUIT EXIT + + ip link set lo up + sep +} + +netdo() +{ + if [ -n "$DEBUG" ]; then + tree "$NET_DIR/" + echo "Calling: $NET $DEBUG apply" + fi + + # shellcheck disable=SC2086 + $NET $DEBUG apply + + if [ -n "$DEBUG" ]; then + ip link + ip addr + tree "$NET_DIR/" + fi +} + +netdown() +{ + # shellcheck disable=SC2086,SC2068 + $NET $DEBUG down $@ + + if [ -n "$DEBUG" ]; then + ip link + ip addr + fi +} + +netup() +{ + # shellcheck disable=SC2086,SC2068 + $NET $DEBUG up $@ + + if [ -n "$DEBUG" ]; then + ip link + ip addr + fi +} + +create_iface_data() +{ + ifname=$1 + ifdir="$NET_DIR/$gen/$ifname" + + mkdir -p "$ifdir/deps" + echo "up" > "$ifdir/admin-state" +} + +create_iface() +{ + ifname=$1 + ifdir="$NET_DIR/$gen/$ifname" + if [ $# -eq 2 ]; then + address=$2 + else + address="" + fi + + create_iface_data $ifname + + if [ ! -f "$ifdir/init.ip" ]; then + echo "link add $ifname type dummy" > "$ifdir/init.ip" + fi + if [ -n "$address" ]; then + cat <<-EOF >>"$ifdir/init.ip" + addr add $address dev $ifname + link set $ifname up + EOF + fi +} + +create_vlan_iface() +{ + ifname=$1 + link=$2 + vid=$3 + if [ $# -eq 4 ]; then + address=$4 + else + address="" + fi + ifdir="$NET_DIR/$gen/$ifname" + + mkdir -p "$ifdir/deps" + ln -s "../../$link" "$ifdir/deps/$link" + + cat <<-EOF >"$ifdir/init.ip" + link add $ifname link $link type vlan id $vid + link set $ifname up + EOF + if [ -n "$address" ]; then + cat <<-EOF >>"$ifdir/init.ip" + addr add $address dev $ifname + EOF + fi + + echo "up" > "$ifdir/admin-state" +} + +# shellcheck disable=SC2124 +add_brport() +{ + brname=$1 + shift + brports=$@ + brdir="$NET_DIR/$gen/$brname" + + mkdir -p "$brdir/deps" + for port in $brports; do + ln -s "../../$port" "$brdir/deps/$port" + + cat <<-EOF >> "$brdir/init.ip" + # Attaching port $port to bridge $brname + link set $port master $brname + link set $port up + EOF + done +} + +# shellcheck disable=SC2124 +del_brport() +{ + brname=$1 + shift + brports=$@ + brdir="$NET_DIR/$gen/$brname" + + for port in $brports; do + cat <<-EOF >"$brdir/exit.ip" + link set $port nomaster + EOF + done +} + +# shellcheck disable=SC2124,SC2086 +create_bridge() +{ + brname=$1 + bropts=$2 + shift 2 + brports=$@ + brdir="$NET_DIR/$gen/$brname" + + mkdir -p "$brdir/deps" + cat <<-EOF > "$brdir/init.ip" + link add $brname type bridge $bropts + EOF + + add_brport "$brname" $brports + cat <<-EOF >> "$brdir/init.ip" + link set $brname up + EOF + + echo "up" > "$brdir/admin-state" +} + +# Create init.bridge commands to be run for $ifname on $brname +# shellcheck disable=SC2124,SC2086 +bridge_init() +{ + brname=$1 + ifname=$2 + shift 2 + vlans=$@ + ifdir="$NET_DIR/$gen/$ifname" + + echo "" > "$ifdir/init.bridge" + for vlan in $vlans; do + echo "vlan add vid $vlan dev $brname self" >> "$ifdir/init.bridge" + done +} + +# shellcheck disable=SC2124 +add_lagport() +{ + lagnm=$1 + shift + lagports=$@ + lagdir="$NET_DIR/$gen/$lagnm" + + for port in $lagports; do + ln -s "../../$port" "$lagdir/deps/$port" + + cat <<-EOF >>"$lagdir/init.ip" + # Attaching port $port to $lagnm + link set $port master $lagnm + link set $port up + EOF + done +} + +# shellcheck disable=SC2124 +del_lagport() +{ + lagnm=$1 + shift + lagports=$@ + lagdir="$NET_DIR/$gen/$lagnm" + + for port in $lagports; do + cat <<-EOF >>"$lagdir/exit.ip" + link set $port nomaster + EOF + done +} + +# shellcheck disable=SC2124,SC2086 +create_lag() +{ + lagnm=$1 + lagopts=$2 + shift 2 + ports=$@ + lagdir="$NET_DIR/$gen/$lagnm" + + mkdir -p "$lagdir/deps" + cat <<-EOF > "$lagdir/init.ip" + link add $lagnm type bond $lagopts + link set $lagnm up + EOF + + add_lagport "$lagnm" $ports + echo "up" > "$lagdir/admin-state" +} + +remove_iface() +{ + ifname=$1 + + cat <<-EOF >"$NET_DIR/$gen/$ifname/exit.ip" + link del $ifname +EOF +} + +assert_iface() +{ + ifname=$1 + if [ $# -gt 1 ]; then + address=$2 + else + address="" + fi + state=$(tr '[:lower:]' '[:upper:]' < "$NET_DIR/$gen/$ifname/admin-state") + + addr=$(ip -br -j addr show "$ifname" | jq -r '.[] | .addr_info[0].local') + plen=$(ip -br -j addr show "$ifname" | jq -r '.[] | .addr_info[0].prefixlen') + addr="$addr/$plen" + updn=$(ip -br -j link show "$ifname" | jq -r '.[] | .flags[] | select(index("UP"))' | head -1) + +# echo "$state => $ifname: $updn $addr" + assert "Verify $ifname state $state" "$state" = "$updn" + if [ -n "$address" ]; then + assert "Verify $ifname address $address" "$address" = "$addr" + fi +} + +assert_noiface() +{ + ifname=$1 + rc=true + + for iface in $(ip -j -br link show |jq -r '.[] .ifname'); do + [ "$iface" = "$ifname" ] || continue + + rc=false + break + done + + assert "Verify $ifname has been removed" $rc +} + +assert_iface_flag() +{ + found=false + ifname=$2 + flag=$3 + msg=$1 + val=$4 + + for f in $(ip -j -br link show "$ifname" |jq -r '.[] .flags[]'); do +# echo "$ifname: FLAG $f ..." + [ "$f" = "$flag" ] || continue + found=true + break + done + +# echo "FLAG $flag found $found, expected $val" + assert "$msg" "$found" = "$val" +} + +assert_bridge_ports() +{ + br="$1" + val="$2" + shift 2 + # shellcheck disable=SC2124 + ports=$@ + + for port in $ports; do + found=false + for brport in $(bridge -j link |jq -r --arg br "$br" '.[] | select(.master == $br).ifname'); do + if [ "$port" = "$brport" ]; then + found=true + break; + fi + done + if [ "$val" = "false" ]; then + not="NOT " + else + not="" + fi + assert "Port $port is ${not}a $br bridge port" "$found" = "$val" + done +} + +assert_lag_ports() +{ + lag="$1" + val="$2" + shift 2 + # shellcheck disable=SC2124 + ports=$@ + + for port in $ports; do + found=false + + if [ "$lag" = "$(ip -d -j link show $port |jq -r '.[].master')" ]; then + found=true + fi + if [ "$val" = "false" ]; then + not="NOT " + else + not="" + fi + assert "Port $port is ${not}a $lag member port" "$found" = "$val" + done +} + +setup diff --git a/src/net/test/three-independent.sh b/src/net/test/three-independent.sh new file mode 100755 index 000000000..26c48be7e --- /dev/null +++ b/src/net/test/three-independent.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# - Verify correct bring up of three independent interfaces +# - Verify removal of one +# - Verify net down +# - Verify net up +TEST_DIR=$(dirname "$0") +. "$TEST_DIR/lib.sh" + +say "Verify bringup of generation $gen" +create_iface eth0 10.0.0.1/24 +create_iface eth1 10.0.1.1/24 +create_iface eth2 10.0.2.1/24 + +netdo + +assert_iface eth0 10.0.0.1/24 +assert_iface eth1 10.0.1.1/24 +assert_iface eth2 10.0.2.1/24 + +sep +say "Verify removal of an interface" +remove_iface eth1 +init_next_gen eth0 eth2 + +netdo + +assert_iface eth0 10.0.0.1/24 +assert_noiface eth1 +assert_iface eth2 10.0.2.1/24 + +sep +say "Verify net down" +netdown +assert_iface_flag "Verify eth0 DOWN" eth0 UP false +assert_iface_flag "Verify eth2 DOWN" eth2 UP false + +sep +say "Verify net up eth2" +netup eth2 +assert_iface_flag "Verify eth0 DOWN" eth0 UP false +assert_iface_flag "Verify eth2 UP" eth2 UP true +