diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0197eac..bbed35f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,10 +10,16 @@ jobs: - name: Update system and add dependencies run: | apk upgrade -Ua - apk add build-base git kyua atf + apk add build-base git kyua atf scdoc - name: Checkout uses: actions/checkout@v2 - - name: Build and run tests + - name: Build + run: make + + - name: Build documentation + run: make docs + + - name: Run tests run: make check diff --git a/.gitignore b/.gitignore index f22f97e..5e2d300 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,17 @@ *.o *.a +*.1 +*.2 +*.3 +*.4 +*.5 +*.6 +*.7 +*.8 +ifupdown ifquery ifup ifdown +ifctrstat +ifparse +*.lock diff --git a/COPYING b/COPYING index 537f879..7240951 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,5 @@ -Copyright (c) 2020 Ariadne Conill +Copyright (c) 2020-2021 Ariadne Conill +Copyright (c) 2020-2021 Maximilian Wilhelm Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/Makefile b/Makefile index 36cd719..9fb6199 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,30 @@ +LAYOUT ?= linux +SCDOC := scdoc +LIBBSD_CFLAGS = +LIBBSD_LIBS = + PACKAGE_NAME := ifupdown-ng -PACKAGE_VERSION := 0.2 -PACKAGE_BUGREPORT := https://github.com/kaniini/ifupdown-ng/issues/new +PACKAGE_VERSION := 0.11.3 +PACKAGE_BUGREPORT := https://github.com/ifupdown-ng/ifupdown-ng/issues/new INTERFACES_FILE := /etc/network/interfaces STATE_FILE := /run/ifstate -CFLAGS = -ggdb3 -Os -Wall -Wextra -CPPFLAGS = -I. -DINTERFACES_FILE=\"${INTERFACES_FILE}\" -DSTATE_FILE=\"${STATE_FILE}\" -DPACKAGE_NAME=\"${PACKAGE_NAME}\" -DPACKAGE_VERSION=\"${PACKAGE_VERSION}\" -DPACKAGE_BUGREPORT=\"${PACKAGE_BUGREPORT}\" +CONFIG_FILE := /etc/network/ifupdown-ng.conf +EXECUTOR_PATH := /usr/libexec/ifupdown-ng + +CFLAGS ?= -ggdb3 -Os +CFLAGS += -Wall -Wextra -Werror +CFLAGS += -Wmissing-declarations -Wmissing-prototypes -Wcast-align -Wpointer-arith -Wreturn-type +CFLAGS += ${LIBBSD_CFLAGS} +CPPFLAGS = -I. +CPPFLAGS += -DINTERFACES_FILE=\"${INTERFACES_FILE}\" +CPPFLAGS += -DSTATE_FILE=\"${STATE_FILE}\" +CPPFLAGS += -DCONFIG_FILE=\"${CONFIG_FILE}\" +CPPFLAGS += -DPACKAGE_NAME=\"${PACKAGE_NAME}\" +CPPFLAGS += -DPACKAGE_VERSION=\"${PACKAGE_VERSION}\" +CPPFLAGS += -DPACKAGE_BUGREPORT=\"${PACKAGE_BUGREPORT}\" +CPPFLAGS += -DEXECUTOR_PATH=\"${EXECUTOR_PATH}\" LIBIFUPDOWN_SRC = \ @@ -19,47 +37,183 @@ LIBIFUPDOWN_SRC = \ libifupdown/state.c \ libifupdown/environment.c \ libifupdown/execute.c \ - libifupdown/lifecycle.c - + libifupdown/lifecycle.c \ + libifupdown/config-parser.c \ + libifupdown/config-file.c \ + libifupdown/compat.c LIBIFUPDOWN_OBJ = ${LIBIFUPDOWN_SRC:.c=.o} LIBIFUPDOWN_LIB = libifupdown.a -MULTICALL_SRC = cmd/multicall.c +MULTICALL_SRC = \ + cmd/multicall.c \ + cmd/multicall-options.c \ + cmd/multicall-exec-options.c \ + cmd/multicall-match-options.c \ + cmd/pretty-print-iface.c MULTICALL_OBJ = ${MULTICALL_SRC:.c=.o} MULTICALL = ifupdown -IFQUERY_SRC = cmd/ifquery.c -IFQUERY_OBJ = ${IFQUERY_SRC:.c=.o} - +# enable ifup/ifdown applets (+16 KB) +CONFIG_IFUPDOWN ?= Y IFUPDOWN_SRC = cmd/ifupdown.c -IFUPDOWN_OBJ = ${IFUPDOWN_SRC:.c=.o} +MULTICALL_${CONFIG_IFUPDOWN}_OBJ += ${IFUPDOWN_SRC:.c=.o} +CMDS_${CONFIG_IFUPDOWN} += ifup ifdown +CPPFLAGS_${CONFIG_IFUPDOWN} += -DCONFIG_IFUPDOWN -CMD_OBJ = ${MULTICALL_OBJ} ${IFQUERY_OBJ} ${IFUPDOWN_OBJ} +# enable ifquery applet (+4 KB) +# [+20 KB without ifup/ifdown] +CONFIG_IFQUERY ?= Y +IFQUERY_SRC = cmd/ifquery.c +MULTICALL_${CONFIG_IFQUERY}_OBJ += ${IFQUERY_SRC:.c=.o} +CMDS_${CONFIG_IFQUERY} += ifquery +CPPFLAGS_${CONFIG_IFQUERY} += -DCONFIG_IFQUERY -CMDS = ifup ifdown ifquery +# enable ifctrstat applet (+1 KB) +CONFIG_IFCTRSTAT ?= Y +IFCTRSTAT_SRC = cmd/ifctrstat.c cmd/ifctrstat-${LAYOUT}.c +MULTICALL_${CONFIG_IFCTRSTAT}_OBJ += ${IFCTRSTAT_SRC:.c=.o} +CMDS_${CONFIG_IFCTRSTAT} += ifctrstat +CPPFLAGS_${CONFIG_IFCTRSTAT} += -DCONFIG_IFCTRSTAT -LIBS = ${LIBIFUPDOWN_LIB} +# enable ifparse applet (+1 KB) +CONFIG_IFPARSE ?= Y +IFPARSE_SRC = cmd/ifparse.c +MULTICALL_${CONFIG_IFPARSE}_OBJ += ${IFPARSE_SRC:.c=.o} +CMDS_${CONFIG_IFPARSE} += ifparse +CPPFLAGS_${CONFIG_IFPARSE} += -DCONFIG_IFPARSE -all: libifupdown.a ${MULTICALL} ${CMDS} +# enable YAML support (+2 KB) +CONFIG_YAML ?= Y +YAML_SRC = \ + libifupdown/yaml-base.c \ + libifupdown/yaml-writer.c +LIBIFUPDOWN_${CONFIG_YAML}_OBJ += ${YAML_SRC:.c=.o} +CPPFLAGS_${CONFIG_YAML} += -DCONFIG_YAML + +LIBIFUPDOWN_OBJ += ${LIBIFUPDOWN_Y_OBJ} +MULTICALL_OBJ += ${MULTICALL_Y_OBJ} +CMDS += ${CMDS_Y} +CPPFLAGS += ${CPPFLAGS_Y} + +EXECUTOR_SCRIPTS_CORE ?= \ + dhcp \ + ipv6-ra \ + static \ + link \ + ppp \ + forward + +EXECUTOR_SCRIPTS_OPT ?= \ + batman \ + bond \ + bridge \ + ethtool \ + gre \ + mpls \ + tunnel \ + vrf \ + vxlan \ + wifi \ + wireguard + +EXECUTOR_SCRIPTS ?= ${EXECUTOR_SCRIPTS_CORE} ${EXECUTOR_SCRIPTS_OPT} + +EXECUTOR_SCRIPTS_STUB ?= + +EXECUTOR_SCRIPTS_NATIVE ?= + +TARGET_LIBS = ${LIBIFUPDOWN_LIB} +LIBS += ${TARGET_LIBS} ${LIBBSD_LIBS} + +all: ${MULTICALL} ${CMDS} ${CMDS}: ${MULTICALL} - ln -s ifupdown $@ + ln -sf ifupdown $@ -${MULTICALL}: ${LIBS} ${CMD_OBJ} - ${CC} -o $@ ${CMD_OBJ} ${LIBS} +${MULTICALL}: ${TARGET_LIBS} ${MULTICALL_OBJ} + ${CC} -o $@ ${MULTICALL_OBJ} ${LIBS} -libifupdown.a: ${LIBIFUPDOWN_OBJ} +${LIBIFUPDOWN_LIB}: ${LIBIFUPDOWN_OBJ} ${AR} -rcs $@ ${LIBIFUPDOWN_OBJ} clean: - rm -f ${LIBIFUPDOWN_OBJ} ${CMD_OBJ} + rm -f ${LIBIFUPDOWN_OBJ} ${MULTICALL_OBJ} + rm -f ${LIBIFUPDOWN_LIB} rm -f ${CMDS} ${MULTICALL} + rm -f ${MANPAGES} -check: libifupdown.a ${CMDS} - kyua test +check: ${LIBIFUPDOWN_LIB} ${CMDS} + kyua test || (kyua report --verbose && exit 1) install: all install -D -m755 ${MULTICALL} ${DESTDIR}/sbin/${MULTICALL} for i in ${CMDS}; do \ ln -s /sbin/${MULTICALL} ${DESTDIR}/sbin/$$i; \ done + for i in ${EXECUTOR_SCRIPTS}; do \ + install -D -m755 executor-scripts/${LAYOUT}/$$i ${DESTDIR}${EXECUTOR_PATH}/$$i; \ + done + for i in ${EXECUTOR_SCRIPTS_STUB}; do \ + install -D -m755 executor-scripts/stub/$$i ${DESTDIR}${EXECUTOR_PATH}/$$i; \ + done + for i in ${EXECUTOR_SCRIPTS_NATIVE}; do \ + install -D -m755 executor-scripts/${LAYOUT}-native/$$i ${DESTDIR}${EXECUTOR_PATH}/$$i; \ + done + install -D -m644 dist/ifupdown-ng.conf.example ${DESTDIR}${CONFIG_FILE}.example + +.scd.1 .scd.2 .scd.3 .scd.4 .scd.5 .scd.6 .scd.7 .scd.8: + ${SCDOC} < $< > $@ + +MANPAGES_5 = \ + doc/ifstate.5 \ + doc/ifupdown-ng.conf.5 \ + doc/interfaces.5 \ + doc/interfaces-bond.5 \ + doc/interfaces-batman.5 \ + doc/interfaces-bridge.5 \ + doc/interfaces-forward.5 \ + doc/interfaces-ppp.5 \ + doc/interfaces-tunnel.5 \ + doc/interfaces-vrf.5 \ + doc/interfaces-vxlan.5 \ + doc/interfaces-wifi.5 \ + doc/interfaces-wireguard.5 + +MANPAGES_7 = \ + doc/ifupdown-executor.7 + +MANPAGES_8 = \ + doc/ifquery.8 \ + doc/ifup.8 \ + doc/ifdown.8 \ + doc/ifctrstat.8 \ + doc/ifparse.8 + +MANPAGES = ${MANPAGES_5} ${MANPAGES_7} ${MANPAGES_8} + +docs: ${MANPAGES} + +install_docs: docs + for i in ${MANPAGES_5}; do \ + target=$$(basename $$i); \ + install -D -m644 $$i ${DESTDIR}/usr/share/man/man5/$$target; \ + done + for i in ${MANPAGES_7}; do \ + target=$$(basename $$i); \ + install -D -m644 $$i ${DESTDIR}/usr/share/man/man7/$$target; \ + done + for i in ${MANPAGES_8}; do \ + target=$$(basename $$i); \ + install -D -m644 $$i ${DESTDIR}/usr/share/man/man8/$$target; \ + done + +.SUFFIXES: .scd .1 .2 .3 .4 .5 .6 .7 .8 + +DIST_NAME = ${PACKAGE_NAME}-${PACKAGE_VERSION} +DIST_TARBALL = ${DIST_NAME}.tar.xz + +distcheck: check dist +dist: ${DIST_TARBALL} +${DIST_TARBALL}: + git archive --format=tar --prefix=${DIST_NAME}/ -o ${DIST_NAME}.tar ${DIST_NAME} + xz ${DIST_NAME}.tar diff --git a/README.md b/README.md index f9a9f98..4b03d23 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,49 @@ # ifupdown-ng -This package is a work in progress implementation of the ifupdown suite. It is -intended to be largely compatible with ifupdown and ifupdown2, with some caveats: +ifupdown-ng is a network device manager that is largely compatible with Debian +ifupdown, BusyBox ifupdown and Cumulus Networks' ifupdown2. + +For more information read the [admin guide](doc/ADMIN-GUIDE.md). + +## Dependency Resolution + +![Dependency resolution example](doc/img/dependency-resolution.png) + +ifupdown-ng uses a dependency resolver to determine interface bring-up order +in a deterministic way. + +This is accomplished through a combination of manual hinting using the `requires` +keyword and dependency learning using native executors. + +For compatibility with some legacy ifupdown executors, we also provide the +`requires` keyword under other environment variables in some cases. + +## Caveats * ifupdown2 python plugins are not supported at this time. An executor could be written to handle them. -* ifupdown-ng uses a SAT solver to determine interface bring-up order, like - ifupdown2. However, relationships must be explicitly defined instead of - inferred by plugins in ifupdown2. This simplifies the executors and ensures - consistent behaviour across executors. - * ifupdown-ng retains compatibility with /etc/network/if-X.d scripts, but will prefer using executors in /usr/libexec/ifupdown-ng where appropriate. -This package is planned to replace BusyBox ifupdown in Alpine at some point in -the future. +## Building + +On musl systems, simply do `make` and `make install` to build and install. + +On glibc systems, you must install `libbsd-dev` or equivalent and additionally define `LIBBSD_CFLAGS` and `LIBBSD_LIBS`: + + # instal packages + apt install build-essential libbsd0 libbsd-dev + + # build ifupdown-ng + make LIBBSD_CFLAGS="$(pkg-config --cflags libbsd-overlay)" LIBBSD_LIBS="$(pkg-config --cflags --libs libbsd-overlay)" + make install + +To run the tests, do `make check`. Running the checks requires `kyua` (`apk add kyua` / `apt install kyua`). + +To build the documentation, do `make docs` and `make install_docs`. Building +the documentation requires scdoc (`apk add scdoc` / `apt install scdoc`). + +## Discussion + +Discuss ifupdown-ng on IRC: irc.oftc.net #ifupdown-ng diff --git a/cmd/ifctrstat-linux.c b/cmd/ifctrstat-linux.c new file mode 100644 index 0000000..8203fc4 --- /dev/null +++ b/cmd/ifctrstat-linux.c @@ -0,0 +1,97 @@ +/* + * cmd/ifctrstat-linux.c + * Purpose: Implement ifctrstat system-specific routines for Linux + * + * Copyright (c) 2020 Adélie Software in the Public Benefit, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include "cmd/multicall.h" +#include "cmd/ifctrstat-linux.h" + +struct counter_desc { + const char *name; + const void *data; +} avail_counters[] = { + {"rx.discard", "rx_dropped"}, + {"rx.errors", "rx_errors"}, + {"rx.octets", "rx_bytes"}, + {"rx.packets", "rx_packets"}, + {"tx.discard", "tx_dropped"}, + {"tx.errors", "tx_errors"}, + {"tx.octets", "tx_bytes"}, + {"tx.packets", "tx_packets"} +}; + +size_t avail_counters_count = ARRAY_SIZE(avail_counters); + +static int +counter_compare(const void *key, const void *candidate) +{ + return strcasecmp((const char *)key, ((struct counter_desc *)candidate)->name); +} + +const char * +read_counter(const char *interface, const char *counter) +{ + FILE *fp; + const char *path; + char full_path[PATH_MAX]; + char buffer[1024]; + size_t in_count; + struct counter_desc *ctrdata; + + errno = 0; + + ctrdata = bsearch(counter, avail_counters, avail_counters_count, sizeof(struct counter_desc), counter_compare); + if (ctrdata) { + path = (const char *)ctrdata->data; + } else { + errno = ENOSYS; + return NULL; + } + + if (snprintf(full_path, PATH_MAX, "/sys/class/net/%s/statistics/%s", interface, path) > PATH_MAX) + { + errno = ENOMEM; + return NULL; + } + + fp = fopen(full_path, "r"); + if (!fp) + { + return NULL; + } + + in_count = fread(buffer, 1, sizeof(buffer), fp); + + if (in_count == sizeof(buffer)) + { + errno = ENOMEM; + fclose(fp); + return NULL; + } + + if (ferror(fp)) + { + return NULL; + } + + fclose(fp); + + /* take away the \n, we add our own */ + buffer[in_count - 1] = '\0'; + + return strdup(buffer); +} diff --git a/cmd/ifctrstat-linux.h b/cmd/ifctrstat-linux.h new file mode 100644 index 0000000..3510013 --- /dev/null +++ b/cmd/ifctrstat-linux.h @@ -0,0 +1,22 @@ +/* + * cmd/ifctrstat-linux.c + * Purpose: Implement ifctrstat system-specific routines for Linux + * + * Copyright (c) 2021 Maximilian Wilhelm + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + + +#ifndef IFUPDOWN_IFCTRSTAT_LINUX__H__GUARD +#define IFUPDOWN_IFCTRSTAT_LINUX__H__GUARD + +extern const char * read_counter(const char *interface, const char *counter); + +#endif diff --git a/cmd/ifctrstat.c b/cmd/ifctrstat.c new file mode 100644 index 0000000..8b0482f --- /dev/null +++ b/cmd/ifctrstat.c @@ -0,0 +1,159 @@ +/* + * cmd/ifctrstat.c + * Purpose: Display statistics about interfaces on the system + * + * Copyright (c) 2020 Adélie Software in the Public Benefit, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "libifupdown/libifupdown.h" +#include "cmd/multicall.h" +#include "cmd/ifctrstat-linux.h" + +extern struct counter_desc { const char *name; const void *data; } avail_counters[]; +extern int avail_counters_count; + +static bool show_label = true; + +static bool +counter_is_valid(const char *candidate) +{ + for (int i = 0; i < avail_counters_count; i++) + { + if (strcasecmp(avail_counters[i].name, candidate) == 0) + return true; + } + + return false; +} + +static void +print_counter(const char *iface, const char *name, const char *value) +{ + (void) iface; + + if (show_label) + fprintf(stdout, "%s: %s\n", name, value); + else + fprintf(stdout, "%s\n", value); +} + +static int +print_all_counters(const char *iface) +{ + int code = EXIT_SUCCESS; + const char *res; + + for (int i = 0; i < avail_counters_count; i++) + { + const char *ctr = avail_counters[i].name; + + res = read_counter(iface, ctr); + if (!res) + { + fprintf(stderr, "%s: could not determine value of %s for interface %s: %s\n", argv0, ctr, iface, strerror(errno)); + code = EXIT_FAILURE; + } + else + { + print_counter(iface, ctr, res); + } + } + + return code; +} + +static void +ifctrstat_list_counters(const char *opt_arg) +{ + (void) opt_arg; + + for (int i = 0; i < avail_counters_count; i++) + { + fprintf(stdout, "%s\n", avail_counters[i].name); + } + + exit(EXIT_SUCCESS); +} + +static void +ifctrstat_set_nolabel(const char *opt_arg) +{ + (void) opt_arg; + show_label = false; +} + +static int +ifctrstat_main(int argc, char *argv[]) +{ + if (optind >= argc) + generic_usage(self_applet, EXIT_FAILURE); + + int idx = optind; + if (argc - idx == 0) + { + fprintf(stderr, "%s: interface required\n", + argv0); + return EXIT_FAILURE; + } + + const char *iface = argv[idx++]; + + if (argc - idx == 0) + { + return print_all_counters(iface); + } + + for (; idx < argc; idx++) + { + if (!counter_is_valid(argv[idx])) + { + fprintf(stderr, "%s: counter %s is not valid or not available\n", argv0, argv[idx]); + return EXIT_FAILURE; + } + + errno = 0; + const char *res = read_counter(iface, argv[idx]); + if (!res) + { + fprintf(stderr, "%s: could not determine value of %s for interface %s: %s\n", argv0, argv[idx], iface, strerror(errno)); + return EXIT_FAILURE; + } + + print_counter(iface, argv[idx], res); + } + + return EXIT_SUCCESS; +} + +static struct if_option local_options[] = { + {'L', "list", NULL, "list available counters", false, ifctrstat_list_counters}, + {'n', "no-label", NULL, "print value without counter label", false, ifctrstat_set_nolabel} +}; + +static struct if_option_group local_option_group = { + .desc = "Program-specific options", + .group_size = ARRAY_SIZE(local_options), + .group = local_options +}; + +struct if_applet ifctrstat_applet = { + .name = "ifctrstat", + .desc = "display statistics about an interface", + .main = ifctrstat_main, + .usage = "ifctrstat [options] \n ifctrstat [options] --list", + .manpage = "8 ifctrstat", + .groups = { &global_option_group, &local_option_group, NULL } +}; diff --git a/cmd/ifparse.c b/cmd/ifparse.c new file mode 100644 index 0000000..e8c0535 --- /dev/null +++ b/cmd/ifparse.c @@ -0,0 +1,224 @@ +/* + * cmd/ifparse.c + * Purpose: Redisplay /e/n/i in alternative formats. + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include "libifupdown/libifupdown.h" + +#ifdef CONFIG_YAML +# include "libifupdown/yaml-base.h" +# include "libifupdown/yaml-writer.h" +#endif + +#include "cmd/multicall.h" +#include "cmd/pretty-print-iface.h" + +static bool show_all = false; +static bool allow_undefined = false; + +static void +set_show_all(const char *arg) +{ + (void) arg; + + show_all = true; +} + +static void +set_allow_undefined(const char *arg) +{ + (void) arg; + + allow_undefined = true; +} + +static const char *output_fmt = "ifupdown"; + +static void +set_output_fmt(const char *arg) +{ + output_fmt = arg; +} + +static struct if_option local_options[] = { + {'F', "format", NULL, "output format to use", true, set_output_fmt}, + {'A', "all", NULL, "show all interfaces", false, set_show_all}, + {'U', "allow-undefined", NULL, "allow querying undefined (virtual) interfaces", false, set_allow_undefined}, +}; + +static struct if_option_group local_option_group = { + .desc = "Program-specific options", + .group_size = ARRAY_SIZE(local_options), + .group = local_options +}; + +#ifdef CONFIG_YAML +static void +prettyprint_interface_yaml(struct lif_interface *iface) +{ + struct lif_yaml_node doc = {}; + + lif_yaml_document_init(&doc, "interfaces"); + + struct lif_yaml_node *iface_node = lif_yaml_node_new_list(iface->ifname); + lif_yaml_node_append_child(&doc, iface_node); + + if (iface->is_auto) + { + struct lif_yaml_node *iface_entry_node = lif_yaml_node_new_boolean("auto", true); + lif_yaml_node_append_child(iface_node, iface_entry_node); + } + + struct lif_node *iter; + LIF_DICT_FOREACH(iter, &iface->vars) + { + struct lif_dict_entry *entry = iter->data; + const char *value = entry->data; + char addr_buf[512]; + + if (!strcmp(entry->key, "address")) + { + struct lif_address *addr = entry->data; + + if (!lif_address_unparse(addr, addr_buf, sizeof addr_buf, true)) + continue; + + value = addr_buf; + } + + struct lif_yaml_node *iface_entry_node = lif_yaml_node_new_string(entry->key, value); + lif_yaml_node_append_child(iface_node, iface_entry_node); + } + + lif_yaml_write(iface_node, stdout, true); + lif_yaml_node_free(&doc); +} +#endif + +struct prettyprint_impl_map { + const char *name; + void (*handle)(struct lif_interface *iface); +}; + +struct prettyprint_impl_map pp_impl_map[] = { + {"ifupdown", prettyprint_interface_eni}, +#ifdef CONFIG_YAML + {"yaml-raw", prettyprint_interface_yaml}, +#endif +}; + +static int +pp_impl_cmp(const void *a, const void *b) +{ + const char *key = a; + const struct prettyprint_impl_map *impl = b; + + return strcmp(key, impl->name); +} + +static int +ifparse_main(int argc, char *argv[]) +{ + struct lif_dict state = {}; + struct lif_dict collection = {}; + struct lif_interface_file_parse_state parse_state = { + .collection = &collection, + }; + + lif_interface_collection_init(&collection); + + if (!lif_state_read_path(&state, exec_opts.state_file)) + { + fprintf(stderr, "%s: could not parse %s\n", argv0, exec_opts.state_file); + return EXIT_FAILURE; + } + + if (!lif_interface_file_parse(&parse_state, exec_opts.interfaces_file)) + { + fprintf(stderr, "%s: could not parse %s\n", argv0, exec_opts.interfaces_file); + return EXIT_FAILURE; + } + + if (match_opts.property == NULL && lif_lifecycle_count_rdepends(&exec_opts, &collection) == -1) + { + fprintf(stderr, "%s: could not validate dependency tree\n", argv0); + return EXIT_FAILURE; + } + + if (!lif_compat_apply(&collection)) + { + fprintf(stderr, "%s: failed to apply compatibility glue\n", argv0); + return EXIT_FAILURE; + } + + struct prettyprint_impl_map *m = bsearch(output_fmt, pp_impl_map, ARRAY_SIZE(pp_impl_map), sizeof(*m), pp_impl_cmp); + if (m == NULL) + { + fprintf(stderr, "%s: %s: output format not supported\n", argv0, output_fmt); + return EXIT_FAILURE; + } + + if (show_all) + { + struct lif_node *n; + + LIF_DICT_FOREACH(n, &collection) + { + struct lif_dict_entry *entry = n->data; + + m->handle(entry->data); + } + + return EXIT_SUCCESS; + } + + if (optind >= argc) + generic_usage(self_applet, EXIT_FAILURE); + + int idx = optind; + for (; idx < argc; idx++) + { + struct lif_dict_entry *entry = lif_dict_find(&collection, argv[idx]); + struct lif_interface *iface = NULL; + + if (entry != NULL) + iface = entry->data; + + if (entry == NULL && allow_undefined) + iface = lif_interface_collection_find(&collection, argv[idx]); + + if (iface == NULL) + { + fprintf(stderr, "%s: unknown interface %s\n", argv0, argv[idx]); + return EXIT_FAILURE; + } + + m->handle(iface); + } + + return EXIT_SUCCESS; +} + +struct if_applet ifparse_applet = { + .name = "ifparse", + .desc = "redisplay interface configuration", + .main = ifparse_main, + .usage = "ifparse [options] \n ifparse [options] --all", + .manpage = "8 ifparse", + .groups = { &global_option_group, &match_option_group, &exec_option_group, &local_option_group }, +}; diff --git a/cmd/ifquery.c b/cmd/ifquery.c index ee21fae..86a3f64 100644 --- a/cmd/ifquery.c +++ b/cmd/ifquery.c @@ -20,50 +20,18 @@ #include #include "libifupdown/libifupdown.h" #include "cmd/multicall.h" +#include "cmd/pretty-print-iface.h" -void -print_interface(struct lif_interface *iface) -{ - if (iface->is_auto) - printf("auto %s\n", iface->ifname); - - printf("iface %s\n", iface->ifname); - - struct lif_node *iter; - LIF_DICT_FOREACH(iter, &iface->vars) - { - struct lif_dict_entry *entry = iter->data; - - if (!strcmp(entry->key, "address")) - { - struct lif_address *addr = entry->data; - char addr_buf[512]; - - if (!lif_address_unparse(addr, addr_buf, sizeof addr_buf, true)) - { - printf(" # warning: failed to unparse address\n"); - continue; - } - - printf(" %s %s\n", entry->key, addr_buf); - } - else - printf(" %s %s\n", entry->key, (const char *) entry->data); - } - - printf("\n"); -} - -void +static void print_interface_dot(struct lif_dict *collection, struct lif_interface *iface, struct lif_interface *parent) { - if (iface->is_up) + if (!lif_lifecycle_query_dependents(&exec_opts, iface, iface->ifname)) return; if (parent != NULL) - printf("\"%s\" -> ", parent->ifname); + printf("\"%s (%zu)\" -> ", parent->ifname, parent->rdepends_count); - printf("\"%s\"", iface->ifname); + printf("\"%s (%zu)\"", iface->ifname, iface->rdepends_count); printf("\n"); @@ -80,51 +48,51 @@ print_interface_dot(struct lif_dict *collection, struct lif_interface *iface, st { struct lif_interface *child_if = lif_interface_collection_find(collection, tokenp); + if (child_if->is_pending) + continue; + + child_if->is_pending = true; print_interface_dot(collection, child_if, iface); - child_if->is_up = true; + child_if->is_pending = false; } } -void -ifquery_usage(void) +static void +print_interface_property(struct lif_interface *iface, const char *property) { - fprintf(stderr, "usage: ifquery [options] \n"); - fprintf(stderr, " ifquery [options] --list\n"); + struct lif_node *iter; + bool printing_address = !strcmp(property, "address"); - fprintf(stderr, "\nOptions:\n"); - fprintf(stderr, " -h, --help this help\n"); - fprintf(stderr, " -V, --version show this program's version\n"); - fprintf(stderr, " -i, --interfaces FILE use FILE for interface definitions\n"); - fprintf(stderr, " -L, --list list matching interfaces\n"); - fprintf(stderr, " -a, --auto only match against interfaces hinted as 'auto'\n"); - fprintf(stderr, " -I, --include PATTERN only match against interfaces matching PATTERN\n"); - fprintf(stderr, " -X, --exclude PATTERN never match against interfaces matching PATTERN\n"); - fprintf(stderr, " -P, --pretty-print pretty print the interfaces instead of just listing\n"); - fprintf(stderr, " -S, --state-file FILE use FILE for state\n"); - fprintf(stderr, " -s, --state show configured state\n"); - fprintf(stderr, " -D, --dot generate a dependency graph\n"); + LIF_DICT_FOREACH(iter, &iface->vars) + { + struct lif_dict_entry *entry = iter->data; - exit(1); + if (strcmp(entry->key, property)) + continue; + + if (printing_address) + { + char addr_buf[512]; + if (!lif_address_format_cidr(iface, entry, addr_buf, sizeof(addr_buf))) + continue; + + printf("%s\n", addr_buf); + } + else + printf("%s\n", (const char *) entry->data); + } } -struct match_options { - bool is_auto; - char *exclude_pattern; - char *include_pattern; - bool pretty_print; - bool dot; -}; - -void +static void list_interfaces(struct lif_dict *collection, struct match_options *opts) { struct lif_node *iter; if (opts->dot) { - printf("digraph interfaces {\n"); - printf("edge [color=blue fontname=Sans fontsize=10]\n"); - printf("node [fontname=Sans fontsize=10]\n"); + printf("digraph interfaces {\n" + "edge [color=blue fontname=Sans fontsize=10]\n" + "node [fontname=Sans fontsize=10]\n"); } LIF_DICT_FOREACH(iter, collection) @@ -144,7 +112,7 @@ list_interfaces(struct lif_dict *collection, struct match_options *opts) continue; if (opts->pretty_print) - print_interface(iface); + prettyprint_interface_eni(iface); else if (opts->dot) print_interface_dot(collection, iface, NULL); else @@ -155,7 +123,10 @@ list_interfaces(struct lif_dict *collection, struct match_options *opts) printf("}\n"); } -void +static bool listing = false, listing_stat = false, listing_running = false; +static bool allow_undefined = false; + +static void list_state(struct lif_dict *state, struct match_options *opts) { struct lif_node *iter; @@ -172,106 +143,132 @@ list_state(struct lif_dict *state, struct match_options *opts) fnmatch(opts->include_pattern, entry->key, 0)) continue; - printf("%s=%s\n", entry->key, (const char *) entry->data); + struct lif_state_record *rec = entry->data; + + if (listing_running) + printf("%s\n", entry->key); + else + printf("%s=%s %zu%s\n", entry->key, rec->mapped_if, rec->refcount, + rec->is_explicit ? " explicit" : ""); } } -int +static void +set_listing(const char *opt_arg) +{ + (void) opt_arg; + listing = true; +} + +static void +set_show_state(const char *opt_arg) +{ + (void) opt_arg; + listing_stat = true; +} + +static void +set_show_running(const char *opt_arg) +{ + (void) opt_arg; + listing_running = true; +} + +static void +set_pretty_print(const char *opt_arg) +{ + (void) opt_arg; + match_opts.pretty_print = true; +} + +static void +set_output_dot(const char *opt_arg) +{ + (void) opt_arg; + match_opts.dot = true; +} + +static void +set_property(const char *opt_arg) +{ + match_opts.property = opt_arg; +} + +static void +set_allow_undefined(const char *opt_arg) +{ + (void) opt_arg; + allow_undefined = true; +} + +static struct if_option local_options[] = { + {'r', "running", NULL, "show configured (running) interfaces", false, set_show_running}, + {'s', "state", NULL, "show configured state", false, set_show_state}, + {'p', "property", "property PROPERTY", "print values of properties matching PROPERTY", true, set_property}, + {'D', "dot", NULL, "generate a dependency graph", false, set_output_dot}, + {'L', "list", NULL, "list matching interfaces", false, set_listing}, + {'P', "pretty-print", NULL, "pretty print the interfaces instead of just listing", false, set_pretty_print}, + {'U', "allow-undefined", NULL, "allow querying undefined (virtual) interfaces", false, set_allow_undefined}, +}; + +static struct if_option_group local_option_group = { + .desc = "Program-specific options", + .group_size = ARRAY_SIZE(local_options), + .group = local_options +}; + +static int ifquery_main(int argc, char *argv[]) { struct lif_dict state = {}; struct lif_dict collection = {}; - struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'V'}, - {"interfaces", required_argument, 0, 'i'}, - {"list", no_argument, 0, 'L'}, - {"auto", no_argument, 0, 'a'}, - {"include", required_argument, 0, 'I'}, - {"exclude", required_argument, 0, 'X'}, - {"pretty-print", no_argument, 0, 'P'}, - {"state-file", required_argument, 0, 'S'}, - {"state", no_argument, 0, 's'}, - {"dot", no_argument, 0, 'D'}, - {NULL, 0, 0, 0} + struct lif_interface_file_parse_state parse_state = { + .collection = &collection, }; - struct match_options match_opts = {}; - bool listing = false, listing_stat = false; - char *interfaces_file = INTERFACES_FILE; - char *state_file = STATE_FILE; - for (;;) + lif_interface_collection_init(&collection); + + if (!lif_state_read_path(&state, exec_opts.state_file)) { - int c = getopt_long(argc, argv, "hVi:LaI:X:PS:sD", long_options, NULL); - if (c == -1) - break; - - switch (c) { - case 'h': - ifquery_usage(); - break; - case 'V': - lif_common_version(); - break; - case 'i': - interfaces_file = optarg; - break; - case 'L': - listing = true; - break; - case 'a': - match_opts.is_auto = true; - break; - case 'I': - match_opts.include_pattern = optarg; - break; - case 'X': - match_opts.exclude_pattern = optarg; - break; - case 'P': - match_opts.pretty_print = true; - break; - case 'S': - state_file = optarg; - break; - case 's': - listing_stat = true; - break; - case 'D': - match_opts.dot = true; - break; - } - } - - if (!lif_state_read_path(&state, state_file)) - { - fprintf(stderr, "%s: could not parse %s\n", argv0, state_file); + fprintf(stderr, "%s: could not parse %s\n", argv0, exec_opts.state_file); return EXIT_FAILURE; } - if (!lif_interface_file_parse(&collection, interfaces_file)) + if (!lif_interface_file_parse(&parse_state, exec_opts.interfaces_file)) { - fprintf(stderr, "%s: could not parse %s\n", argv0, interfaces_file); + fprintf(stderr, "%s: could not parse %s\n", argv0, exec_opts.interfaces_file); + return EXIT_FAILURE; + } + + if (match_opts.property == NULL && lif_lifecycle_count_rdepends(&exec_opts, &collection) == -1) + { + fprintf(stderr, "%s: could not validate dependency tree\n", argv0); + return EXIT_FAILURE; + } + + if (!lif_compat_apply(&collection)) + { + fprintf(stderr, "%s: failed to apply compatibility glue\n", argv0); return EXIT_FAILURE; } /* --list --state is not allowed */ - if (listing && listing_stat) - ifquery_usage(); + if (listing && (listing_stat || listing_running)) + generic_usage(self_applet, EXIT_FAILURE); if (listing) { list_interfaces(&collection, &match_opts); return EXIT_SUCCESS; } - else if (listing_stat) + else if (listing_stat || listing_running) { list_state(&state, &match_opts); return EXIT_SUCCESS; } if (optind >= argc) - ifquery_usage(); + generic_usage(self_applet, EXIT_FAILURE); int idx = optind; for (; idx < argc; idx++) @@ -284,6 +281,9 @@ ifquery_main(int argc, char *argv[]) if (entry != NULL) iface = entry->data; + + if (entry == NULL && allow_undefined) + iface = lif_interface_collection_find(&collection, argv[idx]); } if (iface == NULL) @@ -292,7 +292,10 @@ ifquery_main(int argc, char *argv[]) return EXIT_FAILURE; } - print_interface(iface); + if (match_opts.property != NULL) + print_interface_property(iface, match_opts.property); + else + prettyprint_interface_eni(iface); } return EXIT_SUCCESS; @@ -300,6 +303,9 @@ ifquery_main(int argc, char *argv[]) struct if_applet ifquery_applet = { .name = "ifquery", + .desc = "query interface configuration", .main = ifquery_main, - .usage = ifquery_usage + .usage = "ifquery [options] \n ifquery [options] --list", + .manpage = "8 ifquery", + .groups = { &global_option_group, &match_option_group, &exec_option_group, &local_option_group }, }; diff --git a/cmd/ifupdown.c b/cmd/ifupdown.c index 1ed6afe..7ac3c88 100644 --- a/cmd/ifupdown.c +++ b/cmd/ifupdown.c @@ -3,6 +3,7 @@ * Purpose: bring interfaces up or down * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,38 +19,15 @@ #include #include #include +#include +#include +#include #include "libifupdown/libifupdown.h" #include "cmd/multicall.h" -struct match_options { - bool is_auto; - char *exclude_pattern; - char *include_pattern; -}; - static bool up; -static struct lif_execute_opts exec_opts = {}; -void -ifupdown_usage(void) -{ - fprintf(stderr, "usage: %s [options] \n", argv0); - - fprintf(stderr, "\nOptions:\n"); - fprintf(stderr, " -h, --help this help\n"); - fprintf(stderr, " -V, --version show this program's version\n"); - fprintf(stderr, " -i, --interfaces FILE use FILE for interface definitions\n"); - fprintf(stderr, " -S, --state-file FILE use FILE for state\n"); - fprintf(stderr, " -a, --auto only match against interfaces hinted as 'auto'\n"); - fprintf(stderr, " -I, --include PATTERN only match against interfaces matching PATTERN\n"); - fprintf(stderr, " -X, --exclude PATTERN never match against interfaces matching PATTERN\n"); - fprintf(stderr, " -n, --no-act do not actually run any commands\n"); - fprintf(stderr, " -v, --verbose show what commands are being run\n"); - - exit(1); -} - -bool +static bool is_ifdown() { if (strstr(argv0, "ifdown") != NULL) @@ -58,9 +36,135 @@ is_ifdown() return false; } -bool -change_interface(struct lif_interface *iface, struct lif_dict *collection, struct lif_dict *state, const char *ifname) +static int +acquire_state_lock(const char *state_path, const char *lifname) { + if (exec_opts.mock || exec_opts.no_lock) + return -1; + + char lockpath[4096] = {}; + + snprintf(lockpath, sizeof lockpath, "%s.%s.lock", state_path, lifname); + + int fd = open(lockpath, O_CREAT | O_WRONLY | O_TRUNC, 0600); + if (fd < 0) + { + if (exec_opts.verbose) + fprintf(stderr, "%s: while opening lockfile %s: %s\n", argv0, lockpath, strerror(errno)); + return -2; + } + + int flags = fcntl(fd, F_GETFD); + if (flags < 0) + { + close(fd); + + if (exec_opts.verbose) + fprintf(stderr, "%s: while getting flags for lockfile: %s\n", argv0, strerror(errno)); + return -2; + } + + flags |= FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) + { + close(fd); + + if (exec_opts.verbose) + fprintf(stderr, "%s: while setting lockfile close-on-exec: %s\n", argv0, strerror(errno)); + return -2; + } + + struct flock fl = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET + }; + + if (exec_opts.verbose) + fprintf(stderr, "%s: acquiring lock on %s\n", argv0, lockpath); + + if (fcntl(fd, F_SETLK, &fl) == -1) + { + close(fd); + + if (exec_opts.verbose) + fprintf(stderr, "%s: while locking lockfile: %s\n", argv0, strerror(errno)); + return -2; + } + + return fd; +} + +static bool +skip_interface(struct lif_interface *iface, const char *ifname, struct lif_dict *state, bool update_state) +{ + if (iface->is_template) + { + fprintf(stderr, "%s: cannot change state on %s (template interface)\n", argv0, ifname); + return false; + } + + if (iface->has_config_error) + { + if (exec_opts.force) + { + fprintf(stderr, "%s: (de)configuring interface %s despite config errors\n", argv0, ifname); + return false; + } + else + { + fprintf(stderr, "%s: skipping interface %s due to config errors\n", argv0, ifname); + return true; + } + } + + if (exec_opts.force) + return false; + + if (up && iface->refcount > 0) + { + if (exec_opts.verbose) + fprintf(stderr, "%s: skipping %sinterface %s (already configured), use --force to force configuration\n", + argv0, iface->is_auto ? "auto " : "", ifname); + + if (update_state) + { + iface->is_explicit = true; + lif_state_upsert(state, ifname, iface); + } + + return true; + } + + if (!up && iface->refcount == 0) + { + if (exec_opts.verbose) + fprintf(stderr, "%s: skipping %sinterface %s (already deconfigured), use --force to force deconfiguration\n", + argv0, iface->is_auto ? "auto " : "", ifname); + return true; + } + + return false; +} + +static bool +change_interface(struct lif_interface *iface, struct lif_dict *collection, struct lif_dict *state, const char *ifname, bool update_state) +{ + int lockfd = acquire_state_lock(exec_opts.state_file, ifname); + + if (lockfd == -2) + { + fprintf(stderr, "%s: could not acquire exclusive lock for %s: %s\n", argv0, ifname, strerror(errno)); + return false; + } + + if (skip_interface(iface, ifname, state, update_state)) + { + if (lockfd != -1) + close(lockfd); + + return true; + } + if (exec_opts.verbose) { fprintf(stderr, "%s: changing state of interface %s to '%s'\n", @@ -71,13 +175,26 @@ change_interface(struct lif_interface *iface, struct lif_dict *collection, struc { fprintf(stderr, "%s: failed to change interface %s state to '%s'\n", argv0, ifname, up ? "up" : "down"); + + if (lockfd != -1) + close(lockfd); + return false; } + if (lockfd != -1) + close(lockfd); + + if (up && update_state) + { + iface->is_explicit = true; + lif_state_upsert(state, ifname, iface); + } + return true; } -bool +static bool change_auto_interfaces(struct lif_dict *collection, struct lif_dict *state, struct match_options *opts) { struct lif_node *iter; @@ -98,83 +215,68 @@ change_auto_interfaces(struct lif_dict *collection, struct lif_dict *state, stru fnmatch(opts->include_pattern, iface->ifname, 0)) continue; - if (!change_interface(iface, collection, state, iface->ifname)) + if (!change_interface(iface, collection, state, iface->ifname, false)) return false; } return true; } -int +static int +update_state_file_and_exit(int rc, struct lif_dict *state) +{ + if (exec_opts.mock) + { + exit(rc); + return rc; + } + + if (!lif_state_write_path(state, exec_opts.state_file)) + { + fprintf(stderr, "%s: could not update %s\n", argv0, exec_opts.state_file); + + exit(EXIT_FAILURE); + return EXIT_FAILURE; + } + + exit(rc); + return rc; +} + +static int ifupdown_main(int argc, char *argv[]) { up = !is_ifdown(); struct lif_dict state = {}; struct lif_dict collection = {}; - struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'V'}, - {"interfaces", required_argument, 0, 'i'}, - {"auto", no_argument, 0, 'a'}, - {"include", required_argument, 0, 'I'}, - {"exclude", required_argument, 0, 'X'}, - {"state-file", required_argument, 0, 'S'}, - {"no-act", no_argument, 0, 'n'}, - {"verbose", no_argument, 0, 'v'}, - {NULL, 0, 0, 0} + struct lif_interface_file_parse_state parse_state = { + .collection = &collection, }; - struct match_options match_opts = {}; - char *interfaces_file = INTERFACES_FILE; - char *state_file = STATE_FILE; - for (;;) + lif_interface_collection_init(&collection); + + if (!lif_state_read_path(&state, exec_opts.state_file)) { - int c = getopt_long(argc, argv, "hVi:aI:X:S:nv", long_options, NULL); - if (c == -1) - break; - - switch (c) { - case 'h': - ifupdown_usage(); - break; - case 'V': - lif_common_version(); - break; - case 'i': - interfaces_file = optarg; - break; - case 'a': - match_opts.is_auto = true; - break; - case 'I': - match_opts.include_pattern = optarg; - break; - case 'X': - match_opts.exclude_pattern = optarg; - break; - case 'S': - state_file = optarg; - break; - case 'n': - exec_opts.mock = true; - exec_opts.verbose = true; - break; - case 'v': - exec_opts.verbose = true; - break; - } - } - - if (!lif_state_read_path(&state, state_file)) - { - fprintf(stderr, "%s: could not parse %s\n", argv0, state_file); + fprintf(stderr, "%s: could not parse %s\n", argv0, exec_opts.state_file); return EXIT_FAILURE; } - if (!lif_interface_file_parse(&collection, interfaces_file)) + if (!lif_interface_file_parse(&parse_state, exec_opts.interfaces_file)) { - fprintf(stderr, "%s: could not parse %s\n", argv0, interfaces_file); + fprintf(stderr, "%s: could not parse %s\n", argv0, exec_opts.interfaces_file); + return EXIT_FAILURE; + } + + if (lif_lifecycle_count_rdepends(&exec_opts, &collection) == -1) + { + fprintf(stderr, "%s: could not validate dependency tree\n", argv0); + return EXIT_FAILURE; + } + + if(!lif_compat_apply(&collection)) + { + fprintf(stderr, "%s: failed to apply compatibility glue\n", argv0); return EXIT_FAILURE; } @@ -187,12 +289,12 @@ ifupdown_main(int argc, char *argv[]) if (match_opts.is_auto) { if (!change_auto_interfaces(&collection, &state, &match_opts)) - return EXIT_FAILURE; + return update_state_file_and_exit(EXIT_FAILURE, &state); - return EXIT_SUCCESS; + return update_state_file_and_exit(EXIT_SUCCESS, &state); } else if (optind >= argc) - ifupdown_usage(); + generic_usage(self_applet, EXIT_FAILURE); int idx = optind; for (; idx < argc; idx++) @@ -218,33 +320,33 @@ ifupdown_main(int argc, char *argv[]) if (entry == NULL) { fprintf(stderr, "%s: unknown interface %s\n", argv0, argv[idx]); - return EXIT_FAILURE; + return update_state_file_and_exit(EXIT_FAILURE, &state); } iface = entry->data; } - if (!change_interface(iface, &collection, &state, ifname)) - return EXIT_FAILURE; + if (!change_interface(iface, &collection, &state, ifname, true)) + return update_state_file_and_exit(EXIT_FAILURE, &state); } - if (!exec_opts.mock && !lif_state_write_path(&state, state_file)) - { - fprintf(stderr, "%s: could not update %s\n", argv0, state_file); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; + return update_state_file_and_exit(EXIT_SUCCESS, &state); } struct if_applet ifup_applet = { .name = "ifup", + .desc = "bring interfaces up", .main = ifupdown_main, - .usage = ifupdown_usage + .usage = "ifup [options] ", + .manpage = "8 ifup", + .groups = { &global_option_group, &match_option_group, &exec_option_group, }, }; struct if_applet ifdown_applet = { .name = "ifdown", + .desc = "take interfaces down", .main = ifupdown_main, - .usage = ifupdown_usage + .usage = "ifdown [options] ", + .manpage = "8 ifdown", + .groups = { &global_option_group, &match_option_group, &exec_option_group, }, }; diff --git a/cmd/multicall-exec-options.c b/cmd/multicall-exec-options.c new file mode 100644 index 0000000..5350e62 --- /dev/null +++ b/cmd/multicall-exec-options.c @@ -0,0 +1,103 @@ +/* + * cmd/multicall-exec-options.c + * Purpose: multi-call binary frontend -- option handling + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "cmd/multicall.h" + +#define DEFAULT_TIMEOUT 300 + +struct lif_execute_opts exec_opts = { + .interfaces_file = INTERFACES_FILE, + .executor_path = EXECUTOR_PATH, + .state_file = STATE_FILE, + .timeout = DEFAULT_TIMEOUT, +}; + +static void +set_interfaces_file(const char *opt_arg) +{ + exec_opts.interfaces_file = opt_arg; +} + +static void +set_state_file(const char *opt_arg) +{ + exec_opts.state_file = opt_arg; +} + +static void +set_no_act(const char *opt_arg) +{ + (void) opt_arg; + exec_opts.mock = true; + exec_opts.verbose = true; +} + +static void +set_verbose(const char *opt_arg) +{ + (void) opt_arg; + exec_opts.verbose = true; +} + +static void +set_executor_path(const char *opt_arg) +{ + exec_opts.executor_path = opt_arg; +} + +static void +set_no_lock(const char *opt_arg) +{ + (void) opt_arg; + exec_opts.no_lock = true; +} + +static void +set_force(const char *opt_arg) +{ + (void) opt_arg; + exec_opts.force = true; +} + +static void +set_timeout(const char *opt_arg) +{ + exec_opts.timeout = atoi(opt_arg); + if (exec_opts.timeout < 0) + exec_opts.timeout = DEFAULT_TIMEOUT; +} + +static struct if_option exec_options[] = { + {'f', "force", NULL, "force (de)configuration", false, set_force}, + {'i', "interfaces", "interfaces FILE", "use FILE for interface definitions", true, set_interfaces_file}, + {'l', "no-lock", NULL, "do not use a lockfile to serialize state changes", false, set_no_lock}, + {'n', "no-act", NULL, "do not actually run any commands", false, set_no_act}, + {'v', "verbose", NULL, "show what commands are being run", false, set_verbose}, + {'E', "executor-path", "executor-path PATH", "use PATH for executor directory", true, set_executor_path}, + {'S', "state-file", "state-file FILE", "use FILE for state", true, set_state_file}, + {'T', "timeout", "timeout TIMEOUT", "wait TIMEOUT seconds for executors to complete", true, set_timeout}, +}; + +struct if_option_group exec_option_group = { + .desc = "Execution", + .group_size = ARRAY_SIZE(exec_options), + .group = exec_options +}; diff --git a/cmd/multicall-match-options.c b/cmd/multicall-match-options.c new file mode 100644 index 0000000..8d85d8a --- /dev/null +++ b/cmd/multicall-match-options.c @@ -0,0 +1,55 @@ +/* + * cmd/multicall-match-options.c + * Purpose: multi-call binary frontend -- option handling + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "cmd/multicall.h" + +struct match_options match_opts = {}; + +static void +set_auto(const char *opt_arg) +{ + (void) opt_arg; + match_opts.is_auto = true; +} + +static void +set_include_pattern(const char *opt_arg) +{ + match_opts.include_pattern = opt_arg; +} + +static void +set_exclude_pattern(const char *opt_arg) +{ + match_opts.exclude_pattern = opt_arg; +} + +static struct if_option match_options[] = { + {'a', "auto", NULL, "only match against interfaces hinted as 'auto'", false, set_auto}, + {'I', "include", "include PATTERN", "only match against interfaces matching PATTERN", true, set_include_pattern}, + {'X', "exclude", "exclude PATTERN", "never match against interfaces matching PATTERN", true, set_exclude_pattern}, +}; + +struct if_option_group match_option_group = { + .desc = "Matching interfaces", + .group_size = ARRAY_SIZE(match_options), + .group = match_options +}; diff --git a/cmd/multicall-options.c b/cmd/multicall-options.c new file mode 100644 index 0000000..c2e746a --- /dev/null +++ b/cmd/multicall-options.c @@ -0,0 +1,162 @@ +/* + * cmd/multicall-options.c + * Purpose: multi-call binary frontend -- option handling + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "cmd/multicall.h" + +extern const struct if_applet *self_applet; + +void +generic_usage(const struct if_applet *applet, int result) +{ + fprintf(stderr, "%s", applet->name); + if (applet->desc != NULL) + fprintf(stderr, " - %s", applet->desc); + + fprintf(stderr, "\n"); + + if (applet->usage != NULL) + fprintf(stderr, "\nUsage:\n %s\n", applet->usage); + + size_t iter; + for (iter = 0; applet->groups[iter] != NULL; iter++) + { + const struct if_option_group *group = applet->groups[iter]; + + fprintf(stderr, "\n%s:\n", group->desc); + + size_t group_iter; + for (group_iter = 0; group_iter < group->group_size; group_iter++) + { + const struct if_option *opt = &group->group[group_iter]; + + fprintf(stderr, " "); + + if (opt->short_opt) + fprintf(stderr, "-%c", opt->short_opt); + else + fprintf(stderr, " "); + + if (opt->long_opt) + fprintf(stderr, "%c --%-30s", opt->short_opt ? ',' : ' ', + opt->long_opt_desc ? opt->long_opt_desc : opt->long_opt); + else + fprintf(stderr, "%34s", ""); + + fprintf(stderr, "%s\n", opt->desc); + } + } + + if (applet->manpage != NULL) + fprintf(stderr, "\nFor more information: man %s\n", applet->manpage); + + exit(result); +} + +static void +generic_usage_request(const char *opt_arg) +{ + (void) opt_arg; + + generic_usage(self_applet, EXIT_SUCCESS); +} + +static struct if_option global_options[] = { + {'h', "help", NULL, "this help", false, generic_usage_request}, + {'V', "version", NULL, "show this program's version", false, (void *) lif_common_version}, +}; + +struct if_option_group global_option_group = { + .desc = "Global options", + .group_size = ARRAY_SIZE(global_options), + .group = global_options +}; + +const struct if_option * +lookup_option(const struct if_applet *applet, int opt) +{ + size_t iter; + for (iter = 0; applet->groups[iter] != NULL; iter++) + { + const struct if_option_group *group = applet->groups[iter]; + size_t group_iter; + + for (group_iter = 0; group_iter < group->group_size; group_iter++) + { + const struct if_option *option = &group->group[group_iter]; + + if (option->short_opt == opt) + return option; + } + } + + return NULL; +} + +void +process_options(const struct if_applet *applet, int argc, char *argv[]) +{ + char short_opts[256] = {}, *p = short_opts; + struct option long_opts[256] = {}; + + size_t iter, long_opt_iter = 0; + for (iter = 0; applet->groups[iter] != NULL; iter++) + { + const struct if_option_group *group = applet->groups[iter]; + size_t group_iter; + + for (group_iter = 0; group_iter < group->group_size; group_iter++) + { + const struct if_option *opt = &group->group[group_iter]; + + if (opt->short_opt) + { + *p++ = opt->short_opt; + if (opt->require_argument) + *p++ = ':'; + } + + if (opt->long_opt) + { + /* XXX: handle long-opts without short-opts */ + long_opts[long_opt_iter] = (struct option) { + .name = opt->long_opt, + .has_arg = opt->require_argument ? required_argument : no_argument, + .val = opt->short_opt + }; + + long_opt_iter++; + } + } + } + + for (;;) + { + int c = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (c == -1) + break; + + const struct if_option *opt = lookup_option(applet, c); + if (opt == NULL) + break; + + opt->handle(optarg); + } +} diff --git a/cmd/multicall.c b/cmd/multicall.c index 9437c03..7c3c849 100644 --- a/cmd/multicall.c +++ b/cmd/multicall.c @@ -13,29 +13,56 @@ * from the use of this software. */ +#define _GNU_SOURCE #include #include #include #include +#include #include "cmd/multicall.h" char *argv0; +#ifdef CONFIG_IFQUERY extern struct if_applet ifquery_applet; +#endif + +#ifdef CONFIG_IFUPDOWN extern struct if_applet ifup_applet; extern struct if_applet ifdown_applet; +#endif + +#ifdef CONFIG_IFCTRSTAT +extern struct if_applet ifctrstat_applet; +#endif + +#ifdef CONFIG_IFPARSE +extern struct if_applet ifparse_applet; +#endif + struct if_applet ifupdown_applet; +const struct if_applet *self_applet = NULL; struct if_applet *applet_table[] = { +#ifdef CONFIG_IFCTRSTAT + &ifctrstat_applet, +#endif +#ifdef CONFIG_IFUPDOWN &ifdown_applet, +#endif +#ifdef CONFIG_IFPARSE + &ifparse_applet, +#endif +#ifdef CONFIG_IFQUERY &ifquery_applet, +#endif +#ifdef CONFIG_IFUPDOWN &ifup_applet, +#endif &ifupdown_applet, }; -#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) - -int +static int applet_cmp(const void *a, const void *b) { const char *key = a; @@ -44,13 +71,17 @@ applet_cmp(const void *a, const void *b) return strcmp(key, applet->name); } -void multicall_usage(void); +void multicall_usage(int status) __attribute__((noreturn)); + +struct if_applet ifupdown_applet; int main(int argc, char *argv[]) { argv0 = basename(argv[0]); - struct if_applet **app; + const struct if_applet **app; + + lif_config_load(CONFIG_FILE); app = bsearch(argv0, applet_table, ARRAY_SIZE(applet_table), sizeof(*applet_table), @@ -59,42 +90,48 @@ main(int argc, char *argv[]) if (app == NULL) { fprintf(stderr, "%s: applet not found\n", argv0); - multicall_usage(); + multicall_usage(EXIT_FAILURE); } - return (*app)->main(argc, argv); + self_applet = *app; + + if (self_applet != &ifupdown_applet) + process_options(*app, argc, argv); + + return self_applet->main(argc, argv); } -int +static int multicall_main(int argc, char *argv[]) { if (argc < 2) - multicall_usage(); + multicall_usage(EXIT_FAILURE); return main(argc - 1, argv + 1); } void -multicall_usage(void) +multicall_usage(int status) { - fprintf(stderr, "usage: ifupdown [options]\n"); + fprintf(stderr, + PACKAGE_NAME " " PACKAGE_VERSION "\n" + "usage: ifupdown [options]\n" + "\n" + "Built-in applets:\n"); - fprintf(stderr, "\nBuilt-in applets:\n\t"); for (size_t i = 0; i < ARRAY_SIZE(applet_table); i++) { - if (i != 0) - fprintf(stderr, ", "); + if (applet_table[i] == &ifupdown_applet) + continue; - fprintf(stderr, "%s", applet_table[i]->name); + fprintf(stderr, " %-10s %s\n", applet_table[i]->name, applet_table[i]->desc); } - fprintf(stderr, "\n"); - - exit(EXIT_FAILURE); + exit(status); } struct if_applet ifupdown_applet = { .name = "ifupdown", .main = multicall_main, - .usage = multicall_usage, + .groups = { &global_option_group, NULL } }; diff --git a/cmd/multicall.h b/cmd/multicall.h index 48bd1ea..647e0ba 100644 --- a/cmd/multicall.h +++ b/cmd/multicall.h @@ -13,17 +13,62 @@ * from the use of this software. */ +#include + #ifndef IFUPDOWN_CMD_MULTICALL_H__GUARD #define IFUPDOWN_CMD_MULTICALL_H__GUARD #include "libifupdown/libifupdown.h" +struct if_applet; + +struct if_option { + char short_opt; + const char *long_opt; + const char *long_opt_desc; + const char *desc; + bool require_argument; + void (*const handle)(const char *opt_arg); +}; + +struct if_option_group { + const char *desc; + size_t group_size; + const struct if_option *group; +}; + struct if_applet { const char *name; + const char *desc; + const char *usage; + const char *manpage; int (*const main)(int argc, char *argv[]); - void (*const usage)(void); + const struct if_option_group *groups[16]; }; extern char *argv0; +extern const struct if_applet *self_applet; +extern struct if_option_group global_option_group; + +struct match_options { + bool is_auto; + const char *exclude_pattern; + const char *include_pattern; + bool pretty_print; + bool dot; + const char *property; +}; + +extern struct match_options match_opts; + +extern void process_options(const struct if_applet *applet, int argc, char *argv[]); +extern const struct if_option *lookup_option(const struct if_applet *applet, int opt); + +extern struct if_option_group match_option_group; + +extern struct lif_execute_opts exec_opts; +extern struct if_option_group exec_option_group; + +void generic_usage(const struct if_applet *applet, int result); #endif diff --git a/cmd/pretty-print-iface.c b/cmd/pretty-print-iface.c new file mode 100644 index 0000000..710e12a --- /dev/null +++ b/cmd/pretty-print-iface.c @@ -0,0 +1,56 @@ +/* + * cmd/pretty-print-iface.c + * Purpose: interface pretty-printer (/e/n/i style) + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include "libifupdown/libifupdown.h" +#include "cmd/multicall.h" +#include "cmd/pretty-print-iface.h" + +void +prettyprint_interface_eni(struct lif_interface *iface) +{ + if (!lif_lifecycle_query_dependents(&exec_opts, iface, iface->ifname)) + return; + + if (iface->is_auto) + printf("auto %s\n", iface->ifname); + + printf("%s %s\n", iface->is_template ? "template" : "iface", iface->ifname); + + struct lif_node *iter; + LIF_DICT_FOREACH(iter, &iface->vars) + { + struct lif_dict_entry *entry = iter->data; + + if (!strcmp(entry->key, "address")) + { + struct lif_address *addr = entry->data; + char addr_buf[512]; + + if (!lif_address_unparse(addr, addr_buf, sizeof addr_buf, true)) + { + printf(" # warning: failed to unparse address\n"); + continue; + } + + printf(" %s %s\n", entry->key, addr_buf); + } + else + printf(" %s %s\n", entry->key, (const char *) entry->data); + } + + printf("\n"); +} diff --git a/cmd/pretty-print-iface.h b/cmd/pretty-print-iface.h new file mode 100644 index 0000000..8835ae6 --- /dev/null +++ b/cmd/pretty-print-iface.h @@ -0,0 +1,23 @@ +/* + * cmd/pretty-print-iface.h + * Purpose: interface pretty-printer (/e/n/i style) + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef IFUPDOWN_CMD_PRETTY_PRINT_IFACE_H__GUARD +#define IFUPDOWN_CMD_PRETTY_PRINT_IFACE_H__GUARD + +#include "libifupdown/libifupdown.h" + +extern void prettyprint_interface_eni(struct lif_interface *iface); + +#endif diff --git a/dist/debian/ifupdown-ng.networking.service b/dist/debian/ifupdown-ng.networking.service new file mode 100644 index 0000000..5aad2f9 --- /dev/null +++ b/dist/debian/ifupdown-ng.networking.service @@ -0,0 +1,15 @@ +[Unit] +Description=ifupdown-ng networking initialization +Documentation=man:interfaces(5) man:ifup(8) man:ifdown(8) + +[Service] +Type=oneshot +RemainAfterExit=yes +SyslogIdentifier=networking +TimeoutStopSec=30s +ExecStart=/usr/share/ifupdown-ng/sbin/networking start +ExecStop=/usr/share/ifupdown-ng/sbin/networking stop +ExecRestart=/usr/share/ifupdown-ng/sbin/networking restart + +[Install] +WantedBy=basic.target network.target multi-user.target network-online.target diff --git a/dist/debian/networking b/dist/debian/networking new file mode 100644 index 0000000..327e08d --- /dev/null +++ b/dist/debian/networking @@ -0,0 +1,68 @@ +#!/bin/sh +# +# Wrapper script for networking set up and teardown via unit file +# +# Thu, 01 Oct 2020 22:47:43 +0200 +# -- Maximilian Wilhelm +# + +STATE_DIR="/run/ifsate" + +# Make sure the state dir is present +if [ ! -d "${STATE_DIR}" ]; then + mkdir "${STATE_DIR}" +fi + +# Check for require binaries +if [ ! -x /sbin/ifup -o ! -x /sbin/ifdown ]; then + echo "ifup and/or ifdown not found!" >&2 + exit 1 +fi + +# Apply defaults if present (verbose mode, kill switch, etc.) +CONFIGURE_INTERFACES=yes + +if [ -f /etc/default/networking ]; then + . /etc/default/networking +fi + +ARGS="" +if [ "${VERBOSE}" = yes ]; then + ARGS="-v" +fi + +# Let's go +case "$1" in + start) + if [ "${CONFIGURE_INTERFACES}" = no ]; then + echo "Not configuring network interfaces, see /etc/default/networking" + exit 0 + fi + + ifup -a ${ARGS} + ;; + + stop) + if [ "${SKIP_DOWN_AT_SYSRESET}" = "yes" ] && systemctl list-jobs | egrep -q '(shutdown|reboot|halt|poweroff)\.target'; then + echo ${NAME}':' "Skipping deconfiguring network interfaces" + exit 0 + fi + + ifdown -a ${ARGS} + ;; + + restart) + ifupdown_init + ifdown -a ${ARGS} + ifup -a ${ARGS} + ;; + + # reload missing here! + + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 + ;; +esac + +exit 0 diff --git a/dist/debian/networking.default b/dist/debian/networking.default new file mode 100644 index 0000000..8c6e6f7 --- /dev/null +++ b/dist/debian/networking.default @@ -0,0 +1,13 @@ +# +# Defaults for ifupdown-ng networking service +# + +# Change the below to "yes" if you want ifup/ifdown and it's executors to be +# verbose about what's going on +VERBOSE="no" + +# Set to "yes" if you want to skip deconfiguring all interfaces during system +# reboot and shutdown. This might be of interest in large scale deployments, +# where you might not want to wait for interface deconfiguration to speed up +# shutdown/reboot. +SKIP_DOWN_AT_SYSRESET="yes" diff --git a/dist/ifupdown-ng.conf.example b/dist/ifupdown-ng.conf.example new file mode 100644 index 0000000..10c6076 --- /dev/null +++ b/dist/ifupdown-ng.conf.example @@ -0,0 +1,56 @@ +# This is an example configuration file for ifupdown-ng, which allows +# the system administrator to configure the behaviour of ifupdown-ng. +# +# The settings specified here are the defaults of ifupdown-ng. + +# allow_addon_scripts: +# Enable support for /etc/if-X.d addon scripts. These are used for +# compatibility with legacy setups, and may be disabled for performance +# improvements in setups where only ifupdown-ng executors are used. +# Valid values are 0 and 1, the default is 1. +allow_addon_scripts = 1 + +# allow_any_iface_as_template: +# Enable any interface to act as a template for another interface. +# This is presently the default, but is deprecated. An admin may choose +# to disable this setting in order to require inheritance from specified +# templates. Valid values are 0 and 1, the default is 1. +allow_any_iface_as_template = 1 + +# auto_executor_selection: +# Automatically determine which executors are needed to bring up an +# interface. An admin may choose to disable this setting and explicitly +# define which executors to use with `use` statements. +# Valid values are 0 and 1, the default is 1. +auto_executor_selection = 1 + +# compat_create_interfaces: +# Denotes where or not to create interfaces when compat_* settings are +# active and it would be necessary to create an interface to be fully +# compliant. This could happen when inheriting bridge VLAN settings to +# an interface within a bridges bridge-ports setting but no interface +# stanza is found. Valid values are 0 and 1, the default is 1. +compat_create_interfaces = 1 + +# compat_ifupdown2_bridge_ports_inherit_vlans: +# In ifupdown2 as well as the set on a bridge +# interface will be inherited by all member ports if not set explicitly. +# When set to 1 ifupdown-ng behaves the same way and will internally copy +# both options from the bridge member ports if they are not set on the +# member port. Valid values are 0 and 1, the default is 1. +compat_ifupdown2_bridge_ports_inherit_vlans = 1 + +# implicit_template_conversion: +# In some legacy configs, a template may be declared as an iface, and +# ifupdown-ng automatically converts those declarations to a proper +# template. If this setting is disabled, inheritance will continue to +# work against non-template interfaces without converting them to a +# template. Valid values are 0 and 1, the default is 1. +implicit_template_conversion = 1 + +# use_hostname_for_dhcp: +# Automatically learn the hostname property, used for DHCP configuration +# by querying the system hostname using uname(2). This is basically +# equivalent to `hostname $(hostname)` without having to specify any +# configuration. Valid values are 0 and 1, the default is 1. +use_hostname_for_dhcp = 1 diff --git a/dist/openrc/networking.confd b/dist/openrc/networking.confd new file mode 100644 index 0000000..80540ad --- /dev/null +++ b/dist/openrc/networking.confd @@ -0,0 +1,8 @@ +# Sets the path used for the interface definitions. +#cfgfile="/etc/network/interfaces" + +# Sets the path used for the state database. +#ifstate="/run/ifstate" + +# Skip taking down networking while shutting down. +#keep_network="YES" diff --git a/dist/openrc/networking.initd b/dist/openrc/networking.initd new file mode 100644 index 0000000..eaebdab --- /dev/null +++ b/dist/openrc/networking.initd @@ -0,0 +1,70 @@ +#!/sbin/openrc-run + +: ${cfgfile:="/etc/network/interfaces"} +ifstate=/run/ifstate + +single_iface="${RC_SVCNAME#*.}" +if [ "$single_iface" = "$RC_SVCNAME" ]; then + single_iface= +fi + +depend() { + need localmount + want dev-settle + after bootmisc hwdrivers modules + provide net + keyword -jail -prefix -vserver -docker +} + +# find interfaces we want to start +find_ifaces() { + if [ -n "$single_iface" ]; then + echo $single_iface + else + ifquery -L -a -i "$cfgfile" + fi +} + +# return the list of interfaces we should try stop +find_running_ifaces() { + if [ -n "$single_iface" ]; then + echo $single_iface + else + ifquery -r -i "$cfgfile" -S "$ifstate" + fi +} + +start() { + local iface= ret=1 + ebegin "Starting networking" + eindent + for iface in $(find_ifaces); do + local r=0 + ebegin "$iface" + if ! ifup -i "$cfgfile" -S "$ifstate" $iface >/dev/null; then + ifdown -f -i "$cfgfile" -S "$ifstate" $iface >/dev/null 2>&1 + r=1 + fi + # atleast one interface needs to be started for action + # to be success + eend $r && ret=0 + done + eoutdent + return $ret +} + +stop() { + local iface= + # Don't stop the network at shutdown. + yesno ${keep_network:-YES} && yesno $RC_GOINGDOWN && return 0 + + ebegin "Stopping networking" + eindent + for iface in $(find_running_ifaces); do + ebegin "$iface" + ifdown -i "$cfgfile" -S "$ifstate" -f $iface >/dev/null + eend $? + done + eoutdent + return 0 +} diff --git a/doc/ADMIN-GUIDE.md b/doc/ADMIN-GUIDE.md new file mode 100644 index 0000000..d3e7862 --- /dev/null +++ b/doc/ADMIN-GUIDE.md @@ -0,0 +1,275 @@ +# ifupdown-ng for system administrators + +ifupdown-ng is a network device manager which is backwards +compatible with traditional ifup and ifdown as used on Debian +and Alpine systems, while solving many design deficits with +the original approach through robust error handling and the +use of a dependency-solver to determine interface bring-up +order. + +This guide is intended to walk users through using the +ifupdown-ng system without any assumption of familiarity +with the legacy ifupdown system. + +## Important Filesystem Paths + +The ifupdown-ng system uses the following paths, ranked +in order of importance: + +* `/etc/network/interfaces`: the interface configuration + database, which contains information about what + interfaces should be configured. + +* `/etc/network/ifupdown-ng.conf`: the main configuration + file which controls ifupdown-ng's behaviour. See the + *ifupdown-ng Configuration* section below. + +* `/run/ifstate`: the interface state file, which denotes + what physical interfaces are configured, and what + interface definition they are configured as. + +* `/usr/libexec/ifupdown-ng`: this directory contains the + native ifupdown-ng executors, which are run as necessary + to configure an interface. See the ifupdown-executor(7) + manual page for more information on how these programs + are written. + +* `/etc/network/if-{up|down|pre-up|post-down}.d`: + these directories contain scripts that are run when an + interface is brought up or down. In general, they follow + the same contract described in ifupdown-executor(7). + +All configuration examples in this guide concern the +`/etc/network/interfaces` file. + +## ifupdown-ng Configuration + +ifupdown-ng allows to configure some parts of it's behaviour. +Currently the following settings are supported in +`/etc/network/ifupdown-ng.conf`: + +* `allow_addon_scripts`: Enable support for /etc/if-X.d addon scripts. + These are used for compatibility with legacy setups, and may be + disabled for performance improvements in setups where only + ifupdown-ng executors are used. Valid values are `0` and `1`, + default is `1`. + +* `allow_any_iface_as_template`: Enable any interface to act as a + template for another interface. This is presently the default, + but is deprecated. An admin may choose to disable this setting + in order to require inheritance from specified templates. + Valid values are `0` and `1`, the default is `1`. + +* `auto_executor_selection`: Automatically select executors based + on the presence of their config options. An admin may choose to + disable this setting in order to require explicitly enabling + executors through `use` statements. Valid values are `0` and `1`, + the default is `1`. + +* `compat_create_interfaces`: + Denotes where or not to create interfaces when compat\_* settings are + active and it would be necessary to create an interface to be fully + compliant. This could happen when inheriting bridge VLAN settings to + an interface within a bridges bridge-ports setting but no interface + stanza is found. Valid values are `0` and `1`, the default is `1`. + +* `compat_ifupdown2_bridge_ports_inherit_vlans`: In ifupdown2 `bridge-vids` + as well as the set on a bridge interface will be inherited + by all member ports if not set explicitly. When set to `1` ifupdown-ng + behaves the same way and will internally copy both options from the + bridge member ports if they are not set on the member port. + Valid values are `0` and `1`, the default is `1`. + +* `implicit_template_conversion`: In some legacy configs, a template + may be declared as an iface, and ifupdown-ng automatically converts + those declarations to a proper template. If this setting is + disabled, inheritance will continue to work against non-template + interfaces without converting them to a template. Valid values + are `0` and `1`, the default is `1`. + +* `use_hostname_for_dhcp`: A common configuration pattern with DHCP + interfaces is to use `hostname $(hostname)`. If this setting is + enabled, the `hostname` property will default to the system + hostname. Valid values are `0` and `1`, the default is `1`. + +## Interface Configuration + +### Basic Configuration + +To begin with, lets look at a basic configuration for a +desktop computer. This scenario involves using the DHCP +helper to learn an IPv4 address dynamically. + +In this case, the `/etc/network/interfaces` file would +look like: + +``` +auto eth0 +iface eth0 + use dhcp +``` + +These configuration statements do two things: designate +that `eth0` should be started automatically with the `auto` +keyword, and designate that the `dhcp` executor should be +used to configure the interface. + +As a more detailed explanation, here is a commented version: + +``` +# Start eth0 automatically. +auto eth0 + +# Begin an interface definition for eth0. +iface eth0 + + # Use the dhcp executor to configure eth0. + use dhcp +``` + +### IPv6 RA Configuration + +With IPv6, stateless auto-configuration is typically used to +configure network interfaces. If you are not interested in +using IPv4 at all, you can simply use the `ipv6-ra` executor +to ensure that an interface is configured to accept IPv6 RA +advertisements: + +``` +auto eth0 +iface eth0 + use ipv6-ra +``` + +### Static Configuration + +We can use the `static` executor to configure static IPv4 and +IPv6 addresses. If you use the `address` keyword, the `static` +executor will automatically be used to configure the interface: + +``` +auto eth0 +iface eth0 + address 203.0.113.2/24 + gateway 203.0.113.1 +``` + +#### Multiple Addresses + +A typical scenario on servers is where a server has multiple +IP addresses on a single interface. In this case you simply +add additional `address` lines like this: + +``` +auto eth0 +iface eth0 + address 203.0.113.2/24 + address 203.0.113.3/24 + address 203.0.113.4/24 + gateway 203.0.113.1 +``` + +#### Dual-stack configurations + +Another typical scenario for servers is to run a dual-stack +configuration, where interfaces have both an IPv4 and an IPv6 +address. This is accomplished in a similar way as multi-homing. +You specify the IPv4 and IPv6 addresses you want, followed by +gateways for each: + +``` +auto eth0 +iface eth0 + address 203.0.113.2/24 + address 203.0.113.3/24 + address 203.0.113.4/24 + gateway 203.0.113.1 + + address 2001:db8:1000:2::2/64 + address 2001:db8:1000:2::3/64 + address 2001:db8:1000:2::4/64 + gateway 2001:db8:1000:2::1 +``` + +## Relationships + +As previously mentioned, ifupdown-ng features a dependency +resolver that allows for determining the interface configuration +order. + +![Dependency resolution example](img/dependency-resolution.png) + +In order to make use of this, dependencies can be managed in one +of two ways: + +### Explicit dependency management using `requires` + +The `requires` keyword can be used to manage explicit +dependencies: + +``` +auto eth0 +iface eth0 + use dhcp + +auto gre0 +iface gre0 + requires eth0 + + use gre + gre-endpoint 203.0.113.2 + gre-ttl 255 + gre-flags ignore-df + + address 203.0.113.194/30 + gateway 203.0.113.193 +``` + +### Implicit dependency management using executors + +Executors can declare implicit dependencies which work the same +way as explicit dependencies, but are learned at run-time, for +example: + +``` +auto bond0 +iface bond0 + use bond + + bond-members eth0 eth1 + [...] +``` + +Is with respect to dependency equivalent to: + +``` +auto bond0 +iface bond0 + use bond + + requires eth0 eth1 + [...] +``` + +## Executors + +The ifupdown-ng system is expanded with additional features via +executors. Executors are selected on a per-interface basis using +`use` statements, for example: + +``` +auto eth0 +iface eth0 + use dhcp +``` + +Executors are run in the order specified by the `use` statements. +Some executors are automatically added based on other statements +in an interface definition. To see the full list of executors +used for an interface, use the ifquery(8) command. + +## Questions + +If you have further questions about how to use ifupdown-ng to +configure a specific scenario, drop by the +[ifupdown-ng IRC channel](irc://irc.oftc.net/#ifupdown-ng). diff --git a/doc/ifctrstat.scd b/doc/ifctrstat.scd new file mode 100644 index 0000000..3f17721 --- /dev/null +++ b/doc/ifctrstat.scd @@ -0,0 +1,39 @@ +ifctrstat(8) + +# NAME + +ifctrstat - display interface statistics + +# SYNOPSIS + +*ifctrstat* [<_options_>...] <_interface_> <_counter_> + +*ifctrstat* [<_options_>...] -L|--list + +# DESCRIPTION + +*ifctrstat* is used to query interface statistic counters in +a kernel-agnostic way. This is useful for heterogenous +environments where multiple kernels may be in use. + +# OPTIONS + +*-h, --help* + Display supported options to ifctrstat. + +*-n, --no-label* + Display the requested counter without its label. + +*-L, --list* + List available counters on this system. + +*-V, --version* + Print the ifupdown-ng version and exit. + +# SEE ALSO + +*ifquery*(8) + +# AUTHORS + +A. Wilcox diff --git a/doc/ifdown.scd b/doc/ifdown.scd new file mode 100644 index 0000000..41f3b02 --- /dev/null +++ b/doc/ifdown.scd @@ -0,0 +1,68 @@ +ifdown(8) + +# NAME + +ifdown - take interfaces down + +# SYNOPSIS + +ifdown [<_options_>...] <_interfaces_> + +# DESCRIPTION + +*ifdown* is used to deconfigure interfaces according to how they are +configured in the configuration database. + +# OPTIONS + +*-a, --auto* + Only match interfaces that are marked as _auto_. + +*-f, --force* + Force deconfiguration of the interface. + This option exists for compatibility with other implementations. + +*-h, --help* + Display supported options to ifquery. + +*-i, --interfaces* _FILE_ + Use _FILE_ as the config database. + +*-n, --no-act* + Show what commands would be run instead of actually running + them. Useful for testing configuration changes. + +*-v, --verbose* + Show what commands are being run as they are executed. + +*-E, --executor-path* _PATH_ + Look for executors in the given _PATH_. + +*-I, --include* _PATTERN_ + Include _PATTERN_ when matching against the config or state + database. + +*-S, --state-file* _FILE_ + Use _FILE_ as the state database. + +*-T, --timeout* _TIMEOUT_ + Wait up to _TIMEOUT_ seconds for executors to complete before + raising an error. + +*-V, --version* + Print the ifupdown-ng version and exit. + +*-X, --exclude* _PATTERN_ + Exclude _PATTERN_ when matching against the config or state + database. + +# SEE ALSO + +*ifupdown-ng.conf*(5) +*ifup*(8) +*ifquery*(8) +*interfaces*(5) + +# AUTHORS + +Ariadne Conill diff --git a/doc/ifparse.scd b/doc/ifparse.scd new file mode 100644 index 0000000..ff7b742 --- /dev/null +++ b/doc/ifparse.scd @@ -0,0 +1,66 @@ +ifparse(8) + +# NAME + +ifparse - redisplay interface configuration in different formats + +# SYNOPSIS + +*ifparse* [<_options_>...] <_interfaces_...> + +*ifparse* -A|--all + +# DESCRIPTION + +*ifparse* is used to extract information from the interface configuration +file. It is intended to be used to translate the interface configuration +stanzas between different formats. + +# OPTIONS + +*-a, --auto* + Only match interfaces that are marked as _auto_. + +*-h, --help* + Display supported options to ifquery. + +*-i, --interfaces* _FILE_ + Use _FILE_ as the config database. + +*-F, --format* _FORMAT_ + Use _FORMAT_ to determine what format to use. *ifupdown* and + *yaml-raw* formats are available. + +*-I, --include* _PATTERN_ + Include _PATTERN_ when matching against the config or state + database. + +*-U, --allow-undefined* + Create virtual interfaces for any interfaces not explicitly + defined in the configuration file. This is primarily useful + for property queries. + +*-S, --state-file* _FILE_ + Use _FILE_ as the state database. + +*-T, --timeout* _TIMEOUT_ + Wait up to _TIMEOUT_ seconds for executors to complete before + raising an error. + +*-V, --version* + Print the ifupdown-ng version and exit. + +*-X, --exclude* _PATTERN_ + Exclude _PATTERN_ when matching against the config or state + database. + +# SEE ALSO + +*ifup*(8)++ +*ifdown*(8)++ +*ifquery*(8)++ +*interfaces*(5) + +# AUTHORS + +Ariadne Conill diff --git a/doc/ifquery.scd b/doc/ifquery.scd new file mode 100644 index 0000000..b721135 --- /dev/null +++ b/doc/ifquery.scd @@ -0,0 +1,84 @@ +ifquery(8) + +# NAME + +ifquery - query interface configuration and state + +# SYNOPSIS + +*ifquery* [<_options_>...] <_interfaces_...> + +*ifquery* -L|--list + +*ifquery* -s|--state + +# DESCRIPTION + +*ifquery* is used to extract information from the interface configuration +file. It can also be used to convert from old versions of the interface +configuration file to the current format. + +# OPTIONS + +*-a, --auto* + Only match interfaces that are marked as _auto_. + +*-h, --help* + Display supported options to ifquery. + +*-i, --interfaces* _FILE_ + Use _FILE_ as the config database. + +*-p, --property* _PROPERTY_ + Print the values of matching properties for an interface. + +*-r, --running* + Print the interface names that are marked as running in + the state database. + +*-s, --state* + Query the state database instead of the config database. + +*-D, --dot* + Generate a dependency graph that can be used with GraphViz + *dot*(1). Used with *--list*. + +*-I, --include* _PATTERN_ + Include _PATTERN_ when matching against the config or state + database. + +*-L, --list* + List interfaces which exist in the configuration database. + +*-P, --pretty-print* + When listing interfaces, print their configuration in a format + that is compatible with *interfaces*(5) files. + +*-U, --allow-undefined* + Create virtual interfaces for any interfaces not explicitly + defined in the configuration file. This is primarily useful + for property queries. + +*-S, --state-file* _FILE_ + Use _FILE_ as the state database. + +*-T, --timeout* _TIMEOUT_ + Wait up to _TIMEOUT_ seconds for executors to complete before + raising an error. + +*-V, --version* + Print the ifupdown-ng version and exit. + +*-X, --exclude* _PATTERN_ + Exclude _PATTERN_ when matching against the config or state + database. + +# SEE ALSO + +*ifup*(8)++ +*ifdown*(8)++ +*interfaces*(5) + +# AUTHORS + +Ariadne Conill diff --git a/doc/ifstate.scd b/doc/ifstate.scd new file mode 100644 index 0000000..8098f2f --- /dev/null +++ b/doc/ifstate.scd @@ -0,0 +1,57 @@ +ifstate(5) + +# NAME + +*/run/ifstate* - interface state database + +# DESCRIPTION + +The */run/ifstate* file describes the present state of the interface +configuration -- namely, how many interfaces are up, how many +dependencies each interface has which are up (the refcount), and +whether or not an interface was explicitly brought up due to request +or configuration. + +# FILE SYNTAX + +At a minimum, the */run/ifstate* file contains at least one column, +the physical to logical interface mapping. This column is formatted +as such: + +``` +lo=lo +eth0=eth0 +wlan0=work +``` + +The left side of the mapping is the physical interface, while the right +side is the logical interface. This field is required to be present. + +The next field is the reference count. This is a number that reflects +the number of *active* dependencies an interface has. As interfaces +are brought up and down, the refcount may change. This field is +optional. + +The final field denotes whether or not an interface was brought up +explicitly -- either by being marked as _auto_ or brought up manually +using *ifup*(8). The contents of this field if present is the +_explicit_ keyword. + +# EXAMPLES + +An example from a typical system with localhost, eth0 and a wireguard +VPN: + +``` +lo=lo 1 explicit +eth0=eth0 2 explicit +wg0=wg0 1 explicit +``` + +# SEE ALSO + +*interfaces*(5) + +# AUTHORS + +Ariadne Conill diff --git a/doc/ifup.scd b/doc/ifup.scd new file mode 100644 index 0000000..82978f2 --- /dev/null +++ b/doc/ifup.scd @@ -0,0 +1,71 @@ +ifup(8) + +# NAME + +ifup - bring interfaces up + +# SYNOPSIS + +ifup [<_options_>...] <_interfaces_> + +# DESCRIPTION + +*ifup* is used to configure interfaces according to how they are +configured in the configuration database. + +# OPTIONS + +*-a, --auto* + Only match interfaces that are marked as _auto_. + +*-f, --force* + Force configuration of the interface. + This option exists for compatibility with other implementations. + +*-h, --help* + Display supported options to ifquery. + +*-i, --interfaces* _FILE_ + Use _FILE_ as the config database. + +*-n, --no-act* + Show what commands would be run instead of actually running + them. Useful for testing configuration changes. + +*-v, --verbose* + Show what commands are being run as they are executed. + +*-E, --executor-path* _PATH_ + Look for executors in the given _PATH_. + +*-I, --include* _PATTERN_ + Include _PATTERN_ when matching against the config or state + database. + +*-L, --no-lock* + Do not use a lockfile to serialize state changes. + +*-S, --state-file* _FILE_ + Use _FILE_ as the state database. + +*-T, --timeout* _TIMEOUT_ + Wait up to _TIMEOUT_ seconds for executors to complete before + raising an error. + +*-V, --version* + Print the ifupdown-ng version and exit. + +*-X, --exclude* _PATTERN_ + Exclude _PATTERN_ when matching against the config or state + database. + +# SEE ALSO + +*ifupdown-ng.conf*(5) +*ifdown*(8) +*ifquery*(8) +*interfaces*(5) + +# AUTHORS + +Ariadne Conill diff --git a/doc/ifupdown-executor.scd b/doc/ifupdown-executor.scd new file mode 100644 index 0000000..05de8fa --- /dev/null +++ b/doc/ifupdown-executor.scd @@ -0,0 +1,92 @@ +ifupdown-executor(7) + +# NAME + +*/usr/libexec/ifupdown-ng/program* - ifupdown executor protocol + +# DESCRIPTION + +The ifupdown executors are programs that are typically installed +into the ifupdown-ng executor path. They follow a specific +protocol documented in this man page. + +# PHASES + +Executors are run to react to nine different phases and are not +required to take any specific action. These phases are: + +*depend* + Called to determine if the executor wishes to change + the dependency graph. The executor should write a + space-delimited list of interface names it is dependent + upon to _stdout_. Those interface names will be merged + into the dependency graph. If an executor does not have + any dependencies, it may simply exit 0 without doing + anything. + +*create* + Called before *pre-up*, to explicitly allow for interface + creation if necessary. + +*pre-up* + Called before the interface is going to be brought up. + +*up* + Called when the interface is being brought up. + +*post-up* + Called after the interface was successfully brought up. + +*pre-down* + Called before the interface is going to be taken down. + +*down* + Called when the interface is being taken down. + +*post-down* + Called after the interface was successfully taken down. + +*destroy* + Called after *post-down* to allow for explicitly + destroying an interface if necessary. + +# ENVIRONMENT + +Executors are guaranteed to run with a core set of environment +variables: + +*IFACE* + The name of the interface being configured. + +*INTERFACES_FILE* + The path to the interfaces database file being used. + +*MODE* + Either _start_, _stop_ or _depend_ depending on phase. + This environment variable is present for compatibility + with legacy ifupdown scripts and should not be used in + ifupdown-ng executors. + +*PHASE* + The phase being executed. See the phases section for + more information about phases. + +*VERBOSE* + If present, verbose output is expected from the + executor. + +Additionally, the properties associated with an interface are +provided to executors. The keys are rewritten to begin with +IF_ and are capitalized with dashes converted to underscores. +For example, the property _bridge-ports_ will be rewritten as +_IF_BRIDGE_PORTS_. + +# SEE ALSO + +ifup(8)++ +ifdown(8)++ +interfaces(5) + +# AUTHORS + +Ariadne Conill diff --git a/doc/ifupdown-ng.conf.scd b/doc/ifupdown-ng.conf.scd new file mode 100644 index 0000000..74adac8 --- /dev/null +++ b/doc/ifupdown-ng.conf.scd @@ -0,0 +1,76 @@ +ifupdown-ng.conf(5) + +# NAME + +*ifupdown-ng.conf* - Global configuration file for ifupdown-ng + +# DESCRIPTION + +ifupdown-ng allows to configure some parts of it's behaviour via global +configuration options. + +# GENERAL CONFIGURATION OPTIONS + +*allow_addon_scripts* _bool_ + Enable support for /etc/if-X.d addon scripts. These are used for + compatibility with legacy setups, and may be disabled for + performance improvements in setups where only ifupdown-ng executors + are used. Valid values are _0_ and _1_, the default is _1_. + +*auto_executor_selection* _bool_ + Automatically determine which executors to use. At present, this + is done by inserting `use` statements for the namespace a config + option has. The namespace is separated from the config option with + a dash (`-`). Valid values are _0_ and _1_, the default is _1_. + +*use_hostname_for_dhcp* _bool_ + Automatically learn the hostname property, used for DHCP + configuration by querying the system hostname using uname(2). + This is basically equivalent to `hostname $(hostname)` without + having to specify any configuration. Valid values are _0_ and + _1_, the default is _1_. + +# TEMPLATE RELATED OPTIONS + +*allow_any_iface_as_template* _bool_ + Enable any interface to act as a template for another interface. + This is presently the default, but is deprecated. An admin may + choose to disable this setting in order to require inheritance + from specified templates. Valid values are _0_ and _1_, the + default is _1_. + +*implicit_template_conversion* _bool_ + In some legacy configs, a template may be declared as an iface, and + ifupdown-ng automatically converts those declarations to a proper + template. If this setting is disabled, inheritance will continue + to work against non-template interfaces without converting them to + a template. Valid values are _0_ and _1_, the default is _1_. + +# COMPATIBILITY RELATED OPTIONS + +*compat_create_interfaces* _bool_ + Denotes where or not to create interfaces when compat_\* settings are + active and it would be necessary to create an interface to be fully + compliant. This could happen when inheriting bridge VLAN settings to + an interface within a bridges bridge-ports setting but no interface + stanza is found. Valid values are _0_ and _1_, the default is _1_. + +compat_ifupdown2_bridge_ports_inherit_vlans _bool_ + In ifupdown2 as well as the set on a + bridge interface will be inherited by all member ports if not set + explicitly. When set to 1 ifupdown-ng behaves the same way and will + internally copy both options from the bridge member ports if they + are not set on the member port. Valid values are _0_ and _1_, the + default is _1_. + +# FILES + +/etc/network/ifupdown-ng.conf + +# SEE ALSO + +*interfaces*(5) + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/img/dependency-resolution.png b/doc/img/dependency-resolution.png new file mode 100644 index 0000000..dc28eea Binary files /dev/null and b/doc/img/dependency-resolution.png differ diff --git a/doc/interfaces-batman.scd b/doc/interfaces-batman.scd new file mode 100644 index 0000000..87c75d9 --- /dev/null +++ b/doc/interfaces-batman.scd @@ -0,0 +1,87 @@ +interfaces-batman(5) + +# NAME + +*interfaces-batman* - B.A.T.M.A.N. adv. extensions for the interfaces(5) +file format + +# DESCRIPTION + +Better Approach To Mobile Ad-Hoc Networking (B.A.T.M.A.N.) advanced is +a mesh protocol which provides an Ethernet overlay network over an +Ethernet underlay. The overlay interface is called _meshif_ whereas +underlay interfaces are called _hardif_. + +It's supported in the Linux kernel and thus available in many Linux +environments. The ifupdown-ng exectuor relies on the *batctl* tool +being installed. Support for setting interface based hop-penalties +required Linux Kernel 5.8 or later. + +B.A.T.M.A.N. adv. adds 30-60 bytes of encapsulation overhead depending +on wether netword coding is activated or not. This should be taken into +consideration when setting up overlay networks, particularly on underlay +networks with a conventional 1500 byte MTU. + +See https://www.open-mesh.org/projects/open-mesh/wiki for more details +and updates. + +The following options allow to set up B.A.T.M.A.N. adv. interfaces. + +# BATMAN-RELATED OPTIONS + +*batman-ifaces* _list of interfaces_ + Specifies the underlay interfaces (hardifs) which should be + configured for the B.A.T.M.A.N. adv. meshif defined within + the iface stanza. + +*batman-hop-penalty* _hop-penalty_ + The _hop-penalty_ defines the cost of traversing a node or an + interface. The _hop-penalty_ is a numeric value between 0 and + 255. Historically a _hop-penalty_ could only be set on a meshif, + since B.A.T.M.A.N adv. v2020.3 (included in Kernel 5.8) it can + also be set on a per-interfaces (hardif) basis. + +*batman-gw-mode* _gw-mode_ + Denotes the gateway mode which controls the role this node will + play within this B.A.T.M.A.N. adv. instance. The mode can be + _off_, _client_, or _server_. + +*batman-distributed-arp-table* _mode_ + Activates or deactivates the Distributed ARP table (DAT) within + this B.A.T.M.A.N. adv. instance. Valid values are _enable_ and + _disable_. + +*batman-multicast-mode* _mode_ + Activates or deactivates the multicast mode of this B.A.T.M.A.N. + adv. instance. Valid values are _enable_ and _disable_. + +# EXAMPLES + +A B.A.T.M.A.N. adv. _meshif_: + +``` +auto bat-pad-cty +iface bat-pad-cty + batman-ifaces dummy-pad-cty vlan1234 + batman-hop-penalty 5 + # + hwaddress f2:00:c1:01:00:00 + mtu 1500 +``` + +A B.A.T.M.A.N. adv. member interfaces (_hardif_): + +``` +auto vlan1234 +iface vlan1234 + mtu 1560 + batman-hop-penalty 10 +``` + +# SEE ALSO + +*batctl*(8) + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces-bond.scd b/doc/interfaces-bond.scd new file mode 100644 index 0000000..d09e9db --- /dev/null +++ b/doc/interfaces-bond.scd @@ -0,0 +1,204 @@ +interfaces-bond(5) + +# NAME + +*interfaces-bond* - Bonding/LAG extensions for the interfaces(5) file format + +# DESCRIPTION + +The Linux implementation for Ling Aggregation Groups (LAGs) is called +_bonding_, whereas a LAG interface is called _bond_. The Linux bonding +implementation supports active/passive setups, classical EtherChannels +as well as LACP (802.3ad). + +The following options set up bonding/LAG interfaces with ifupdown-ng. + +See https://www.kernel.org/doc/Documentation/networking/bonding.rst and +for more information. + +# BOND-RELATED OPTIONS + +A bond interface must have at least one member port set. All other +options are optional. + +*bond-members* _list of interfaces_ + Denotes the physical member interfaces to form this LAG. For + compatiblity to ifupdown1 and ifupdown2 _slaves_ as well as + _bond-slaves_ are an alias for this option. This option is + required. + +*bond-mode* _mode_ + Denotes the mode for this LAG. The _mode_ can be given as string + or as numerical value. Valid values are _balance-rr_ (0), + _active-backup_ (1), _balance-xor_ (2), _broadcast_ (3), + _802.3ad_ (4), _balance-tlb_ (5), _balance-alb_ (6). + The default is _balance-rr_. + +*bond-xmit-hash-policy* _policy_ + Denotes the hash policy/algorithm used to distribute packets + across the physical links. This only applies for modes + _balance-alb_, _balance-tlb_, _balance-xor_, and _802.3ad_. + The _policy_ can be given as string or as numerical value. + Valid values are _layer2_ (0), _layer3+4_ (1), _layer2+3_ (2), + _encap2+3_ (3), and _encap3+4_ (4). The default is _layer2_. + +*bond-min-links* _number_ + Denotes the minimum number of available links before turning on + carrier. + +*bond-miimon* _interval_ + Denotes the MII link monitoring frequency in milliseconds. + This determines how often the link state of each slave is + inspected for link failures. A value of zero disables MII + link monitoring. The default is 0. + +*bond-use-carrier* _bool_ + Denotes wether miimon uses MII or ethtool ioctls vs. the + netif_carrier_ok() call to determine member link status. + A value of 1 enables the use of netif_carrier_ok(), a value of + 0 will use the deprecated MII / ETHTOOL ioctls. The default + is 1. + +*bond-updelay* _delay_ + Denotes the delay in milliseconds before considering link up, + in milliseconds. The default is 0. + +*bond-downdelay* _delay_ + Denotes the delay in milliseconds before considering link down, + in milliseconds. The default is 0. + +*bond-all-slaves-active* _bool_ + Denotes wether duplicate frames (received on inactive ports) + should be dropped (0) or delivered (1). The default is 0. + +*bond-packets-per-slave* _num_packets_ + Denotes the number of packets to transmit through a member + before moving to the next one. When set to 0 then a slave is + chosen at random. The valid range is 0 - 65535; the default + value is 1. This option has effect only in balance-rr mode. + +*bond-lp-interval* _interval_ + Denotes the interval in seconds between sending learning packets + to each members peer switch. The valid range is 1 - 0x7fffffff; + the default value is 1. This option has effect only in modes + balance-tlb and balance-alb. + +*bond-resend-igmp* _number_ + Denotes the number of IGMP membership reports to send after a + link failover happend. The valid range is 0 - 255; a value of + 0 prevents the IGMP membership report from being issued in + response to the failover event. The default is 1. + This option is useful for bonding modes balance-rr, active-backup + balance-tlb and balance-alb, in which a failover can switch the + IGMP traffic from one slave to another. + +# LACP-RELATED OPTIONS + +The following options are only valid in LACP (802.3ad) mode. + +*bond-lacp-rate* _rate_ + Denotes the _rate_ of LACPDU requested from the peer. The _rate_ + can be given as string or as numerical value. Valid values are + slow (0) and fast (1). The default is slow. + +*bond-ad-select* _mode_ + Denotes the 802.3ad aggregation selection logic. The _mode_ can + be given as string or as numerical value. Valid values are + _stable_ (0), _bandwidth_ (1) and _cound_ (2). The default is + _stable_. + +*bond-ad-actor-sys-prio* _priority_ + Denotes the LACP system priority. The allowed range is 1 - 65535. + The default value is 65535. + +*bond-ad-user-port-key* _key_ + Denotes the upper 10 bits of the port-key. he values can be from + 0 - 1023. The default is 0. + + +# ACTIVE/BACKUP-RELATED OPTIONS + +The following options are only valid in active/passive setups. + +*bond-primary* _interface_ + Denotes the primary member interface The specified device will + always be the active slave while it is available. The primary + option is only valid for active-backup, balance-tlb and + balance-alb mode. + +*bond-primary-reselect* _policy_ + Denotes the reselection policy for the primary member interface. + Valid values are _always_ (0), _better_ (1), and _failure_ (2). + The default is _always_. + +*bond-fail-over-mac* _mode_ + Denotes whether active-backup mode should set all member + interfaces to the same MAC address at enslavement (the + traditional behavior), or, when enabled, perform special + handling of the bond's MAC address in accordance with the + selected policy. Valid values are _none_ (0), _active_ (1), + _follow_ (2). The default is _none_. + +*bond-num-grat-arp* _count_ + Denotes the number of peer notifications (gratuitous ARPs and + unsolicited IPv6 Neighbor Advertisements) to be issued after a + failover event. The valid range is 0 - 255; the default is 1. + +*bond-num-unsol-na* _count_ + This is an alias for _bond-num-grat-arp_ + +*bond-peer-notif-delay* _interval_ + Denotes the interval in milliseconds, between each peer + notification (gratuitous ARP and unsolicited IPv6 Neighbor + Advertisement) issued after a failover event. The default + is 0 which means to match the value of the link monitor + interval. + +# ARP-RELATED OPTIONS + +The following options configure ARP link monitoring. +The ARP monitor works by periodically checking the slave +devices to determine whether they have sent or received +traffic recently. Regular traffic is generated via ARP +probes issued for the addresses specified by the +_bond-arp-ip-target_ option. + +*bond-arp-interval* _interval_ + Denotes the frequency in milliseconds to send ARP probes. + +*bond-arp-ip-target* _IPv4 address_ + Denotes the IP addresses to use as ARP monitoring peers when + _bond-arp-interval_ is > 0. + +*bond-arp-validate* _mode_ + Specifies whether or not ARP probes and replies should be + validated in any mode that supports arp monitoring, or whether + non-ARP traffic should be filtered (disregarded) for link + monitoring purposes. Valid values are _none_ (0), _active_ (1), + _backup_ (2), _all_ (3), _filter_ (4), _filter_active_ (5), and + _filter_backup_ (6). The default is _none_. + +*bond-arp-all-targets* _mode_ + Denotes the number of _bond-arp-ip-targets_ that have to be + reachable to consider the member interface to be up. Valid + options are _any_ (0) and _all_ (1). The default is _any_. + +# EXAMPLES + +A bond using two links and LACP (802.3ad): + +``` +auto bond0 +iface bond0 + bond-members eth0 eth1 + bond-mode 802.3ad + bond-xmit-hash-policy layer3+4 + bond-min-links 1 + # + address 192.0.2.42/24 + address 2001:db8::42/64 +``` + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces-bridge.scd b/doc/interfaces-bridge.scd new file mode 100644 index 0000000..4ad3eb6 --- /dev/null +++ b/doc/interfaces-bridge.scd @@ -0,0 +1,173 @@ +interfaces-bridge(5) + +# NAME + +*interfaces-bridge* - Bridge extensions for the interfaces(5) file format + +# DESCRIPTION + +Linux has support for Ethernet bridging interfaces which act like an +Ethernet switch within the Linux Kernel. The following options allow +to set up Ethernet bridges and adding configured interfaces to bridges. + +See *ip-link*(8) for more details about the options listed below. + +# BRIDGE-RELATED OPTIONS + +*bridge-ports* _list of interfaces_ + A space separated list of interfaces which should be configured + as member interfaces of this bridge. This option must be set + for the bridge to be configured. + +*bridge-hw* _MAC address_ + Denotes the _MAC address_ the bridge should use. + +*bridge-ageing* _seconds_ + Denotes the time in seconds after which a MAC address will be + removed from the Forwarding DataBase (FDB) after not having + seen a frame with this source address. + +*bridge-vlan-aware* _bool_ + Denotes wether or not the bridge should be aware of 802.1q VLANs. + _bool_ can be given as _yes_/_no_ or _0_/_1_. The defaul is _no_. + See related options for configuring vlan-aware bridges, below. + +# SPANNING TREE RELATED BRIDGE OPTIONS + +*bridge-stp* _state_ + Activates or deactivates IEEE 802.1d Spanning Tree Protocol + (STP) support of the bridge. Valid values are _on_/_off_. + +*bridge-bridgeprio* _priority_ + Sets the bridge's priority to _priority_. The priority value is + a number between 0 and 65535. Lower priority values are better. + The bridge with the lowest priority will be elected _root + bridge_. + +*bridge-fd* _seconds_ + Denotes the bridge forward delay in seconds. Valid values are + between 2 and 30. + +*bridge-hello* _seconds_ + Denotes the bridge hello time in seconds. Valid values are + between 1 and 10. + +*bridge-maxage* _seconds_ + Denotes the seconds until another bridge is considerd dead + after reception of its last STP hello message. Valid values + are between 6 and 40. + +# OPTIONS FOR VLAN-AWARE-BRIDGES + +The following options only have an effect on vlan-aware bridges and +their ports. + +All settings can be applied on the bridge interface itself and all member +port iface stanzas. If applied on the bridge interface they take effect +for the bridge interface itself and might be inherited to _bridge-ports_ +depending on the compatibility settings configured in *ifupdown-ng.conf*(5). + +Configuring VLAN options on the bridge interface might be required for +setting up a VLAN interface to one of the VLANs carried within the bridge. +See the EXAMPLES section for an example for this scenario. + +See *ifupdown-ng.conf*(5) for more information about compatiblity settings +mentioned below. + +*bridge-access* _vlan ID_ + Configure the given _vlan ID_ for untagged ingress and egress + on this interface. The common description for this kind of + configuration is called "access port". + +*bridge-pvid* _vlan ID_ + Denotes the _vlan ID_ to considered a PVID at ingress. + Any untagged frames received on this interface will be + assigned to this _vlan ID_. The default PVID is _1_. + + If compatibility to ifupdown2 bridge port inheritance is active + a _bridge-pvid_ set on the bridge will be inherited to any + interface configured in _bridge-ports_ without a _bridge-pvid_ set. + +*bridge-vids* _list of vlan IDs_ + Denotes the space separated list of VLANs to be allowed tagged + ingress/egress on this interface. + + If compatibility to ifupdown2 bridge port inheritance is active + a _bridge-vids_ set on the bridge will be inherited to any + interface configured in _bridge-ports_ without _bridge-vids_ set. + +*bridge-allow-untagged* _bool_ + Denotes wether or not the bridge should allow untagged frames on + ingress as well as egress. If set to _no_ untagged frames will be + droppped on ingress and none will be sent. _bool_ can be given as + _yes_/_no_ or _0_/_1_. The defaul is _yes_. + +# EXAMPLES + +A simple layer 2 only bridge: + +``` +auto br0 +iface br0 + bridge-ports eth0 veth-vm1 tap0 + bridge-fd 0 + bridge-stp off +``` + +A bridge with layer 3 configuration: + +``` +auto br0 +iface br0 + bridge-ports eth0 veth-vm1 tap0 + bridge-fd 0 + bridge-stp off + # + address 192.0.2.42/24 + address 2001:db8::42/64 +``` + +A layer 2 only vlan-aware bridge: + +``` +auto bond0 +iface bond0 + bond-members eth0 eth1 + bridge-vids 23 42 84 1337 + +auto br0 +iface br0 + bridge-ports bond0 +``` + +A vlan-aware bridge with a VLAN interface on top: + +``` +auto eth0 +iface eth0 + bridge-vids 23 42 84 1337 + +auto br0 +iface br0 + bridge-ports eth0 + bridge-vlan-aware yes + bridge-vids 42 + +auto vlan42 +iface vlan42 + vlan-raw-device br0 + # + address 192.0.2.42/24 + address 2001:db8::42/64 +``` + +# SEE ALSO + +*interfaces*(5) +*ifupdown-ng.conf*(5) +*ip-link*(8) +*bridge*(8) + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces-forward.scd b/doc/interfaces-forward.scd new file mode 100644 index 0000000..b5b0f7f --- /dev/null +++ b/doc/interfaces-forward.scd @@ -0,0 +1,48 @@ +interfaces-forward(5) + +# NAME + +*interfaces-forward* - forwarding vocabulary for the interfaces(5) file format + +# DESCRIPTION + +Linux allows for configuration of IP packet forwarding behavior on a protocol +and interface basis. The following options allow for this configuration. + +# FORWARDING-RELATED OPTIONS + +The forward executor will only modify the sysctl configuration if these options +are provided, otherwise other mechanisms such as /etc/sysctl.conf may be used. + +*forward-ipv4* _yes|no_ + Whether the interface should forward unicast IPv4 packets. + +*forward-ipv6* _yes|no_ + Whether the interface should forward unicast IPv6 packets. + +*forward-ipv4-mc* _yes|no_ + Whether the interface should forward multicast IPv4 packets. + +*forward-ipv6-mc* _yes|no_ + Whether the interface should forward multicast IPv6 packets. + +# EXAMPLES + +The typical home router scenario will want to forward both IPv4 and IPv6 +packets: + +``` +iface WAN + use dhcp + forward-ipv4 yes + forward-ipv6 yes + +iface LAN + address 192.168.0.1/24 + forward-ipv4 yes + forward-ipv6 yes +``` + +# AUTHORS + +Ariadne Conill diff --git a/doc/interfaces-mpls.scd b/doc/interfaces-mpls.scd new file mode 100644 index 0000000..5f54e02 --- /dev/null +++ b/doc/interfaces-mpls.scd @@ -0,0 +1,40 @@ +interfaces-mpls(5) + +# NAME + +*interfaces-mpls* - MPLS vocabulary for the interfaces(5) file format + +# DESCRIPTION + +Linux allows has support for MultiProtocol Label Switching (MPLS) for a while +now. The following options allow for this configuration. + +# MPLS-RELATED OPTIONS + +The MPLS executor will only modify the sysctl configuration if these options +are provided, otherwise other mechanisms such as /etc/sysctl.conf may be used. +If MPLS is enabled on (at least) one interface the executor will load the +_mpls_iptunnel_ kernel module. + +Be aware that you have to set the _platform_labels_ sysctl to make MPLS work. +See https://www.kernel.org/doc/Documentation/networking/mpls-sysctl.rst for +more details on the MPLS related knobs in the Linux kernel. + + +*mpls-enable* _yes|no_ + Control whether packets can be input on this interface. If disabled, + packets carrying an MPLS label will be discarded without further + processing. + +# EXAMPLES + +``` +iface eth0 + address 2001:db8:08:15::42/64 + # + mpls-enable yes +``` + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces-ppp.scd b/doc/interfaces-ppp.scd new file mode 100644 index 0000000..5d12555 --- /dev/null +++ b/doc/interfaces-ppp.scd @@ -0,0 +1,37 @@ +interfaces-ppp(5) + +# NAME + +*interfaces-ppp* - PPP extensions for the interfaces(5) file format + +# DESCRIPTION + +The Point-to-Point Protocol (PPP) usually is used for dial-up lines, +most common for over Digital Subscriber Lines (DSL) to connect to an +Internet Service Provider (ISP). The following options allow to set +up PPP dial-up connections. + +# PPP-RELATED OPTIONS + +*ppp-provider* _provider_ + Denotes the file name of the _provider_ configuration file + within the _/etc/ppp/peers/_ directory which should be used + to set up the PPP connection. + +*ppp-physdev* _interfaces_ + Denotes the physical (underlay) interface which is used to + set up the PPP connection. + +# EXAMPLES + +A PPP connection to _local-ISP_: + +``` +auto ppp0 +iface ppp0 + ppp-provider local-ISP +``` + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces-tunnel.scd b/doc/interfaces-tunnel.scd new file mode 100644 index 0000000..b924e93 --- /dev/null +++ b/doc/interfaces-tunnel.scd @@ -0,0 +1,161 @@ +interfaces-tunnel(5) + +# NAME + +*interfaces-tunnel* - Tunnel extensions for the interfaces(5) file format + +# DESCRIPTION + +The following options set up tunneling interfaces with ifupdown-ng. + +# TUNNEL-RELATED OPTIONS + +A tunnel interface must have a mode, remote IP and a local IP or device +set, all other options are optional. + +*tunnel-mode* _mode_ + Denotes the mode for this tunnel. Basically all tunnel modes supported + by Linux / iproute2 are supported as well. This includes but is not + limited to _gre_/_gretap_, _ip6gre_/_ip6gretap_, _ipip_/_ip6ip_/_sit_. + +*tunnel-local* _IP_ + Denotes the IP address used as the local tunnel endpoint. According + to the _tunnel-mode_ an IPv4 or IPv6 address has to be given. + For compatiblity to ifupdown1 _local_ is an alias for this option. + +*tunnel-local-dev* _interface_ + When the local IP address the tunnel should be established from isn't + static and therefore might change (e.g. configured by DHCP or PPP) it + might be desireable to just use the address configured on _interface_. + When _tunnel-local-dev_ is given instead of _tunnel-local_ ifupdown-ng + will try to determine the IP address set on the given _interface_ with + respect to the address family required to set up a tunnel of the given + _mode_ and use this to set up the tunnel. + +*tunnel-remote* _IP_ + Denotes the IP address used as the remote tunnel endpoint. According + to the _tunnel-mode_ an IPv4 or IPv6 address has to be given. + For compatiblity to ifupdown1 _endpoint_ is an alias for this option. + +*tunnel-physdev* _interface_ + Denotes the _interface_ the encapsulated packets should be sent out by. + This comes in handy when using VRFs to denote that the local tunnel + endpoint should be terminated in VRF _interface_ or the VRF associated + with _interface_. + + Note: Depending on the _mode_ of the tunnel either the VRF interface + or the real underlay interface may have to given as _interface_. + +*tunnel-ttl* _ttl_ + Denotes the TTL value to use in outgoing packets. _ttl_ is a number in the + range 1 - 255 whereas 0 is a special value meaning that packets inherit the + TTL value. The default for IPv4 tunnels is to inherit the TTL, for IPv6 + tunnels it's 64. For compatiblity to ifupdown1 _ttl_ is an alias for this option. + + + +# IPIP/SIT-RELATED OPTIONS + +*tunnel-encap* _encap_ + Denotes the type of secondary UDP encapsulation to use for this tunnel + if any. Supported _encap_ values are _fou_, _gue_, and _none_. + _fou_ indicates Foo-Over-UDP, _gue_ indicates Generic UDP Encapsulation. + +# GRE-RELATED OPTIONS + +*tunnel-encap* _encap_ + Denotes the type of secondary UDP encapsulation to use for this tunnel + if any. Supported _encap_ values are _fou_, _gue_, and _none_. + _fou_ indicates Foo-Over-UDP, _gue_ indicates Generic UDP Encapsulation. + +*tunnel-key* _key_ + Denotes the_key to used for keyed GRE to allow multiple tunnels between + the same two endpoints. _key_ is either a number or an IPv4 address- + like dotted quad. The key parameter specifies the same key to use in both + directions. The _tunnel-ikey_ and _tunnel-okey_ parameters specify different + keys for input and output. For compatiblity to ifupdown1 _key_ is an alias + for this option. + +*tunnel-hoplimit* _ttl_ + Denotes the Hop Limit value to use in outgoing packets for _ip6gre_/_ip6gretap_ + tunnels. + +*tunnel-ignore-df* _bool_ + Denotes wether to enable/disable IPv4 DF suppression on this tunnel. Normally + datagrams that exceed the MTU will be fragmented; the presence of the DF flag + inhibits this, resulting instead in an ICMP Unreachable (Fragmentation Required) + message. Enabling this attribute causes the DF flag to be ignored. + +*tunnel-ikey* _key_ + Denotes the key to used for keyed GRE for packets received. See _tunnel-key_ + for details. + +*tunnel-okey* _key_ + Denotes the key to used for keyed GRE for packets sent out. See _tunnel-key_ + for details. + +*tunnel-pmtudisc* _bool_ + Denotes wether to enable/disable Path MTU Discovery on this tunnel. It is + enabled by default. Note that a fixed ttl is incompatible with this option: + tunneling with a fixed ttl always makes pmtu discovery. + +*tunnel-tos* _tos_ + Denotes the TOS value to use in outgoing packets. + + +# EXAMPLES + +A simple GRE tunnel + +``` +auto gre0 +iface gre0 + tunnel-mode gre + tunnel-remote 198.51.100.1 + tunnel-local 203.0.113.2 + # + address 192.0.2.42/24 + address 2001:db8::42/64 +``` + +A GRE tunnel where the local IP is learned from _eth0_ + +``` +auto gre1 +iface gre1 + tunnel-mode gre + tunnel-remote 198.51.100.1 + tunnel-local-dev eth0 + # + address 192.0.2.42/24 + address 2001:db8::42/64 +``` + +A GRE tunnel which transfers encapasulated packets via _eth0_ which is part +of a VRF. + +``` +auto eth0 +iface eth0 + address 203.0.113.2/24 + gateway 203.0.113.1 + vrf vrf_external + +auto tun-vrf +iface tun-vrf + tunnel-mode gre + tunnel-remote 198.51.100.1 + tunnel-local 203.0.113.2 + tunnel-physdev eth0 + # + address 192.0.2.42/24 + address 2001:db8::42/64 + +auto vrf_external +iface vrf_external + vrf-table 1023 +``` + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces-vrf.scd b/doc/interfaces-vrf.scd new file mode 100644 index 0000000..c808cf0 --- /dev/null +++ b/doc/interfaces-vrf.scd @@ -0,0 +1,58 @@ +interfaces-vrf(5) + +# NAME + +*interfaces-vrf* - VRF extensions for the interfaces(5) file format + +# DESCRIPTION + +Linux has support for Virtual Routing and Forwarding (VRF) instances +since Kernel >= 4.4. The following options allow to set up VRFs and +adding configured interfaces to VRFs. + +Note that in the Linux Kernel VRFs are represented as network interfaces, +too. See https://www.kernel.org/doc/Documentation/networking/vrf.rst for +more details. + +# VRF-RELATED OPTIONS + +*vrf-table* _table id_ + The _id_ of the kernel routing table associated with this + VRF interface. This parameter indicates that the interface + where it is specified shall be a VRF. + +*vrf* _vrf interface_ + The _vrf_ the interface should be assigned to. This parameter + is specified on regular interfaces which should be within the + given _vrf_. + +# EXAMPLES + +A VRF interface: + +``` +auto vrf_external +iface vrf_external + vrf-table 1023 +``` + +A regular interface which should be within a VRF: + +``` +auto eth0 +iface eth0 + address 192.2.0.42/24 + address 2001:db8::42/64 + gateway 192.2.0.1 + gateway 2001:db::1 + vrf vrf_external +``` + +# SEE ALSO + +*ip-vrf*(8) +*ip-link*(8) + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces-vxlan.scd b/doc/interfaces-vxlan.scd new file mode 100644 index 0000000..a6dd8b6 --- /dev/null +++ b/doc/interfaces-vxlan.scd @@ -0,0 +1,131 @@ +interfaces-vxlan(5) + +# NAME + +*interfaces-vxlan* - VXLAN extensions for the interfaces(5) file format + +# DESCRIPTION + +Virtual eXtensible LAN (VXLAN) is an overlay network to carry Layer 2 over +an IP network while accommodating a very large number of tenants. It is +defined in RFC 7348. + +Be aware that VXLAN encapsulation adds 50 bytes of overhead to the IP packet +header (inner Ethernet header + VXLAN + UDP + IP). This should be taken into +consideration when setting up overlay networks, particularly on underlay +networks with a conventional 1500 byte MTU. + +The following options set up VXLAN Tunnel EndPoints (VTEP) interfaces with +ifupdown-ng. + +See https://www.kernel.org/doc/Documentation/networking/vxlan.rst and +https://vincent.bernat.ch/en/blog/2017-vxlan-linux for more information. + +# VXLAN-RELATED OPTIONS + +A VXLAN Virtual Tunnel Endpoint (VTEP) interface must an ID set. All +other options are optional. + +*vxlan-id* _VNI ID_ + Denotes the VXLAN Network Identifier (VNI) ID for this interface. + This parameter is required for VTEP interfaces. + +*vxlan-physdev* _interface_ + Specifies the physical ("underlay") device to use for tunnel + endpoint communication. This is required for setups using + multicast. + +*vxlan-local-ip* _address_ + Specifies the source IP address to use in outgoing packets. + For compatiblity with ifupdown2 _vxlan-local-tunnelip_ is an + alias for this parameter. + +*vxlan-peer-ips* _list of IP addresses_ + Specifies the unicast destination IP address(es) to use in outgoing + packets when the destination link layer address is not known in + the VXLAN device forwarding database. This option can be used to + form Point-to-Point as well as Point-to-Multipoint VXLAN tunnels/ + overlays depending on how many peer IPs are given. If more than one + IP address is given a Point-to-Multipoint overlay is being set up + and ingress / head-end replication will be used by the Linux Kernel. + This option cannot be used together with _vxlan-peer-group_ option. + For compatiblity with ifupdown2 _vxlan-remoteip_ is an alias for this option + and for compatibility with previos versions of ifupdown-ng _vxlan-remote-ip_ + is an alias for this option, too. + +*vxlan-peer-group* _multicast group_ + Specifies the multicast group address to join, requires _vxlan-phsydev_ + to be set as well. This parameter cannot be specified in combination + with the _vxlan-peer-ips_ parameter. For compatibility with ifupdown2 + _vxlan-svcnodeip_ is an alias for this option and for compatibility + with previos version of ifupdown-ng _vxlan-remote-group_ is an alias, too. + +*vxlan-learning* _on/off_ + Specifies if unknown source link layer addresses and IP addresses + are entered into the VXLAN device forwarding database. + +*vxlan-ageing* _seconds_ + Specifies the lifetime in seconds of FDB entries learnt by the kernel. + +*vxlan-dstport* _port_ + Specifies the UDP destination port of the remote VXLAN tunnel endpoint. + The default is 4789. + +# EXAMPLES + +A VTEP with multiple peers addressed via a multicast group: + +``` +auto vx_v1001_padcty +iface vx_v1001_padcty + vxlan-id 655617 + vxlan-physdev vlan1001 + vxlan-remote-group 225.10.1.1 + # + hwaddress f2:00:c1:01:10:01 + mtu 1560 +``` + +The same works just fine with IPv6 in the underlay: + +``` +auto vx_v1400_padcty +iface vx_v1400_padcty + vxlan-id 917505 + vxlan-physdev vlan1400 + vxlan-peer-group ff42:1400::1 + # + hwaddress f2:00:0d:01:14:00 + mtu 1560 +``` + +Note that the underlay must have an MTU of at least 1610 to +carry the encapsulated packets of the two VTEPs above. + + +A VTEP with one peer (unicast point-to-point configuration): + +``` +auto vx_ptp1 +iface vx_ptp1 + vxlan-id 2342 + vxlan-local-ip 192.0.2.42 + vxlan-peer-ips 198.51.100.23 + # + hwaddress f2:00:c1:01:10:01 +``` + + +A VTEP with multiple peers (unicast point-to-multipoint with ingress / head-end replication): + +``` +auto vx_her +iface vx_her + vxlan-id 1337 + vxlan-local-ip 2001:db8:1::1 + vxlan-peer-ips 2001:db8:2::23 2001:db8:3::42 2001:db8:4::84 +``` + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces-wifi.scd b/doc/interfaces-wifi.scd new file mode 100644 index 0000000..a169723 --- /dev/null +++ b/doc/interfaces-wifi.scd @@ -0,0 +1,63 @@ +interfaces-wifi(5) + +# NAME + +*interfaces-wifi* - WiFi vocabulary for the interfaces(5) file format + +# DESCRIPTION + +Wi-Fi (the IEEE 802.11 family of protocols) is a commonly used wireless +networking standard. The following options allow for configuration of +Wi-Fi client interfaces. + +WPA-secured networks are managed using *wpa_supplicant*(8), while insecure +networks are managed directly with *iwconfig*(8). + +# WIFI-RELATED OPTIONS + +*wifi-config-path* _path_ + Denotes the absolute _path_ to a *wpa_supplicant* configuration file. + If no path is given, _/run/wpa_supplicant..conf_ will be + used for a temporary configuration file. This option may not be used + with other configuration options. + +*wifi-ssid* _ssid_ + The SSID the Wi-Fi client should connect to. + +*wifi-psk* _psk_ + The passphrase for connecting to the Wi-Fi network. If unset, the + client will connect without WPA2 encryption. + +# EXAMPLES + +A typical setup may involve connecting to a home and work network. To +achieve this, we can define a pair of virtual interfaces called *wifi-home* +and *wifi-work*, which connect to their respective wifi networks: + +``` +iface wifi-home + use dhcp + wifi-ssid HomeNetwork + wifi-psk ExamplePassphrase + +iface wifi-work + use dhcp + wifi-config-path /etc/network/wpa-work.conf +``` + +The virtual interfaces can be used with *ifup* and *ifdown*: + +``` +# ifup wlan0=wifi-home +# ifdown wlan0 +# ifup wlan0=wifi-work +``` + +# SEE ALSO + +*iwconfig*(8)++ +*wpa_supplicant*(8) + +# AUTHORS + +Ariadne Conill diff --git a/doc/interfaces-wireguard.scd b/doc/interfaces-wireguard.scd new file mode 100644 index 0000000..79f4285 --- /dev/null +++ b/doc/interfaces-wireguard.scd @@ -0,0 +1,56 @@ +interfaces-wireguard(5) + +# NAME + +*interfaces-wireguard* - Wireguard extensions for the interfaces(5) file format + +# DESCRIPTION + +Wireguard is a comtemporary in-Kernel layer 3 VPN protocol implementation +which aims to provide fast and secure tunnels. The following options +allow to set up Wireguard VPN tunnels. + +# WIREGUARD-RELATED OPTIONS + +*wireguard-config-path* _path_ + Denotes the absolute _path_ to the Wireguard configuration file. + If no path is given, _/etc/wireguard/.conf_ will be + used. In the latter case _use wireguard_ has to be explicitly + set to the interface configuration. + + Be aware that the given configuration file will be loaded using + *wg setconf* and not with *wg-quick*. The file format for both + tools isn't compatible so you have to make sure you provide a + valid configuration file for the *wg* tool. If you already have + a configuration file for *wg-quick* you can set up the tunnel + manually once and then dump the configuration using *wg showconf* + and save this to _path_. + + +# EXAMPLES + +A Wireguard VPN tunnel with explicit configuration file specified + +``` +auto wg-foo +iface wg-foo + wireguard-config-path /etc/wireguard/foo.conf + # + address 192.0.2.23/42 + address 2001:db8::23/64 +``` + +A Wireguard VPN tunnel with implicit configuration file: + +``` +auto wg-bar +iface wg-bar + use wireguard + # + address 192.0.2.23/42 + address 2001:db8::23/64 +``` + +# AUTHORS + +Maximilian Wilhelm diff --git a/doc/interfaces.scd b/doc/interfaces.scd new file mode 100644 index 0000000..f4a6473 --- /dev/null +++ b/doc/interfaces.scd @@ -0,0 +1,262 @@ +interfaces(5) + +# NAME + +*/etc/network/interfaces* - interface configuration database + +# DESCRIPTION + +The */etc/network/interfaces* file is used to specify how network +interfaces are configured. The file is processed by *ifquery*(8), +*ifup*(8) and *ifdown*(8) to introspect and change system state. + +In most cases, syntax from legacy implementations is supported as +well, but that syntax is not discussed in detail here. + +# FILE SYNTAX + +The interface configuration database is composed of a series of +stanzas. Hash symbols designate comments, which are ignored by +the system. + +A stanza is a collection of triples, where a triple is a key and +value combination that is related to an *object*. Triples which +are not associated with an *object* are considered to be part +of the root of the configuration tree. + +All keywords are case-sensitive and are expected to be lower-case. + +The following is a simple example of a stanza: + +``` +auto eth0 +iface eth0 + address 203.0.113.2/24 + gateway 203.0.113.1 +``` + +This stanza defines an interface named *eth0* which is configured +with an address of *203.0.113.2* and gateway of *203.0.113.1*. + +# SUPPORTED KEYWORDS FOR UNASSOCIATED TRIPLES + +*auto* _object_ + Designates that _object_ should be automatically configured + by the system when appropriate. + +*iface* _object_ + Begins a new declaration for _object_. Any child keyword + associated with the declaration will be stored inside + _object_. + +*source* _filename_ + Includes the file _filename_ as configuration data. + +*source-directory* _directory_ + Includes the files in _directory_ as configuration data. + +*template* _object_ + Begins a new declaration for _object_, like *iface*, except + that _object_ is defined as a *template*. + +# SUPPORTED KEYWORDS FOR OBJECT TRIPLES + +Any keyword may be used inside an interface declaration block, but +the system will only respond to certain keywords by default: + +*address* _address_ + Associates an IPv4 or IPv6 address in CIDR notation with + the parent interface. If an IP address without a prefix + length is given a given _netmask_ attribute is used if present. + If neither a prefix length nor a _netmask_ are given a /24 or /64 + prefix length is presumed for IPv4 / IPv6 as of compatibility + reasons to classic ifupdown. + +*netmask* _netmask_ + Associates a fallback netmask with the parent interface for + addresses which do not have a CIDR length set. This option + is for backwards compatibility and should not be used in new + deployments. + +*point-to-point* _address_ + Sets the given IPv4 _address_ as the peer address on the + interface. This setting only takes effect for the IPv4 address + familiy and only makes sense in combination with a /32 netmask. + For compatiblity with ifupdown and ifupdown2, _pointopoint_ is + an alias for this parameter. + +*gateway* _address_ + Associates an IPv4 or IPv6 address with the parent interface + for use as a default route (gateway). This usually is given + once for IPv4 and once for IPv6 (in a Dual-Stack setup). + +*link-type* _link-type_ + Denotes the link-type of the interface. When set to _dummy_, + the interface is created as a virtual dummy interfaces. + When set to _veth_ the interface is created as virtual veth + interface (pair). + +*veth-peer-name* _peer-name_ + Denotes the name of the veth peer interfaces. If not set + the kernel will name the veth peer interface as _vethN_ + with N being an integer number. + +*alias* _alias_ + Sets the given alias on the interface. + +*requires* _interfaces_... + Designates one or more required interfaces that must be + brought up before configuration of the parent interface. + Interfaces associated with the parent are taken down at + the same time as the parent. + +*inherit* _object_ + Designates that the configured interface should inherit + configuration data from _object_. Normally _object_ + must be a *template*. + +*use* _executor_ + Designates that an executor should be used. See _EXECUTORS_ + section for more information on executors. + +*pre-down* _command_ + Runs _command_ before taking the interface down. + +*down* _command_ + Runs _command_ when the interface is taken down. + +*post-down* _command_ + Runs _command_ after taking the interface down. + +*pre-up* _command_ + Runs _command_ before bringing the interface up. + +*up* _command_ + Runs _command_ when the interface is brought up. + +*post-up* _command_ + Runs _command_ after bringing the interface up. + +Additional packages such as *bonding*, *bridge*, *tunnel*, *vrf* and +*vxlan* add additional keywords to this vocabulary. + +# EXECUTORS + +The *use* keyword designates that an _executor_ should be used. +This system is extendable by additional packages, but the +most common executors are: + +*batman* + The interface is a B.A.T.M.A.N. adv. mesh interface. + Configuration of B.A.T.M.A.N. adv. interfaces requires the + *batctl* untiliy to be installed. + +*bond* + The interface is a bonded interface. Configuration + of bonded interfaces requires the *bonding* package + to be installed. + +*bridge* + The interface is an ethernet bridge. Configuration + of ethernet bridges requires the *bridge* package + to be installed. + +*dhcp* + Use a DHCP client to learn the IPv4 address of an + interface. + +*forward* + Configures forwarding settings on the interface. + +*loopback* + Designates the interface as a loopback device. + +*ppp* + Designates the interface as a PPP device. Configuration + of PPP interfaces require the *ppp* and probably the *pppoe* + packages to be installed. + +*tunnel* + The interface is a tunnel. Configuration of tunnels + requires the *tunnel* package to be installed on Alpine + Linux. + +*vrf* + The interface is a VRF. Configuration of VRFs requires + the *vrf* package to be installed. + +*vxlan* + The interface is a Virtual Extensible LAN (VXLAN) tunnel + endpoint. + +*wifi* + The interface is a Wi-Fi (IEEE 802.11) client interface. + Configuration of the WiFi client interface requires the + *wireless-tools* package to be installed. + The *wpa_supplicant* package must also be installed to + connect to hotspots using WPA-based security. + +*wireguard* + The interface is a Wireguard VPN tunnel endpoint. + +Check *interfaces-(5)* for further informaton about a given +executor and available configuration parameters. + +If the _auto\_executor\_selection_ ifupdown-ng.conf option is enabled, +*use* statements will automatically be added for executors when their +configuration statements are present in the interfaces file. + +# EXAMPLES + +Configure a bridge interface *br0* with *bond0* attached to it, +which is a failover between *eth0* and *eth1*. This requires +the *bonding* and *bridge* packages to be installed: + +``` +auto br0 +iface br0 + use bridge + requires bond0 + address 203.0.113.2/24 + gateway 203.0.113.1 + +iface bond0 + use bond + requires eth0 eth1 + bond-mode 802.3ad + bond-xmit-hash-policy layer2+3 +``` + +Configure a network interface to use DHCP to learn its IPv4 +address: + +``` +auto eth0 +iface eth0 + use dhcp +``` + +# SEE ALSO + +*ifstate*(5) +*ifupdown-ng.conf*(5) +*ifup*(8) +*ifdown*(8) +*ifquery*(8) +*ifctrstat*(8) +*interfaces-batman*(5) +*interfaces-bond*(5) +*interfaces-bridge*(5) +*interfaces-forward*(5) +*interfaces-mpls*(5) +*interfaces-ppp*(5) +*interfaces-tunnel*(5) +*interfaces-vrf*(5) +*interfaces-vxlan*(5) +*interfaces-wifi*(5) +*interfaces-wireguard*(5) + +# AUTHORS + +Ariadne Conill ++ +Maximilian Wilhelm diff --git a/executor-scripts/linux/batman b/executor-scripts/linux/batman new file mode 100644 index 0000000..a6cd054 --- /dev/null +++ b/executor-scripts/linux/batman @@ -0,0 +1,126 @@ +#!/bin/sh +# +# Maximilian Wilhelm +# -- Wed 26 Aug 2020 08:15:58 PM CEST +# +# This executor is responsible for setting up the main B.A.T.M.A.N. adv. interface (eg. bat0) +# as well as managing settings of the underlying interfaces (hardifs). +# +# See interfaces-batman(5) for a list of supported options for hardifs as well as meshifs. +# +if [ "$VERBOSE" ]; then + set -x +fi + +if ! which batctl >/dev/null 2>&1; then + echo "Error: batctl utility not found!" >&2 + exit 1 +fi + +# +# Compatiblity glue: Newer versions of batctl print a deprecation +# warning when called with -m . Avoid spamming the log and +# producting SPAM by silently handling this here. +mesh_if_param="-m" +if batctl -h 2>&1 | grep -q "meshif"; then + mesh_if_param="meshif" +fi + +# +# Functions to manage main B.A.T.M.A.N. adv. interface +batctl_if () { + for iface in ${IF_BATMAN_IFACES}; do + ${MOCK} batctl "${mesh_if_param}" "${IFACE}" interface "$1" "${iface}" + done +} + +set_meshif_options () { + # We only care for options of format IF_BATMAN_ + env | grep '^IF_BATMAN_[A-Z0-9_]\+' | while IFS="=" read opt value; do + # Members, ignore-regex, routing-algo, and throughput_override are handled seperately + if [ "${opt}" = "IF_BATMAN_IFACES" -o \ + "${opt}" = "IF_BATMAN_IFACES_IGNORE_REGEX" -o \ + "${opt}" = "IF_BATMAN_ROUTING_ALGO" -o \ + "${opt}" = "IF_BATMAN_THROUGHPUT_OVERRIDE" ]; then + continue + fi + + # Convert options for the actual name + real_opt=$(echo "${opt}" | sed -e 's/^IF_BATMAN_\([A-Z0-9_]\+\)/\1/' | tr '[A-Z]' '[a-z]') + + ${MOCK} batctl "${mesh_if_param}" "${IFACE}" "${real_opt}" "${value}" + done + +} + +set_routing_algo () { + if [ "${IF_BATMAN_ROUTING_ALGO}" != "BATMAN_IV" -a "${IF_BATMAN_ROUTING_ALGO}" != "BATMAN_V" ]; then + echo "Invalid routing algorithm \"$1\"." + return + fi + + batctl ra "${IF_BATMAN_ROUTING_ALGO}" +} + + +# +# Functions to manage B.A.T.M.A.N. adv. underlay interfaces (hardifs) +set_hardif_options () { + # Query hardif parameter manually for now + for hardif in ${IF_BATMAN_IFACES}; do + penalty=$(ifquery -p "batman-hop-penalty" "${hardif}") + if [ "${penalty}" ]; then + ${MOCK} batctl hardif "${hardif}" hop_penalty "${penalty}" + fi + + throughput=$(ifquery -p "batman-throughput-override" "${hardif}") + if [ "${throughput}" ]; then + ${MOCK} batctl hardif "${hardif}" throughput_override "${througput}" + fi + done +} + + +case "${PHASE}" in + depend) + if [ "${IF_BATMAN_IFACES}" ]; then + echo "${IF_BATMAN_IFACES}" + fi + ;; + + create) + # Main B.A.T.M.A.N. adv. interface + if [ "${IF_BATMAN_IFACES}" ]; then + if [ "${IF_BATMAN_ROUTING_ALGO}" ]; then + set_routing_algo + fi + + if [ ! -d "/sys/class/net/${IFACE}" ]; then + batctl "${mesh_if_param}" "${IFACE}" interface create || true + fi + fi + ;; + + pre-up) + # Main B.A.T.M.A.N. adv. interface (meshif) + if [ "${IF_BATMAN_IFACES}" ]; then + batctl_if add + set_meshif_options + set_hardif_options + fi + ;; + + destroy) + if [ "${IF_BATMAN_IFACES}" -a -d "/sys/class/net/${IFACE}" ]; then + # Newer versions of batctl provide the "interface destroy" API, try to use it + if ! batctl "${mesh_if_param}" "${IFACE}" interface destroy 2>/dev/null; then + # Fall back to old style member interface removal + batctl_if del + fi + fi + ;; + + *) + exit 0 + ;; +esac diff --git a/executor-scripts/linux/bond b/executor-scripts/linux/bond new file mode 100755 index 0000000..bff2d05 --- /dev/null +++ b/executor-scripts/linux/bond @@ -0,0 +1,57 @@ +#!/bin/sh +# +# This executor is responsible for setting up bond/LAG interfaces. +# +# Sat, 03 Oct 2020 20:42:19 +0200 +# -- Maximilian Wilhelm +# +[ -n "$VERBOSE" ] && set -x + +get_bond_options() { + # We only care for options of format IF_BOND_ + env | grep '^IF_BOND_[A-Z0-9_]\+' | while IFS="=" read opt value; do + # Members are handled seperately + if [ "${opt}" = "IF_BOND_MEMBERS" ]; then + continue + fi + + # Convert options for the actual name + real_opt=$(echo "${opt}" | sed -e 's/^IF_BOND_\([A-Z0-9_]\+\)/\1/' | tr '[A-Z]' '[a-z]') + + echo -n " ${real_opt} ${value}" + done +} + +case "$PHASE" in + depend) + echo "${IF_BOND_MEMBERS}" + ;; + + create) + if [ -d "/sys/class/net/${IFACE}" ]; then + exit 0 + fi + + # Gather bonding options for this interface + options=$(get_bond_options) + + # Create bond + ${MOCK} ip link add "${IFACE}" type bond ${options} + + # Add members to bundle + for member_iface in ${IF_BOND_MEMBERS}; do + # Work around the current execution order + ${MOCK} ip link set "${member_iface}" down + ${MOCK} ip link set master "${IFACE}" "${member_iface}" + ${MOCK} ip link set "${member_iface}" up + done + ;; + + destroy) + if [ -z "${MOCK}" -a ! -d "/sys/class/net/${IFACE}" ]; then + exit 0 + fi + + ${MOCK} ip link del "${IFACE}" + ;; +esac diff --git a/executor-scripts/linux/bridge b/executor-scripts/linux/bridge new file mode 100755 index 0000000..a3feee2 --- /dev/null +++ b/executor-scripts/linux/bridge @@ -0,0 +1,302 @@ +#!/bin/sh +[ -n "$VERBOSE" ] && set -x + +# Copyright (C) 2012, 2020 Natanael Copa +# Copyright (C) 2020 Ariadne Conill +# Copyright (C) 2020 Maximilian Wilhelm +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# This software is provided 'as is' and without any warranty, express or +# implied. In no event shall the authors be liable for any damages arising +# from the use of this software. + +################################################################################ +# Bridge management functions # +################################################################################ + +all_ports_exist() { + local i= + for i in "$@"; do + [ -d /sys/class/net/$i ] || return 1 + done + return 0 +} + +wait_ports() { + local timeout= waitports= + [ -z "$IF_BRIDGE_WAITPORT" ] && return 0 + set -- $IF_BRIDGE_WAITPORT + timeout="$1" + shift + waitports="$@" + [ -z "$waitports" ] && waitports="$PORTS" + while ! all_ports_exist $waitports; do + [ "$timeout" -eq 0 ] && return 0 + timeout=$(($timeout - 1)) + sleep 1 + done +} + +all_ports() { + local i= + for i in /sys/class/net/*/ifindex; do + i=${i%/*} + i=${i##*/} + case "$i" in + lo|$IFACE) continue;; + *) echo $i;; + esac + done +} + +add_ports() { + local port= + for port in $PORTS; do + if [ -n "$IF_BRIDGE_HW" ]; then + ip link set dev $port addr $IF_BRIDGE_HW + fi + ip link set dev $port master $IFACE && ip link set dev $port up + done +} + +del_ports() { + local port= + for port in $PORTS; do + ip link set dev $port down + ip link set dev $port nomaster + done +} + +set_bridge_opts_brctl() { + [ -n "$IF_BRIDGE_AGEING" ] \ + && brctl setageing $IFACE $IF_BRIDGE_AGEING + [ -n "$IF_BRIDGE_BRIDGEPRIO" ] \ + && brctl setbridgeprio $IFACE $IF_BRIDGE_BRIDGEPRIO + [ -n "$IF_BRIDGE_FD" ] \ + && brctl setfd $IFACE $IF_BRIDGE_FD + [ -n "$IF_BRIDGE_HELLO" ] \ + && brctl sethello $IFACE $IF_BRIDGE_HELLO + [ -n "$IF_BRIDGE_MAXAGE" ] \ + && brctl setmaxage $IFACE $IF_BRIDGE_MAXAGE + [ -n "$IF_BRIDGE_PATHCOST" ] \ + && brctl setpathcost $IFACE $IF_BRIDGE_PATHCOST + [ -n "$IF_BRIDGE_PORTPRIO" ] \ + && brctl setportprio $IFACE $IF_BRIDGE_PORTPRIO + [ -n "$IF_BRIDGE_STP" ] \ + && brctl stp $IFACE $IF_BRIDGE_STP +} + +yesno() { + case "$1" in + yes|YES|true|TRUE|1) + echo 1 + ;; + *) + echo 0 + ;; + esac +} + +set_bridge_opts_iproute2() { + [ -n "$IF_BRIDGE_AGEING" ] \ + && ip link set dev $IFACE type bridge ageing_time $IF_BRIDGE_AGEING + [ -n "$IF_BRIDGE_BRIDGEPRIO" ] \ + && ip link set dev $IFACE type bridge priority $IF_BRIDGE_BRIDGEPRIO + [ -n "$IF_BRIDGE_FD" ] \ + && ip link set dev $IFACE type bridge forward_delay $IF_BRIDGE_FD + [ -n "$IF_BRIDGE_HELLO" ] \ + && ip link set dev $IFACE type bridge hello_time $IF_BRIDGE_HELLO + [ -n "$IF_BRIDGE_MAXAGE" ] \ + && ip link set dev $IFACE type bridge max_age $IF_BRIDGE_MAXAGE + [ -n "$IF_BRIDGE_PATHCOST" ] \ + && bridge link set dev $IFACE cost $IF_BRIDGE_PATHCOST + [ -n "$IF_BRIDGE_PORTPRIO" ] \ + && bridge link set dev $IFACE priority $IF_BRIDGE_PORTPRIO + [ -n "$IF_BRIDGE_STP" ] \ + && ip link set dev $IFACE type bridge stp $(yesno $IF_BRIDGE_STP) + [ -n "$IF_BRIDGE_VLAN_AWARE" ] \ + && ip link set dev $IFACE type bridge vlan_filtering $(yesno $IF_BRIDGE_VLAN_AWARE) +} + +set_bridge_opts() { + [ -x /sbin/bridge ] && set_bridge_opts_iproute2 && return 0 + [ -x /sbin/brctl ] && set_bridge_opts_brctl && return 0 +} + + +all_ports_ready() { + local port= + for port in $PORTS; do + case $(cat /sys/class/net/$IFACE/brif/$port/state) in + ""|0|3) ;; # 0 = disabled, 3 = forwarding + [12]) return 1;; + esac + done + return 0 +} + +find_maxwait() { + awk '{printf("%.f\n", 2 * $0 / 100); }' \ + /sys/class/net/$IFACE/bridge/forward_delay +} + +wait_bridge() { + local timeout=$IF_BRIDGE_MAXWAIT + if [ -z "$timeout" ]; then + timeout=$(find_maxwait) + fi + ip link set dev $IFACE up + while ! all_ports_ready; do + [ $timeout -eq 0 ] && break + timeout=$(($timeout - 1)) + sleep 1 + done +} + + +################################################################################ +# Bridge port management functions # +################################################################################ + +configure_access_port() { + port="$1" + vlan="$2" + self="$3" + + # Cleans all existing VLANs (probably at least VLAN 1) + bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | while read vid flags; do + bridge vlan del vid "${vid}" dev "${port}" ${self} + done + + bridge vlan add vid "${vlan}" pvid untagged dev "${port}" ${self} +} + +configure_trunk_port() { + port="$1" + self="$2" + + # Working on the bridge itself? + if [ "${self}" ]; then + allow_untagged="${IF_BRIDGE_ALLOW_UNTAGGED}" + pvid="${IF_BRIDGE_PVID}" + vids="${IF_BRIDGE_VIDS}" + else + allow_untagged=$(ifquery -p bridge-allow-untagged ${port} 2>/dev/null || true) + pvid=$(ifquery -p bridge-pvid ${port} 2>/dev/null || true) + vids=$(ifquery -p bridge-vids ${port} 2>/dev/null || true) + fi + + # If bridge-allow-untagged if set to off, remove untagged VLAN. If it's + # one of our bridge-vids, it will be set again later. + if [ "${allow_untagged}" -a "$(yesno ${allow_untagged})" = 0 ]; then + untagged_vid=$(bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | awk '/Untagged/ { print $1 }') + if [ "${untagged_vid}" ]; then + bridge vlan del vid "${untagged_vid}" dev "${port}" ${self} + fi + fi + + # The vlan specified is to be considered a PVID at ingress. + # Any untagged frames will be assigned to this VLAN. + if [ "${pvid}" ]; then + cur_pvid=$(bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | awk '/PVID/ { print $1 }') + if [ "${cur_pvid}" ]; then + bridge vlan del vid ${cur_pvid} dev "${port}" ${self} + fi + + bridge vlan add vid "${pvid}" dev "${port}" pvid untagged ${self} + fi + + # Add regular tagged VLANs + for vid in ${vids}; do + bridge vlan add vid $vid dev "${port}" ${self} + done +} + +# Configure VLANs on the bridge interface itself +set_bridge_vlans() { + # Shall the bridge interface be an untagged port? + if [ "${IF_BRIDGE_ACCESS}" ]; then + configure_access_port "${IFACE}" "${IF_BRIDGE_ACCESS}" "self" + + # Configure bridge interface as trunk port + else + configure_trunk_port "${IFACE}" "self" + fi +} + +# Configure VLANs on the bridge-ports +set_bridge_port_vlans() { + for port in ${PORTS}; do + access_vlan=$(ifquery -p bridge-access ${port} 2>/dev/null || true) + + # Shall this prot interface be an untagged port? + if [ "${access_vlan}" ]; then + configure_access_port "${port}" "${access_vlan}" + + # Configure port as trunk + else + configure_trunk_port "${port}" + fi + done +} + +case "$IF_BRIDGE_PORTS" in +"") ;; +none) PORTS="";; +all) PORTS=$(all_ports);; +*) PORTS="$IF_BRIDGE_PORTS";; +esac + +case "$PHASE" in +depend) + # Called for the bridge interface + if [ "${IF_BRIDGE_PORTS}" ]; then + echo "$PORTS" + fi + ;; + +create) + # Called for the bridge interface + if [ "${IF_BRIDGE_PORTS}" -a ! -d "/sys/class/net/${IFACE}" ]; then + ip link add "${IFACE}" type bridge + fi + ;; + +pre-up) + # Called for the bridge interface + if [ "${IF_BRIDGE_PORTS}" ]; then + wait_ports + set_bridge_opts + set_bridge_vlans + add_ports + set_bridge_port_vlans + wait_bridge + + # Called for a bridge member port + elif [ "${IF_BRIDGE_VIDS}" -o "${IF_BRIDGE_PVID}" -o "${IF_BRIDGE_ACCESS}" -o "${IF_BRIDGE_ALLOW_UNTAGGED}" ]; then + # Eventually we want to configure VLAN settings of member ports here. + # The current execution model does not allow this, so this is a no-op + # for now and we work around this by configuring ports while configuring + # the bridge. + true + fi + ;; + +post-down) + # Called for the bridge interface + if [ "${IF_BRIDGE_PORTS}" ]; then + del_ports + ip link set dev $IFACE down + fi + ;; + +destroy) + # Called for the bridge interface + if [ "${IF_BRIDGE_PORTS}" -a -d "/sys/class/net/${IFACE}" ]; then + ip link del "${IFACE}" + fi + ;; +esac diff --git a/executor-scripts/linux/dhcp b/executor-scripts/linux/dhcp new file mode 100755 index 0000000..3374ab7 --- /dev/null +++ b/executor-scripts/linux/dhcp @@ -0,0 +1,67 @@ +#!/bin/sh +# some users provide a shell fragment for the hostname property. +[ -n "$IF_DHCP_HOSTNAME" ] && IF_DHCP_HOSTNAME=$(eval echo $IF_DHCP_HOSTNAME) + +determine_implementation() { + [ -n "$IF_DHCP_PROGRAM" ] && echo "$IF_DHCP_PROGRAM" && return + [ -x /sbin/dhcpcd ] && echo "dhcpcd" && return + [ -x /usr/sbin/dhclient ] && echo "dhclient" && return + [ -x /sbin/udhcpc ] && echo "udhcpc" && return + echo "could not find a supported DHCP implementation" + exit 1 +} + +start() { + case "$1" in + dhcpcd) + [ -n "$IF_DHCP_HOSTNAME" ] && optargs="$optargs -h $IF_DHCP_HOSTNAME" + [ -n "$IF_DHCP_VENDOR" ] && optargs="$optargs -i $IF_DHCP_VENDOR" + [ -n "$IF_DHCP_CLIENT_ID" ] && optargs="$optargs -i $IF_DHCP_CLIENT_ID" + [ -n "$IF_DHCP_LEASETIME" ] && optargs="$optargs -l $IF_DHCP_LEASETIME" + ${MOCK} /sbin/dhcpcd $optargs $IFACE + ;; + dhclient) + # Specific config file given? + if [ -n "$IF_DHCP_CONFIG" ]; then + optargs="$optargs -cf $IF_DHCP_CONFIG" + fi + + ${MOCK} /usr/sbin/dhclient -pf /var/run/dhclient.$IFACE.pid $optargs $IFACE + ;; + udhcpc) + optargs=$(eval echo $IF_UDHCPC_OPTS) + [ -n "$IF_DHCP_HOSTNAME" ] && optargs="$optargs -x hostname:$IF_DHCP_HOSTNAME" + [ -n "$IF_DHCP_CLIENT_ID" ] && optargs="$optargs -c $IF_DHCP_CLIENT_ID" + [ -n "$IF_DHCP_SCRIPT" ] && optargs="$optargs -s $IF_DHCP_SCRIPT" + ${MOCK} /sbin/udhcpc -b -R -p /var/run/udhcpc.$IFACE.pid -i $IFACE $optargs + ;; + *) + ;; + esac +} + +stop() { + case "$1" in + dhcpcd) + ${MOCK} /sbin/dhcpcd -k $IFACE + ;; + dhclient) + ${MOCK} kill -9 $(cat /var/run/dhclient.$IFACE.pid) 2>/dev/null + ;; + udhcpc) + ${MOCK} kill $(cat /var/run/udhcpc.$IFACE.pid) + ;; + *) + ;; + esac +} + +impl=$(determine_implementation) + +[ -z "$VERBOSE" ] || set -x + +case "$PHASE" in +up) start $impl ;; +down) stop $impl ;; +*) ;; +esac diff --git a/executor-scripts/linux/ethtool b/executor-scripts/linux/ethtool new file mode 100755 index 0000000..6cc62d9 --- /dev/null +++ b/executor-scripts/linux/ethtool @@ -0,0 +1,58 @@ +#!/bin/sh +# gather params for a given prefix, based on executor-scripts/linux/tunnel. +gather_params() { + env | sed -E " + s/^IF_${1}_([A-Z0-9_]+)=(.+)/\1\n\2/ + ta + d + :a + h + y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ + P + g + s/.*\n//" | sed -E "s/_/-/g" +} + +case "$PHASE" in +pre-up) + settings="\ + ${IF_ETHTOOL_ETHERNET_PORT:+ port $IF_ETHTOOL_ETHERNET_PORT} + ${IF_ETHTOOL_MSGLVL:+ msglvl $IF_ETHTOOL_MSGLVL} + " + [ -z "$settings" ] || $MOCK ethtool --change "$IFACE" $settings + ;; +up) + # first do the link settings. + link_settings="${IF_ETHTOOL_LINK_SPEED:+ speed $IF_ETHTOOL_LINK_SPEED}${IF_ETHTOOL_LINK_DUPLEX:+ duplex $IF_ETHTOOL_LINK_DUPLEX}" + + # ethernet-wol can have a second arg (key), split into $1 and $2 + set -- $IF_ETHTOOL_ETHERNET_WOL + link_settings="$link_settings${1:+ wol $1}${2:+ sopass $2}" + + # handle ethtool-ethernet-autoneg like Debian would + case "$IF_ETHTOOL_ETHERNET_AUTONEG" in + '') + ;; + on|off) + link_settings="$link_settings autoneg $IF_ETHTOOL_ETHERNET_AUTONEG" + ;; + *) + link_settings="$link_settings autoneg on advertise $IF_ETHTOOL_ETHERNET_AUTONEG" + ;; + esac + + [ -z "$link_settings" ] || $MOCK ethtool --change "$IFACE" $link_settings + + pause_settings=$(gather_params ETHTOOL_PAUSE) + [ -z "$pause_settings" ] || $MOCK ethtool --pause "$IFACE" $pause_settings + + offload_settings=$(gather_params ETHTOOL_OFFLOAD) + [ -z "$offload_settings" ] || $MOCK ethtool --offload "$IFACE" $offload_settings + + dma_settings=$(gather_params ETHTOOL_DMA_RING) + [ -z "$dma_settings" ] || $MOCK ethtool --set-ring "$IFACE" $dma_settings + + coalesce_settings=$(gather_params ETHTOOL_COALESCE) + [ -z "$coalesce_settings" ] || $MOCK ethtool --coalesce "$IFACE" $coalesce_settings + ;; +esac diff --git a/executor-scripts/linux/forward b/executor-scripts/linux/forward new file mode 100755 index 0000000..ef8db94 --- /dev/null +++ b/executor-scripts/linux/forward @@ -0,0 +1,19 @@ +#!/bin/sh + +yesno() { + case "$1" in + yes|1) echo 1 ;; + *) echo 0 ;; + esac +} + +[ "$PHASE" != "up" ] && exit 0 +[ -z "$VERBOSE" ] || set -x + +[ -n "$IF_FORWARD_IPV4" ] && ${MOCK} /bin/sh -c "echo $(yesno $IF_FORWARD_IPV4) > /proc/sys/net/ipv4/conf/$IFACE/forwarding" +[ -n "$IF_FORWARD_IPV6" ] && ${MOCK} /bin/sh -c "echo $(yesno $IF_FORWARD_IPV6) > /proc/sys/net/ipv6/conf/$IFACE/forwarding" + +[ -n "$IF_FORWARD_IPV4_MC" ] && ${MOCK} /bin/sh -c "echo $(yesno $IF_FORWARD_IPV4_MC) > /proc/sys/net/ipv4/conf/$IFACE/mc_forwarding" +[ -n "$IF_FORWARD_IPV6_MC" ] && ${MOCK} /bin/sh -c "echo $(yesno $IF_FORWARD_IPV6_MC) > /proc/sys/net/ipv6/conf/$IFACE/mc_forwarding" + +exit 0 diff --git a/executor-scripts/linux/gre b/executor-scripts/linux/gre new file mode 100755 index 0000000..97983d0 --- /dev/null +++ b/executor-scripts/linux/gre @@ -0,0 +1,28 @@ +#!/bin/sh +# Executor for advanced GRE tunnel management. + +[ -z "$IF_GRE_LOCAL" ] && exit 1 +[ -z "$IF_GRE_REMOTE" ] && exit 1 +[ -z "$IF_GRE_MODE" ] && IF_GRE_MODE="gre" + +COMMAND="link" +FAMILY="-4" +[ "$IF_GRE_MODE" = "ip6gre" ] && FAMILY="-6" + +PARAMS="mode $IF_GRE_MODE local '$IF_GRE_LOCAL' remote '$IF_GRE_REMOTE'" +[ -n "$IF_GRE_TTL" ] && PARAMS="$PARAMS ttl '$IF_GRE_TTL'" +[ -n "$IF_GRE_FLAGS" ] && PARAMS="$PARAMS $IF_GRE_FLAGS" + +[ -n "$PARAMS" ] || exit 0 + +case "$PHASE" in +create) + ${MOCK} eval ip $FAMILY $COMMAND add $IFACE $PARAMS + ;; +destroy) + ${MOCK} ip $FAMILY $COMMAND del $IFACE + ;; +depend) + echo "$IF_GRE_DEV" + ;; +esac diff --git a/executor-scripts/linux/ipv6-ra b/executor-scripts/linux/ipv6-ra new file mode 100755 index 0000000..dd7a9fb --- /dev/null +++ b/executor-scripts/linux/ipv6-ra @@ -0,0 +1,16 @@ +#!/bin/sh +start() { + ${MOCK} /bin/sh -c "echo 1 > /proc/sys/net/ipv6/conf/$IFACE/accept_ra" +} + +stop() { + ${MOCK} /bin/sh -c "echo 0 > /proc/sys/net/ipv6/conf/$IFACE/accept_ra" +} + +[ -z "$VERBOSE" ] || set -x + +case "$PHASE" in +up) start $impl ;; +down) stop $impl ;; +*) ;; +esac diff --git a/executor-scripts/linux/link b/executor-scripts/linux/link new file mode 100755 index 0000000..f3795e2 --- /dev/null +++ b/executor-scripts/linux/link @@ -0,0 +1,112 @@ +#!/bin/sh +[ -n "$VERBOSE" ] && set -x + +is_vlan() { + case "$IFACE" in + *#*) return 1 ;; + *:*) return 1 ;; + vlan*.*) return 1 ;; + vlan*) + IF_VLAN_ID="${IFACE#vlan}" + [ -n "${IF_VLAN_RAW_DEVICE:-}" ] && return 0 + return 1 + ;; + *.*) + IF_VLAN_RAW_DEVICE="${IFACE%.*}" + IF_VLAN_ID="${IFACE##*.}" + return 0 + ;; + *) + [ -z "${IF_VLAN_ID:-}" ] && return 1 + [ -z "${IF_VLAN_RAW_DEVICE:-}" ] && return 1 + return 0 + ;; + esac +} + +case "$PHASE" in +depend) + # vlan-raw-device + if is_vlan; then + echo "$IF_VLAN_RAW_DEVICE" + + # veth-peer-name + elif [ "${IF_LINK_TYPE}" = "veth" -a "${IF_VETH_PEER_NAME}" ]; then + echo "${IF_VETH_PEER_NAME}" + fi + ;; + +create) + if [ "${IF_LINK_TYPE}" = "dummy" ]; then + if [ -d "/sys/class/net/${IFACE}" ]; then + iface_type=$(ip -d link show dev "${IFACE}" | head -n3 | tail -n1 | awk '{ print $1 }') + if [ "${iface_type}" != 'dummy' ]; then + echo "Interface ${IFACE} exists but is of type ${iface_type} instead of dummy" + exit 1 + fi + + exit 0 + fi + + ${MOCK} ip link add "${IFACE}" type dummy + + elif [ "${IF_LINK_TYPE}" = "veth" ]; then + if [ ! -d "/sys/class/net/${IFACE}" ]; then + ARGS="" + if [ "${IF_VETH_PEER_NAME}" ]; then + ARGS="peer ${IF_VETH_PEER_NAME}" + fi + + ${MOCK} ip link add "${IFACE}" type veth ${ARGS} + fi + + elif is_vlan; then + if [ -d "/sys/class/net/${IFACE}" ]; then + exit 0 + fi + + if [ -z "${MOCK}" ]; then + if [ ! -d "/sys/class/net/${IF_VLAN_RAW_DEVICE}" ]; then + echo "Underlay device ${IF_VLAN_RAW_DEVICE} for ${IFACE} does not exist" + exit 1 + fi + + if ! [ -d /proc/net/vlan ]; then + echo "Loading 8021q kernel module for VLAN support" + ${MOCK} modprobe 8021q + fi + fi + + ${MOCK} ip link add link "${IF_VLAN_RAW_DEVICE}" name "${IFACE}" type vlan id "${IF_VLAN_ID}" + fi + ;; +up) + IF_LINK_OPTIONS="$IF_LINK_OPTIONS" + [ -n "$IF_MTU" ] && IF_LINK_OPTIONS="$IF_LINK_OPTIONS mtu $IF_MTU" + [ -n "$IF_HWADDRESS" ] && IF_LINK_OPTIONS="$IF_LINK_OPTIONS address $IF_HWADDRESS" + + ${MOCK} ip link set up dev "${IFACE}" ${IF_LINK_OPTIONS} + + # Set alias is configured + if [ "${IF_ALIAS}" ]; then + ${MOCK} ip link set alias "${IF_ALIAS}" dev "${IFACE}" + fi + ;; +down) + # Don't complain about a vanished interface when downing it + if [ -z "${MOCK}" -a ! -d "/sys/class/net/${IFACE}" ]; then + exit 0 + fi + + ${MOCK} ip link set down dev "${IFACE}" + ;; +destroy) + if [ "${IF_LINK_TYPE}" = "dummy" ] || [ "${IF_LINK_TYPE}" = "veth" ] || is_vlan; then + if [ -z "${MOCK}" -a ! -d "/sys/class/net/${IFACE}" ]; then + exit 0 + fi + + ${MOCK} ip link del "${IFACE}" + fi + ;; +esac diff --git a/executor-scripts/linux/mpls b/executor-scripts/linux/mpls new file mode 100755 index 0000000..97d42c9 --- /dev/null +++ b/executor-scripts/linux/mpls @@ -0,0 +1,36 @@ +#!/bin/sh +# +# Maximilian Wilhelm +# -- Thu, 17 Dec 2020 03:02:10 +0100 +# +# This executor is responsible for setting up MPLS decapsulation on a given interface. +# +# See interfaces-mpls(5) for a list of supported options. +# + +yesno() { + case "$1" in + yes|1) echo 1 ;; + *) echo 0 ;; + esac +} + +[ -z "$VERBOSE" ] || set -x + +# We only operate in pre-up phase +[ "$PHASE" != "pre-up" ] && exit 0 + + +if [ "$IF_MPLS_ENABLE" ]; then + value=$(yesno $IF_MPLS_ENABLE) + + # Load mpls module if we should enable MPLS decap on (at least) one interface + if [ "${value}" = 1 ]; then + ${MOCK} modprobe mpls_iptunnel + fi + + # If MPLS support isn't loaded and we are not MOCKing, carry on + if [ -f "/proc/sys/net/mpls/conf/$IFACE/input" -o "${MOCK}" ]; then + ${MOCK} /bin/sh -c "echo ${value} > /proc/sys/net/mpls/conf/$IFACE/input" + fi +fi diff --git a/executor-scripts/linux/ppp b/executor-scripts/linux/ppp new file mode 100755 index 0000000..8d5d6d2 --- /dev/null +++ b/executor-scripts/linux/ppp @@ -0,0 +1,14 @@ +#!/bin/sh +[ -z "$IF_PPP_PROVIDER" ] && exit 0 + +case "$PHASE" in +create) + ${MOCK} pon $IF_PPP_PROVIDER + ;; +destroy) + ${MOCK} poff $IF_PPP_PROVIDER + ;; +depend) + echo "$IF_PPP_PHYSDEV" + ;; +esac diff --git a/executor-scripts/linux/static b/executor-scripts/linux/static new file mode 100755 index 0000000..36af638 --- /dev/null +++ b/executor-scripts/linux/static @@ -0,0 +1,56 @@ +#!/bin/sh +[ -z "${VERBOSE}" ] || set -x + +[ -z "${IF_METRIC}" ] && IF_METRIC="1" +[ -n "${IF_VRF_TABLE}" ] && VRF_TABLE="table ${IF_VRF_TABLE}" +[ -n "${IF_VRF_MEMBER}" ] && VRF_TABLE="vrf ${IF_VRF_MEMBER}" +[ -n "${IF_METRIC}" ] && METRIC="metric ${IF_METRIC}" + + +addr_family() { + if [ "$1" != "${1#*[0-9].[0-9]}" ]; then + echo "-4" + elif [ "$1" != "${1#*:[0-9a-fA-F]}" ]; then + echo "-6" + else + exit 1 + fi +} + +configure_addresses() { + for addr in ${IF_ADDRESSES}; do + addrfam=$(addr_family ${addr}) + if [ "${IF_POINT_TO_POINT}" -a "${addrfam}" = "-4" ]; then + PEER="peer ${IF_POINT_TO_POINT}" + else + PEER="" + fi + + ${MOCK} ip "${addrfam}" addr add "${addr}" ${PEER} dev "${IFACE}" + done +} + +configure_gateways() { + for gw in ${IF_GATEWAYS}; do + addrfam=$(addr_family ${gw}) + ${MOCK} ip "${addrfam}" route add default via "${gw}" ${VRF_TABLE} ${METRIC} dev "${IFACE}" + done +} + +flush() { + cmd="addr" + arg="dev ${IFACE}" + + ${MOCK} ip ${cmd} flush ${arg} +} + +case "$PHASE" in +up) + configure_addresses add + configure_gateways add + ;; +down) + flush + ;; +*) exit 0 ;; +esac diff --git a/executor-scripts/linux/tunnel b/executor-scripts/linux/tunnel new file mode 100755 index 0000000..b69ac8c --- /dev/null +++ b/executor-scripts/linux/tunnel @@ -0,0 +1,128 @@ +#!/bin/sh +# Based on alpine's tunnel configuration script. +# Copyright (c) 2017 Kaarle Ritvanen +# Copyright (c) 2020 Ariadne Conill (extended for ifupdown-ng) +# Copyright (c) 2021 Maximilian Wilhelm (make sure mode/type is 1st parameter, add more options) + +[ -z "$IF_TUNNEL_LOCAL" -a -z "$IF_TUNNEL_LOCAL_DEV" ] && exit 1 +[ -z "$IF_TUNNEL_REMOTE" ] && exit 1 +[ -z "$IF_TUNNEL_MODE" ] && exit 1 + +[ -n "$VERBOSE" ] && set -x + +yesno() { + case "$1" in + yes|1) echo 1 ;; + *) echo 0 ;; + esac +} + +# Figure out address familiy +FAMILY="4" + +case "$IF_TUNNEL_MODE" in +vti6|ipip6|ip6*) + FAMILY="6" + ;; +esac + +# Figure out object type - gretap tunnels have to create using ip link +# and therefor require 'type' keyword instead of 'mode' +OBJECT="tunnel" +TYPE_KW="mode" +case "${IF_TUNNEL_MODE}" in +*gretap) + OBJECT="link" + TYPE_KW="type" + + # Strip possible "ip6" from tunnel mode + TUNNEL_MODE="gretap" + ;; + +*) + # Store tunnel type/mode separaltely and unset input variable to exclude it + # from PARAMS below + TUNNEL_MODE="$IF_TUNNEL_MODE" + unset IF_TUNNEL_MODE + ;; +esac + + +# If 'tunnel-local ' was not given but 'tunnel-local-dev ' is given try +# to figure out the IP of the underlay device (wrt the address family used for this +# tunnel) and use this to set up the tunnel +if [ ${PHASE} = "create" -a ! "${IF_TUNNEL_LOCAL}" -a "${IF_TUNNEL_LOCAL_DEV}" ]; then + if [ "${FAMILY}" = "4" ]; then + ip=$(ip -4 -brief addr show dev "${IF_TUNNEL_LOCAL_DEV}" 2>/dev/null | awk '{ print $3 }' | cut -d/ -f1) + + # For IPv6 we try to use a non-temporary address (-> privacy extensions) + else + # Get all IPv6 addres configured on $IF_TUNNEL_LOCAL_DEV which are not + # temporary (due to privacy extensions). We do not filter for "mgmtaddr" + # "scope global" etc. as we don't want to make further assumptions on + # whether a user wants to use a link local address configured to this interface. + # + # The assumption that a temporary address configured by PE isn't suited + # to terminate a tunnel should hold in nearly all setups, I hope. + ip=$(ip -6 addr show dev "${IF_TUNNEL_LOCAL_DEV}" -temporary | grep inet6 | head -n1 | awk '{ print $2 }' | cut -d/ -f1) + fi + + if [ ! "${ip}" ]; then + echo "Unable to determine the IPv${FAMILIY} address of tunnel-local-dev ${IF_TUNNEL_LOCAL_DEV}!" + exit 1 + fi + + unset IF_TUNNEL_LOCAL_DEV + export IF_TUNNEL_LOCAL="${ip}" +fi + + +# Handle boolean switches +MORE_PARAMS="" +if [ "${IF_TUNNEL_IGNORE_DF}" ]; then + if $(yesno "${IF_TUNNEL_IGNORE_DF}"); then + MORE_PARAMS="ignore-df" + else + MORE_PARAMS="noignore-df" + fi + + unset IF_TUNNEL_IGNORE_DF +fi + +if [ "${IF_TUNNEL_PMTUDISC}" ]; then + if $(yesno "${IF_TUNNEL_PMTUDISC}"); then + MORE_PARAMS="pmtudisc" + else + MORE_PARAMS="nopmtudisc" + fi + + unset IF_TUNNEL_PMTUDISC +fi + + +# Mangle residual IF_TUNNEL_* params into single string +PARAMS=$(set | sed -E ' + s/^IF_TUNNEL_([A-Z0-9_]+)=(.+)/\1\n\2/ + ta + d + :a + h + y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ + P + g + s/.*\n// + ') + +[ "$PARAMS" ] || exit 0 + +case "$PHASE" in +create) + ${MOCK} eval ip -$FAMILY $OBJECT add $IFACE $TYPE_KW $TUNNEL_MODE $PARAMS $MORE_PARAMS + ;; +destroy) + ${MOCK} ip -$FAMILY $OBJECT del $IFACE + ;; +depend) + echo "${IF_TUNNEL_DEV}" "${IF_TUNNEL_LOCAL_DEV}" + ;; +esac diff --git a/executor-scripts/linux/vrf b/executor-scripts/linux/vrf new file mode 100755 index 0000000..7ca12ab --- /dev/null +++ b/executor-scripts/linux/vrf @@ -0,0 +1,30 @@ +#!/bin/sh +handle_init() { + ${MOCK} /sbin/ip link $1 $IFACE type vrf table $IF_VRF_TABLE + ${MOCK} /sbin/ip rule $1 iif $IFACE table $IF_VRF_TABLE + ${MOCK} /sbin/ip rule $1 oif $IFACE table $IF_VRF_TABLE +} + +handle_member() { + ${MOCK} /sbin/ip link set $IFACE master $IF_VRF_MEMBER +} + +[ -n "$VERBOSE" ] && set -x + +case "$PHASE" in +create) + [ -n "$IF_VRF_TABLE" ] && handle_init "add" + ;; +pre-up) + [ -n "$IF_VRF_MEMBER" ] && handle_member + ;; +destroy) + [ -n "$IF_VRF_TABLE" ] && handle_init "del" + ;; +depend) + echo "$IF_VRF_MEMBER" + ;; +*) + exit 0 + ;; +esac diff --git a/executor-scripts/linux/vxlan b/executor-scripts/linux/vxlan new file mode 100755 index 0000000..2910742 --- /dev/null +++ b/executor-scripts/linux/vxlan @@ -0,0 +1,96 @@ +#!/bin/sh +# +# This executor is responsible for setting up the Virtual Extensible LAN (VXLAN) overlay interfaces. +# +# Fri, 02 Oct 2020 01:10:29 +0200 +# -- Maximilian Wilhelm +# +# Known options for the main interface are: +# +# IF_VXLAN_ID The VXLAN Network Identifier (VNI) +# IF_VXLAN_PHYSDEV Specifies the physical device to use for tunnel endpoint communication +# IF_VXLAN_LOCAL_IP Specifies the source IP address to use in outgoing packets +# IF_VXLAN_PEER_IPS Space separated list of IPs of the remote VTEP endpoint (for ptp/ptmp mode with ingress replication) +# IF_VXLAN_PEER_GROUP Multicast group to use for this VNI (for ptmp mode with multicast) +# IF_VXLAN_LEARNING Wether to activate MAC learning on this instance (on/off) +# IF_VXLAN_AGEING Specifies the lifetime in seconds of FDB entries learnt by the kernel +# IF_VXLAN_DSTPORT UDP destination port to communicate to the remote VXLAN tunnel endpoint (default 4789) +# +[ -n "$VERBOSE" ] && set -x + +# No VNI, nuthin' to do for us +if [ ! "${IF_VXLAN_ID}" ]; then + exit 0 +fi + +case "$PHASE" in + depend) + if [ "${IF_VXLAN_PHYSDEV}" ]; then + echo "${IF_VXLAN_PHYSDEV}" + fi + ;; + + create) + if [ -d "/sys/class/net/${IFACE}" ]; then + exit 0 + fi + + # Input validation + if [ "${IF_VXLAN_PEER_IPS}" -a "${IF_VXLAN_PEER_GROUP}" ]; then + echo "Error on ${IFACE} (vxlan): Only one of 'vxlan-peer-ips' and 'vxlan-peer-group' can be used!" >&2 + exit 1 + fi + + # Check if we should operate in unicast ptp or ptmp mode + if [ "${IF_VXLAN_PEER_IPS}" ]; then + # If it's only one thing which looks like an IPv4/IPv6 address we assume it's ptp + if echo "${IF_VXLAN_PEER_IPS}" | grep -q '^[[:space:]]*[[:xdigit:].:]\+[[:space:]]*$'; then + UCAST_MODE="ptp" + else + UCAST_MODE="ptmp" + fi + fi + + # Gather arguments + ARGS="" + [ "${IF_VXLAN_PHYSDEV}" ] && ARGS="${ARGS} dev ${IF_VXLAN_PHYSDEV}" + [ "${IF_VXLAN_LOCAL_IP}" ] && ARGS="${ARGS} local ${IF_VXLAN_LOCAL_IP}" + [ "${UCAST_MODE}" = "ptp" ] && ARGS="${ARGS} remote ${IF_VXLAN_PEER_IPS}" + [ "${IF_VXLAN_PEER_GROUP}" ] && ARGS="${ARGS} group ${IF_VXLAN_PEER_GROUP}" + [ "${IF_VXLAN_AGEING}" ] && ARGS="${ARGS} ageing ${IF_VXLAN_AGEING}" + + # Linux uses non-standard default port - WTF? + if [ "${IF_VXLAN_DSTPORT}" ]; then + ARGS="${ARGS} dstport ${IF_VXLAN_DSTPORT}" + else + ARGS="${ARGS} dstport 4789" + fi + + case "${IF_VXLAN_LEARNING}" in + on|yes) + ARGS="${ARGS} learning" + ;; + + off|no) + ARGS="${ARGS} nolearning" + ;; + esac + + ${MOCK} ip link add "${IFACE}" type vxlan id "${IF_VXLAN_ID}" ${ARGS} + + # Set up FDB entries for peer VTEPs + if [ "${UCAST_MODE}" = "ptmp" ]; then + for peer in ${IF_VXLAN_PEER_IPS}; do + ${MOCK} bridge fdb append 00:00:00:00:00:00 dev "${IFACE}" dst "${peer}" self permanent + done + fi + ;; + + destroy) + if [ -z "${MOCK}" -a ! -d "/sys/class/net/${IFACE}" ]; then + exit 0 + fi + + ${MOCK} ip link del "${IFACE}" + ;; +esac diff --git a/executor-scripts/linux/wifi b/executor-scripts/linux/wifi new file mode 100755 index 0000000..6524aa8 --- /dev/null +++ b/executor-scripts/linux/wifi @@ -0,0 +1,119 @@ +#!/bin/sh +# Copyright (c) 2020 Ariadne Conill +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# This software is provided 'as is' and without any warranty, express or +# implied. In no event shall the authors be liable for any damages arising +# from the use of this software. +# +# Manage wifi connections using wpa_supplicant. +# +# Vocabulary: +# wifi-ssid - The SSID name to connect to. +# wifi-psk - The pre-shared key to use. +# wifi-config - A path to a wpa_supplicant config file, for special configs. +# +# If wifi-config is not set, wifi-ssid and wifi-psk are required, and a config +# will be generated as /run/wpa_supplicant.$IFACE.conf. +# +# The wpa_supplicant PID is stored in /run/wpa_supplicant.$IFACE.pid. + +die() { + printf "ERROR: %s\n" "$1" >&2 + exit 1 +} + +[ -z "$IFACE" ] && die "IFACE not set" +[ -z "$PHASE" ] && die "PHASE not set" +PIDFILE="/run/wpa_supplicant.$IFACE.pid" + +# Do not allow mixing wifi-config-path and wifi-ssid/wifi-psk. +[ -n "$IF_WIFI_CONFIG_PATH" -a -n "$IF_WIFI_SSID" ] && die "wifi-config-path cannot be used with wifi-ssid" +[ -n "$IF_WIFI_CONFIG_PATH" -a -n "$IF_WIFI_PSK" ] && die "wifi-config-path cannot be used with wifi-psk" + +# Set IF_WIFI_CONFIG_PATH to the default path if not already set. +WIFI_CONFIG_PATH="$IF_WIFI_CONFIG_PATH" +[ -z "$WIFI_CONFIG_PATH" ] && WIFI_CONFIG_PATH="/run/wpa_supplicant.$IFACE.conf" + +# Supplicant options. +WPA_SUPPLICANT_OPTS="-qq -B -i$IFACE -c$WIFI_CONFIG_PATH -P$PIDFILE" + +# Given $IF_WIFI_SSID and $IF_WIFI_PSK, generate a config file at $WIFI_CONFIG_PATH. +generate_config() { + [ -z "$IF_WIFI_SSID" ] && die "wifi-ssid not set" + [ -z "$IF_WIFI_PSK" ] && die "wifi-psk not set" + + # We use a pipeline here to avoid leaking PSK into the process name. + (echo $IF_WIFI_PSK | /sbin/wpa_passphrase $IF_WIFI_SSID) >$WIFI_CONFIG_PATH + + [ ! -e "$WIFI_CONFIG_PATH" ] && die "failed to write temporary config: $WIFI_CONFIG_PATH" +} + +# Should we use the supplicant? +use_supplicant() { + [ -n "$IF_WIFI_CONFIG_PATH" ] && return 0 + [ -n "$IF_WIFI_PSK" ] && return 0 + + return 1 +} + +# Either start a supplicant process for $IFACE, or use iwconfig to trigger an +# association attempt. +start() { + if use_supplicant; then + # If there is no config file located at $WIFI_CONFIG_PATH, generate one. + [ ! -e "$WIFI_CONFIG_PATH" ] && generate_config + + /sbin/wpa_supplicant $WPA_SUPPLICANT_OPTS + else + /usr/sbin/iwconfig $IFACE essid -- "$IF_WIFI_SSID" ap any + fi +} + +# Stop wpa_supplicant safely +stop_wpa_supplicant() { + # Remove generated config file + [ -z "$IF_WIFI_CONFIG_PATH" ] && rm -- "$WIFI_CONFIG_PATH" + + # If there is no PIDFILE, there is nothing we can do + [ ! -f "$PIDFILE" ] && return + + pid=$(cat "$PIDFILE") + rm -- "$PIDFILE" + + # If there is no process with this PID running, we're done here + if [ ! -d "/proc/$pid/" ]; then + return + fi + + # Verify that the name of the running process matches wpa_supplicant + progname_path=$(readlink -n "/proc/$pid/exe") + progname=$(basename "$progname_path") + if [ "$progname" = "wpa_supplicant" ]; then + kill -9 $pid 2>/dev/null + fi +} + +# Either stop the supplicant process for $IFACE, or use iwconfig to dissociate +# from the current SSID. +stop() { + if use_supplicant; then + stop_wpa_supplicant + else + /usr/sbin/iwconfig $IFACE essid any + fi +} + +[ -z "$VERBOSE" ] || set -x + +case "$PHASE" in +pre-up) + start + ;; +post-down) + stop + ;; +esac diff --git a/executor-scripts/linux/wireguard b/executor-scripts/linux/wireguard new file mode 100755 index 0000000..5480862 --- /dev/null +++ b/executor-scripts/linux/wireguard @@ -0,0 +1,15 @@ +#!/bin/sh +[ -n "$VERBOSE" ] && set -x +[ -z "$IF_WIREGUARD_CONFIG_PATH" ] && IF_WIREGUARD_CONFIG_PATH="/etc/wireguard/$IFACE.conf" + +case "$PHASE" in +create) + ${MOCK} ip link add $IFACE type wireguard + ;; +pre-up) + ${MOCK} wg setconf $IFACE $IF_WIREGUARD_CONFIG_PATH + ;; +destroy) + ${MOCK} ip link delete dev $IFACE + ;; +esac diff --git a/executor-scripts/stub/bond b/executor-scripts/stub/bond new file mode 100755 index 0000000..0548543 --- /dev/null +++ b/executor-scripts/stub/bond @@ -0,0 +1,5 @@ +#!/bin/sh +[ -z "$IF_BOND_MEMBERS" ] && IF_BOND_MEMBERS="$IF_BOND_SLAVES" +case "$PHASE" in +depend) echo "$IF_BOND_MEMBERS" ;; +esac diff --git a/executor-scripts/stub/bridge b/executor-scripts/stub/bridge new file mode 100755 index 0000000..5eee08c --- /dev/null +++ b/executor-scripts/stub/bridge @@ -0,0 +1,8 @@ +#!/bin/sh +case "$PHASE" in +depend) + if [ "$IF_BRIDGE_PORTS" != "none" ]; then + echo "$IF_BRIDGE_PORTS" + fi + ;; +esac diff --git a/libifupdown/compat.c b/libifupdown/compat.c new file mode 100644 index 0000000..1c8f233 --- /dev/null +++ b/libifupdown/compat.c @@ -0,0 +1,114 @@ +/* + * libifupdown/compat.c + * Purpose: compatiblity glue to other implementations + * + * Copyright (c) 2020 Maximilian Wilhelm + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include "libifupdown/compat.h" +#include "libifupdown/config-file.h" +#include "libifupdown/dict.h" +#include "libifupdown/interface.h" +#include "libifupdown/tokenize.h" + +static bool +compat_ifupdown2_bridge_ports_inherit_vlans(struct lif_dict *collection) +{ + struct lif_node *iter; + + /* Loop through all interfaces and search for bridges */ + LIF_DICT_FOREACH(iter, collection) + { + struct lif_dict_entry *entry = iter->data; + struct lif_interface *bridge = entry->data; + + /* We only care for bridges */ + if (!bridge->is_bridge) + continue; + + struct lif_dict_entry *bridge_pvid = lif_dict_find(&bridge->vars, "bridge-pvid"); + struct lif_dict_entry *bridge_vids = lif_dict_find(&bridge->vars, "bridge-vids"); + + /* If there's nothing to inherit here, carry on */ + if (bridge_pvid == NULL && bridge_vids == NULL) + continue; + + struct lif_dict_entry *bridge_ports_entry = lif_dict_find(&bridge->vars, "bridge-ports"); + + /* This SHOULD not happen, but better save than sorry */ + if (bridge_ports_entry == NULL) + continue; + + char bridge_ports_str[4096] = {}; + strlcpy(bridge_ports_str, bridge_ports_entry->data, sizeof bridge_ports_str); + + /* If there are no bridge-ports configured, carry on */ + if (strcmp(bridge_ports_str, "none") == 0) + continue; + + /* Loop over all bridge-ports and set bridge-pvid and bridge-vid if not set already */ + char *bufp = bridge_ports_str; + for (char *tokenp = lif_next_token(&bufp); *tokenp; tokenp = lif_next_token(&bufp)) + { + entry = lif_dict_find(collection, tokenp); + + /* There might be interfaces give within the bridge-ports for which there is no + * interface stanza. If this is the case, we add one, so we can inherit the + * bridge-vids/pvid to it. */ + struct lif_interface *bridge_port; + if (entry) + bridge_port = entry->data; + + else if (lif_config.compat_create_interfaces) + { + bridge_port = lif_interface_collection_find(collection, tokenp); + if (bridge_port == NULL) + { + fprintf(stderr, "Failed to add interface \"%s\"", tokenp); + return false; + } + } + + /* We would have to creaet an interface, but shouldn't */ + else + { + fprintf(stderr, "compat: Missing interface stanza for bridge-port \"%s\" but should not create one.\n", + tokenp); + continue; + } + + /* Maybe pimp bridge-pvid */ + struct lif_dict_entry *port_pvid = lif_dict_find(&bridge_port->vars, "bridge-pvid"); + if (bridge_pvid && !port_pvid) + lif_dict_add(&bridge_port->vars, "bridge-pvid", bridge_pvid->data); + + /* Maybe pimp bridge-vids */ + struct lif_dict_entry *port_vids = lif_dict_find(&bridge_port->vars, "bridge-vids"); + if (bridge_vids && !port_vids) + lif_dict_add(&bridge_port->vars, "bridge-vids", bridge_vids->data); + } + } + + return true; +} + +bool +lif_compat_apply(struct lif_dict *collection) +{ + if (lif_config.compat_ifupdown2_bridge_ports_inherit_vlans && + !compat_ifupdown2_bridge_ports_inherit_vlans(collection)) + return false; + + return true; +} diff --git a/libifupdown/compat.h b/libifupdown/compat.h new file mode 100644 index 0000000..2bdfef2 --- /dev/null +++ b/libifupdown/compat.h @@ -0,0 +1,24 @@ +/* + * libifupdown/compat.h + * Purpose: compatiblity glue to other implementations + * + * Copyright (c) 2020 Maximilian Wilhelm + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBIFUPDOWN__COMPAT_H +#define LIBIFUPDOWN__COMPAT_H + +#include "libifupdown/config-file.h" +#include "libifupdown/dict.h" + +extern bool lif_compat_apply (struct lif_dict *collection); + +#endif diff --git a/libifupdown/config-file.c b/libifupdown/config-file.c new file mode 100644 index 0000000..2f98f72 --- /dev/null +++ b/libifupdown/config-file.c @@ -0,0 +1,75 @@ +/* + * libifupdown/config-file.c + * Purpose: config file loading + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include "libifupdown/libifupdown.h" + +struct lif_config_file lif_config = { + .allow_addon_scripts = true, + .allow_any_iface_as_template = true, + .auto_executor_selection = true, + .compat_create_interfaces = true, + .compat_ifupdown2_bridge_ports_inherit_vlans = true, + .implicit_template_conversion = true, + .use_hostname_for_dhcp = true, +}; + +static bool +set_bool_value(const char *key, const char *value, void *opaque) +{ + (void) key; + + if (*value == '1' || + *value == 'Y' || *value == 'y' || + *value == 'T' || *value == 't') + *(bool *) opaque = true; + else if (*value == '0' || + *value == 'N' || *value == 'n' || + *value == 'F' || *value == 'f') + *(bool *) opaque = false; + else + return false; + + return true; +} + +static struct lif_config_handler handlers[] = { + {"allow_addon_scripts", set_bool_value, &lif_config.allow_addon_scripts}, + {"allow_any_iface_as_template", set_bool_value, &lif_config.allow_any_iface_as_template}, + {"auto_executor_selection", set_bool_value, &lif_config.auto_executor_selection}, + {"compat_create_interfaces", set_bool_value, &lif_config.compat_create_interfaces}, + {"compat_ifupdown2_bridge_ports_inherit_vlans", set_bool_value, &lif_config.compat_ifupdown2_bridge_ports_inherit_vlans}, + {"implicit_template_conversion", set_bool_value, &lif_config.implicit_template_conversion}, + {"use_hostname_for_dhcp", set_bool_value, &lif_config.use_hostname_for_dhcp}, +}; + +bool +lif_config_load(const char *filename) +{ + FILE *fd = fopen(filename, "r"); + + if (fd == NULL) + { +#if 0 + fprintf(stderr, "ifupdown-ng: cannot open config %s: %s\n", + filename, strerror(errno)); +#endif + return false; + } + + return lif_config_parse_file(fd, filename, handlers, ARRAY_SIZE(handlers)); +} diff --git a/libifupdown/config-file.h b/libifupdown/config-file.h new file mode 100644 index 0000000..25bd269 --- /dev/null +++ b/libifupdown/config-file.h @@ -0,0 +1,35 @@ +/* + * libifupdown/config-file.h + * Purpose: config file loading + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBIFUPDOWN__CONFIG_FILE_H +#define LIBIFUPDOWN__CONFIG_FILE_H + +#include + +struct lif_config_file { + bool allow_addon_scripts; + bool allow_any_iface_as_template; + bool auto_executor_selection; + bool compat_create_interfaces; + bool compat_ifupdown2_bridge_ports_inherit_vlans; + bool implicit_template_conversion; + bool use_hostname_for_dhcp; +}; + +extern struct lif_config_file lif_config; + +extern bool lif_config_load(const char *filename); + +#endif diff --git a/libifupdown/config-parser.c b/libifupdown/config-parser.c new file mode 100644 index 0000000..45005c6 --- /dev/null +++ b/libifupdown/config-parser.c @@ -0,0 +1,87 @@ +/* + * libifupdown/config-parser.c + * Purpose: config parsing + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "libifupdown/config-parser.h" +#include "libifupdown/fgetline.h" +#include "libifupdown/tokenize.h" + +static int +handler_cmp(const void *a, const void *b) +{ + const char *key = a; + const struct lif_config_handler *hdl = b; + + return strcmp(key, hdl->key); +} + +bool +lif_config_parse_file(FILE *fd, const char *filename, struct lif_config_handler *handlers, size_t handler_count) +{ + char linebuf[4096]; + size_t lineno = 0; + + while (lif_fgetline(linebuf, sizeof linebuf, fd)) + { + char *bufp = linebuf; + char *key = lif_next_token_eq(&bufp); + char *value = lif_next_token_eq(&bufp); + + lineno++; + + if (!*key || !*value) + continue; + + if (*key == '#') + continue; + + struct lif_config_handler *hdl = bsearch(key, handlers, handler_count, sizeof(*handlers), + handler_cmp); + + if (hdl == NULL) + { + fprintf(stderr, "ifupdown-ng: %s:%zu: warning: unknown config setting %s\n", + filename, lineno, key); + continue; + } + + if (!hdl->handle(key, value, hdl->opaque)) + { + fclose(fd); + return false; + } + } + + fclose(fd); + return true; +} + +bool +lif_config_parse(const char *filename, struct lif_config_handler *handlers, size_t handler_count) +{ + FILE *f = fopen(filename, "r"); + + if (f == NULL) + { + fprintf(stderr, "ifupdown-ng: unable to parse %s: %s\n", filename, strerror(errno)); + return false; + } + + return lif_config_parse_file(f, filename, handlers, handler_count); +} diff --git a/libifupdown/config-parser.h b/libifupdown/config-parser.h new file mode 100644 index 0000000..86c1762 --- /dev/null +++ b/libifupdown/config-parser.h @@ -0,0 +1,32 @@ +/* + * libifupdown/config-parser.h + * Purpose: config parsing + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBIFUPDOWN__CONFIG_PARSER_H +#define LIBIFUPDOWN__CONFIG_PARSER_H + +#include +#include +#include + +struct lif_config_handler { + const char *key; + bool (*handle)(const char *key, const char *value, void *opaque); + void *opaque; +}; + +extern bool lif_config_parse_file(FILE *f, const char *filename, struct lif_config_handler *handlers, size_t handler_count); +extern bool lif_config_parse(const char *filename, struct lif_config_handler *handlers, size_t handler_count); + +#endif diff --git a/libifupdown/dict.c b/libifupdown/dict.c index 4ac7085..971a4d3 100644 --- a/libifupdown/dict.c +++ b/libifupdown/dict.c @@ -3,6 +3,7 @@ * Purpose: wrapping linked lists to provide a naive dictionary * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,6 +14,7 @@ * from the use of this software. */ +#include #include #include #include "libifupdown/dict.h" @@ -50,7 +52,41 @@ lif_dict_add(struct lif_dict *dict, const char *key, void *data) } struct lif_dict_entry * -lif_dict_find(struct lif_dict *dict, const char *key) +lif_dict_add_once(struct lif_dict *dict, const char *key, void *data, + lif_dict_cmp_t compar) +{ + struct lif_list *existing = lif_dict_find_all(dict, key); + if (existing != NULL) + { + bool found = false; + struct lif_node *iter; + LIF_LIST_FOREACH(iter, existing->head) + { + if (!compar(data, iter->data)) + { + found = true; + break; + } + } + + lif_list_free_nodes(existing); + + if (found) + return NULL; + } + + struct lif_dict_entry *entry = calloc(1, sizeof *entry); + + entry->key = strdup(key); + entry->data = data; + + lif_node_insert_tail(&entry->node, entry, &dict->list); + + return entry; +} + +struct lif_dict_entry * +lif_dict_find(const struct lif_dict *dict, const char *key) { struct lif_node *iter; @@ -65,6 +101,31 @@ lif_dict_find(struct lif_dict *dict, const char *key) return NULL; } +struct lif_list * +lif_dict_find_all(const struct lif_dict *dict, const char *key) +{ + struct lif_list *entries = calloc(1, sizeof *entries); + struct lif_node *iter; + + LIF_LIST_FOREACH(iter, dict->list.head) + { + struct lif_dict_entry *entry = iter->data; + if (!strcmp(entry->key, key)) + { + struct lif_node *new = calloc(1, sizeof *new); + lif_node_insert_tail(new, entry->data, entries); + } + } + + if (entries->length == 0) + { + free(entries); + return NULL; + } + + return entries; +} + void lif_dict_delete(struct lif_dict *dict, const char *key) { diff --git a/libifupdown/dict.h b/libifupdown/dict.h index 298b349..8475370 100644 --- a/libifupdown/dict.h +++ b/libifupdown/dict.h @@ -3,6 +3,7 @@ * Purpose: wrapping linked lists to provide a naive dictionary * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -34,10 +35,17 @@ struct lif_dict_entry { #define LIF_DICT_FOREACH_SAFE(iter, iter_next, dict) \ LIF_LIST_FOREACH_SAFE((iter), (iter_next), (dict)->list.head) +#define LIF_DICT_FOREACH_REVERSE(iter, dict) \ + LIF_LIST_FOREACH_REVERSE((iter), (dict)->list.tail) + +typedef int (*lif_dict_cmp_t)(const void *, const void *); + extern void lif_dict_init(struct lif_dict *dict); extern void lif_dict_fini(struct lif_dict *dict); extern struct lif_dict_entry *lif_dict_add(struct lif_dict *dict, const char *key, void *data); -extern struct lif_dict_entry *lif_dict_find(struct lif_dict *dict, const char *key); +extern struct lif_dict_entry *lif_dict_add_once(struct lif_dict *dict, const char *key, void *data, lif_dict_cmp_t compar); +extern struct lif_dict_entry *lif_dict_find(const struct lif_dict *dict, const char *key); +extern struct lif_list *lif_dict_find_all(const struct lif_dict *dict, const char *key); extern void lif_dict_delete(struct lif_dict *dict, const char *key); extern void lif_dict_delete_entry(struct lif_dict *dict, struct lif_dict_entry *entry); diff --git a/libifupdown/execute.c b/libifupdown/execute.c index 04d29e6..538e6e6 100644 --- a/libifupdown/execute.c +++ b/libifupdown/execute.c @@ -20,14 +20,96 @@ #include #include #include +#include #include #include #include +#include #include "libifupdown/execute.h" #define SHELL "/bin/sh" +#if defined(__linux__) +# include +#endif + +/* POSIX compatible fallback using waitpid(2) and usleep(3) */ +static inline bool +lif_process_monitor_busyloop(pid_t child, int timeout_sec, int *status) +{ + int ticks = 0; + + while (ticks < timeout_sec * 20) + { + /* Ugly hack: most executors finish very quickly, + * so give them a chance to finish before sleeping. + */ + usleep(50); + + if (waitpid(child, status, WNOHANG) == child) + return true; + + usleep(49950); + ticks++; + } + + return false; +} + +#if defined(__linux__) && defined(__NR_pidfd_open) + +/* TODO: remove this wrapper once musl and glibc gain pidfd_open() directly. */ +static inline int +lif_pidfd_open(pid_t pid, unsigned int flags) +{ + return syscall(__NR_pidfd_open, pid, flags); +} + +static inline bool +lif_process_monitor_procdesc(pid_t child, int timeout_sec, int *status) +{ + int pidfd = lif_pidfd_open(child, 0); + + /* pidfd_open() not available, fall back to busyloop */ + if (pidfd == -1 && errno == ENOSYS) + return lif_process_monitor_busyloop(child, timeout_sec, status); + + struct pollfd pfd = { + .fd = pidfd, + .events = POLLIN, + }; + + if (poll(&pfd, 1, timeout_sec * 1000) < 1) + return false; + + waitpid(child, status, 0); + close(pidfd); + return true; +} + +#endif + +static inline bool +lif_process_monitor(const char *cmdbuf, pid_t child, int timeout_sec) +{ + int status; + +#if defined(__linux__) && defined(__NR_pidfd_open) + if (lif_process_monitor_procdesc(child, timeout_sec, &status)) + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +#else + if (lif_process_monitor_busyloop(child, timeout_sec, &status)) + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +#endif + + fprintf(stderr, "execution of '%s': timeout after %d seconds\n", cmdbuf, timeout_sec); + kill(child, SIGKILL); + waitpid(child, &status, 0); + + return false; +} + bool lif_execute_fmt(const struct lif_execute_opts *opts, char *const envp[], const char *fmt, ...) { @@ -53,8 +135,112 @@ lif_execute_fmt(const struct lif_execute_opts *opts, char *const envp[], const c return false; } - int status; - waitpid(child, &status, 0); - - return WIFEXITED(status) && WEXITSTATUS(status) == 0; + return lif_process_monitor(cmdbuf, child, opts->timeout); +} + +bool +lif_execute_fmt_with_result(const struct lif_execute_opts *opts, char *buf, size_t bufsize, char *const envp[], const char *fmt, ...) +{ + char cmdbuf[4096]; + va_list va; + + va_start(va, fmt); + vsnprintf(cmdbuf, sizeof cmdbuf, fmt, va); + va_end(va); + + pid_t child; + char *argv[] = { SHELL, "-c", cmdbuf, NULL }; + + if (opts->verbose) + puts(cmdbuf); + + if (opts->mock) + return true; + + int pipefds[2]; + if (pipe(pipefds) < 0) + { + fprintf(stderr, "execute '%s': %s\n", cmdbuf, strerror(errno)); + return false; + } + + posix_spawn_file_actions_t file_actions; + + posix_spawn_file_actions_init(&file_actions); + posix_spawn_file_actions_addclose(&file_actions, pipefds[0]); + posix_spawn_file_actions_adddup2(&file_actions, pipefds[1], 1); + posix_spawn_file_actions_addclose(&file_actions, pipefds[1]); + + if (posix_spawn(&child, SHELL, &file_actions, NULL, argv, envp) != 0) + { + fprintf(stderr, "execute '%s': %s\n", cmdbuf, strerror(errno)); + return false; + } + + posix_spawn_file_actions_destroy(&file_actions); + + close(pipefds[1]); + + struct pollfd pfd = { + .fd = pipefds[0], + .events = POLLIN + }; + + if (poll(&pfd, 1, -1) < 1) + goto no_result; + + if (read(pipefds[0], buf, bufsize) < 0) + { + fprintf(stderr, "reading from pipe: %s\n", strerror(errno)); + return false; + } + +no_result: + return lif_process_monitor(cmdbuf, child, opts->timeout); +} + +bool +lif_file_is_executable(const char *path) +{ + struct stat st; + + if (stat(path, &st)) + return false; + + if (!S_ISREG(st.st_mode)) + return false; + + return !access(path, X_OK); +} + +bool +lif_maybe_run_executor(const struct lif_execute_opts *opts, char *const envp[], const char *executor, const char *phase, const char *lifname) +{ + if (opts->verbose) + fprintf(stderr, "ifupdown: %s: attempting to run %s executor for phase %s\n", lifname, executor, phase); + + char pathbuf[4096]; + + snprintf(pathbuf, sizeof pathbuf, "%s/%s", opts->executor_path, executor); + + if (!lif_file_is_executable(pathbuf)) + return true; + + return lif_execute_fmt(opts, envp, "%s", pathbuf); +} + +bool +lif_maybe_run_executor_with_result(const struct lif_execute_opts *opts, char *const envp[], const char *executor, char *buf, size_t bufsize, const char *phase, const char *lifname) +{ + if (opts->verbose) + fprintf(stderr, "ifupdown: %s: attempting to run %s executor for phase %s\n", lifname, executor, phase); + + char pathbuf[4096]; + + snprintf(pathbuf, sizeof pathbuf, "%s/%s", opts->executor_path, executor); + + if (!lif_file_is_executable(pathbuf)) + return true; + + return lif_execute_fmt_with_result(opts, buf, bufsize, envp, "%s", pathbuf); } diff --git a/libifupdown/execute.h b/libifupdown/execute.h index b1f2eb5..905cd84 100644 --- a/libifupdown/execute.h +++ b/libifupdown/execute.h @@ -22,8 +22,18 @@ struct lif_execute_opts { bool verbose; bool mock; + bool no_lock; + bool force; + const char *executor_path; + const char *interfaces_file; + const char *state_file; + int timeout; }; extern bool lif_execute_fmt(const struct lif_execute_opts *opts, char *const envp[], const char *fmt, ...); +extern bool lif_execute_fmt_with_result(const struct lif_execute_opts *opts, char *buf, size_t bufsize, char *const envp[], const char *fmt, ...); +extern bool lif_file_is_executable(const char *path); +extern bool lif_maybe_run_executor(const struct lif_execute_opts *opts, char *const envp[], const char *executor, const char *phase, const char *lifname); +extern bool lif_maybe_run_executor_with_result(const struct lif_execute_opts *opts, char *const envp[], const char *executor, char *buf, size_t bufsize, const char *phase, const char *lifname); #endif diff --git a/libifupdown/interface-file.c b/libifupdown/interface-file.c index f1dcbad..d0fa233 100644 --- a/libifupdown/interface-file.c +++ b/libifupdown/interface-file.c @@ -3,6 +3,7 @@ * Purpose: /etc/network/interfaces parser * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,128 +15,519 @@ */ #include +#include #include #include -#include "libifupdown/interface-file.h" -#include "libifupdown/fgetline.h" -#include "libifupdown/tokenize.h" +#include +#include +#include "libifupdown/libifupdown.h" + +/* internally rewrite problematic ifupdown2 tokens to ifupdown-ng equivalents */ +struct remap_token { + const char *token; + const char *alternative; +}; + +/* this list must be in alphabetical order for bsearch */ +static const struct remap_token tokens[] = { + {"bond-ad-sys-priority", "bond-ad-actor-sys-prio"}, /* ifupdown2 */ + {"bond-slaves", "bond-members"}, /* legacy ifupdown, ifupdown2 */ + {"client", "dhcp-client-id"}, /* legacy ifupdown */ + {"driver-message-level", "ethtool-msglvl"}, /* Debian ethtool integration */ + {"endpoint", "tunnel-remote"}, /* legacy ifupdown */ + {"ethernet-autoneg", "ethtool-ethernet-autoneg"}, /* Debian ethtool integration */ + {"ethernet-pause-autoneg", "ethtool-pause-autoneg"}, /* Debian ethtool integration */ + {"ethernet-pause-rx", "ethtool-pause-rx"}, /* Debian ethtool integration */ + {"ethernet-pause-tx", "ethtool-pause-tx"}, /* Debian ethtool integration */ + {"ethernet-port", "ethtool-ethernet-port"}, /* Debian ethtool integration */ + {"ethernet-wol", "ethtool-ethernet-wol"}, /* Debian ethtool integration */ + {"gro-offload", "ethtool-offload-gro"}, /* ifupdown2 */ + {"gso-offload", "ethtool-offload-gso"}, /* ifupdown2 */ + {"hardware-dma-ring-rx", "ethtool-dma-ring-rx"}, /* Debian ethtool integration */ + {"hardware-dma-ring-rx-jumbo", "ethtool-dma-ring-rx-jumbo"}, /* Debian ethtool integration */ + {"hardware-dma-ring-rx-mini", "ethtool-dma-ring-rx-mini"}, /* Debian ethtool integration */ + {"hardware-dma-ring-tx", "ethtool-dma-ring-tx"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-adaptive-rx", "ethtool-coalesce-adaptive-rx"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-adaptive-tx", "ethtool-coalesce-adaptive-tx"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-pkt-rate-high", "ethtool-coalesce-pkt-rate-high"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-pkt-rate-low", "ethtool-coalesce-pkt-rate-low"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-rx-frames", "ethtool-coalesce-rx-frames"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-rx-frames-high", "ethtool-coalesce-rx-frames-high"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-rx-frames-irq", "ethtool-coalesce-rx-frames-irq"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-rx-frames-low", "ethtool-coalesce-rx-frames-low"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-rx-usecs", "ethtool-coalesce-rx-usecs"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-rx-usecs-high", "ethtool-coalesce-rx-usecs-high"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-rx-usecs-irq", "ethtool-coalesce-rx-usecs-irq"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-rx-usecs-low", "ethtool-coalesce-rx-usecs-low"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-sample-interval", "ethtool-coalesce-sample-interval"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-stats-block-usecs", "ethtool-coalesce-stats-block-usecs"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-tx-frames", "ethtool-coalesce-tx-frames"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-tx-frames-high", "ethtool-coalesce-tx-frames-high"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-tx-frames-irq", "ethtool-coalesce-tx-frames-irq"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-tx-frames-low", "ethtool-coalesce-tx-frames-low"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-tx-usecs", "ethtool-coalesce-tx-usecs"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-tx-usecs-high", "ethtool-coalesce-tx-usecs-high"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-tx-usecs-irq", "ethtool-coalesce-tx-usecs-irq"}, /* Debian ethtool integration */ + {"hardware-irq-coalesce-tx-usecs-low", "ethtool-coalesce-tx-usecs-low"}, /* Debian ethtool integration */ + {"hostname", "dhcp-hostname"}, /* legacy ifupdown */ + {"key", "tunnel-key"}, /* legacy ifupdown */ + {"leasetime", "dhcp-leasetime"}, /* legacy ifupdown */ + {"link-autoneg", "ethtool-ethernet-autoneg"}, /* ifupdown2 */ + {"link-duplex", "ethtool-link-duplex"}, /* Debian ethtool integration */ + {"link-fec", "ethtool-link-fec"}, /* ifupdown2 */ + {"link-speed", "ethtool-link-speed"}, /* Debian ethtool integration */ + {"local", "tunnel-local"}, /* legacy ifupdown */ + {"lro-offload", "ethtool-offload-lro"}, /* ifupdown2 */ + {"mode", "tunnel-mode"}, /* legacy ifupdown */ + {"offload-gro", "ethtool-offload-gro"}, /* Debian ethtool integration */ + {"offload-gso", "ethtool-offload-gso"}, /* Debian ethtool integration */ + {"offload-lro", "ethtool-offload-lro"}, /* Debian ethtool integration */ + {"offload-rx", "ethtool-offload-rx"}, /* Debian ethtool integration */ + {"offload-sg", "ethtool-offload-sg"}, /* Debian ethtool integration */ + {"offload-tso", "ethtool-offload-tso"}, /* Debian ethtool integration */ + {"offload-tx", "ethtool-offload-tx"}, /* Debian ethtool integration */ + {"offload-ufo", "ethtool-offload-ufo"}, /* Debian ethtool integration */ + {"pointopoint", "point-to-point"}, /* legacy ifupdown, ifupdown2 */ + {"provider", "ppp-provider"}, /* legacy ifupdown, ifupdown2 */ + {"script", "dhcp-script"}, /* legacy ifupdown */ + {"rx-offload", "ethtool-offload-rx"}, /* ifupdown2 */ + {"tso-offload", "ethtool-offload-tso"}, /* ifupdown2 */ + {"ttl", "tunnel-ttl"}, /* legacy ifupdown */ + {"tunnel-endpoint", "tunnel-remote"}, /* ifupdown2 */ + {"tunnel-physdev", "tunnel-dev"}, /* ifupdown2 */ + {"tx-offload", "ethtool-offload-tx"}, /* ifupdown2 */ + {"ufo-offload", "ethtool-offload-ufo"}, /* ifupdown2 */ + {"vendor", "dhcp-vendor"}, /* legacy ifupdown */ + {"vrf", "vrf-member"}, /* ifupdown2 */ + {"vxlan-local-tunnelip", "vxlan-local-ip"}, /* ifupdown2 */ + {"vxlan-remote-group", "vxlan-peer-group"}, /* ifupdown-ng */ + {"vxlan-remoteip", "vxlan-peer-ips"}, /* ifupdown2 */ + {"vxlan-remote-ip", "vxlan-peer-ips"}, /* ifupdown-ng */ + {"vxlan-svcnodeip", "vxlan-peer-group"}, /* ifupdown2 */ +}; + +static int +token_cmp(const void *a, const void *b) +{ + const char *key = a; + const struct remap_token *token = b; + + return strcmp(key, token->token); +} + +static char * +maybe_remap_token(const char *token) +{ + const struct remap_token *tok = NULL; + static char tokbuf[4096]; + + tok = bsearch(token, tokens, ARRAY_SIZE(tokens), sizeof(*tokens), token_cmp); + strlcpy(tokbuf, tok != NULL ? tok->alternative : token, sizeof tokbuf); + + return tokbuf; +} + +static void +report_error(struct lif_interface_file_parse_state *state, const char *errfmt, ...) +{ + char errbuf[4096]; + + va_list va; + va_start(va, errfmt); + vsnprintf(errbuf, sizeof errbuf, errfmt, va); + va_end(va); + + fprintf(stderr, "%s:%zu: %s\n", state->cur_filename, state->cur_lineno, errbuf); +} + +static bool +handle_address(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + (void) token; + + char *addr = lif_next_token(&bufp); + + if (state->cur_iface == NULL) + { + report_error(state, "%s '%s' without interface", token, addr); + /* Ignore this address, but don't fail hard */ + return true; + } + + lif_interface_address_add(state->cur_iface, addr); + + return true; +} + +static bool +handle_auto(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + (void) token; + + char *ifname = lif_next_token(&bufp); + if (!*ifname && state->cur_iface == NULL) + { + report_error(state, "auto without interface"); + return true; + } + else + { + state->cur_iface = lif_interface_collection_find(state->collection, ifname); + if (state->cur_iface == NULL) + return false; + } + + if (!state->cur_iface->is_template) + state->cur_iface->is_auto = true; + + if (state->cur_iface->is_auto) + state->cur_iface->is_explicit = true; + + return true; +} + +static bool +handle_gateway(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + (void) token; + + char *addr = lif_next_token(&bufp); + + if (state->cur_iface == NULL) + { + report_error(state, "%s '%s' without interface", token, addr); + /* Ignore this gateway, but don't fail hard */ + return true; + } + + lif_interface_use_executor(state->cur_iface, "static"); + lif_dict_add(&state->cur_iface->vars, token, strdup(addr)); + + return true; +} + +static bool +handle_generic(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + if (state->cur_iface == NULL) + return true; + + token = maybe_remap_token(token); + + /* This smells like a bridge */ + if (strcmp(token, "bridge-ports") == 0) + state->cur_iface->is_bridge = true; + + /* Skip any leading whitespaces in value for */ + while (isspace (*bufp)) + bufp++; + + lif_dict_add(&state->cur_iface->vars, token, strdup(bufp)); + + if (!lif_config.auto_executor_selection) + return true; + + /* Check if token looks like - and assume is an addon */ + char *word_end = strchr(token, '-'); + if (word_end != NULL) + { + /* Copy word1 to not mangle *token */ + char *addon = strndup(token, word_end - token); + lif_interface_use_executor(state->cur_iface, addon); + free(addon); + } + + return true; +} + +static bool +handle_hostname(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + char *hostname = lif_next_token(&bufp); + + if (state->cur_iface == NULL) + { + report_error(state, "%s '%s' without interface", token, hostname); + /* Ignore this hostname, but don't fail hard */ + return true; + } + + lif_dict_delete(&state->cur_iface->vars, "dhcp-hostname"); + lif_dict_add(&state->cur_iface->vars, "dhcp-hostname", strdup(hostname)); + + return true; +} + +static bool handle_inherit(struct lif_interface_file_parse_state *state, char *token, char *bufp); + +static bool +handle_iface(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + char *ifname = lif_next_token(&bufp); + if (!*ifname) + { + report_error(state, "%s without any other tokens", token); + /* This is broken but not fatal */ + return true; + } + + /* if we have a current interface, call lif_interface_finalize to finalize any + * address properties by converting them to CIDR and flushing the netmask property. + */ + if (state->cur_iface != NULL) + lif_interface_finalize(state->cur_iface); + + state->cur_iface = lif_interface_collection_find(state->collection, ifname); + if (state->cur_iface == NULL) + { + report_error(state, "could not upsert interface %s", ifname); + return false; + } + + /* mark the state->cur_iface as a template iface if `template` keyword + * is used. + */ + if (!strcmp(token, "template")) + { + state->cur_iface->is_auto = false; + state->cur_iface->is_template = true; + } + + /* in original ifupdown config, we can have "inet loopback" + * or "inet dhcp" or such to designate hints. lets pick up + * those hints here. + */ + token = lif_next_token(&bufp); + while (*token) + { + if (!strcmp(token, "dhcp")) + lif_interface_use_executor(state->cur_iface, "dhcp"); + else if (!strcmp(token, "ppp")) + lif_interface_use_executor(state->cur_iface, "ppp"); + else if (!strcmp(token, "inherits")) + { + if (!handle_inherit(state, token, bufp)) + return false; + } + + token = lif_next_token(&bufp); + } + + return true; +} + +static bool +handle_inherit(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + char *target = lif_next_token(&bufp); + + if (state->cur_iface == NULL) + { + report_error(state, "%s '%s' without interface", token, target); + /* This is broken but not fatal */ + return true; + } + + if (!*target) + { + report_error(state, "iface %s: unspecified inherit target", state->cur_iface->ifname); + /* Mark this interface as errornous but carry on */ + state->cur_iface->has_config_error = true; + return true; + } + + struct lif_interface *parent = lif_interface_collection_find(state->collection, target); + if (parent == NULL) + { + report_error(state, "iface %s: could not inherit from %s: not found", + state->cur_iface->ifname, target); + /* Mark this interface as errornous but carry on */ + state->cur_iface->has_config_error = true; + return true; + + } + + if (!lif_config.allow_any_iface_as_template && !parent->is_template) + { + report_error(state, "iface %s: could not inherit from %ss: inheritence from non-template interface not allowed", + state->cur_iface->ifname, target); + /* Mark this interface as errornous but carry on */ + state->cur_iface->has_config_error = true; + return true; + } + + if (!lif_interface_collection_inherit(state->cur_iface, parent)) + { + report_error(state, "iface %s: could not inherit from %s", state->cur_iface->ifname, target); + /* Mark this interface as errornous but carry on */ + state->cur_iface->has_config_error = true; + return true; + } + + return true; +} + +static bool +handle_source(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + (void) token; + + char *source_filename = lif_next_token(&bufp); + if (!*source_filename) + { + report_error(state, "missing filename to source"); + /* Broken but not fatal */ + return true; + } + + return lif_interface_file_parse(state, source_filename); +} + +static bool +handle_source_directory(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + (void) token; + + char *source_directory = lif_next_token(&bufp); + if (!*source_directory) + { + report_error(state, "missing directory to source"); + /* Broken but not fatal */ + return true; + } + + DIR *source_dir = opendir(source_directory); + if (source_dir == NULL) + { + report_error(state, "while opening directory %s: %s", source_directory, strerror(errno)); + /* Broken but not fatal */ + return true; + } + + struct dirent *dirent_p; + for (dirent_p = readdir(source_dir); dirent_p != NULL; dirent_p = readdir(source_dir)) + { + if (dirent_p->d_type != DT_REG) + continue; + + char pathbuf[4096]; + snprintf(pathbuf, sizeof pathbuf, "%s/%s", source_directory, dirent_p->d_name); + + if (!lif_interface_file_parse(state, pathbuf)) + { + closedir(source_dir); + return false; + } + } + + closedir(source_dir); + return true; +} + +static bool +handle_use(struct lif_interface_file_parse_state *state, char *token, char *bufp) +{ + char *executor = lif_next_token(&bufp); + + if (state->cur_iface == NULL) + { + report_error(state, "%s '%s' without interface", token, executor); + /* Broken but not fatal */ + return true; + } + + lif_interface_use_executor(state->cur_iface, executor); + return true; +} + +/* map keywords to parser functions */ +struct parser_keyword { + const char *token; + bool (*handle)(struct lif_interface_file_parse_state *state, char *token, char *bufp); +}; + +static const struct parser_keyword keywords[] = { + {"address", handle_address}, + {"auto", handle_auto}, + {"dhcp-hostname", handle_hostname}, + {"gateway", handle_gateway}, + {"hostname", handle_hostname}, + {"iface", handle_iface}, + {"inherit", handle_inherit}, + {"interface", handle_iface}, + {"source", handle_source}, + {"source-directory", handle_source_directory}, + {"template", handle_iface}, + {"use", handle_use}, +}; + +static int +keyword_cmp(const void *a, const void *b) +{ + const char *key = a; + const struct parser_keyword *token = b; + + return strcmp(key, token->token); +} bool -lif_interface_file_parse(struct lif_dict *collection, const char *filename) +lif_interface_file_parse(struct lif_interface_file_parse_state *state, const char *filename) { - lif_interface_collection_init(collection); - struct lif_interface *cur_iface = NULL; + struct lif_dict_entry *entry = lif_dict_find(&state->loaded, filename); + if (entry != NULL) + { + report_error(state, "skipping already included file %s", filename); + return true; + } FILE *f = fopen(filename, "r"); if (f == NULL) return false; + const char *old_filename = state->cur_filename; + state->cur_filename = filename; + + size_t old_lineno = state->cur_lineno; + state->cur_lineno = 0; + + lif_dict_add(&state->loaded, filename, NULL); + char linebuf[4096]; while (lif_fgetline(linebuf, sizeof linebuf, f) != NULL) { + state->cur_lineno++; + char *bufp = linebuf; char *token = lif_next_token(&bufp); if (!*token || !isalpha(*token)) continue; - if (!strcmp(token, "source")) + const struct parser_keyword *parserkw = + bsearch(token, keywords, ARRAY_SIZE(keywords), sizeof(*keywords), keyword_cmp); + + if (parserkw != NULL) { - char *source_filename = lif_next_token(&bufp); - if (!*source_filename) + if (!parserkw->handle(state, token, bufp)) goto parse_error; - - if (!strcmp(filename, source_filename)) - { - fprintf(stderr, "%s: attempt to source %s would create infinite loop\n", - filename, source_filename); - goto parse_error; - } - - lif_interface_file_parse(collection, source_filename); - } - else if (!strcmp(token, "auto")) - { - char *ifname = lif_next_token(&bufp); - if (!*ifname && cur_iface == NULL) - goto parse_error; - else - { - cur_iface = lif_interface_collection_find(collection, ifname); - if (cur_iface == NULL) - goto parse_error; - } - - cur_iface->is_auto = true; - } - else if (!strcmp(token, "iface")) - { - char *ifname = lif_next_token(&bufp); - if (!*ifname) - goto parse_error; - - cur_iface = lif_interface_collection_find(collection, ifname); - if (cur_iface == NULL) - goto parse_error; - - /* in original ifupdown config, we can have "inet loopback" - * or "inet dhcp" or such to designate hints. lets pick up - * those hints here. - */ - char *inet_type = lif_next_token(&bufp); - if (!*inet_type) - continue; - - char *hint = lif_next_token(&bufp); - if (!*hint) - continue; - - if (!strcmp(hint, "dhcp")) - { - cur_iface->is_dhcp = true; - lif_dict_add(&cur_iface->vars, "use", strdup("dhcp")); - } - } - else if (!strcmp(token, "use")) - { - char *executor = lif_next_token(&bufp); - - /* pass requires as compatibility env vars to appropriate executors (bridge, bond) */ - if (!strcmp(executor, "dhcp")) - cur_iface->is_dhcp = true; - else if (!strcmp(executor, "loopback")) - cur_iface->is_loopback = true; - else if (!strcmp(executor, "bridge")) - cur_iface->is_bridge = true; - else if (!strcmp(executor, "bond")) - cur_iface->is_bond = true; - - lif_dict_add(&cur_iface->vars, token, strdup(executor)); - } - else if (!strcmp(token, "address")) - { - char *addr = lif_next_token(&bufp); - - if (cur_iface == NULL) - { - fprintf(stderr, "%s: address '%s' without interface\n", filename, addr); - goto parse_error; - } - - lif_interface_address_add(cur_iface, addr); - } - else if (cur_iface != NULL) - { - lif_dict_add(&cur_iface->vars, token, strdup(bufp)); } + else if (!handle_generic(state, token, bufp)) + goto parse_error; } fclose(f); + + /* finalize any open interface */ + if (state->cur_iface != NULL) + lif_interface_finalize(state->cur_iface); + + state->cur_filename = old_filename; + state->cur_lineno = old_lineno; return true; parse_error: - fprintf(stderr, "libifupdown: %s: failed to parse line \"%s\"\n", - filename, linebuf); fclose(f); + state->cur_filename = old_filename; + state->cur_lineno = old_lineno; return false; } diff --git a/libifupdown/interface-file.h b/libifupdown/interface-file.h index 5769445..91fae97 100644 --- a/libifupdown/interface-file.h +++ b/libifupdown/interface-file.h @@ -18,7 +18,17 @@ #include #include "libifupdown/interface.h" +#include "libifupdown/dict.h" -extern bool lif_interface_file_parse(struct lif_dict *collection, const char *filename); +struct lif_interface_file_parse_state { + struct lif_interface *cur_iface; + struct lif_dict *collection; + const char *cur_filename; + size_t cur_lineno; + + struct lif_dict loaded; +}; + +extern bool lif_interface_file_parse(struct lif_interface_file_parse_state *state, const char *filename); #endif diff --git a/libifupdown/interface.c b/libifupdown/interface.c index 7d59813..46462fd 100644 --- a/libifupdown/interface.c +++ b/libifupdown/interface.c @@ -3,6 +3,7 @@ * Purpose: interface management * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,7 +16,9 @@ #include #include +#include #include "libifupdown/interface.h" +#include "libifupdown/config-file.h" bool lif_address_parse(struct lif_address *address, const char *presentation) @@ -56,12 +59,75 @@ lif_address_unparse(const struct lif_address *address, char *buf, size_t buflen, return true; } +static inline size_t +count_set_bits(const char *netmask) +{ + /* netmask set to CIDR length */ + if (strchr(netmask, '.') == NULL) + return strtol(netmask, NULL, 10); + + size_t r = 0; + struct in_addr in; + + if (inet_pton(AF_INET, netmask, &in) == 0) + return r; + + /* take the IP, put it in host endian order, and + * flip it so that all the set bits are set to the right. + * then we can simply count down from 32 and right-shift + * until the bit field is all zero. + */ + unsigned int bits = htonl(in.s_addr); + for (bits = ~bits, r = 32; bits; bits >>= 1, r--) + ; + + return r; +} + +static inline size_t +determine_interface_netmask(const struct lif_interface *iface, const struct lif_address *addr) +{ + /* if netmask is not set, default to /24 or /64, ifupdown does so too */ + size_t netmask = addr->domain == AF_INET6 ? 64 : 24; + + struct lif_dict_entry *entry = lif_dict_find(&iface->vars, "netmask"); + if (entry != NULL) + netmask = count_set_bits(entry->data); + + return netmask; +} + +bool +lif_address_format_cidr(const struct lif_interface *iface, struct lif_dict_entry *entry, char *buf, size_t buflen) +{ + struct lif_address *addr = entry->data; + size_t orig_netmask = addr->netmask; + + if (!addr->netmask) + addr->netmask = determine_interface_netmask(iface, addr); + + if (!lif_address_unparse(addr, buf, buflen, true)) + { + addr->netmask = orig_netmask; + return false; + } + + addr->netmask = orig_netmask; + return true; +} + void lif_interface_init(struct lif_interface *interface, const char *ifname) { memset(interface, '\0', sizeof *interface); interface->ifname = strdup(ifname); + + lif_interface_use_executor(interface, "link"); + + /* keep the 'vlan' executor as a config hint for backwards compatibility */ + if (strchr(ifname, '.') != NULL) + lif_interface_use_executor(interface, "vlan"); } bool @@ -75,7 +141,10 @@ lif_interface_address_add(struct lif_interface *interface, const char *address) return false; } + lif_interface_use_executor(interface, "static"); + lif_dict_add(&interface->vars, "address", addr); + return true; } @@ -125,6 +194,61 @@ lif_interface_fini(struct lif_interface *interface) free(interface->ifname); } +void +lif_interface_use_executor(struct lif_interface *interface, const char *executor) +{ + char *exec_addon = strdup(executor); + + if (lif_dict_add_once(&interface->vars, "use", exec_addon, (lif_dict_cmp_t) strcmp) == NULL) + free(exec_addon); + + /* pass requires as compatibility env vars to appropriate executors (bridge, bond) */ + if (!strcmp(executor, "bridge")) + interface->is_bridge = true; + else if (!strcmp(executor, "bond")) + interface->is_bond = true; + + if (strcmp(executor, "dhcp") || !lif_config.use_hostname_for_dhcp) + return; + + /* learn a reasonable default hostname */ + struct utsname un; + if (uname(&un) < 0) + return; + + lif_dict_add(&interface->vars, "dhcp-hostname", strdup(un.nodename)); +} + +void +lif_interface_finalize(struct lif_interface *interface) +{ + struct lif_node *iter; + + /* convert all addresses to CIDR notation. */ + LIF_DICT_FOREACH(iter, &interface->vars) + { + struct lif_dict_entry *entry = iter->data; + + if (strcmp(entry->key, "address")) + continue; + + struct lif_address *addr = entry->data; + + if (!addr->netmask) + addr->netmask = determine_interface_netmask(interface, addr); + } + + /* with all addresses converted to CIDR, netmask property is no longer needed. */ + struct lif_dict_entry *entry = lif_dict_find(&interface->vars, "netmask"); + + if (entry != NULL) + { + free(entry->data); + + lif_dict_delete_entry(&interface->vars, entry); + } +} + void lif_interface_collection_init(struct lif_dict *collection) { @@ -134,10 +258,9 @@ lif_interface_collection_init(struct lif_dict *collection) /* always enable loopback interface as part of a collection */ if_lo = lif_interface_collection_find(collection, "lo"); - if_lo->is_auto = if_lo->is_loopback = true; - lif_dict_add(&if_lo->vars, "use", strdup("loopback")); - - lif_interface_address_add(if_lo, "127.0.0.1/8"); + if_lo->is_auto = true; + if_lo->is_explicit = true; + lif_interface_use_executor(if_lo, "loopback"); } void @@ -208,3 +331,41 @@ lif_interface_collection_delete(struct lif_dict *collection, struct lif_interfac lif_dict_delete_entry(collection, entry); } + +bool +lif_interface_collection_inherit(struct lif_interface *interface, struct lif_interface *parent) +{ + /* maybe convert any interface we are inheriting from into a template */ + if (lif_config.implicit_template_conversion) + parent->is_template = true; + + lif_dict_add(&interface->vars, "inherit", strdup(parent->ifname)); + interface->is_bond = parent->is_bond; + interface->is_bridge = parent->is_bridge; + + /* copy the variables */ + struct lif_node *iter; + LIF_DICT_FOREACH(iter, &parent->vars) + { + struct lif_dict_entry *entry = iter->data; + + if (!strcmp(entry->key, "address")) + { + struct lif_address *addr = calloc(1, sizeof *addr); + struct lif_address *other_addr = entry->data; + + memcpy(addr, other_addr, sizeof *addr); + + lif_dict_add(&interface->vars, entry->key, addr); + } + else + { + char *value = strdup(entry->data); + + if (lif_dict_add_once(&interface->vars, entry->key, value, (lif_dict_cmp_t) strcmp) == NULL) + free(value); + } + } + + return true; +} diff --git a/libifupdown/interface.h b/libifupdown/interface.h index ae7abfe..49c867d 100644 --- a/libifupdown/interface.h +++ b/libifupdown/interface.h @@ -3,6 +3,7 @@ * Purpose: interface management * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -31,9 +32,6 @@ struct lif_address { int domain; }; -extern bool lif_address_parse(struct lif_address *address, const char *presentation); -extern bool lif_address_unparse(const struct lif_address *address, char *buf, size_t buflen, bool with_netmask); - /* * Interfaces are contained in a dictionary, with the interfaces mapped by * interface name to their `struct lif_interface`. @@ -47,15 +45,19 @@ extern bool lif_address_unparse(const struct lif_address *address, char *buf, si struct lif_interface { char *ifname; - bool is_dhcp; - bool is_loopback; bool is_auto; bool is_bridge; bool is_bond; + bool is_template; + bool is_pending; + bool is_explicit; + + bool has_config_error; /* error found in interface configuration */ struct lif_dict vars; - bool is_up; + size_t refcount; /* > 0 if up, else 0 */ + size_t rdepends_count; /* > 0 if any reverse dependency */ }; #define LIF_INTERFACE_COLLECTION_FOREACH(iter, collection) \ @@ -64,15 +66,22 @@ struct lif_interface { #define LIF_INTERFACE_COLLECTION_FOREACH_SAFE(iter, iter_next, collection) \ LIF_DICT_FOREACH_SAFE((iter), (iter_next), (collection)) +extern bool lif_address_parse(struct lif_address *address, const char *presentation); +extern bool lif_address_unparse(const struct lif_address *address, char *buf, size_t buflen, bool with_netmask); +extern bool lif_address_format_cidr(const struct lif_interface *iface, struct lif_dict_entry *entry, char *buf, size_t buflen); + extern void lif_interface_init(struct lif_interface *interface, const char *ifname); extern bool lif_interface_address_add(struct lif_interface *interface, const char *address); extern void lif_interface_address_delete(struct lif_interface *interface, const char *address); extern void lif_interface_fini(struct lif_interface *interface); +extern void lif_interface_use_executor(struct lif_interface *interface, const char *executor); +extern void lif_interface_finalize(struct lif_interface *interface); extern void lif_interface_collection_init(struct lif_dict *collection); extern void lif_interface_collection_fini(struct lif_dict *collection); extern struct lif_interface *lif_interface_collection_find(struct lif_dict *collection, const char *ifname); extern struct lif_interface *lif_interface_collection_upsert(struct lif_dict *collection, struct lif_interface *interface); +extern bool lif_interface_collection_inherit(struct lif_interface *interface, struct lif_interface *parent); extern void lif_interface_collection_delete(struct lif_dict *collection, struct lif_interface *interface); #endif diff --git a/libifupdown/libifupdown.h b/libifupdown/libifupdown.h index 87cc5fd..823adb0 100644 --- a/libifupdown/libifupdown.h +++ b/libifupdown/libifupdown.h @@ -27,5 +27,12 @@ #include "libifupdown/execute.h" #include "libifupdown/lifecycle.h" #include "libifupdown/tokenize.h" +#include "libifupdown/config-file.h" +#include "libifupdown/config-parser.h" +#include "libifupdown/compat.h" + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) +#endif #endif diff --git a/libifupdown/lifecycle.c b/libifupdown/lifecycle.c index 1605c8e..6bc9ffe 100644 --- a/libifupdown/lifecycle.c +++ b/libifupdown/lifecycle.c @@ -3,6 +3,7 @@ * Purpose: management of interface lifecycle (bring up, takedown, reload) * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,6 +16,9 @@ #include #include +#include +#include +#include #include "libifupdown/environment.h" #include "libifupdown/execute.h" @@ -22,17 +26,18 @@ #include "libifupdown/lifecycle.h" #include "libifupdown/state.h" #include "libifupdown/tokenize.h" +#include "libifupdown/config-file.h" + +#define BUFFER_LEN 4096 static bool -handle_commands_for_phase(const struct lif_execute_opts *opts, char *const envp[], struct lif_interface *iface, const char *lifname, const char *phase) +handle_commands_for_phase(const struct lif_execute_opts *opts, char *const envp[], const struct lif_interface *iface, const char *phase) { - struct lif_node *iter; - - (void) lifname; + const struct lif_node *iter; LIF_DICT_FOREACH(iter, &iface->vars) { - struct lif_dict_entry *entry = iter->data; + const struct lif_dict_entry *entry = iter->data; if (strcmp(entry->key, phase)) continue; @@ -45,171 +50,162 @@ handle_commands_for_phase(const struct lif_execute_opts *opts, char *const envp[ return true; } -static bool -handle_address(const struct lif_execute_opts *opts, struct lif_address *addr, const char *cmd, const char *lifname) +static inline bool +handle_single_executor_for_phase(const struct lif_dict_entry *entry, const struct lif_execute_opts *opts, char *const envp[], const char *phase, const char *lifname) { - char addrbuf[4096]; - - if (!lif_address_unparse(addr, addrbuf, sizeof addrbuf, true)) - return false; - - return lif_execute_fmt(opts, NULL, "/sbin/ip -%d addr %s %s dev %s", - addr->domain == AF_INET ? 4 : 6, cmd, addrbuf, lifname); -} - -static bool -handle_gateway(const struct lif_execute_opts *opts, const char *gateway, const char *cmd) -{ - int ipver = strchr(gateway, ':') ? 6 : 4; - - return lif_execute_fmt(opts, NULL, "/sbin/ip -%d route %s default via %s", - ipver, cmd, gateway); -} - -static bool -handle_pre_up(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *lifname) -{ - (void) opts; - (void) iface; - (void) lifname; - - return true; -} - -static bool -handle_up(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *lifname) -{ - struct lif_node *iter; - - if (!lif_execute_fmt(opts, NULL, "/sbin/ip link set up dev %s", lifname)) - return false; - - if (iface->is_loopback) + if (strcmp(entry->key, "use")) return true; - LIF_DICT_FOREACH(iter, &iface->vars) - { - struct lif_dict_entry *entry = iter->data; - - if (!strcmp(entry->key, "address")) - { - struct lif_address *addr = entry->data; - - if (!handle_address(opts, addr, "add", lifname)) - return false; - } - else if (!strcmp(entry->key, "gateway")) - { - if (!handle_gateway(opts, entry->data, "add")) - return false; - } - } - - if (iface->is_dhcp) - { - /* XXX: determine which dhcp client we should use */ - if (!lif_execute_fmt(opts, NULL, "/sbin/udhcpc -b -R -p /var/run/udhcpc.%s.pid -i %s", lifname, lifname)) - return false; - } - - return true; -} - -static bool -handle_down(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *lifname) -{ - struct lif_node *iter; - - if (iface->is_loopback) - goto skip_addresses; - - LIF_DICT_FOREACH(iter, &iface->vars) - { - struct lif_dict_entry *entry = iter->data; - - if (!strcmp(entry->key, "address")) - { - struct lif_address *addr = entry->data; - - if (!handle_address(opts, addr, "del", lifname)) - return false; - } - else if (!strcmp(entry->key, "gateway")) - { - if (!handle_gateway(opts, entry->data, "del")) - return false; - } - } - - if (iface->is_dhcp) - { - /* XXX: determine which dhcp client we should use */ - if (!lif_execute_fmt(opts, NULL, "/bin/kill $(cat /var/run/udhcpc.%s.pid)", lifname)) - return false; - } - -skip_addresses: - if (!lif_execute_fmt(opts, NULL, "/sbin/ip link set down dev %s", lifname)) + const char *cmd = entry->data; + if (!lif_maybe_run_executor(opts, envp, cmd, phase, lifname)) return false; return true; } static bool -handle_post_down(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *lifname) +handle_executors_for_phase(const struct lif_execute_opts *opts, char *const envp[], const struct lif_interface *iface, bool up, const char *phase) { - (void) opts; - (void) iface; - (void) lifname; + const struct lif_node *iter; + + if (up) + { + LIF_DICT_FOREACH(iter, &iface->vars) + handle_single_executor_for_phase(iter->data, opts, envp, phase, iface->ifname); + } + else + { + LIF_DICT_FOREACH_REVERSE(iter, &iface->vars) + handle_single_executor_for_phase(iter->data, opts, envp, phase, iface->ifname); + } return true; } -bool -lif_lifecycle_run_phase(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *phase, const char *lifname, bool up) +static bool +query_dependents_from_executors(const struct lif_execute_opts *opts, char *const envp[], const struct lif_interface *iface, char *buf, size_t bufsize, const char *phase) { - char **envp = NULL; + const struct lif_node *iter; - lif_environment_push(&envp, "IFACE", iface->ifname); - lif_environment_push(&envp, "PHASE", phase); + LIF_DICT_FOREACH(iter, &iface->vars) + { + char resbuf[1024] = {}; + const struct lif_dict_entry *entry = iter->data; + struct lif_execute_opts exec_opts = { + .verbose = opts->verbose, + .executor_path = opts->executor_path, + .interfaces_file = opts->interfaces_file, + .timeout = opts->timeout, + }; - /* try to provide $METHOD for ifupdown1 scripts if we can */ - if (iface->is_loopback) - lif_environment_push(&envp, "METHOD", "loopback"); - else if (iface->is_dhcp) - lif_environment_push(&envp, "METHOD", "dhcp"); + if (strcmp(entry->key, "use")) + continue; - /* same for $MODE */ - if (up) - lif_environment_push(&envp, "MODE", "start"); - else - lif_environment_push(&envp, "MODE", "stop"); + const char *cmd = entry->data; + if (!lif_maybe_run_executor_with_result(&exec_opts, envp, cmd, resbuf, sizeof resbuf, phase, iface->ifname)) + return false; - struct lif_node *iter; + if (!*resbuf) + continue; + + strlcat(buf, " ", bufsize); + strlcat(buf, resbuf, bufsize); + } + + return true; +} + +static bool +append_to_buffer(char **buffer, size_t *buffer_len, char **end, const char *value) +{ + size_t value_len = strlen (value); + + /* Make sure there is enough room to add the value to the buffer */ + if (*buffer_len < strlen (*buffer) + value_len + 2) + { + size_t end_offset = *end - *buffer; + char *tmp = realloc (*buffer, *buffer_len * 2); + + if (tmp != NULL) + { + *buffer = tmp; + *end = tmp + end_offset; + *buffer_len = *buffer_len * 2; + } + else + return false; + } + + /* Append value to buffer */ + size_t printed = snprintf (*end, value_len + 2, "%s ", value); + if (printed < value_len + 1) + /* Here be dragons */ + return false; + + /* Move end pointer to last printed byte */ + *end += printed; + + return true; +} + +static void +build_environment(char **envp[], const struct lif_execute_opts *opts, const struct lif_interface *iface, const char *lifname, const char *phase, const char *mode) +{ + if (lifname == NULL) + lifname = iface->ifname; + + lif_environment_push(envp, "IFACE", lifname); + lif_environment_push(envp, "PHASE", phase); + lif_environment_push(envp, "MODE", mode); + lif_environment_push(envp, "METHOD", "none"); + + if (opts->verbose) + lif_environment_push(envp, "VERBOSE", "1"); + + if (opts->interfaces_file) + lif_environment_push(envp, "INTERFACES_FILE", opts->interfaces_file); + + const struct lif_node *iter; bool did_address = false, did_gateway = false; + /* Allocate a buffer for all possible addresses, if any */ + char *addresses = calloc (BUFFER_LEN, 1); + size_t addresses_size = BUFFER_LEN; + char *addresses_end = addresses; + + /* Allocate a buffer for all possible gateways, if any */ + char *gateways = calloc (BUFFER_LEN, 1); + size_t gateways_size = BUFFER_LEN; + char *gateways_end = gateways; + LIF_DICT_FOREACH(iter, &iface->vars) { struct lif_dict_entry *entry = iter->data; if (!strcmp(entry->key, "address")) { + char addrbuf[4096]; + + if (!lif_address_format_cidr(iface, entry, addrbuf, sizeof(addrbuf))) + continue; + + /* Append address to buffer */ + append_to_buffer(&addresses, &addresses_size, &addresses_end, addrbuf); + + /* Only print IF_ADDRESS once */ if (did_address) continue; - struct lif_address *addr = entry->data; - char addrbuf[4096]; - - if (!lif_address_unparse(addr, addrbuf, sizeof addrbuf, true)) - continue; - - lif_environment_push(&envp, "IF_ADDRESS", addrbuf); + lif_environment_push(envp, "IF_ADDRESS", addrbuf); did_address = true; continue; } else if (!strcmp(entry->key, "gateway")) { + /* Append address to buffer */ + append_to_buffer(&gateways, &gateways_size, &gateways_end, entry->data); + if (did_gateway) continue; @@ -218,10 +214,7 @@ lif_lifecycle_run_phase(const struct lif_execute_opts *opts, struct lif_interfac else if (!strcmp(entry->key, "requires")) { if (iface->is_bridge) - lif_environment_push(&envp, "IF_BRIDGE_PORTS", (const char *) entry->data); - - if (iface->is_bond) - lif_environment_push(&envp, "IF_BOND_SLAVES", (const char *) entry->data); + lif_environment_push(envp, "IF_BRIDGE_PORTS", (const char *) entry->data); } char envkey[4096] = "IF_"; @@ -236,43 +229,132 @@ lif_lifecycle_run_phase(const struct lif_execute_opts *opts, struct lif_interfac *ep = '_'; } - lif_environment_push(&envp, envkey, (const char *) entry->data); + lif_environment_push(envp, envkey, (const char *) entry->data); } - if (!strcmp(phase, "pre-up")) + if (addresses != NULL) + lif_environment_push(envp, "IF_ADDRESSES", addresses); + if (gateways != NULL) + lif_environment_push(envp, "IF_GATEWAYS", gateways); + + /* Clean up */ + free (addresses); + free (gateways); +} + +bool +lif_lifecycle_query_dependents(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *lifname) +{ + char deps[4096] = {}; + char final_deps[4096] = {}; + + if (lifname == NULL) + lifname = iface->ifname; + + char **envp = NULL; + + build_environment(&envp, opts, iface, lifname, "depend", "depend"); + + struct lif_dict_entry *entry = lif_dict_find(&iface->vars, "requires"); + if (entry != NULL) + strlcpy(deps, entry->data, sizeof deps); + + if (!query_dependents_from_executors(opts, envp, iface, deps, sizeof deps, "depend")) + return false; + + char *p = deps; + while (*p) { - if (!handle_pre_up(opts, iface, lifname)) - goto on_error; - } - else if (!strcmp(phase, "up")) - { - if (!handle_up(opts, iface, lifname)) - goto on_error; - } - else if (!strcmp(phase, "down")) - { - if (!handle_down(opts, iface, lifname)) - goto on_error; - } - else if (!strcmp(phase, "post-down")) - { - if (!handle_post_down(opts, iface, lifname)) - goto on_error; + char *token = lif_next_token(&p); + + if (strstr(final_deps, token) != NULL) + continue; + + strlcat(final_deps, token, sizeof final_deps); + strlcat(final_deps, " ", sizeof final_deps); } - handle_commands_for_phase(opts, envp, iface, lifname, phase); + if (entry != NULL) + { + free(entry->data); + entry->data = strdup(final_deps); + } + else if (*final_deps) + lif_dict_add(&iface->vars, "requires", strdup(final_deps)); + + lif_environment_free(&envp); + + return true; +} + +bool +lif_lifecycle_run_phase(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *phase, const char *lifname, bool up) +{ + char **envp = NULL; + + build_environment(&envp, opts, iface, lifname, phase, up ? "start" : "stop"); + + if (!handle_executors_for_phase(opts, envp, iface, up, phase)) + goto handle_error; + + if (!handle_commands_for_phase(opts, envp, iface, phase)) + goto handle_error; + + /* if we don't need to support /etc/if-X.d we're done here */ + if (!lif_config.allow_addon_scripts) + goto out_free; + + /* Check if scripts dir for this phase is present and bail out if it isn't */ + struct stat dir_stat; + char dir_path[4096]; + snprintf (dir_path, 4096, "/etc/network/if-%s.d", phase); + + if (stat (dir_path, &dir_stat) != 0 || S_ISDIR (dir_stat.st_mode) == 0) { + goto out_free; + } /* we should do error handling here, but ifupdown1 doesn't */ - lif_execute_fmt(opts, envp, "/bin/run-parts /etc/network/if-%s.d", phase); + lif_execute_fmt(opts, envp, "/bin/run-parts %s", dir_path); +out_free: lif_environment_free(&envp); return true; -on_error: +handle_error: lif_environment_free(&envp); return false; } +/* this function returns true if we can skip processing the interface for now, + * otherwise false. + */ +static bool +handle_refcounting(struct lif_dict *state, struct lif_interface *iface, bool up) +{ + size_t orig_refcount = iface->refcount; + + if (up) + lif_state_ref_if(state, iface->ifname, iface); + else + lif_state_unref_if(state, iface->ifname, iface); + +#ifdef DEBUG_REFCOUNTING + fprintf(stderr, "handle_refcounting(): orig_refcount=%zu, refcount=%zu, direction=%s\n", + orig_refcount, iface->refcount, up ? "UP" : "DOWN"); +#endif + + /* if going up and orig_refcount > 0 -- we're already configured. */ + if (up && orig_refcount > 0) + return true; + + /* if going down and iface->refcount > 1 -- we still have other dependents. */ + if (!up && iface->refcount > 1) + return true; + + /* we can change this interface -- no blocking dependents. */ + return false; +} + static bool handle_dependents(const struct lif_execute_opts *opts, struct lif_interface *parent, struct lif_dict *collection, struct lif_dict *state, bool up) { @@ -282,6 +364,9 @@ handle_dependents(const struct lif_execute_opts *opts, struct lif_interface *par if (requires == NULL) return true; + /* set the parent's pending flag to break dependency cycles */ + parent->is_pending = true; + char require_ifs[4096] = {}; strlcpy(require_ifs, requires->data, sizeof require_ifs); char *bufp = require_ifs; @@ -290,24 +375,67 @@ handle_dependents(const struct lif_execute_opts *opts, struct lif_interface *par { struct lif_interface *iface = lif_interface_collection_find(collection, tokenp); - /* already up or down, skip */ - if (up == iface->is_up) + if (iface->has_config_error) + { + if (opts->force) + fprintf (stderr, "ifupdown: (de)configuring dependent interface %s (of %s) despite config errors\n", + iface->ifname, parent->ifname); + else + { + fprintf (stderr, "ifupdown: skipping dependent interface %s (of %s) as it has config errors\n", + iface->ifname, parent->ifname); + continue; + } + } + + /* if handle_refcounting returns true, it means we've already + * configured the interface, or it is too soon to deconfigure + * the interface. + */ + if (handle_refcounting(state, iface, up)) + { + if (opts->verbose) + fprintf(stderr, "ifupdown: skipping dependent interface %s (of %s) -- %s\n", + iface->ifname, parent->ifname, + up ? "already configured" : "transient dependencies still exist"); + continue; + } + + if (!up && iface->is_explicit) + { + if (opts->verbose) + fprintf(stderr, "ifupdown: skipping dependent interface %s (of %s) -- interface is marked as explicitly configured\n", + iface->ifname, parent->ifname); + + continue; + } if (opts->verbose) fprintf(stderr, "ifupdown: changing state of dependent interface %s (of %s) to %s\n", iface->ifname, parent->ifname, up ? "up" : "down"); if (!lif_lifecycle_run(opts, iface, collection, state, iface->ifname, up)) + { + parent->is_pending = false; return false; + } } + parent->is_pending = false; return true; } bool lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *iface, struct lif_dict *collection, struct lif_dict *state, const char *lifname, bool up) { + /* if we're already pending, exit */ + if (iface->is_pending) + return true; + + if (iface->is_template) + return false; + if (lifname == NULL) lifname = iface->ifname; @@ -321,6 +449,9 @@ lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *ifa * but, right now neither debian ifupdown or busybox ifupdown do any recovery, * so we wont right now. */ + if (!lif_lifecycle_run_phase(opts, iface, "create", lifname, up)) + return false; + if (!lif_lifecycle_run_phase(opts, iface, "pre-up", lifname, up)) return false; @@ -330,9 +461,7 @@ lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *ifa if (!lif_lifecycle_run_phase(opts, iface, "post-up", lifname, up)) return false; - lif_state_upsert(state, lifname, iface); - - iface->is_up = true; + lif_state_ref_if(state, lifname, iface); } else { @@ -345,14 +474,126 @@ lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *ifa if (!lif_lifecycle_run_phase(opts, iface, "post-down", lifname, up)) return false; + if (!lif_lifecycle_run_phase(opts, iface, "destroy", lifname, up)) + return false; + /* when going up, dependents go down last. */ if (!handle_dependents(opts, iface, collection, state, up)) return false; - lif_state_delete(state, lifname); - - iface->is_up = false; + lif_state_unref_if(state, lifname, iface); } return true; } + +static bool +count_interface_rdepends(const struct lif_execute_opts *opts, struct lif_dict *collection, struct lif_interface *parent, size_t depth) +{ + /* if we have looped, return true immediately to break the loop. */ + if (parent->is_pending) + return true; + + /* query our dependents if we don't have them already */ + if (!lif_lifecycle_query_dependents(opts, parent, parent->ifname)) + return false; + + /* set rdepends_count to depth, dependents will be depth + 1 */ + parent->is_pending = true; + parent->rdepends_count = depth; + + struct lif_dict_entry *requires = lif_dict_find(&parent->vars, "requires"); + + /* no dependents, nothing to worry about */ + if (requires == NULL) + { + parent->is_pending = false; + return true; + } + + /* walk any dependents */ + char require_ifs[4096] = {}; + strlcpy(require_ifs, requires->data, sizeof require_ifs); + char *bufp = require_ifs; + + for (char *tokenp = lif_next_token(&bufp); *tokenp; tokenp = lif_next_token(&bufp)) + { + struct lif_interface *iface = lif_interface_collection_find(collection, tokenp); + + if (!count_interface_rdepends(opts, collection, iface, depth + 1)) + { + parent->is_pending = false; + return false; + } + } + + parent->is_pending = false; + return true; +} + +ssize_t +lif_lifecycle_count_rdepends(const struct lif_execute_opts *opts, struct lif_dict *collection) +{ + struct lif_node *iter; + + LIF_DICT_FOREACH(iter, collection) + { + struct lif_dict_entry *entry = iter->data; + struct lif_interface *iface = entry->data; + + /* start depth at interface's rdepends_count, which will be 0 for the root, + * but will be more if additional rdepends are found... + */ + if (!count_interface_rdepends(opts, collection, iface, iface->rdepends_count)) + { + fprintf(stderr, "ifupdown: dependency graph is broken for interface %s\n", iface->ifname); + return -1; + } + } + + /* figure out the max depth */ + size_t maxdepth = 0; + + LIF_DICT_FOREACH(iter, collection) + { + struct lif_dict_entry *entry = iter->data; + struct lif_interface *iface = entry->data; + + if (iface->rdepends_count > maxdepth) + maxdepth = iface->rdepends_count; + } + + /* move the collection to a temporary list so we can reorder it */ + struct lif_list temp_list = {}; + struct lif_node *iter_next; + + LIF_LIST_FOREACH_SAFE(iter, iter_next, collection->list.head) + { + void *data = iter->data; + + lif_node_delete(iter, &collection->list); + memset(iter, 0, sizeof *iter); + + lif_node_insert(iter, data, &temp_list); + } + + /* walk backwards from maxdepth to 0, readding nodes */ + for (ssize_t curdepth = maxdepth; curdepth > -1; curdepth--) + { + LIF_LIST_FOREACH_SAFE(iter, iter_next, temp_list.head) + { + struct lif_dict_entry *entry = iter->data; + struct lif_interface *iface = entry->data; + + if ((ssize_t) iface->rdepends_count != curdepth) + continue; + + lif_node_delete(iter, &temp_list); + memset(iter, 0, sizeof *iter); + + lif_node_insert(iter, entry, &collection->list); + } + } + + return maxdepth; +} diff --git a/libifupdown/lifecycle.h b/libifupdown/lifecycle.h index e5b0adc..fecf9fe 100644 --- a/libifupdown/lifecycle.h +++ b/libifupdown/lifecycle.h @@ -19,8 +19,10 @@ #include "libifupdown/interface.h" #include "libifupdown/execute.h" +extern bool lif_lifecycle_query_dependents(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *lifname); extern bool lif_lifecycle_run_phase(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *phase, const char *lifname, bool up); extern bool lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *iface, struct lif_dict *collection, struct lif_dict *state, const char *lifname, bool up); +extern ssize_t lif_lifecycle_count_rdepends(const struct lif_execute_opts *opts, struct lif_dict *collection); #endif diff --git a/libifupdown/list.c b/libifupdown/list.c index 6a44f86..6ee43fe 100644 --- a/libifupdown/list.c +++ b/libifupdown/list.c @@ -3,6 +3,7 @@ * Purpose: linked lists * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,8 +15,25 @@ */ #include +#include #include "libifupdown/list.h" +void +lif_list_free_nodes(struct lif_list *list) +{ + if (list == NULL) + return; + + struct lif_node *iter, *iter_next; + + LIF_LIST_FOREACH_SAFE(iter, iter_next, list->head) + { + free (iter); + } + + free (list); +} + void lif_node_insert(struct lif_node *node, void *data, struct lif_list *list) { diff --git a/libifupdown/list.h b/libifupdown/list.h index 326eca0..c029038 100644 --- a/libifupdown/list.h +++ b/libifupdown/list.h @@ -3,6 +3,7 @@ * Purpose: linked lists * * Copyright (c) 2020 Ariadne Conill + * Copyright (c) 2020 Maximilian Wilhelm * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,6 +30,8 @@ struct lif_list { size_t length; }; +extern void lif_list_free_nodes(struct lif_list *list); + extern void lif_node_insert(struct lif_node *node, void *data, struct lif_list *list); extern void lif_node_insert_tail(struct lif_node *node, void *data, struct lif_list *list); extern void lif_node_delete(struct lif_node *node, struct lif_list *list); @@ -37,6 +40,9 @@ extern void lif_node_delete(struct lif_node *node, struct lif_list *list); for ((iter) = (head); (iter) != NULL; (iter) = (iter)->next) #define LIF_LIST_FOREACH_SAFE(iter, iter_next, head) \ - for ((iter) = (head), (iter_next) = (iter)->next; (iter) != NULL; (iter) = (iter_next), (iter_next) = (iter) != NULL ? (iter)->next : NULL) + for ((iter) = (head), (iter_next) = (iter) != NULL ? (iter)->next : NULL; (iter) != NULL; (iter) = (iter_next), (iter_next) = (iter) != NULL ? (iter)->next : NULL) + +#define LIF_LIST_FOREACH_REVERSE(iter, tail) \ + for ((iter) = (tail); (iter) != NULL; (iter) = (iter)->prev) #endif diff --git a/libifupdown/state.c b/libifupdown/state.c index 89ad105..05701ce 100644 --- a/libifupdown/state.c +++ b/libifupdown/state.c @@ -13,27 +13,46 @@ * from the use of this software. */ +#include #include #include "libifupdown/state.h" #include "libifupdown/fgetline.h" +#include "libifupdown/tokenize.h" bool lif_state_read(struct lif_dict *state, FILE *fd) { char linebuf[4096]; + while (lif_fgetline(linebuf, sizeof linebuf, fd)) { - char *ifname = linebuf; + char *bufp = linebuf; + char *ifname = lif_next_token(&bufp); + char *refcount = lif_next_token(&bufp); + char *explicit = lif_next_token(&bufp); + size_t rc = 1; char *equals_p = strchr(linebuf, '='); + bool is_explicit = false; + + if (*explicit && !strcmp(explicit, "explicit")) + is_explicit = true; + + if (*refcount) + { + rc = strtoul(refcount, NULL, 10); + + if (rc == 0 || rc == ULONG_MAX) + rc = 1; + } if (equals_p == NULL) { - lif_state_upsert(state, ifname, &(struct lif_interface){ .ifname = ifname }); + lif_state_upsert(state, ifname, &(struct lif_interface){ .ifname = ifname, .refcount = rc, .is_explicit = is_explicit }); continue; } *equals_p++ = '\0'; - lif_state_upsert(state, ifname, &(struct lif_interface){ .ifname = equals_p }); + lif_state_upsert(state, ifname, &(struct lif_interface){ .ifname = equals_p, .refcount = rc, .is_explicit = is_explicit }); } return true; @@ -45,8 +64,9 @@ lif_state_read_path(struct lif_dict *state, const char *path) FILE *fd = fopen(path, "r"); bool ret; + /* if file cannot be opened, assume an empty state */ if (fd == NULL) - return false; + return true; ret = lif_state_read(state, fd); fclose(fd); @@ -54,10 +74,39 @@ lif_state_read_path(struct lif_dict *state, const char *path) return ret; } +void +lif_state_ref_if(struct lif_dict *state, const char *ifname, struct lif_interface *iface) +{ + iface->refcount++; + lif_state_upsert(state, ifname, iface); +} + +void +lif_state_unref_if(struct lif_dict *state, const char *ifname, struct lif_interface *iface) +{ + if (iface->refcount == 0) + return; + + iface->refcount--; + + if (iface->refcount) + lif_state_upsert(state, ifname, iface); + else + lif_state_delete(state, ifname); +} + void lif_state_upsert(struct lif_dict *state, const char *ifname, struct lif_interface *iface) { - lif_dict_add(state, ifname, strdup(iface->ifname)); + lif_state_delete(state, ifname); + + struct lif_state_record *rec = calloc(1, sizeof(*rec)); + + rec->mapped_if = strdup(iface->ifname); + rec->refcount = iface->refcount; + rec->is_explicit = iface->is_explicit; + + lif_dict_add(state, ifname, rec); } void @@ -68,7 +117,10 @@ lif_state_delete(struct lif_dict *state, const char *ifname) if (entry == NULL) return; - free(entry->data); + struct lif_state_record *rec = entry->data; + free(rec->mapped_if); + free(rec); + lif_dict_delete_entry(state, entry); } @@ -80,8 +132,10 @@ lif_state_write(const struct lif_dict *state, FILE *f) LIF_DICT_FOREACH(iter, state) { struct lif_dict_entry *entry = iter->data; + struct lif_state_record *rec = entry->data; - fprintf(f, "%s=%s\n", entry->key, (const char *) entry->data); + fprintf(f, "%s=%s %zu%s\n", entry->key, rec->mapped_if, rec->refcount, + rec->is_explicit ? " explicit" : ""); } } @@ -107,7 +161,8 @@ lif_state_lookup(struct lif_dict *state, struct lif_dict *if_collection, const c if (entry == NULL) return NULL; - struct lif_dict_entry *if_entry = lif_dict_find(if_collection, (const char *) entry->data); + struct lif_state_record *rec = entry->data; + struct lif_dict_entry *if_entry = lif_dict_find(if_collection, rec->mapped_if); if (if_entry == NULL) return NULL; @@ -123,9 +178,11 @@ lif_state_sync(struct lif_dict *state, struct lif_dict *if_collection) LIF_DICT_FOREACH(iter, state) { struct lif_dict_entry *entry = iter->data; - struct lif_interface *iface = lif_interface_collection_find(if_collection, entry->key); + struct lif_state_record *rec = entry->data; + struct lif_interface *iface = lif_interface_collection_find(if_collection, rec->mapped_if); - iface->is_up = true; + iface->refcount = rec->refcount; + iface->is_explicit = rec->is_explicit; } return true; diff --git a/libifupdown/state.h b/libifupdown/state.h index 584deca..ae2ba5c 100644 --- a/libifupdown/state.h +++ b/libifupdown/state.h @@ -17,11 +17,21 @@ #define LIBIFUPDOWN_STATE_H__GUARD #include +#include #include "libifupdown/interface.h" +struct lif_state_record { + char *mapped_if; + size_t refcount; + + bool is_explicit; +}; + extern bool lif_state_read(struct lif_dict *state, FILE *f); extern bool lif_state_read_path(struct lif_dict *state, const char *path); extern void lif_state_upsert(struct lif_dict *state, const char *ifname, struct lif_interface *iface); +extern void lif_state_ref_if(struct lif_dict *state, const char *ifname, struct lif_interface *iface); +extern void lif_state_unref_if(struct lif_dict *state, const char *ifname, struct lif_interface *iface); extern void lif_state_delete(struct lif_dict *state, const char *ifname); extern void lif_state_write(const struct lif_dict *state, FILE *f); extern bool lif_state_write_path(const struct lif_dict *state, const char *path); diff --git a/libifupdown/tokenize.h b/libifupdown/tokenize.h index 751c2f2..f0aed3d 100644 --- a/libifupdown/tokenize.h +++ b/libifupdown/tokenize.h @@ -18,6 +18,24 @@ #include +static inline char * +lif_next_token_eq(char **buf) +{ + char *out = *buf; + + while (*out && (isspace(*out) || *out == '=')) + out++; + + char *end = out; + while (*end && !isspace(*end) && *end != '=') + end++; + + *end++ = '\0'; + *buf = end; + + return out; +} + static inline char * lif_next_token(char **buf) { diff --git a/libifupdown/version.c b/libifupdown/version.c index 1d8ea08..8f2bb47 100644 --- a/libifupdown/version.c +++ b/libifupdown/version.c @@ -21,19 +21,20 @@ void lif_common_version(void) { - printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); - - printf("\nCopyright (c) 2020 Ariadne Conill \n\n"); - - printf("Permission to use, copy, modify, and/or distribute this software for any\n"); - printf("purpose with or without fee is hereby granted, provided that the above\n"); - printf("copyright notice and this permission notice appear in all copies.\n\n"); - - printf("This software is provided 'as is' and without any warranty, express or\n"); - printf("implied. In no event shall the authors be liable for any damages arising\n"); - printf("from the use of this software.\n\n"); - - printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT); + printf(PACKAGE_NAME " " PACKAGE_VERSION "\n" + "\n" + "Copyright (c) 2020 Ariadne Conill \n" + "Copyright (c) 2020 Maximilian Wilhelm \n" + "\n" + "Permission to use, copy, modify, and/or distribute this software for any\n" + "purpose with or without fee is hereby granted, provided that the above\n" + "copyright notice and this permission notice appear in all copies.\n" + "\n" + "This software is provided 'as is' and without any warranty, express or\n" + "implied. In no event shall the authors be liable for any damages arising\n" + "from the use of this software.\n" + "\n" + "Report bugs at <" PACKAGE_BUGREPORT ">.\n"); exit(EXIT_SUCCESS); } diff --git a/libifupdown/yaml-base.c b/libifupdown/yaml-base.c new file mode 100644 index 0000000..58c4cbc --- /dev/null +++ b/libifupdown/yaml-base.c @@ -0,0 +1,127 @@ +/* + * libifupdown/yaml-base.c + * Purpose: YAML implementation -- base + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include "libifupdown/libifupdown.h" +#include "libifupdown/yaml-base.h" + +void +lif_yaml_document_init(struct lif_yaml_node *doc, const char *name) +{ + memset(doc, '\0', sizeof *doc); + doc->value_type = LIF_YAML_OBJECT; + + if (name != NULL) + doc->name = strdup(name); +} + +struct lif_yaml_node * +lif_yaml_document_new(const char *name) +{ + struct lif_yaml_node *doc = calloc(1, sizeof *doc); + + lif_yaml_document_init(doc, name); + doc->malloced = true; + + return doc; +} + +struct lif_yaml_node * +lif_yaml_node_new_boolean(const char *name, bool value) +{ + struct lif_yaml_node *node = calloc(1, sizeof *node); + + node->malloced = true; + node->value_type = LIF_YAML_BOOLEAN; + + if (name != NULL) + node->name = strdup(name); + + node->value.bool_value = value; + + return node; +} + +struct lif_yaml_node * +lif_yaml_node_new_string(const char *name, const char *value) +{ + struct lif_yaml_node *node = calloc(1, sizeof *node); + + node->malloced = true; + node->value_type = LIF_YAML_STRING; + + if (name != NULL) + node->name = strdup(name); + + if (value != NULL) + node->value.str_value = strdup(value); + + return node; +} + +struct lif_yaml_node * +lif_yaml_node_new_object(const char *name) +{ + struct lif_yaml_node *node = calloc(1, sizeof *node); + + node->malloced = true; + node->value_type = LIF_YAML_OBJECT; + + if (name != NULL) + node->name = strdup(name); + + return node; +} + +struct lif_yaml_node * +lif_yaml_node_new_list(const char *name) +{ + struct lif_yaml_node *node = calloc(1, sizeof *node); + + node->malloced = true; + node->value_type = LIF_YAML_LIST; + + if (name != NULL) + node->name = strdup(name); + + return node; +} + +void +lif_yaml_node_free(struct lif_yaml_node *node) +{ + struct lif_node *iter, *next; + + LIF_LIST_FOREACH_SAFE(iter, next, node->children.head) + { + struct lif_yaml_node *iter_node = iter->data; + + lif_yaml_node_free(iter_node); + } + + free(node->name); + + if (node->value_type == LIF_YAML_STRING) + free(node->value.str_value); + + if (node->malloced) + free(node); +} + +void +lif_yaml_node_append_child(struct lif_yaml_node *parent, struct lif_yaml_node *child) +{ + lif_node_insert_tail(&child->node, child, &parent->children); +} diff --git a/libifupdown/yaml-base.h b/libifupdown/yaml-base.h new file mode 100644 index 0000000..5c7c9ce --- /dev/null +++ b/libifupdown/yaml-base.h @@ -0,0 +1,52 @@ +/* + * libifupdown/yaml-base.h + * Purpose: YAML implementation -- base + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBIFUPDOWN_YAML_BASE_H__GUARD +#define LIBIFUPDOWN_YAML_BASE_H__GUARD + +#include "libifupdown/libifupdown.h" + +/* this is a subset of types supported by our implementation */ +enum lif_yaml_value { + LIF_YAML_STRING, + LIF_YAML_LIST, + LIF_YAML_OBJECT, + LIF_YAML_BOOLEAN +}; + +struct lif_yaml_node { + struct lif_node node; + + bool malloced; + char *name; + enum lif_yaml_value value_type; + union { + char *str_value; /* for string nodes */ + bool bool_value; /* for boolean nodes */ + } value; + struct lif_list children; /* for list and object nodes */ +}; + +extern void lif_yaml_document_init(struct lif_yaml_node *doc, const char *name); +extern struct lif_yaml_node *lif_yaml_document_new(const char *name); + +extern struct lif_yaml_node *lif_yaml_node_new_boolean(const char *name, bool value); +extern struct lif_yaml_node *lif_yaml_node_new_string(const char *name, const char *value); +extern struct lif_yaml_node *lif_yaml_node_new_object(const char *name); +extern struct lif_yaml_node *lif_yaml_node_new_list(const char *name); +extern void lif_yaml_node_free(struct lif_yaml_node *node); +extern void lif_yaml_node_append_child(struct lif_yaml_node *parent, struct lif_yaml_node *child); + +#endif diff --git a/libifupdown/yaml-writer.c b/libifupdown/yaml-writer.c new file mode 100644 index 0000000..6f6df94 --- /dev/null +++ b/libifupdown/yaml-writer.c @@ -0,0 +1,66 @@ +/* + * libifupdown/yaml-writer.c + * Purpose: YAML implementation -- writer + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include "libifupdown/libifupdown.h" +#include "libifupdown/yaml-base.h" +#include "libifupdown/yaml-writer.h" + +static const size_t INDENT_WIDTH = 2; + +static void +lif_yaml_write_node(const struct lif_yaml_node *node, FILE *f, size_t indent, bool type_annotations) +{ + const struct lif_node *iter; + + if (node->name != NULL) + fprintf(f, "%*s%s: ", (int) indent, "", node->name); + + size_t child_indent = indent + INDENT_WIDTH; + + switch (node->value_type) + { + case LIF_YAML_BOOLEAN: + fprintf(f, "%s%s\n", type_annotations ? "!!bool " : "", node->value.bool_value ? "true" : "false"); + break; + case LIF_YAML_STRING: + fprintf(f, "%s%s\n", type_annotations ? "!!str " : "", node->value.str_value); + break; + case LIF_YAML_OBJECT: + fprintf(f, "\n"); + break; + case LIF_YAML_LIST: + fprintf(f, "\n"); + child_indent += INDENT_WIDTH; + break; + } + + LIF_LIST_FOREACH(iter, node->children.head) + { + const struct lif_yaml_node *iter_node = iter->data; + + if (node->value_type == LIF_YAML_LIST) + fprintf(f, "%*s-\n", (int) (child_indent - INDENT_WIDTH), ""); + + lif_yaml_write_node(iter_node, f, child_indent, type_annotations); + } +} + +void +lif_yaml_write(const struct lif_yaml_node *doc, FILE *f, bool type_annotations) +{ + lif_yaml_write_node(doc, f, 0, type_annotations); +} diff --git a/libifupdown/yaml-writer.h b/libifupdown/yaml-writer.h new file mode 100644 index 0000000..7772dc8 --- /dev/null +++ b/libifupdown/yaml-writer.h @@ -0,0 +1,24 @@ +/* + * libifupdown/yaml-writer.h + * Purpose: YAML implementation -- writer + * + * Copyright (c) 2020 Ariadne Conill + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBIFUPDOWN_YAML_WRITER_H__GUARD +#define LIBIFUPDOWN_YAML_WRITER_H__GUARD + +#include "libifupdown/libifupdown.h" +#include "libifupdown/yaml-base.h" + +extern void lif_yaml_write(const struct lif_yaml_node *doc, FILE *f, bool type_annotations); + +#endif diff --git a/tests/Kyuafile b/tests/Kyuafile index 784a85b..6a8019b 100644 --- a/tests/Kyuafile +++ b/tests/Kyuafile @@ -2,6 +2,9 @@ syntax(2) test_suite('ifupdown-ng') +atf_test_program{name='multicall_test'} atf_test_program{name='ifquery_test'} atf_test_program{name='ifup_test'} atf_test_program{name='ifdown_test'} + +include('linux/Kyuafile') diff --git a/tests/executors/bond b/tests/executors/bond new file mode 120000 index 0000000..1322f47 --- /dev/null +++ b/tests/executors/bond @@ -0,0 +1 @@ +mock-executor \ No newline at end of file diff --git a/tests/executors/bridge b/tests/executors/bridge new file mode 120000 index 0000000..1322f47 --- /dev/null +++ b/tests/executors/bridge @@ -0,0 +1 @@ +mock-executor \ No newline at end of file diff --git a/tests/executors/dhcp b/tests/executors/dhcp new file mode 120000 index 0000000..1322f47 --- /dev/null +++ b/tests/executors/dhcp @@ -0,0 +1 @@ +mock-executor \ No newline at end of file diff --git a/tests/executors/link b/tests/executors/link new file mode 120000 index 0000000..1322f47 --- /dev/null +++ b/tests/executors/link @@ -0,0 +1 @@ +mock-executor \ No newline at end of file diff --git a/tests/executors/mock-dependency-generator b/tests/executors/mock-dependency-generator new file mode 100755 index 0000000..040035e --- /dev/null +++ b/tests/executors/mock-dependency-generator @@ -0,0 +1,7 @@ +#!/bin/sh + +[ -z "$IF_MOCK_DEPENDS" ] && IF_MOCK_DEPENDS="eth0 eth1 eth2 eth3 eth4" + +case "$PHASE" in +depend) echo "$IF_MOCK_DEPENDS" ;; +esac diff --git a/tests/executors/mock-executor b/tests/executors/mock-executor new file mode 100755 index 0000000..039e4d0 --- /dev/null +++ b/tests/executors/mock-executor @@ -0,0 +1,2 @@ +#!/bin/sh +exit 0 diff --git a/tests/executors/static b/tests/executors/static new file mode 120000 index 0000000..1322f47 --- /dev/null +++ b/tests/executors/static @@ -0,0 +1 @@ +mock-executor \ No newline at end of file diff --git a/tests/executors/vlan b/tests/executors/vlan new file mode 100755 index 0000000..ba37599 --- /dev/null +++ b/tests/executors/vlan @@ -0,0 +1,7 @@ +#!/bin/sh +case "$PHASE" in +depend) + RAW_DEVICE="${IFACE%.*}" + echo "$RAW_DEVICE" + ;; +esac diff --git a/tests/fixtures/deferred-teardown-1.ifstate b/tests/fixtures/deferred-teardown-1.ifstate new file mode 100644 index 0000000..188647b --- /dev/null +++ b/tests/fixtures/deferred-teardown-1.ifstate @@ -0,0 +1,6 @@ +lo=lo 1 +br0=br0 1 +bond0=bond0 2 +eth0=eth0 3 +eth1=eth1 2 +tun0=tun0 1 diff --git a/tests/fixtures/deferred-teardown-1.interfaces b/tests/fixtures/deferred-teardown-1.interfaces new file mode 100644 index 0000000..61930e2 --- /dev/null +++ b/tests/fixtures/deferred-teardown-1.interfaces @@ -0,0 +1,9 @@ +auto br0 +iface br0 + requires bond0 + +iface bond0 + requires eth0 eth1 + +iface tun0 + requires eth0 diff --git a/tests/fixtures/deferred-teardown-2.ifstate b/tests/fixtures/deferred-teardown-2.ifstate new file mode 100644 index 0000000..23f77d7 --- /dev/null +++ b/tests/fixtures/deferred-teardown-2.ifstate @@ -0,0 +1,5 @@ +eth0=eth0 5 +tun0=tun0 1 +tun1=tun1 1 +tun2=tun2 1 +tun3=tun3 1 diff --git a/tests/fixtures/deferred-teardown-2.interfaces b/tests/fixtures/deferred-teardown-2.interfaces new file mode 100644 index 0000000..b7ecc98 --- /dev/null +++ b/tests/fixtures/deferred-teardown-2.interfaces @@ -0,0 +1,11 @@ +iface tun0 + requires eth0 + +iface tun1 + requires eth0 + +iface tun2 + requires eth0 + +iface tun3 + requires eth0 diff --git a/tests/fixtures/dependency-loop.ifstate b/tests/fixtures/dependency-loop.ifstate new file mode 100644 index 0000000..4b7a3ef --- /dev/null +++ b/tests/fixtures/dependency-loop.ifstate @@ -0,0 +1,3 @@ +lo=lo +a=a +b=b diff --git a/tests/fixtures/dependency-loop.interfaces b/tests/fixtures/dependency-loop.interfaces new file mode 100644 index 0000000..e164928 --- /dev/null +++ b/tests/fixtures/dependency-loop.interfaces @@ -0,0 +1,9 @@ +auto a +iface a + use link + requires b + +auto b +iface b + use link + requires a diff --git a/tests/fixtures/dhcp-hostname-rewrite.interfaces b/tests/fixtures/dhcp-hostname-rewrite.interfaces new file mode 100644 index 0000000..0aa4f71 --- /dev/null +++ b/tests/fixtures/dhcp-hostname-rewrite.interfaces @@ -0,0 +1,10 @@ +iface eth0 + use dhcp + hostname foo + +iface eth1 + use dhcp + +iface eth2 + use dhcp + dhcp-hostname bar \ No newline at end of file diff --git a/tests/fixtures/gre.interfaces b/tests/fixtures/gre.interfaces new file mode 100644 index 0000000..a53ebec --- /dev/null +++ b/tests/fixtures/gre.interfaces @@ -0,0 +1,12 @@ +auto tun0 +iface tun0 + gre-local 203.0.113.2 + gre-remote 203.0.113.1 + gre-dev eth0 + gre-ttl 255 + gre-flags nopmtudisc ignore-df + address 203.0.113.10/30 + gateway 203.0.113.9 + +iface eth0 + address 203.0.113.2/30 diff --git a/tests/fixtures/inheritance.interfaces b/tests/fixtures/inheritance.interfaces new file mode 100644 index 0000000..8b5f487 --- /dev/null +++ b/tests/fixtures/inheritance.interfaces @@ -0,0 +1,10 @@ +iface base0 + address 203.0.113.2/24 + address 2001:db8:1000:2::2/64 + +iface inherit0 inherits base0 + address 203.0.113.3/24 + +iface inherit1 + inherit base0 + address 203.0.113.4/24 diff --git a/tests/fixtures/mock-dependency-generator-2.ifstate b/tests/fixtures/mock-dependency-generator-2.ifstate new file mode 100644 index 0000000..6320c87 --- /dev/null +++ b/tests/fixtures/mock-dependency-generator-2.ifstate @@ -0,0 +1,4 @@ +eth0=eth0 +eth1=eth1 +bond0=bond0 +br0=br0 diff --git a/tests/fixtures/mock-dependency-generator-2.interfaces b/tests/fixtures/mock-dependency-generator-2.interfaces new file mode 100644 index 0000000..1f5708d --- /dev/null +++ b/tests/fixtures/mock-dependency-generator-2.interfaces @@ -0,0 +1,7 @@ +iface br0 + use mock-dependency-generator + mock-depends bond0 + +iface bond0 + use mock-dependency-generator + mock-depends eth0 eth1 diff --git a/tests/fixtures/mock-dependency-generator.ifstate b/tests/fixtures/mock-dependency-generator.ifstate new file mode 100644 index 0000000..39cf8ca --- /dev/null +++ b/tests/fixtures/mock-dependency-generator.ifstate @@ -0,0 +1,6 @@ +eth0=eth0 +eth1=eth1 +eth2=eth2 +eth3=eth3 +eth4=eth4 +br0=br0 diff --git a/tests/fixtures/mock-dependency-generator.interfaces b/tests/fixtures/mock-dependency-generator.interfaces new file mode 100644 index 0000000..d07127f --- /dev/null +++ b/tests/fixtures/mock-dependency-generator.interfaces @@ -0,0 +1,2 @@ +iface br0 + use mock-dependency-generator diff --git a/tests/fixtures/ppp-legacy.interfaces b/tests/fixtures/ppp-legacy.interfaces new file mode 100644 index 0000000..8a83490 --- /dev/null +++ b/tests/fixtures/ppp-legacy.interfaces @@ -0,0 +1,4 @@ +auto ppp0 +iface ppp0 inet ppp + requires eth0 + provider someisp diff --git a/tests/fixtures/ppp.interfaces b/tests/fixtures/ppp.interfaces new file mode 100644 index 0000000..4d4cb9b --- /dev/null +++ b/tests/fixtures/ppp.interfaces @@ -0,0 +1,4 @@ +auto ppp0 +iface ppp0 + requires eth0 + ppp-provider someisp diff --git a/tests/fixtures/stanza-merging.interfaces b/tests/fixtures/stanza-merging.interfaces new file mode 100644 index 0000000..6327cbc --- /dev/null +++ b/tests/fixtures/stanza-merging.interfaces @@ -0,0 +1,14 @@ +# cidr and without-cidr should be equivalent +iface cidr + address 203.0.113.1/32 + +iface cidr + address 203.0.113.2/24 + +iface without-cidr + address 203.0.113.1 + netmask 32 + +iface without-cidr + address 203.0.113.2 + netmask 24 diff --git a/tests/fixtures/static-eth0-ptp.interfaces b/tests/fixtures/static-eth0-ptp.interfaces new file mode 100644 index 0000000..e6a3f53 --- /dev/null +++ b/tests/fixtures/static-eth0-ptp.interfaces @@ -0,0 +1,7 @@ +auto eth0 +iface eth0 + address 203.0.113.2/32 + pointopoint 192.0.2.1 + gateway 192.0.2.1 + address 2001:db8:1000:2::2/64 + gateway 2001:db8:1000:2::1 diff --git a/tests/fixtures/static-eth0-v4-netmask.interfaces b/tests/fixtures/static-eth0-v4-netmask.interfaces new file mode 100644 index 0000000..e9207e8 --- /dev/null +++ b/tests/fixtures/static-eth0-v4-netmask.interfaces @@ -0,0 +1,5 @@ +auto eth0 +iface eth0 + address 203.0.113.2 + netmask 255.255.255.248 + gateway 203.0.113.1 diff --git a/tests/fixtures/static-eth0-v6-netmask.interfaces b/tests/fixtures/static-eth0-v6-netmask.interfaces new file mode 100644 index 0000000..3707e80 --- /dev/null +++ b/tests/fixtures/static-eth0-v6-netmask.interfaces @@ -0,0 +1,5 @@ +auto eth0 +iface eth0 + address 2001:db8:1000:2::2 + netmask 112 + gateway 2001:db8:1000:2::1 diff --git a/tests/fixtures/teardown-dep-ordering.ifstate b/tests/fixtures/teardown-dep-ordering.ifstate new file mode 100644 index 0000000..aef421a --- /dev/null +++ b/tests/fixtures/teardown-dep-ordering.ifstate @@ -0,0 +1,3 @@ +dummy=dummy 2 +bat=bat 2 +br=br 1 diff --git a/tests/fixtures/teardown-dep-ordering.interfaces b/tests/fixtures/teardown-dep-ordering.interfaces new file mode 100644 index 0000000..551ad3d --- /dev/null +++ b/tests/fixtures/teardown-dep-ordering.interfaces @@ -0,0 +1,13 @@ +auto dummy +iface dummy + link-type dummy + +auto bat +iface bat + link-type dummy + requires dummy + +auto br +iface br + link-type dummy + requires bat diff --git a/tests/fixtures/tunnel-ifupdown2.interfaces b/tests/fixtures/tunnel-ifupdown2.interfaces new file mode 100644 index 0000000..f38e139 --- /dev/null +++ b/tests/fixtures/tunnel-ifupdown2.interfaces @@ -0,0 +1,12 @@ +auto tun0 +iface tun0 + tunnel-local 203.0.113.2 + tunnel-endpoint 203.0.113.1 + tunnel-mode gre + tunnel-physdev eth0 + tunnel-ttl 255 + address 203.0.113.10/30 + gateway 203.0.113.9 + +iface eth0 + address 203.0.113.2/30 diff --git a/tests/fixtures/tunnel-legacy.interfaces b/tests/fixtures/tunnel-legacy.interfaces new file mode 100644 index 0000000..c20ecf7 --- /dev/null +++ b/tests/fixtures/tunnel-legacy.interfaces @@ -0,0 +1,12 @@ +auto tun0 +iface tun0 + local 203.0.113.2 + endpoint 203.0.113.1 + mode gre + tunnel-dev eth0 + ttl 255 + address 203.0.113.10/30 + gateway 203.0.113.9 + +iface eth0 + address 203.0.113.2/30 diff --git a/tests/fixtures/tunnel.interfaces b/tests/fixtures/tunnel.interfaces new file mode 100644 index 0000000..12a081b --- /dev/null +++ b/tests/fixtures/tunnel.interfaces @@ -0,0 +1,12 @@ +auto tun0 +iface tun0 + tunnel-local 203.0.113.2 + tunnel-remote 203.0.113.1 + tunnel-mode gre + tunnel-dev eth0 + tunnel-ttl 255 + address 203.0.113.10/30 + gateway 203.0.113.9 + +iface eth0 + address 203.0.113.2/30 diff --git a/tests/fixtures/vlan-complex.interfaces b/tests/fixtures/vlan-complex.interfaces new file mode 100644 index 0000000..e06b617 --- /dev/null +++ b/tests/fixtures/vlan-complex.interfaces @@ -0,0 +1,18 @@ +# From Alpine issue #11885. +iface lo inet loopback + +auto eth0 +iface eth0 + address 1.2.3.4/24 + address abcd:ef12:3456:3::4/64 + mtu 8000 + +auto servers +iface servers + address 1.2.10.4/24 + gateway 1.2.10.1 + address abcd:ef12:3456:10::4/64 + gateway abcd:ef12:3456:10::1 + mtu 8000 + vlan-raw-device eth0 + vlan_id 5 diff --git a/tests/fixtures/vlan-named.interfaces b/tests/fixtures/vlan-named.interfaces new file mode 100644 index 0000000..73e8ae2 --- /dev/null +++ b/tests/fixtures/vlan-named.interfaces @@ -0,0 +1,4 @@ +iface servers + vlan-raw-device eth0 + vlan-id 8 + address 2001:db8:1000:2::2/64 diff --git a/tests/fixtures/vlan.ifstate b/tests/fixtures/vlan.ifstate new file mode 100644 index 0000000..e360c74 --- /dev/null +++ b/tests/fixtures/vlan.ifstate @@ -0,0 +1,2 @@ +eth0=eth0 +eth0.8=eth0.8 diff --git a/tests/fixtures/vlan.interfaces b/tests/fixtures/vlan.interfaces new file mode 100644 index 0000000..398c848 --- /dev/null +++ b/tests/fixtures/vlan.interfaces @@ -0,0 +1,2 @@ +iface eth0.8 + address 2001:db8:1000:2::2/64 diff --git a/tests/fixtures/vrf-ifupdown2.interfaces b/tests/fixtures/vrf-ifupdown2.interfaces new file mode 100644 index 0000000..242cba5 --- /dev/null +++ b/tests/fixtures/vrf-ifupdown2.interfaces @@ -0,0 +1,7 @@ +iface vrf-red + vrf-table 1 + +auto eth0 +iface eth0 + use dhcp + vrf vrf-red diff --git a/tests/fixtures/vrf.interfaces b/tests/fixtures/vrf.interfaces new file mode 100644 index 0000000..fc40df2 --- /dev/null +++ b/tests/fixtures/vrf.interfaces @@ -0,0 +1,8 @@ +iface vrf-red + vrf-table 1 + gateway 203.0.113.2 + +auto eth0 +iface eth0 + use dhcp + vrf-member vrf-red diff --git a/tests/fixtures/wireguard.interfaces b/tests/fixtures/wireguard.interfaces new file mode 100644 index 0000000..fed6429 --- /dev/null +++ b/tests/fixtures/wireguard.interfaces @@ -0,0 +1,9 @@ +iface eth0 + address 203.0.113.2/24 + gateway 203.0.113.1 + +auto wg0 +iface wg0 + use wireguard + address 1.2.3.4/24 + requires eth0 diff --git a/tests/fixtures/without-netmask.interfaces b/tests/fixtures/without-netmask.interfaces new file mode 100644 index 0000000..7537c67 --- /dev/null +++ b/tests/fixtures/without-netmask.interfaces @@ -0,0 +1,5 @@ +iface v6 + address 2001:470:1f10::1 + +iface v4 + address 203.0.113.2 diff --git a/tests/ifdown_test b/tests/ifdown_test index 8813359..fb6cf8e 100755 --- a/tests/ifdown_test +++ b/tests/ifdown_test @@ -7,96 +7,188 @@ tests_init \ lo_always_auto \ dual_stack \ static_ipv4 \ + static_ipv4_netmask \ static_ipv6 \ + static_ipv6_netmask \ inet_dhcp \ use_dhcp \ alias_eth0_home \ alias_eth0_work \ - bonded_bridge + bonded_bridge \ + learned_dependency \ + learned_dependency_2 \ + learned_executor \ + implicit_vlan \ + deferred_teardown_1 \ + deferred_teardown_2 \ + deferred_teardown_3 \ + teardown_dep_ordering \ + regress_opt_f \ + dependency_loop_breaking noargs_body() { atf_check -s exit:1 -e ignore ifdown -S/dev/null } lo_always_auto_body() { - atf_check -s exit:0 -e ignore -o match:'ip link set down dev lo' \ - ifdown -S/dev/null -i/dev/null -n -a + atf_check -s exit:0 -e ignore -o match:'executors/link' \ + ifdown -f -S/dev/null -E $EXECUTORS -i/dev/null -n -a } dual_stack_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set down dev lo' \ - -o match:'ip link set down dev eth0' \ - -o match:'del 203.0.113.2/24 dev eth0' \ - -o match:'del 2001:db8:1000:2::2/64 dev eth0' \ - -o match:'default via 203.0.113.1' \ - -o match:'default via 2001:db8:1000:2::1' \ - ifdown -S/dev/null -i $FIXTURES/static-eth0.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/static' \ + ifdown -f -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0.interfaces -n -a } static_ipv4_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set down dev lo' \ - -o match:'ip link set down dev eth0' \ - -o match:'del 203.0.113.2/24 dev eth0' \ - -o match:'default via 203.0.113.1' \ - ifdown -S/dev/null -i $FIXTURES/static-eth0-v4.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/static' \ + ifdown -f -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0-v4.interfaces -n -a +} + +static_ipv4_netmask_body() { + atf_check -s exit:0 -e ignore \ + -o match:'executors/link' \ + -o match:'executors/static' \ + ifdown -f -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0-v4-netmask.interfaces -n -a } static_ipv6_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set down dev lo' \ - -o match:'ip link set down dev eth0' \ - -o match:'del 2001:db8:1000:2::2/64 dev eth0' \ - -o match:'default via 2001:db8:1000:2::1' \ - ifdown -S/dev/null -i $FIXTURES/static-eth0-v6.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/static' \ + ifdown -f -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0-v6.interfaces -n -a +} + +static_ipv6_netmask_body() { + atf_check -s exit:0 -e ignore \ + -o match:'executors/link' \ + -o match:'executors/static' \ + ifdown -f -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0-v6-netmask.interfaces -n -a } inet_dhcp_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set down dev lo' \ - -o match:'ip link set down dev eth0' \ - -o match:'dhc' \ - ifdown -S/dev/null -i $FIXTURES/dhcp-eth0.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/dhcp' \ + ifdown -f -S/dev/null -E $EXECUTORS -i $FIXTURES/dhcp-eth0.interfaces -n -a } use_dhcp_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set down dev lo' \ - -o match:'ip link set down dev eth0' \ - -o match:'dhc' \ - ifdown -S/dev/null -i $FIXTURES/use-dhcp-eth0.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/dhcp' \ + ifdown -f -S/dev/null -E $EXECUTORS -i $FIXTURES/use-dhcp-eth0.interfaces -n -a } alias_eth0_home_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set down dev wlan0' \ - -o match:'dhc' \ + -o match:'executors/link' \ + -o match:'executors/dhcp' \ ifdown -S $FIXTURES/alias-home.ifstate \ - -i $FIXTURES/alias-home-work.interfaces -n wlan0 + -E $EXECUTORS -i $FIXTURES/alias-home-work.interfaces -n wlan0 } alias_eth0_work_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set down dev wlan0' \ - -o match:'del 203.0.113.2/24 dev wlan0' \ - -o match:'del 2001:db8:1000:2::2/64 dev wlan0' \ - -o match:'default via 203.0.113.1' \ - -o match:'default via 2001:db8:1000:2::1' \ + -o match:'executors/link' \ + -o match:'executors/static' \ ifdown -S $FIXTURES/alias-work.ifstate \ - -i $FIXTURES/alias-home-work.interfaces -n wlan0 + -E $EXECUTORS -i $FIXTURES/alias-home-work.interfaces -n wlan0 } bonded_bridge_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set down dev eth0' \ - -o match:'ip link set down dev eth1' \ - -o match:'ip link set down dev bond0' \ - -o match:'ip link set down dev br0' \ - -o match:'del 203.0.113.2/24 dev br0' \ - -o match:'del 2001:db8:1000:2::2/64 dev br0' \ - -o match:'default via 203.0.113.1' \ - -o match:'default via 2001:db8:1000:2::1' \ + -o match:'executors/link' \ + -o match:'executors/bond' \ + -o match:'executors/bridge' \ + -o match:'executors/static' \ ifdown -S $FIXTURES/bonded-bridge.ifstate \ - -i $FIXTURES/bonded-bridge.interfaces -n br0 + -E $EXECUTORS -i $FIXTURES/bonded-bridge.interfaces -n br0 +} + +learned_dependency_body() { + atf_check -s exit:0 -o ignore \ + -e match:"eth0" \ + -e match:"eth1" \ + -e match:"eth2" \ + -e match:"eth3" \ + -e match:"eth4" \ + ifdown -n -S $FIXTURES/mock-dependency-generator.ifstate \ + -E $EXECUTORS \ + -i $FIXTURES/mock-dependency-generator.interfaces br0 +} + +learned_dependency_2_body() { + atf_check -s exit:0 -o ignore \ + -e match:"bond0" \ + -e match:"eth0" \ + -e match:"eth1" \ + ifdown -n -S $FIXTURES/mock-dependency-generator-2.ifstate \ + -E $EXECUTORS \ + -i $FIXTURES/mock-dependency-generator-2.interfaces br0 +} + +learned_executor_body() { + atf_check -s exit:0 -o ignore \ + -e match:"attempting to run mock executor" \ + ifdown -n -S $FIXTURES/mock-dependency-generator-2.ifstate \ + -E $EXECUTORS \ + -i $FIXTURES/mock-dependency-generator-2.interfaces br0 +} + +implicit_vlan_body() { + atf_check -s exit:0 -o ignore \ + -e match:"attempting to run vlan executor" \ + -e match:"attempting to run link executor" \ + ifdown -n -S $FIXTURES/vlan.ifstate -E $EXECUTORS -i $FIXTURES/vlan.interfaces eth0.8 +} + +deferred_teardown_1_body() { + atf_check -s exit:0 -o ignore \ + -e match:"skipping dependent interface eth0 \\(of bond0\\) -- transient dependencies still exist" \ + -e match:"changing state of dependent interface eth1 \\(of bond0\\) to down" \ + ifdown -n -S $FIXTURES/deferred-teardown-1.ifstate -E $EXECUTORS \ + -i $FIXTURES/deferred-teardown-1.interfaces br0 +} + +deferred_teardown_2_body() { + atf_check -s exit:0 -o ignore \ + -e match:"skipping dependent interface eth0 \\(of tun0\\) -- transient dependencies still exist" \ + -e match:"skipping dependent interface eth0 \\(of tun1\\) -- transient dependencies still exist" \ + -e match:"skipping dependent interface eth0 \\(of tun2\\) -- transient dependencies still exist" \ + ifdown -n -S $FIXTURES/deferred-teardown-2.ifstate -E $EXECUTORS \ + -i $FIXTURES/deferred-teardown-2.interfaces tun0 tun1 tun2 +} + +deferred_teardown_3_body() { + atf_check -s exit:0 -o ignore \ + -e match:"skipping dependent interface eth0 \\(of tun0\\) -- transient dependencies still exist" \ + -e match:"skipping dependent interface eth0 \\(of tun1\\) -- transient dependencies still exist" \ + -e match:"skipping dependent interface eth0 \\(of tun2\\) -- transient dependencies still exist" \ + -e match:"changing state of dependent interface eth0 \\(of tun3\\) to down" \ + ifdown -n -S $FIXTURES/deferred-teardown-2.ifstate -E $EXECUTORS \ + -i $FIXTURES/deferred-teardown-2.interfaces tun0 tun1 tun2 tun3 +} + +teardown_dep_ordering_body() { + atf_check -s exit:0 -o ignore \ + -e match:"skipping auto interface bat" \ + -e match:"skipping auto interface dummy" \ + ifdown -n -i $FIXTURES/teardown-dep-ordering.interfaces \ + -S $FIXTURES/teardown-dep-ordering.ifstate -E $EXECUTORS -a +} + +regress_opt_f_body() { + atf_check -s exit:0 -o ignore -e ignore \ + ifdown -n -S $FIXTURES/vlan.ifstate -E $EXECUTORS -i $FIXTURES/vlan.interfaces -f eth0.8 +} + +dependency_loop_breaking_body() { + atf_check -s exit:0 -o ignore \ + -e match:"ifdown: skipping auto interface a \\(already deconfigured\\), use --force to force deconfiguration" \ + ifdown -n -i $FIXTURES/dependency-loop.interfaces -E $EXECUTORS -a } diff --git a/tests/ifquery_test b/tests/ifquery_test index 52318f5..955bb6c 100755 --- a/tests/ifquery_test +++ b/tests/ifquery_test @@ -12,7 +12,38 @@ tests_init \ use_dhcp \ state_query_home \ state_query_work \ - state_print + state_print \ + learned_dependency \ + learned_dependency_2 \ + learned_executor \ + inheritance_0 \ + inheritance_1 \ + implicit_vlan \ + vrf_dependency \ + vrf_ifupdown2_rewrite \ + vrf_ifupdown2_dependency \ + vrf_implicit_static_gateway \ + ppp_dependency \ + ppp_legacy_rewrite \ + tunnel_dependency \ + tunnel_legacy_dependency \ + tunnel_legacy_rewrite \ + tunnel_ifupdown2_dependency \ + tunnel_ifupdown2_rewrite \ + gre_dependency \ + vlan_explicit_learned_dependency \ + vlan_guessed_learned_dependency \ + vlan_complex_learned_dependency \ + wireguard \ + allow_undefined_positive \ + allow_undefined_negative \ + default_netmask_v4 \ + default_netmask_v6 \ + stanza_merging_with_cidr \ + stanza_merging_without_cidr \ + dhcp_hostname_rewrite \ + dhcp_hostname_inference \ + dhcp_hostname_replacement noargs_body() { atf_check -s exit:1 -e ignore ifquery -S/dev/null @@ -69,3 +100,197 @@ state_print_body() { atf_check -s exit:0 -o match:"wlan0=work" \ ifquery -S $FIXTURES/alias-work.ifstate -i $FIXTURES/alias-home-work.interfaces -s } + +learned_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0 eth1 eth2 eth3 eth4" \ + ifquery -E $EXECUTORS -i $FIXTURES/mock-dependency-generator.interfaces br0 +} + +learned_dependency_2_body() { + atf_check -s exit:0 -o match:"requires bond0" \ + ifquery -E $EXECUTORS -i $FIXTURES/mock-dependency-generator-2.interfaces br0 +} + +learned_executor_body() { + atf_check -s exit:0 -o match:"use mock" \ + ifquery -E $EXECUTORS -i $FIXTURES/mock-dependency-generator-2.interfaces br0 +} + +inheritance_0_body() { + atf_check -s exit:0 -o match:"inherit base0" \ + -o match:"address 203.0.113.2/24" \ + -o match:"address 203.0.113.3/24" \ + -o match:"address 2001:db8:1000:2::2/64" \ + ifquery -E $EXECUTORS -i $FIXTURES/inheritance.interfaces inherit0 +} + +inheritance_1_body() { + atf_check -s exit:0 -o match:"inherit base0" \ + -o match:"address 203.0.113.2/24" \ + -o match:"address 203.0.113.4/24" \ + -o match:"address 2001:db8:1000:2::2/64" \ + ifquery -E $EXECUTORS -i $FIXTURES/inheritance.interfaces inherit1 +} + +implicit_vlan_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + -o match:"use vlan" \ + ifquery -E $EXECUTORS -i $FIXTURES/vlan.interfaces eth0.8 +} + +vrf_dependency_body() { + atf_check -s exit:0 -o match:"requires vrf-red" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/vrf.interfaces eth0 +} + +vrf_ifupdown2_rewrite_body() { + atf_check -s exit:0 -o match:"vrf-member vrf-red" \ + ifquery -E $EXECUTORS -i $FIXTURES/vrf-ifupdown2.interfaces eth0 +} + +vrf_ifupdown2_dependency_body() { + atf_check -s exit:0 -o match:"requires vrf-red" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/vrf-ifupdown2.interfaces eth0 +} + +vrf_implicit_static_gateway_body() { + atf_check -s exit:0 -o match:"use static" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/vrf.interfaces vrf-red +} + +ppp_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/ppp.interfaces ppp0 +} + +ppp_legacy_rewrite_body() { + atf_check -s exit:0 -o match:"ppp-provider someisp" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/ppp-legacy.interfaces ppp0 +} + +tunnel_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/tunnel.interfaces tun0 +} + +tunnel_legacy_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/tunnel-legacy.interfaces tun0 +} + +tunnel_ifupdown2_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/tunnel-ifupdown2.interfaces tun0 +} + +tunnel_legacy_rewrite_body() { + atf_check -s exit:0 \ + -o match:"tunnel-local 203.0.113.2" \ + -o match:"tunnel-remote 203.0.113.1" \ + -o match:"tunnel-mode gre" \ + -o match:"tunnel-ttl 255" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/tunnel-legacy.interfaces tun0 +} + +tunnel_ifupdown2_rewrite_body() { + atf_check -s exit:0 \ + -o match:"tunnel-local 203.0.113.2" \ + -o match:"tunnel-remote 203.0.113.1" \ + -o match:"tunnel-mode gre" \ + -o match:"tunnel-ttl 255" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/tunnel-ifupdown2.interfaces tun0 +} + +gre_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/gre.interfaces tun0 +} + +vlan_explicit_learned_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + -o match:"use vlan" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/vlan-named.interfaces servers +} + +vlan_guessed_learned_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + -o match:"use vlan" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/vlan.interfaces eth0.8 +} + +vlan_complex_learned_dependency_body() { + atf_check -s exit:0 -o match:"requires eth0" \ + -o match:"use vlan" \ + -o match:"address 1.2.10.4/24" \ + -o match:"gateway 1.2.10.1" \ + -o match:"address abcd:ef12:3456:10::4/64" \ + -o match:"gateway abcd:ef12:3456:10::1" \ + -o match:"vlan-raw-device eth0" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/vlan-complex.interfaces servers +} + +wireguard_body() { + atf_check -s exit:0 \ + -o match:"requires eth0" \ + -o match:"use wireguard" \ + ifquery -E $EXECUTORS_LINUX -i $FIXTURES/wireguard.interfaces wg0 +} + +allow_undefined_positive_body() { + atf_check -s exit:0 \ + -o ignore \ + -e ignore \ + ifquery -U -i /dev/null -p address foo +} + +allow_undefined_negative_body() { + atf_check -s exit:1 \ + -o ignore \ + -e ignore \ + ifquery -i /dev/null -p address foo +} + +default_netmask_v4_body() { + atf_check -s exit:0 \ + -o match:"203.0.113.2/24" \ + ifquery -i $FIXTURES/without-netmask.interfaces -p address v4 +} + +default_netmask_v6_body() { + atf_check -s exit:0 \ + -o match:"2001:470:1f10::1/64" \ + ifquery -i $FIXTURES/without-netmask.interfaces -p address v6 +} + +stanza_merging_with_cidr_body() { + atf_check -s exit:0 \ + -o match:"203.0.113.1/32" \ + -o match:"203.0.113.2/24" \ + ifquery -i $FIXTURES/stanza-merging.interfaces -p address cidr +} + +stanza_merging_without_cidr_body() { + atf_check -s exit:0 \ + -o match:"203.0.113.1/32" \ + -o match:"203.0.113.2/24" \ + ifquery -i $FIXTURES/stanza-merging.interfaces -p address without-cidr +} + +dhcp_hostname_rewrite_body() { + atf_check -s exit:0 \ + -o match:"dhcp-hostname foo" \ + ifquery -i $FIXTURES/dhcp-hostname-rewrite.interfaces -P eth0 +} + +dhcp_hostname_inference_body() { + hostname=$(uname -n) + atf_check -s exit:0 \ + -o match:"dhcp-hostname $hostname" \ + ifquery -i $FIXTURES/dhcp-hostname-rewrite.interfaces -P eth1 +} + +dhcp_hostname_replacement_body() { + atf_check -s exit:0 \ + -o match:"dhcp-hostname bar" \ + ifquery -i $FIXTURES/dhcp-hostname-rewrite.interfaces -P eth2 +} \ No newline at end of file diff --git a/tests/ifup_test b/tests/ifup_test index 2091d0f..5c619a4 100755 --- a/tests/ifup_test +++ b/tests/ifup_test @@ -7,93 +7,142 @@ tests_init \ lo_always_auto \ dual_stack \ static_ipv4 \ + static_ipv4_netmask \ static_ipv6 \ + static_ipv6_netmask \ inet_dhcp \ use_dhcp \ alias_eth0_home \ alias_eth0_work \ - bonded_bridge + bonded_bridge \ + learned_dependency \ + learned_dependency_2 \ + learned_executor \ + implicit_vlan \ + teardown_dep_ordering \ + dependency_loop_breaking noargs_body() { atf_check -s exit:1 -e ignore ifup -S/dev/null } lo_always_auto_body() { - atf_check -s exit:0 -e ignore -o match:'ip link set up dev lo' \ - ifup -S/dev/null -i/dev/null -n -a + atf_check -s exit:0 -e ignore -o match:'executors/link' \ + ifup -S/dev/null -E $EXECUTORS -i/dev/null -n -a } dual_stack_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set up dev lo' \ - -o match:'ip link set up dev eth0' \ - -o match:'add 203.0.113.2/24 dev eth0' \ - -o match:'add 2001:db8:1000:2::2/64 dev eth0' \ - -o match:'default via 203.0.113.1' \ - -o match:'default via 2001:db8:1000:2::1' \ - ifup -S/dev/null -i $FIXTURES/static-eth0.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/static' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0.interfaces -n -a } static_ipv4_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set up dev lo' \ - -o match:'ip link set up dev eth0' \ - -o match:'add 203.0.113.2/24 dev eth0' \ - -o match:'default via 203.0.113.1' \ - ifup -S/dev/null -i $FIXTURES/static-eth0-v4.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/static' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0-v4.interfaces -n -a +} + +static_ipv4_netmask_body() { + atf_check -s exit:0 -e ignore \ + -o match:'executors/link' \ + -o match:'executors/static' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0-v4-netmask.interfaces -n -a } static_ipv6_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set up dev lo' \ - -o match:'ip link set up dev eth0' \ - -o match:'add 2001:db8:1000:2::2/64 dev eth0' \ - -o match:'default via 2001:db8:1000:2::1' \ - ifup -S/dev/null -i $FIXTURES/static-eth0-v6.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/static' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0-v6.interfaces -n -a +} + +static_ipv6_netmask_body() { + atf_check -s exit:0 -e ignore \ + -o match:'executors/link' \ + -o match:'executors/static' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/static-eth0-v6-netmask.interfaces -n -a } inet_dhcp_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set up dev lo' \ - -o match:'ip link set up dev eth0' \ - -o match:'dhc' \ - ifup -S/dev/null -i $FIXTURES/dhcp-eth0.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/dhcp' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/dhcp-eth0.interfaces -n -a } use_dhcp_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set up dev lo' \ - -o match:'ip link set up dev eth0' \ - -o match:'dhc' \ - ifup -S/dev/null -i $FIXTURES/use-dhcp-eth0.interfaces -n -a + -o match:'executors/link' \ + -o match:'executors/dhcp' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/use-dhcp-eth0.interfaces -n -a } alias_eth0_home_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set up dev wlan0' \ - -o match:'dhc' \ - ifup -S/dev/null -i $FIXTURES/alias-home-work.interfaces -n wlan0=home + -o match:'executors/link' \ + -o match:'executors/dhcp' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/alias-home-work.interfaces -n wlan0=home } alias_eth0_work_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set up dev wlan0' \ - -o match:'add 203.0.113.2/24 dev wlan0' \ - -o match:'add 2001:db8:1000:2::2/64 dev wlan0' \ - -o match:'default via 203.0.113.1' \ - -o match:'default via 2001:db8:1000:2::1' \ - ifup -S/dev/null -i $FIXTURES/alias-home-work.interfaces -n wlan0=work + -o match:'executors/link' \ + -o match:'executors/static' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/alias-home-work.interfaces -n wlan0=work } bonded_bridge_body() { atf_check -s exit:0 -e ignore \ - -o match:'ip link set up dev eth0' \ - -o match:'ip link set up dev eth1' \ - -o match:'ip link set up dev bond0' \ - -o match:'ip link set up dev br0' \ - -o match:'add 203.0.113.2/24 dev br0' \ - -o match:'add 2001:db8:1000:2::2/64 dev br0' \ - -o match:'default via 203.0.113.1' \ - -o match:'default via 2001:db8:1000:2::1' \ - ifup -S/dev/null -i $FIXTURES/bonded-bridge.interfaces -n br0 + -o match:'executors/link' \ + -o match:'executors/bond' \ + -o match:'executors/bridge' \ + -o match:'executors/static' \ + ifup -S/dev/null -E $EXECUTORS -i $FIXTURES/bonded-bridge.interfaces -n br0 +} + +learned_dependency_body() { + atf_check -s exit:0 -o ignore \ + -e match:"eth0" \ + -e match:"eth1" \ + -e match:"eth2" \ + -e match:"eth3" \ + -e match:"eth4" \ + ifup -n -S/dev/null -E $EXECUTORS -i $FIXTURES/mock-dependency-generator.interfaces br0 +} + +learned_dependency_2_body() { + atf_check -s exit:0 -o ignore \ + -e match:"bond0" \ + -e match:"eth0" \ + -e match:"eth1" \ + ifup -n -S/dev/null -E $EXECUTORS -i $FIXTURES/mock-dependency-generator-2.interfaces br0 +} + +learned_executor_body() { + atf_check -s exit:0 -o ignore \ + -e match:"attempting to run mock executor" \ + ifup -n -S/dev/null -E $EXECUTORS -i $FIXTURES/mock-dependency-generator-2.interfaces br0 +} + +implicit_vlan_body() { + atf_check -s exit:0 -o ignore \ + -e match:"attempting to run vlan executor" \ + -e match:"attempting to run link executor" \ + ifup -n -S/dev/null -E $EXECUTORS -i $FIXTURES/vlan.interfaces eth0.8 +} + +teardown_dep_ordering_body() { + atf_check -s exit:0 -o ignore \ + -e match:"skipping auto interface bat" \ + -e match:"skipping auto interface dummy" \ + ifup -n -i $FIXTURES/teardown-dep-ordering.interfaces -E $EXECUTORS -a +} + +dependency_loop_breaking_body() { + atf_check -s exit:0 -o ignore \ + -e match:"ifup: skipping auto interface a \\(already configured\\), use --force to force configuration" \ + ifup -n -i $FIXTURES/dependency-loop.interfaces -E $EXECUTORS -a } diff --git a/tests/linux/Kyuafile b/tests/linux/Kyuafile new file mode 100644 index 0000000..2e668b8 --- /dev/null +++ b/tests/linux/Kyuafile @@ -0,0 +1,18 @@ +syntax(2) + +test_suite('ifupdown-ng') + +atf_test_program{name='bond_test'} +atf_test_program{name='dhcp_test'} +atf_test_program{name='ethtool_test'} +atf_test_program{name='forward_test'} +atf_test_program{name='gre_test'} +atf_test_program{name='ipv6-ra_test'} +atf_test_program{name='link_test'} +atf_test_program{name='mpls_test'} +atf_test_program{name='ppp_test'} +atf_test_program{name='static_test'} +atf_test_program{name='tunnel_test'} +atf_test_program{name='vrf_test'} +atf_test_program{name='vxlan_test'} +atf_test_program{name='wireguard_test'} diff --git a/tests/linux/bond_test b/tests/linux/bond_test new file mode 100755 index 0000000..916e09b --- /dev/null +++ b/tests/linux/bond_test @@ -0,0 +1,30 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/bond" + +tests_init \ + create_lacp_basic \ + create_lacp_real + +create_lacp_basic_body() { + export IFACE=bond0 PHASE=create MOCK=echo IF_BOND_MODE=802.3ad IF_BOND_MEMBERS="eth0 eth1" + atf_check -s exit:0 \ + -o match:'ip link add bond0 type bond mode 802.3ad' \ + -o match:'ip link set eth0 down' \ + -o match:'ip link set master bond0 eth0' \ + -o match:'ip link set eth0 up' \ + -o match:'ip link set master bond0 eth1' \ + ${EXECUTOR} +} + +create_lacp_real_body() { + export IFACE=bond0 PHASE=create MOCK=echo IF_BOND_MODE=802.3ad IF_BOND_MEMBERS="eth0 eth1" \ + IF_BOND_MIN_LINKS="1" IF_BOND_XMIT_HASH_POLICY="layer3+4" + atf_check -s exit:0 \ + -o match:'ip link add bond0 type bond' \ + -o match:'mode 802.3ad' \ + -o match:'min_links 1' \ + -o match:'xmit_hash_policy layer3\+4' \ + ${EXECUTOR} +} diff --git a/tests/linux/dhcp_test b/tests/linux/dhcp_test new file mode 100755 index 0000000..d3d78f1 --- /dev/null +++ b/tests/linux/dhcp_test @@ -0,0 +1,54 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/dhcp" + +tests_init udhcpc_up \ + dhcpcd_up \ + dhcpcd_down \ + dhclient_up \ + udhcpc_opts_up \ + udhcpc_opts_up_subshell \ + hostname_subshell + +udhcpc_up_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_DHCP_PROGRAM=udhcpc + atf_check -s exit:0 -o match:'/sbin/udhcpc -b -R -p /var/run/udhcpc.eth0.pid -i eth0' \ + ${EXECUTOR} +} + +dhcpcd_up_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_DHCP_PROGRAM=dhcpcd + atf_check -s exit:0 -o match:'/sbin/dhcpcd eth0' \ + ${EXECUTOR} +} + +dhcpcd_down_body() { + export IFACE=eth0 PHASE=down MOCK=echo IF_DHCP_PROGRAM=dhcpcd + atf_check -s exit:0 -o match:'/sbin/dhcpcd -k eth0' \ + ${EXECUTOR} +} + +dhclient_up_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_DHCP_PROGRAM=dhclient + atf_check -s exit:0 -o match:'/usr/sbin/dhclient -pf /var/run/dhclient.eth0.pid eth0' \ + ${EXECUTOR} +} + +udhcpc_opts_up_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_DHCP_PROGRAM=udhcpc IF_UDHCPC_OPTS="-O search" + atf_check -s exit:0 -o match:'/sbin/udhcpc -b -R -p /var/run/udhcpc.eth0.pid -i eth0 -O search' \ + ${EXECUTOR} +} + +udhcpc_opts_up_subshell_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_DHCP_PROGRAM=udhcpc IF_UDHCPC_OPTS="-O search -x hostname:\$(echo test)" + atf_check -s exit:0 -o match:'/sbin/udhcpc -b -R -p /var/run/udhcpc.eth0.pid -i eth0 -O search -x hostname:test' \ + ${EXECUTOR} +} + +hostname_subshell_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_DHCP_PROGRAM=udhcpc IF_DHCP_HOSTNAME="\$(echo test)" + atf_check -s exit:0 -o match:'/sbin/udhcpc -b -R -p /var/run/udhcpc.eth0.pid -i eth0 -x hostname:test' \ + ${EXECUTOR} +} diff --git a/tests/linux/ethtool_test b/tests/linux/ethtool_test new file mode 100755 index 0000000..d12d64a --- /dev/null +++ b/tests/linux/ethtool_test @@ -0,0 +1,483 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/ethtool" + +tests_init \ + pre_up_msglvl \ + pre_up_ethernet_port \ + up_speed \ + up_duplex \ + up_wol \ + up_wol_sopass \ + up_autoneg_simple \ + up_autoneg_mask \ + up_pause_autoneg \ + up_pause_tx \ + up_pause_rx \ + up_offload_gro \ + up_offload_gso \ + up_offload_lro \ + up_offload_rx \ + up_offload_sg \ + up_offload_tso \ + up_offload_tx \ + up_offload_ufo \ + up_dma_ring_rx \ + up_dma_ring_rx_jumbo \ + up_dma_ring_rx_mini \ + up_dma_ring_tx \ + up_coalesce_adaptive_rx \ + up_coalesce_adaptive_tx \ + up_coalesce_pkt_rate_low \ + up_coalesce_pkt_rate_high \ + up_coalesce_sample_interval \ + up_coalesce_stats_block_usecs \ + up_coalesce_rx_frames \ + up_coalesce_rx_frames_low \ + up_coalesce_rx_frames_irq \ + up_coalesce_rx_frames_high \ + up_coalesce_rx_usecs \ + up_coalesce_rx_usecs_low \ + up_coalesce_rx_usecs_irq \ + up_coalesce_rx_usecs_high \ + up_coalesce_tx_frames \ + up_coalesce_tx_frames_low \ + up_coalesce_tx_frames_irq \ + up_coalesce_tx_frames_high \ + up_coalesce_tx_usecs \ + up_coalesce_tx_usecs_low \ + up_coalesce_tx_usecs_irq \ + up_coalesce_tx_usecs_high + +pre_up_msglvl_body() { + export IFACE="eth0" PHASE="pre-up" IF_ETHTOOL_MSGLVL="debug on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'msglvl debug on' \ + ${EXECUTOR} +} + +pre_up_ethernet_port_body() { + export IFACE="eth0" PHASE="pre-up" IF_ETHTOOL_ETHERNET_PORT="4" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'port 4' \ + ${EXECUTOR} +} + +up_speed_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_LINK_SPEED="1000" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'speed 1000' \ + ${EXECUTOR} +} + +up_duplex_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_LINK_DUPLEX="full" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'duplex full' \ + ${EXECUTOR} +} + +up_wol_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_ETHERNET_WOL="g" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'wol g' \ + ${EXECUTOR} +} + +up_wol_sopass_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_ETHERNET_WOL="s abc123" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'wol s sopass abc123' \ + ${EXECUTOR} +} + +up_autoneg_simple_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_ETHERNET_AUTONEG="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'autoneg on' \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_ETHERNET_AUTONEG="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'autoneg off' \ + ${EXECUTOR} +} + +up_autoneg_mask_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_ETHERNET_AUTONEG="1000/full" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --change eth0' \ + -o match:'autoneg on advertise 1000/full' \ + ${EXECUTOR} +} + +up_pause_autoneg_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_PAUSE_AUTONEG="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --pause eth0' \ + -o match:"autoneg on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_PAUSE_AUTONEG="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --pause eth0' \ + -o match:"autoneg off" \ + ${EXECUTOR} +} + +up_pause_tx_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_PAUSE_TX="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --pause eth0' \ + -o match:"tx on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_PAUSE_TX="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --pause eth0' \ + -o match:"tx off" \ + ${EXECUTOR} +} + +up_pause_rx_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_PAUSE_RX="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --pause eth0' \ + -o match:"rx on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_PAUSE_RX="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --pause eth0' \ + -o match:"rx off" \ + ${EXECUTOR} +} + +up_offload_gro_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_GRO="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"gro on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_GRO="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"gro off" \ + ${EXECUTOR} +} + +up_offload_gso_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_GSO="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"gso on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_GSO="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"gso off" \ + ${EXECUTOR} +} + +up_offload_lro_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_LRO="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"lro on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_LRO="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"lro off" \ + ${EXECUTOR} +} + +up_offload_rx_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_RX="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"rx on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_RX="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"rx off" \ + ${EXECUTOR} +} + +up_offload_sg_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_SG="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"sg on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_SG="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"sg off" \ + ${EXECUTOR} +} + +up_offload_tso_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_TSO="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"tso on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_TSO="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"tso off" \ + ${EXECUTOR} +} + +up_offload_tx_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_TX="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"tx on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_TX="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"tx off" \ + ${EXECUTOR} +} + +up_offload_ufo_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_UFO="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"ufo on" \ + ${EXECUTOR} + + export IFACE="eth0" PHASE="up" IF_ETHTOOL_OFFLOAD_UFO="off" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --offload eth0' \ + -o match:"ufo off" \ + ${EXECUTOR} +} + +up_dma_ring_rx_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_DMA_RING_RX="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --set-ring eth0' \ + -o match:"rx 1024" \ + ${EXECUTOR} +} + +up_dma_ring_rx_jumbo_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_DMA_RING_RX_JUMBO="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --set-ring eth0' \ + -o match:"rx-jumbo 1024" \ + ${EXECUTOR} +} + +up_dma_ring_rx_mini_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_DMA_RING_RX_MINI="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --set-ring eth0' \ + -o match:"rx-mini 1024" \ + ${EXECUTOR} +} + +up_dma_ring_tx_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_DMA_RING_TX="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --set-ring eth0' \ + -o match:"tx 1024" \ + ${EXECUTOR} +} + +up_coalesce_adaptive_rx_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_ADAPTIVE_RX="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"adaptive-rx on" \ + ${EXECUTOR} +} + +up_coalesce_adaptive_tx_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_ADAPTIVE_TX="on" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"adaptive-tx on" \ + ${EXECUTOR} +} + +up_coalesce_pkt_rate_low_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_PKT_RATE_LOW="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"pkt-rate-low 1024" \ + ${EXECUTOR} +} + +up_coalesce_pkt_rate_high_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_PKT_RATE_HIGH="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"pkt-rate-high 1024" \ + ${EXECUTOR} +} + +up_coalesce_sample_interval_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_SAMPLE_INTERVAL="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"sample-interval 1024" \ + ${EXECUTOR} +} + +up_coalesce_stats_block_usecs_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_STATS_BLOCK_USECS="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"stats-block-usecs 1024" \ + ${EXECUTOR} +} + +up_coalesce_rx_frames_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_RX_FRAMES="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"rx-frames 1024" \ + ${EXECUTOR} +} + +up_coalesce_rx_frames_low_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_RX_FRAMES_LOW="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"rx-frames-low 1024" \ + ${EXECUTOR} +} + +up_coalesce_rx_frames_irq_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_RX_FRAMES_IRQ="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"rx-frames-irq 1024" \ + ${EXECUTOR} +} + +up_coalesce_rx_frames_high_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_RX_FRAMES_HIGH="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"rx-frames-high 1024" \ + ${EXECUTOR} +} + +up_coalesce_rx_usecs_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_RX_USECS="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"rx-usecs 1024" \ + ${EXECUTOR} +} + +up_coalesce_rx_usecs_low_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_RX_USECS_LOW="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"rx-usecs-low 1024" \ + ${EXECUTOR} +} + +up_coalesce_rx_usecs_irq_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_RX_USECS_IRQ="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"rx-usecs-irq 1024" \ + ${EXECUTOR} +} + +up_coalesce_rx_usecs_high_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_RX_USECS_HIGH="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"rx-usecs-high 1024" \ + ${EXECUTOR} +} + +up_coalesce_tx_frames_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_TX_FRAMES="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"tx-frames 1024" \ + ${EXECUTOR} +} + +up_coalesce_tx_frames_low_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_TX_FRAMES_LOW="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"tx-frames-low 1024" \ + ${EXECUTOR} +} + +up_coalesce_tx_frames_irq_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_TX_FRAMES_IRQ="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"tx-frames-irq 1024" \ + ${EXECUTOR} +} + +up_coalesce_tx_frames_high_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_TX_FRAMES_HIGH="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"tx-frames-high 1024" \ + ${EXECUTOR} +} + +up_coalesce_tx_usecs_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_TX_USECS="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"tx-usecs 1024" \ + ${EXECUTOR} +} + +up_coalesce_tx_usecs_low_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_TX_USECS_LOW="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"tx-usecs-low 1024" \ + ${EXECUTOR} +} + +up_coalesce_tx_usecs_irq_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_TX_USECS_IRQ="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"tx-usecs-irq 1024" \ + ${EXECUTOR} +} + +up_coalesce_tx_usecs_high_body() { + export IFACE="eth0" PHASE="up" IF_ETHTOOL_COALESCE_TX_USECS_HIGH="1024" MOCK="echo" + atf_check -s exit:0 \ + -o match:'ethtool --coalesce eth0' \ + -o match:"tx-usecs-high 1024" \ + ${EXECUTOR} +} diff --git a/tests/linux/forward_test b/tests/linux/forward_test new file mode 100755 index 0000000..a4838b4 --- /dev/null +++ b/tests/linux/forward_test @@ -0,0 +1,90 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/forward" + +tests_init \ + up_forward_v4 \ + up_forward_v6 \ + up_forward_v4_mc \ + up_forward_v6_mc + +up_forward_v4_body() { + export IF_FORWARD_IPV4= IF_FORWARD_IPV6= IF_FORWARD_IPV4_MC= IF_FORWARD_IPV6_MC= + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV4=1 + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv4/conf/eth0/forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV4=yes + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv4/conf/eth0/forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV4=0 + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv4/conf/eth0/forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV4=no + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv4/conf/eth0/forwarding' \ + ${EXECUTOR} +} + +up_forward_v6_body() { + export IF_FORWARD_IPV4= IF_FORWARD_IPV6= IF_FORWARD_IPV4_MC= IF_FORWARD_IPV6_MC= + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV6=1 + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv6/conf/eth0/forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV6=yes + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv6/conf/eth0/forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV6=0 + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv6/conf/eth0/forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV6=no + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv6/conf/eth0/forwarding' \ + ${EXECUTOR} +} + +up_forward_v4_mc_body() { + export IF_FORWARD_IPV4= IF_FORWARD_IPV6= IF_FORWARD_IPV4_MC= IF_FORWARD_IPV6_MC= + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV4_MC=1 + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv4/conf/eth0/mc_forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV4_MC=yes + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv4/conf/eth0/mc_forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV4_MC=0 + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv4/conf/eth0/mc_forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV4_MC=no + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv4/conf/eth0/mc_forwarding' \ + ${EXECUTOR} +} + +up_forward_v6_mc_body() { + export IF_FORWARD_IPV4= IF_FORWARD_IPV6= IF_FORWARD_IPV4_MC= IF_FORWARD_IPV6_MC= + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV6_MC=1 + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv6/conf/eth0/mc_forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV6_MC=yes + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv6/conf/eth0/mc_forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV6_MC=0 + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv6/conf/eth0/mc_forwarding' \ + ${EXECUTOR} + + export IFACE=eth0 PHASE=up MOCK=echo IF_FORWARD_IPV6_MC=no + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv6/conf/eth0/mc_forwarding' \ + ${EXECUTOR} +} diff --git a/tests/linux/gre_test b/tests/linux/gre_test new file mode 100755 index 0000000..688717a --- /dev/null +++ b/tests/linux/gre_test @@ -0,0 +1,45 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/gre" + +tests_init \ + basic_bringup \ + basic_teardown \ + flags_bringup + +basic_bringup_body() { + export MOCK=echo IFACE=tun0 PHASE=create \ + IF_GRE_LOCAL=1.2.3.4 IF_GRE_REMOTE=5.6.7.8 \ + IF_GRE_TTL=255 + atf_check -s exit:0 \ + -o match:"ip -4 link add tun0" \ + -o match:"mode gre" \ + -o match:"ttl '255'" \ + -o match:"local '1.2.3.4'" \ + -o match:"remote '5.6.7.8'" \ + ${EXECUTOR} +} + +basic_teardown_body() { + export MOCK=echo IFACE=tun0 PHASE=destroy \ + IF_GRE_LOCAL=1.2.3.4 IF_GRE_REMOTE=5.6.7.8 \ + IF_GRE_TTL=255 + atf_check -s exit:0 \ + -o match:"ip -4 link del tun0" \ + ${EXECUTOR} +} + +flags_bringup_body() { + export MOCK=echo IFACE=tun0 PHASE=create \ + IF_GRE_LOCAL=1.2.3.4 IF_GRE_REMOTE=5.6.7.8 \ + IF_GRE_TTL=255 IF_GRE_FLAGS="nopmtudisc ignore-df" + atf_check -s exit:0 \ + -o match:"ip -4 link add tun0" \ + -o match:"mode gre" \ + -o match:"ttl '255'" \ + -o match:"local '1.2.3.4'" \ + -o match:"remote '5.6.7.8'" \ + -o match:"nopmtudisc ignore-df" \ + ${EXECUTOR} +} diff --git a/tests/linux/ipv6-ra_test b/tests/linux/ipv6-ra_test new file mode 100755 index 0000000..994823d --- /dev/null +++ b/tests/linux/ipv6-ra_test @@ -0,0 +1,18 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/ipv6-ra" + +tests_init up down + +up_body() { + export IFACE=lo PHASE=up MOCK=echo MOCK_ESC=\\ + atf_check -s exit:0 -o match:'echo 1 > /proc/sys/net/ipv6/conf/lo/accept_ra' \ + ${EXECUTOR} +} + +down_body() { + export IFACE=lo PHASE=down MOCK=echo MOCK_ESC=\\ + atf_check -s exit:0 -o match:'echo 0 > /proc/sys/net/ipv6/conf/lo/accept_ra' \ + ${EXECUTOR} +} diff --git a/tests/linux/link_test b/tests/linux/link_test new file mode 100755 index 0000000..7d570d8 --- /dev/null +++ b/tests/linux/link_test @@ -0,0 +1,86 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/link" + +tests_init \ + up \ + down \ + mtu \ + hwaddress \ + vlan_explicit_create \ + vlan_explicit_destroy \ + vlan_guessed_create \ + vlan_guessed_destroy \ + vlan_explicit_depend \ + vlan_guessed_depend \ + dummy_create + +up_body() { + export IFACE=lo PHASE=up MOCK=echo + atf_check -s exit:0 -o match:'ip link set up dev lo' \ + ${EXECUTOR} +} + +down_body() { + export IFACE=lo PHASE=down MOCK=echo + atf_check -s exit:0 -o match:'ip link set down dev lo' \ + ${EXECUTOR} +} + +mtu_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_MTU=1492 + atf_check -s exit:0 -o match:'ip link set up dev eth0 mtu 1492' \ + ${EXECUTOR} +} + +hwaddress_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_HWADDRESS=12:34:56:78:90:ab + atf_check -s exit:0 -o match:'ip link set up dev eth0 address 12:34:56:78:90:ab' \ + ${EXECUTOR} +} + +vlan_explicit_create_body() { + export IFACE=servers PHASE=create MOCK=echo \ + IF_VLAN_RAW_DEVICE="eth0" IF_VLAN_ID="123" + atf_check -s exit:0 -o match:'ip link add link eth0 name servers type vlan id 123' \ + ${EXECUTOR} +} + +vlan_explicit_destroy_body() { + export IFACE=servers PHASE=destroy MOCK=echo \ + IF_VLAN_RAW_DEVICE="eth0" IF_VLAN_ID="123" + atf_check -s exit:0 -o match:'ip link del servers' \ + ${EXECUTOR} +} + +vlan_guessed_create_body() { + export IFACE=eth0.8 PHASE=create MOCK=echo + atf_check -s exit:0 -o match:'ip link add link eth0 name eth0.8 type vlan id 8' \ + ${EXECUTOR} +} + +vlan_guessed_destroy_body() { + export IFACE=eth0.8 PHASE=destroy MOCK=echo + atf_check -s exit:0 -o match:'ip link del eth0.8' \ + ${EXECUTOR} +} + +vlan_explicit_depend_body() { + export IFACE=servers PHASE=depend \ + IF_VLAN_RAW_DEVICE="eth0" IF_VLAN_ID="123" + atf_check -s exit:0 -o match:'eth0' \ + ${EXECUTOR} +} + +vlan_guessed_depend_body() { + export IFACE=eth0.8 PHASE=depend + atf_check -s exit:0 -o match:'eth0' \ + ${EXECUTOR} +} + +dummy_create_body() { + export IFACE=yolo IF_LINK_TYPE=dummy PHASE=create MOCK=echo + atf_check -s exit:0 -o match:'ip link add yolo type dummy' \ + ${EXECUTOR} +} diff --git a/tests/linux/mpls_test b/tests/linux/mpls_test new file mode 100755 index 0000000..1755c5c --- /dev/null +++ b/tests/linux/mpls_test @@ -0,0 +1,23 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/mpls" + +tests_init \ + mpls_enable \ + mpls_disable + +mpls_enable_body() { + export MOCK=echo IFACE=vlan2342 PHASE=pre-up IF_MPLS_ENABLE=yes + atf_check -s exit:0 \ + -o match:"modprobe mpls_iptunnel" \ + -o match:"echo 1 > /proc/sys/net/mpls/conf/vlan2342/input" \ + ${EXECUTOR} +} + +mpls_disable_body() { + export MOCK=echo IFACE=vlan2342 PHASE=pre-up IF_MPLS_ENABLE=no + atf_check -s exit:0 \ + -o match:"echo 0 > /proc/sys/net/mpls/conf/vlan2342/input" \ + ${EXECUTOR} +} diff --git a/tests/linux/ppp_test b/tests/linux/ppp_test new file mode 100755 index 0000000..1b9e18c --- /dev/null +++ b/tests/linux/ppp_test @@ -0,0 +1,22 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/ppp" + +tests_init \ + bringup \ + teardown \ + +bringup_body() { + export MOCK=echo IFACE=ppp0 PHASE=create IF_PPP_PROVIDER=someisp + atf_check -s exit:0 \ + -o match:'pon someisp' \ + ${EXECUTOR} +} + +teardown_body() { + export MOCK=echo IFACE=ppp0 PHASE=destroy IF_PPP_PROVIDER=someisp + atf_check -s exit:0 \ + -o match:'poff someisp' \ + ${EXECUTOR} +} diff --git a/tests/linux/static_test b/tests/linux/static_test new file mode 100755 index 0000000..f3912e5 --- /dev/null +++ b/tests/linux/static_test @@ -0,0 +1,55 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/static" + +tests_init \ + up \ + up_ptp \ + down \ + vrf_up \ + metric_up + +up_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_ADDRESSES="203.0.113.2/24 2001:db8:1000:2::2/64" \ + IF_GATEWAYS="203.0.113.1 2001:db8:1000:2::1" + atf_check -s exit:0 \ + -o match:'addr add 203.0.113.2/24 dev eth0' \ + -o match:'addr add 2001:db8:1000:2::2/64 dev eth0' \ + -o match:'route add default via 203.0.113.1 metric 1 dev eth0' \ + -o match:'route add default via 2001:db8:1000:2::1 metric 1 dev eth0' \ + ${EXECUTOR} +} + +up_ptp_body() { + export IFACE=eth0 PHASE=up MOCK=echo IF_ADDRESSES="203.0.113.2/32 2001:db8:1000:2::2/64" \ + IF_GATEWAYS="192.0.2.1 2001:db8:1000:2::1" IF_POINT_TO_POINT="192.0.2.1" + atf_check -s exit:0 \ + -o match:'addr add 203.0.113.2/32 peer 192.0.2.1 dev eth0' \ + -o match:'route add default via 192.0.2.1 metric 1 dev eth0' \ + -o match:'addr add 2001:db8:1000:2::2/64 dev eth0' \ + -o match:'route add default via 2001:db8:1000:2::1 metric 1 dev eth0' \ + ${EXECUTOR} +} + +down_body() { + export IFACE=eth0 PHASE=down MOCK=echo IF_ADDRESSES="203.0.113.2/24 2001:db8:1000:2::2/64" \ + IF_GATEWAYS="203.0.113.1 2001:db8:1000:2::1" + atf_check -s exit:0 \ + -o match:'addr flush dev eth0' \ + ${EXECUTOR} +} + +vrf_up_body() { + export IFACE=vrf-red PHASE=up MOCK=echo IF_GATEWAYS=203.0.113.2 IF_VRF_TABLE=1 + atf_check -s exit:0 \ + -o match:'route add default via 203.0.113.2 table 1' \ + ${EXECUTOR} +} + +metric_up_body() { + export IFACE=vrf-red PHASE=up MOCK=echo IF_GATEWAYS=203.0.113.2 IF_VRF_TABLE=1 IF_METRIC=20 + atf_check -s exit:0 \ + -o match:'route add default via 203.0.113.2 table 1 metric 20' \ + ${EXECUTOR} +} diff --git a/tests/linux/tunnel_test b/tests/linux/tunnel_test new file mode 100755 index 0000000..49a296a --- /dev/null +++ b/tests/linux/tunnel_test @@ -0,0 +1,65 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/tunnel" + +tests_init \ + tunnel_bringup \ + tunnel_teardown \ + gretap_up \ + gretap_down \ + ip6gretap_up + +tunnel_bringup_body() { + export MOCK=echo IFACE=tun0 PHASE=create IF_TUNNEL_MODE=gre \ + IF_TUNNEL_LOCAL=1.2.3.4 IF_TUNNEL_REMOTE=5.6.7.8 \ + IF_TUNNEL_TTL=255 + atf_check -s exit:0 \ + -o match:"ip -4 tunnel add tun0" \ + -o match:"mode gre" \ + -o match:"ttl '255'" \ + -o match:"local '1.2.3.4'" \ + -o match:"remote '5.6.7.8'" \ + ${EXECUTOR} +} + +tunnel_teardown_body() { + export MOCK=echo IFACE=tun0 PHASE=destroy IF_TUNNEL_MODE=gre \ + IF_TUNNEL_LOCAL=1.2.3.4 IF_TUNNEL_REMOTE=5.6.7.8 \ + IF_TUNNEL_TTL=255 + atf_check -s exit:0 \ + -o match:"ip -4 tunnel del tun0" \ + ${EXECUTOR} +} + + +gretap_up_body() { + export MOCK=echo IFACE=foo PHASE=create IF_TUNNEL_MODE=gretap \ + IF_TUNNEL_LOCAL=1.2.3.4 IF_TUNNEL_REMOTE=5.6.7.8 + atf_check -s exit:0 \ + -o match:"ip -4 link add foo" \ + -o match:"type gretap" \ + -o match:"local '1.2.3.4'" \ + -o match:"remote '5.6.7.8'" \ + ${EXECUTOR} +} + +gretap_down_body() { + export MOCK=echo IFACE=foo PHASE=destroy IF_TUNNEL_MODE=gretap \ + IF_TUNNEL_LOCAL=1.2.3.4 IF_TUNNEL_REMOTE=5.6.7.8 + atf_check -s exit:0 \ + -o match:"ip -4 link del foo" \ + ${EXECUTOR} +} + +ip6gretap_up_body() { + export MOCK=echo IFACE=foo PHASE=create IF_TUNNEL_MODE=ip6gretap \ + IF_TUNNEL_LOCAL=2001:db8::aaaa IF_TUNNEL_REMOTE=2001:db8::eeee + atf_check -s exit:0 \ + -o match:"ip -6 link add foo" \ + -o match:"type gretap" \ + -o match:"local '2001:db8::aaaa'" \ + -o match:"remote '2001:db8::eeee'" \ + ${EXECUTOR} +} + diff --git a/tests/linux/vrf_test b/tests/linux/vrf_test new file mode 100755 index 0000000..97756f0 --- /dev/null +++ b/tests/linux/vrf_test @@ -0,0 +1,34 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/vrf" + +tests_init \ + leader_bringup \ + leader_teardown \ + member_bringup + +leader_bringup_body() { + export MOCK=echo IFACE=vrf-red PHASE=create IF_VRF_TABLE=1 IF_VRF_MEMBER= + atf_check -s exit:0 \ + -o match:'ip link add vrf-red type vrf table 1' \ + -o match:'ip rule add iif vrf-red table 1' \ + -o match:'ip rule add oif vrf-red table 1' \ + ${EXECUTOR} +} + +leader_teardown_body() { + export MOCK=echo IFACE=vrf-red PHASE=destroy IF_VRF_TABLE=1 IF_VRF_MEMBER= + atf_check -s exit:0 \ + -o match:'ip link del vrf-red type vrf table 1' \ + -o match:'ip rule del iif vrf-red table 1' \ + -o match:'ip rule del oif vrf-red table 1' \ + ${EXECUTOR} +} + +member_bringup_body() { + export MOCK=echo IFACE=eth0 PHASE=pre-up IF_VRF_MEMBER=vrf-red IF_VRF_TABLE= + atf_check -s exit:0 \ + -o match:'ip link set eth0 master vrf-red' \ + ${EXECUTOR} +} diff --git a/tests/linux/vxlan_test b/tests/linux/vxlan_test new file mode 100755 index 0000000..f667c60 --- /dev/null +++ b/tests/linux/vxlan_test @@ -0,0 +1,66 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/vxlan" + +tests_init \ + create_simple \ + create_ucast_ptp \ + create_ucast_ptmp \ + create_mcast \ + create_physdev \ + create_dstport \ + create_nolearning \ + destroy \ + +create_simple_body() { + export IFACE=vx_foo PHASE=create MOCK=echo IF_VXLAN_ID=2342 + atf_check -s exit:0 -o match:'ip link add vx_foo type vxlan id 2342 dstport 4789' \ + ${EXECUTOR} +} + +create_ucast_ptp_body() { + export IFACE=vx_foo PHASE=create MOCK=echo IF_VXLAN_ID=2342 IF_VXLAN_PEER_IPS=192.2.0.42 + atf_check -s exit:0 -o match:'ip link add vx_foo type vxlan id 2342 remote 192.2.0.42' \ + ${EXECUTOR} +} + +create_ucast_ptmp_body() { + export IFACE=vx_foo PHASE=create MOCK=echo IF_VXLAN_ID=2342 IF_VXLAN_PEER_IPS="10.0.0.1 10.0.0.2 10.0.0.3" + atf_check -s exit:0 \ + -o match:'ip link add vx_foo type vxlan id 2342 dstport 4789' \ + -o match:'bridge fdb append 00:00:00:00:00:00 dev vx_foo dst 10.0.0.1 self permanent' \ + -o match:'bridge fdb append 00:00:00:00:00:00 dev vx_foo dst 10.0.0.2 self permanent' \ + -o match:'bridge fdb append 00:00:00:00:00:00 dev vx_foo dst 10.0.0.3 self permanent' \ + ${EXECUTOR} +} + +create_mcast_body() { + export IFACE=vx_foo PHASE=create MOCK=echo IF_VXLAN_ID=2342 IF_VXLAN_PEER_GROUP=225.0.8.15 + atf_check -s exit:0 -o match:'ip link add vx_foo type vxlan id 2342 group 225.0.8.15' \ + ${EXECUTOR} +} + +create_physdev_body() { + export IFACE=vx_foo PHASE=create MOCK=echo IF_VXLAN_ID=2342 IF_VXLAN_PHYSDEV=eth0 + atf_check -s exit:0 -o match:'ip link add vx_foo type vxlan id 2342 dev eth0' \ + ${EXECUTOR} +} + +create_dstport_body() { + export IFACE=vx_foo PHASE=create MOCK=echo IF_VXLAN_ID=2342 IF_VXLAN_DSTPORT=1234 + atf_check -s exit:0 -o match:'ip link add vx_foo type vxlan id 2342 dstport 1234' \ + ${EXECUTOR} +} + +create_nolearning_body() { + export IFACE=vx_foo PHASE=create MOCK=echo IF_VXLAN_ID=2342 IF_VXLAN_LEARNING=no + atf_check -s exit:0 -o match:'ip link add vx_foo type vxlan id 2342 dstport 4789 nolearning' \ + ${EXECUTOR} +} + +destroy_body() { + export IFACE=vx_foo PHASE=destroy MOCK=echo IF_VXLAN_ID=2342 + atf_check -s exit:0 -o match:'ip link del vx_foo' \ + ${EXECUTOR} +} diff --git a/tests/linux/wireguard_test b/tests/linux/wireguard_test new file mode 100755 index 0000000..8b5f709 --- /dev/null +++ b/tests/linux/wireguard_test @@ -0,0 +1,39 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/../test_env.sh +EXECUTOR="$(atf_get_srcdir)/../../executor-scripts/linux/wireguard" + +tests_init \ + create \ + pre_up \ + pre_up_specified_config \ + destroy + +create_body() { + export IFACE=wg0 PHASE=create MOCK=echo + atf_check -s exit:0 \ + -o match:'ip link add wg0 type wireguard' \ + ${EXECUTOR} +} + +pre_up_body() { + export IFACE=wg0 PHASE=pre-up MOCK=echo + atf_check -s exit:0 \ + -o match:'wg setconf wg0 /etc/wireguard/wg0.conf' \ + ${EXECUTOR} +} + +pre_up_specified_config_body() { + export IFACE=wg0 PHASE=pre-up MOCK=echo \ + IF_WIREGUARD_CONFIG_PATH=/etc/wireguard/vpn.conf + atf_check -s exit:0 \ + -o match:'wg setconf wg0 /etc/wireguard/vpn.conf' \ + ${EXECUTOR} +} + +destroy_body() { + export IFACE=wg0 PHASE=destroy MOCK=echo + atf_check -s exit:0 \ + -o match:'ip link delete dev wg0' \ + ${EXECUTOR} +} diff --git a/tests/multicall_test b/tests/multicall_test new file mode 100755 index 0000000..0b4c3ab --- /dev/null +++ b/tests/multicall_test @@ -0,0 +1,11 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + regress_getopt + +regress_getopt_body() { + atf_check -e not-inline:'-F: applet not found' -o ignore -s exit:1 \ + ifupdown ifquery -F +} diff --git a/tests/test_env.sh b/tests/test_env.sh index 07a20f7..870dc86 100644 --- a/tests/test_env.sh +++ b/tests/test_env.sh @@ -1,5 +1,7 @@ -PATH="$(atf_get_srcdir)/..:$PATH" +PATH="$(atf_get_srcdir)/..:$(atf_get_srcdir)/../..:$PATH" FIXTURES="$(atf_get_srcdir)/fixtures" +EXECUTORS="$(atf_get_srcdir)/executors" +EXECUTORS_LINUX="$(atf_get_srcdir)/../executor-scripts/linux" tests_init() { TESTS="$@"