diff --git a/board/common/busybox_defconfig b/board/common/busybox_defconfig index 739bbcf08..cb0653a55 100644 --- a/board/common/busybox_defconfig +++ b/board/common/busybox_defconfig @@ -1137,9 +1137,9 @@ CONFIG_SV_DEFAULT_SERVICE_DIR="" CONFIG_SH_IS_ASH=y # CONFIG_SH_IS_HUSH is not set # CONFIG_SH_IS_NONE is not set -# CONFIG_BASH_IS_ASH is not set +CONFIG_BASH_IS_ASH=y # CONFIG_BASH_IS_HUSH is not set -CONFIG_BASH_IS_NONE=y +# CONFIG_BASH_IS_NONE is not set CONFIG_SHELL_ASH=y CONFIG_ASH=y # CONFIG_ASH_OPTIMIZE_FOR_SIZE is not set diff --git a/board/common/rootfs/etc/finit.d/available/netd.conf b/board/common/rootfs/etc/finit.d/available/netd.conf deleted file mode 100644 index e6e0b79bb..000000000 --- a/board/common/rootfs/etc/finit.d/available/netd.conf +++ /dev/null @@ -1,2 +0,0 @@ -#set DEBUG=1 -service [2345] name:netd netd -- Network route daemon diff --git a/board/common/rootfs/etc/finit.d/enabled/netd.conf b/board/common/rootfs/etc/finit.d/enabled/netd.conf deleted file mode 120000 index 7afbfcdef..000000000 --- a/board/common/rootfs/etc/finit.d/enabled/netd.conf +++ /dev/null @@ -1 +0,0 @@ -../available/netd.conf \ No newline at end of file diff --git a/board/common/rootfs/usr/bin/askline b/board/common/rootfs/usr/bin/askline index ec27c6d94..b025e7096 100755 --- a/board/common/rootfs/usr/bin/askline +++ b/board/common/rootfs/usr/bin/askline @@ -1,15 +1,24 @@ -#!/bin/bash +#!/bin/sh # Prompt for a single-line value, prefilled with the current one. # # askline LABEL FILE # # FILE holds the current value on entry and the new value on exit. +# Editing the value in place needs bash, on builds where bash is ash +# the current value is shown as the default, and Enter keeps it. LABEL=$1 FILE=$2 current=$(cat "$FILE" 2>/dev/null) -read -er -p "$LABEL> " -i "$current" value || exit 1 - umask 0177 + +if [ -n "$(bash -c 'echo "$BASH_VERSION"' 2>/dev/null)" ]; then + exec bash -c 'read -er -p "$1> " -i "$2" value || exit 1 + printf "%s" "$value" > "$3"' askline "$LABEL" "$current" "$FILE" +fi + +printf '%s [%s]> ' "$LABEL" "$current" +read -r value || exit 1 +[ -n "$value" ] || value=$current printf '%s' "$value" > "$FILE" diff --git a/configs/aarch64_minimal_defconfig b/configs/aarch64_minimal_defconfig index 7bdd3f652..d47433f39 100644 --- a/configs/aarch64_minimal_defconfig +++ b/configs/aarch64_minimal_defconfig @@ -31,6 +31,7 @@ BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig" BR2_LINUX_KERNEL_INSTALL_TARGET=y BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig" +BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y BR2_PACKAGE_STRACE=y BR2_PACKAGE_STRESS_NG=y BR2_PACKAGE_JQ=y @@ -63,7 +64,6 @@ BR2_PACKAGE_AVAHI_DEFAULT_SERVICES=y BR2_PACKAGE_CHRONY=y BR2_PACKAGE_DNSMASQ=y BR2_PACKAGE_ETHTOOL=y -BR2_PACKAGE_FRR=y # BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set BR2_PACKAGE_IPROUTE2=y BR2_PACKAGE_IPUTILS=y @@ -81,8 +81,6 @@ BR2_PACKAGE_SOCAT=y BR2_PACKAGE_TCPDUMP=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y -BR2_PACKAGE_BASH=y -BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_SUDO=y BR2_PACKAGE_GETENT=y BR2_PACKAGE_KMOD_TOOLS=y diff --git a/configs/arm_minimal_defconfig b/configs/arm_minimal_defconfig index 91cbef4b4..c1b12bca1 100644 --- a/configs/arm_minimal_defconfig +++ b/configs/arm_minimal_defconfig @@ -33,6 +33,7 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/arm/linux_ BR2_LINUX_KERNEL_INSTALL_TARGET=y BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig" +BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y BR2_PACKAGE_STRACE=y BR2_PACKAGE_STRESS_NG=y BR2_PACKAGE_JQ=y @@ -65,7 +66,6 @@ BR2_PACKAGE_AVAHI_DEFAULT_SERVICES=y BR2_PACKAGE_CHRONY=y BR2_PACKAGE_DNSMASQ=y BR2_PACKAGE_ETHTOOL=y -BR2_PACKAGE_FRR=y # BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set BR2_PACKAGE_IPROUTE2=y BR2_PACKAGE_IPUTILS=y @@ -83,8 +83,6 @@ BR2_PACKAGE_SOCAT=y BR2_PACKAGE_TCPDUMP=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y -BR2_PACKAGE_BASH=y -BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_SUDO=y BR2_PACKAGE_GETENT=y BR2_PACKAGE_KMOD_TOOLS=y diff --git a/configs/x86_64_minimal_defconfig b/configs/x86_64_minimal_defconfig index e39d0eb15..8d001481e 100644 --- a/configs/x86_64_minimal_defconfig +++ b/configs/x86_64_minimal_defconfig @@ -31,6 +31,7 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/lin BR2_LINUX_KERNEL_INSTALL_TARGET=y BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig" +BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y BR2_PACKAGE_STRACE=y BR2_PACKAGE_STRESS_NG=y BR2_PACKAGE_JQ=y @@ -62,7 +63,6 @@ BR2_PACKAGE_AVAHI_DEFAULT_SERVICES=y BR2_PACKAGE_CHRONY=y BR2_PACKAGE_DNSMASQ=y BR2_PACKAGE_ETHTOOL=y -BR2_PACKAGE_FRR=y # BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set BR2_PACKAGE_IPROUTE2=y BR2_PACKAGE_IPUTILS=y @@ -80,8 +80,6 @@ BR2_PACKAGE_SOCAT=y BR2_PACKAGE_TCPDUMP=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y -BR2_PACKAGE_BASH=y -BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_SUDO=y BR2_PACKAGE_GETENT=y BR2_PACKAGE_KMOD_TOOLS=y diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 837e15d2e..f176a5b61 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -11,6 +11,8 @@ All notable changes to the project are documented in this file. - Add IPv6 dynamic routing: RIPng and OSPFv3. Both reuse the existing ietf-rip and ietf-ospf models, selected per control-plane-protocol by the `ripng`/`ospfv3` type and the IPv6 address-family +- Support building without Frr, which also drops bash. The minimal + defconfigs are now built this way, with static routing only, issue #1670 [v26.09.0][] - 2026-09-30 ------------------------- diff --git a/doc/routing.md b/doc/routing.md index 5baac8d13..b4af8e5bf 100644 --- a/doc/routing.md +++ b/doc/routing.md @@ -593,6 +593,11 @@ client, and IPv4 link-local (IPv4) routes, are injected into Frr to let it weigh all routes before installing them into the kernel routing table (sometimes referred to as FIB). +Some builds have static routing only. OSPF, RIP, and BFD are then not +available, and the routing YANG model does not list its `ospf`, `rip`, +and `bfd` features. Route preference still selects between routes to +the same destination, and a route with preference 255 is never used. + Routes have different weights made up from a *distance* and a *metric*. The kernel routing table only talks about *metric*, which unfortunately is **not the same** -- this is one of the reasons why the term *route diff --git a/package/confd/confd.mk b/package/confd/confd.mk index 1394511ca..02a2249c8 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -25,6 +25,12 @@ else CONFD_CONF_OPTS += --disable-containers endif +ifeq ($(BR2_PACKAGE_FRR),y) +CONFD_CONF_OPTS += --enable-frr +else +CONFD_CONF_OPTS += --disable-frr +endif + ifeq ($(BR2_PACKAGE_FEATURE_WIFI),y) CONFD_CONF_OPTS += --enable-wifi else @@ -124,6 +130,18 @@ define CONFD_INSTALL_YANG_MODULES_CONTAINERS $(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/containers.inc endef endif +ifeq ($(BR2_PACKAGE_PODMAN),y) +define CONFD_INSTALL_CONTAINERS + $(INSTALL) -D -m 0755 $(CONFD_PKGDIR)/container $(TARGET_DIR)/usr/sbin/container + $(INSTALL) -D -m 0644 $(CONFD_PKGDIR)/container@.conf $(FINIT_D)/available/container@.conf +endef +endif +ifeq ($(BR2_PACKAGE_FRR),y) +define CONFD_INSTALL_YANG_MODULES_FRR + $(COMMON_SYSREPO_ENV) \ + $(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/frr.inc +endef +endif ifeq ($(BR2_PACKAGE_FEATURE_WIFI),y) define CONFD_INSTALL_YANG_MODULES_WIFI $(COMMON_SYSREPO_ENV) \ @@ -179,6 +197,8 @@ CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_NETCONF_SERVER CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_NETCONF_SERVER CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_CONTAINERS +CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_CONTAINERS +CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_FRR CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_WIFI CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_GPS CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_WEBUI diff --git a/board/common/rootfs/usr/sbin/container b/package/confd/container similarity index 100% rename from board/common/rootfs/usr/sbin/container rename to package/confd/container diff --git a/board/common/rootfs/etc/finit.d/available/container@.conf b/package/confd/container@.conf similarity index 100% rename from board/common/rootfs/etc/finit.d/available/container@.conf rename to package/confd/container@.conf diff --git a/package/netd/netd.conf b/package/netd/netd.conf index fb2f6499d..34d7e6e63 100644 --- a/package/netd/netd.conf +++ b/package/netd/netd.conf @@ -1,3 +1,2 @@ #set DEBUG=1 -service name:netd log \ - [2345] netd -p /run/netd.pid -- Network route daemon +service [2345] name:netd netd -- Network route daemon diff --git a/package/netd/netd.mk b/package/netd/netd.mk index bd5e23f4a..f3ee0d6a4 100644 --- a/package/netd/netd.mk +++ b/package/netd/netd.mk @@ -34,8 +34,18 @@ else NETD_CONF_OPTS += --without-frr endif +# With an FRR backend netd feeds staticd, so it must not start before it +ifeq ($(BR2_PACKAGE_NETD_LINUX),y) +NETD_CONDITION = +else +NETD_CONDITION = +endif + define NETD_INSTALL_EXTRA cp $(NETD_PKGDIR)/tmpfiles.conf $(TARGET_DIR)/etc/tmpfiles.d/netd.conf + cp $(NETD_PKGDIR)/netd.conf $(FINIT_D)/available/netd.conf + $(SED) 's||$(NETD_CONDITION)|' $(FINIT_D)/available/netd.conf + ln -sf ../available/netd.conf $(FINIT_D)/enabled/netd.conf endef NETD_TARGET_FINALIZE_HOOKS += NETD_INSTALL_EXTRA diff --git a/package/skeleton-init-finit/skeleton-init-finit.mk b/package/skeleton-init-finit/skeleton-init-finit.mk index 036451228..b0aca142b 100644 --- a/package/skeleton-init-finit/skeleton-init-finit.mk +++ b/package/skeleton-init-finit/skeleton-init-finit.mk @@ -84,6 +84,10 @@ endef SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_SET_DROPBEAR endif +SKELETON_INIT_FINIT_FRR_DAEMONS = \ + babeld bfdd bgpd mgmtd eigrpd isisd ldpd ospfd ospf6d pathd \ + ripd ripngd staticd vrrpd zebra + ifeq ($(BR2_PACKAGE_FRR),y) ifeq ($(BR2_PACKAGE_NETD_FRR_CONF),y) define SKELETON_INIT_FINIT_SET_FRR @@ -92,7 +96,7 @@ define SKELETON_INIT_FINIT_SET_FRR endef else define SKELETON_INIT_FINIT_SET_FRR - for svc in babeld bfdd bgpd mgmtd eigrpd isisd ldpd ospfd ospf6d pathd ripd ripngd staticd vrrpd zebra; do \ + for svc in $(SKELETON_INIT_FINIT_FRR_DAEMONS); do \ cp $(SKELETON_INIT_FINIT_AVAILABLE)/frr/$$svc.conf $(FINIT_D)/available/$$svc.conf; \ done ln -sf ../available/zebra.conf $(FINIT_D)/enabled/zebra.conf @@ -114,6 +118,14 @@ endef endif SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_SET_FRR_MGMTD_GRPC +else # !BR2_PACKAGE_FRR +# The skeleton is copied wholesale, drop the FRR bits from a build without it +define SKELETON_INIT_FINIT_UNSET_FRR + rm -rf $(TARGET_DIR)/etc/frr $(FINIT_D)/available/frr + rm -f $(addprefix $(TARGET_DIR)/etc/default/,$(SKELETON_INIT_FINIT_FRR_DAEMONS)) + rm -f $(TARGET_DIR)/usr/lib/tmpfiles.d/frr.conf +endef +SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_UNSET_FRR endif # BR2_PACKAGE_FRR ifeq ($(BR2_PACKAGE_INADYN),y) diff --git a/board/common/rootfs/usr/lib/tmpfiles.d/frr.conf b/package/skeleton-init-finit/skeleton/usr/lib/tmpfiles.d/frr.conf similarity index 100% rename from board/common/rootfs/usr/lib/tmpfiles.d/frr.conf rename to package/skeleton-init-finit/skeleton/usr/lib/tmpfiles.d/frr.conf diff --git a/package/statd/statd.mk b/package/statd/statd.mk index 46ee7b5ee..e29361cf5 100644 --- a/package/statd/statd.mk +++ b/package/statd/statd.mk @@ -27,6 +27,12 @@ else STATD_CONF_OPTS += --disable-containers endif +ifeq ($(BR2_PACKAGE_FRR),y) +STATD_CONF_OPTS += --enable-frr +else +STATD_CONF_OPTS += --disable-frr +endif + define STATD_BUILD_PYTHON cd $(STATD_SITE)/python && \ $(PKG_PYTHON_PEP517_ENV) $(HOST_DIR)/bin/python3 $(PKG_PYTHON_PEP517_BUILD_CMD) -o $(@D)/python/dist diff --git a/src/bin/copy.c b/src/bin/copy.c index dec94fc7e..e4cacaaef 100644 --- a/src/bin/copy.c +++ b/src/bin/copy.c @@ -311,6 +311,21 @@ static void sysrepo_print_error(sr_session_ctx_t *sess) warnx("%s (%d)", msg, erri->err->err_code); } +/* + * The NACM subscription has a listener thread that refers to the + * session and connection, so it must go first, before NACM itself. + */ +static void sysrepo_exit(sr_subscription_ctx_t *sub, sr_session_ctx_t *sess) +{ + sr_conn_ctx_t *conn = sr_session_get_connection(sess); + + if (sub) + sr_unsubscribe(sub); + sr_nacm_destroy(); + sr_session_stop(sess); + sr_disconnect(conn); +} + /* Connect to sysrepo and create NACM-aware session on running datastore */ static int sysrepo_init(sr_conn_ctx_t **conn, sr_session_ctx_t **sess, sr_subscription_ctx_t **sub) @@ -352,8 +367,12 @@ static int sysrepo_init(sr_conn_ctx_t **conn, sr_session_ctx_t **sess, return SR_ERR_OK; fail: sysrepo_print_error(*sess); - sr_session_stop(*sess); - sr_disconnect(*conn); + if (*sess) + sysrepo_exit(*sub, *sess); + else + sr_disconnect(*conn); + *sess = NULL; + *sub = NULL; return err; } @@ -369,9 +388,7 @@ static sr_session_ctx_t *sysrepo_session(const struct infix_ds *ds) if (!sess) return NULL; - conn = sr_session_get_connection(sess); - sr_session_stop(sess); - sr_disconnect(conn); + sysrepo_exit(sub, sess); sess = NULL; sub = NULL; return NULL; @@ -1037,12 +1054,7 @@ static int rpc_exec(const char *rpc_xpath, int argc, char *argv[]) cleanup: sr_free_values(input, icnt); sr_free_values(output, ocnt); - if (sub) - sr_nacm_destroy(); - if (sess) - sr_session_stop(sess); - if (conn) - sr_disconnect(conn); + sysrepo_exit(sub, sess); return rc; } diff --git a/src/confd/configure.ac b/src/confd/configure.ac index a3b701b88..28f0c6e2f 100644 --- a/src/confd/configure.ac +++ b/src/confd/configure.ac @@ -45,6 +45,10 @@ AC_ARG_ENABLE(containers, AS_HELP_STRING([--enable-containers], [Enable support for containers]),,[ enable_containers=no]) +AC_ARG_ENABLE(frr, + AS_HELP_STRING([--enable-frr], [Enable dynamic routing (OSPF, RIP, BFD) with FRR]),,[ + enable_frr=no]) + AC_ARG_ENABLE(wifi, AS_HELP_STRING([--enable-wifi], [Enable support for Wi-Fi]),,[ enable_wifi=no]) @@ -84,6 +88,9 @@ AC_ARG_WITH(crypt, AS_IF([test "x$enable_containers" = "xyes"], [ AC_DEFINE(CONTAINERS, 1, [Built with container support])]) +AS_IF([test "x$enable_frr" = "xyes"], [ + AC_DEFINE(HAVE_FRR, 1, [Built with FRR dynamic routing support])]) + AS_IF([test "x$enable_wifi" = "xyes"], [ AC_DEFINE(HAVE_WIFI, 1, [Built with Wi-Fi support])]) @@ -190,6 +197,7 @@ cat < 0) { + touch(RIPD_SIGNAL_NEXT); + return 1; + } + } else if (!strcmp(type, "infix-routing:ripng")) { + if (parse_rip(session, lydx_get_child(cplane, "rip"), fp, 1) > 0) { + touch(RIPNGD_SIGNAL_NEXT); + return 1; + } + } + + return 0; +} + +static void frr_activate(void) +{ + int ospfd_enabled = fexist(OSPFD_CONF_NEXT); + int ospf6d_enabled = fexist(OSPF6D_CONF_NEXT); + int bfdd_enabled = fexist(BFDD_SIGNAL_NEXT); + int ripd_enabled = fexist(RIPD_SIGNAL_NEXT); + int ripngd_enabled = fexist(RIPNGD_SIGNAL_NEXT); + + /* Generate complete /etc/frr/daemons (for watchfrr/frrinit.sh) */ + frr_daemons_write(ospfd_enabled, ospf6d_enabled, ripd_enabled, ripngd_enabled, bfdd_enabled); + + if (bfdd_enabled) + (void)rename(BFDD_SIGNAL_NEXT, BFDD_SIGNAL); + else + (void)remove(BFDD_SIGNAL); + + if (ospfd_enabled) { + (void)remove(OSPFD_CONF_PREV); + (void)rename(OSPFD_CONF, OSPFD_CONF_PREV); + (void)rename(OSPFD_CONF_NEXT, OSPFD_CONF); + } else { + (void)remove(OSPFD_CONF); + } + + if (ospf6d_enabled) { + (void)remove(OSPF6D_CONF_PREV); + (void)rename(OSPF6D_CONF, OSPF6D_CONF_PREV); + (void)rename(OSPF6D_CONF_NEXT, OSPF6D_CONF); + } else { + (void)remove(OSPF6D_CONF); + } + + if (ripd_enabled) + (void)rename(RIPD_SIGNAL_NEXT, RIPD_SIGNAL); + else + (void)remove(RIPD_SIGNAL); + + if (ripngd_enabled) + (void)rename(RIPNGD_SIGNAL_NEXT, RIPNGD_SIGNAL); + else + (void)remove(RIPNGD_SIGNAL); + + /* Enable/disable FRR daemons as standalone finit services. + * Harmless no-op when using watchfrr (services not installed). */ + ospfd_enabled ? finit_enable("ospfd") : finit_disable("ospfd"); + if (ospfd_enabled) + finit_reload("ospfd"); + ospf6d_enabled ? finit_enable("ospf6d") : finit_disable("ospf6d"); + if (ospf6d_enabled) + finit_reload("ospf6d"); + ripd_enabled ? finit_enable("ripd") : finit_disable("ripd"); + ripngd_enabled ? finit_enable("ripngd") : finit_disable("ripngd"); + bfdd_enabled ? finit_enable("bfdd") : finit_disable("bfdd"); +} +#else +static void frr_abort(void) +{ +} + +static void frr_prepare(void) +{ +} + +static int frr_parse(sr_session_ctx_t *session, const char *type, struct lyd_node *cplane, FILE *fp) +{ + return 0; +} + +static void frr_activate(void) +{ +} +#endif /* HAVE_FRR */ + int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) { - int netd_enabled = 0, ospfd_enabled = 0, ospf6d_enabled = 0, bfdd_enabled = 0, ripd_enabled = 0, ripngd_enabled = 0; + int netd_enabled = 0; struct lyd_node *cplane, *cplanes; int rc = SR_ERR_OK; FILE *fp; @@ -643,34 +759,20 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly break; case SR_EV_ABORT: /* User abort, or other plugin failed */ - /* Drop every next-config, a stale one would otherwise be - * activated by a later commit that does not touch its - * protocol. */ (void)remove(NETD_CONF_NEXT); - (void)remove(OSPFD_CONF_NEXT); - (void)remove(OSPF6D_CONF_NEXT); - (void)remove(RIPD_SIGNAL_NEXT); - (void)remove(RIPNGD_SIGNAL_NEXT); - (void)remove(BFDD_SIGNAL_NEXT); + frr_abort(); return SR_ERR_OK; case SR_EV_DONE: /* Check if passed validation in previous event */ netd_enabled = fexist(NETD_CONF_NEXT); - ospfd_enabled = fexist(OSPFD_CONF_NEXT); - ospf6d_enabled = fexist(OSPF6D_CONF_NEXT); - bfdd_enabled = fexist(BFDD_SIGNAL_NEXT); - ripd_enabled = fexist(RIPD_SIGNAL_NEXT); - ripngd_enabled = fexist(RIPNGD_SIGNAL_NEXT); goto activate; default: return SR_ERR_OK; } - /* Reset the shared BFDD signal; parse_ospf/parse_ospf6 re-set it if any - * OSPF instance enables BFD this commit. */ - (void)remove(BFDD_SIGNAL_NEXT); + frr_prepare(); cplanes = lydx_get_descendant(config, "routing", "control-plane-protocols", "control-plane-protocol", NULL); @@ -691,22 +793,8 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly num = parse_static_routes(session, lydx_get_child(cplane, "static-routes"), fp); if (num > 0) netd_enabled = 1; - } else if (!strcmp(type, "infix-routing:ospfv2")) { - parse_ospf(session, lydx_get_child(cplane, "ospf")); - } else if (!strcmp(type, "infix-routing:ospfv3")) { - parse_ospf6(session, lydx_get_child(cplane, "ospf")); - } else if (!strcmp(type, "infix-routing:ripv2")) { - num = parse_rip(session, lydx_get_child(cplane, "rip"), fp, 0); - if (num > 0) { - touch(RIPD_SIGNAL_NEXT); - netd_enabled = 1; - } - } else if (!strcmp(type, "infix-routing:ripng")) { - num = parse_rip(session, lydx_get_child(cplane, "rip"), fp, 1); - if (num > 0) { - touch(RIPNGD_SIGNAL_NEXT); - netd_enabled = 1; - } + } else if (frr_parse(session, type, cplane, fp)) { + netd_enabled = 1; } } @@ -719,46 +807,9 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly /* For SR_EV_ENABLED we activate immediately (no SR_EV_DONE follows) */ netd_enabled = fexist(NETD_CONF_NEXT); - ospfd_enabled = fexist(OSPFD_CONF_NEXT); - ospf6d_enabled = fexist(OSPF6D_CONF_NEXT); - bfdd_enabled = fexist(BFDD_SIGNAL_NEXT); - ripd_enabled = fexist(RIPD_SIGNAL_NEXT); - ripngd_enabled = fexist(RIPNGD_SIGNAL_NEXT); activate: - /* Generate complete /etc/frr/daemons (for watchfrr/frrinit.sh) */ - frr_daemons_write(ospfd_enabled, ospf6d_enabled, ripd_enabled, ripngd_enabled, bfdd_enabled); - - if (bfdd_enabled) - (void)rename(BFDD_SIGNAL_NEXT, BFDD_SIGNAL); - else - (void)remove(BFDD_SIGNAL); - - if (ospfd_enabled) { - (void)remove(OSPFD_CONF_PREV); - (void)rename(OSPFD_CONF, OSPFD_CONF_PREV); - (void)rename(OSPFD_CONF_NEXT, OSPFD_CONF); - } else { - (void)remove(OSPFD_CONF); - } - - if (ospf6d_enabled) { - (void)remove(OSPF6D_CONF_PREV); - (void)rename(OSPF6D_CONF, OSPF6D_CONF_PREV); - (void)rename(OSPF6D_CONF_NEXT, OSPF6D_CONF); - } else { - (void)remove(OSPF6D_CONF); - } - - if (ripd_enabled) - (void)rename(RIPD_SIGNAL_NEXT, RIPD_SIGNAL); - else - (void)remove(RIPD_SIGNAL); - - if (ripngd_enabled) - (void)rename(RIPNGD_SIGNAL_NEXT, RIPNGD_SIGNAL); - else - (void)remove(RIPNGD_SIGNAL); + frr_activate(); /* netd handles both static routes and RIP, assembles frr.conf */ if (netd_enabled) { @@ -769,21 +820,10 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly (void)remove(NETD_CONF); } - /* Enable/disable FRR daemons as standalone finit services. - * Harmless no-op when using watchfrr (services not installed). */ - ospfd_enabled ? finit_enable("ospfd") : finit_disable("ospfd"); - if (ospfd_enabled) - finit_reload("ospfd"); - ospf6d_enabled ? finit_enable("ospf6d") : finit_disable("ospf6d"); - if (ospf6d_enabled) - finit_reload("ospf6d"); - ripd_enabled ? finit_enable("ripd") : finit_disable("ripd"); - ripngd_enabled ? finit_enable("ripngd") : finit_disable("ripngd"); - bfdd_enabled ? finit_enable("bfdd") : finit_disable("bfdd"); - /* - * Signal netd to reload - it assembles /etc/frr/frr.conf and - * Finit propagates the restart to the frr sysv service + * Signal netd to reload. With FRR it assembles /etc/frr/frr.conf + * and Finit propagates the restart to the frr sysv service, without + * it netd installs the static routes in the kernel itself. */ if (finit_reload("netd")) ERROR("Failed to signal netd for reload"); diff --git a/src/confd/src/system.c b/src/confd/src/system.c index 77cae7375..a4c100fbc 100644 --- a/src/confd/src/system.c +++ b/src/confd/src/system.c @@ -54,10 +54,11 @@ struct sr_change { static char *nm = NULL; static char *id = NULL; -/* TODO: add `#ifdef HAVE_FOO` around optional features. */ static const char *admin_groups[] = { "wheel", +#ifdef HAVE_FRR "frrvty", +#endif NULL }; diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 55a8a4cac..7363a3abe 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -31,7 +31,7 @@ MODULES=( "ieee802-dot1q-types@2022-10-29.yang" "infix-ip@2026-04-28.yang" "infix-if-type@2026-01-07.yang" - "infix-routing@2026-07-22.yang" + "infix-routing@2026-09-28.yang" "ieee802-dot1ab-lldp@2022-03-15.yang" "infix-lldp@2025-05-05.yang" "infix-dhcp-common@2025-12-21.yang" diff --git a/src/confd/yang/confd/infix-routing.yang b/src/confd/yang/confd/infix-routing.yang index aeb44daf3..25b495898 100644 --- a/src/confd/yang/confd/infix-routing.yang +++ b/src/confd/yang/confd/infix-routing.yang @@ -26,6 +26,14 @@ module infix-routing { contact "kernelkit@googlegroups.com"; description "Deviations and augments for ietf-routing, ietf-ospf, and ietf-rip."; + revision 2026-09-28 { + description "Add ospf, rip, and bfd features, where ospf covers OSPFv2 + and OSPFv3, and rip covers RIPv2 and RIPng. A build without + FRR leaves them off, so only static routes can be configured + and the device advertises exactly what it supports."; + reference "Issue #1670"; + } + revision 2026-07-22 { description "Add IPv6 dynamic routing support (RIPng and OSPFv3). Introduce the ripng and ospfv3 routing-type identities, @@ -142,6 +150,16 @@ module infix-routing { deviate not-supported; } + feature ospf { + description "OSPFv2 and OSPFv3 dynamic routing, provided by FRR."; + } + feature rip { + description "RIPv2 and RIPng dynamic routing, provided by FRR."; + } + feature bfd { + description "Bidirectional Forwarding Detection, provided by FRR."; + } + identity kernel { base rt:routing-protocol; description @@ -169,26 +187,31 @@ module infix-routing { description "Infix routing type"; } identity ospfv2 { + if-feature "ospf"; base ospf:ospfv2; base infix-routing-type; description "OSPFv2 (IPv4) routing protocol"; } identity ospfv3 { + if-feature "ospf"; base ospf:ospfv3; base infix-routing-type; description "OSPFv3 (IPv6) routing protocol"; } identity ripv2 { + if-feature "rip"; base rip:ripv2; base infix-routing-type; description "RIPv2 (IPv4) routing protocol"; } identity ripng { + if-feature "rip"; base rip:ripng; base infix-routing-type; description "RIPng (IPv6) routing protocol"; } identity bfdv1 { + if-feature "bfd"; base bfd-types:bfdv1; base infix-routing-type; description "BFD protocol version 1"; diff --git a/src/confd/yang/confd/infix-routing@2026-07-22.yang b/src/confd/yang/confd/infix-routing@2026-09-28.yang similarity index 100% rename from src/confd/yang/confd/infix-routing@2026-07-22.yang rename to src/confd/yang/confd/infix-routing@2026-09-28.yang diff --git a/src/confd/yang/frr.inc b/src/confd/yang/frr.inc new file mode 100644 index 000000000..5fe65eb0b --- /dev/null +++ b/src/confd/yang/frr.inc @@ -0,0 +1,4 @@ +# -*- sh -*- +MODULES=( + "infix-routing -e ospf -e rip -e bfd" +) diff --git a/src/netd/src/linux_backend.c b/src/netd/src/linux_backend.c index 271642a13..117f8bbbc 100644 --- a/src/netd/src/linux_backend.c +++ b/src/netd/src/linux_backend.c @@ -241,6 +241,8 @@ static int route_exists(struct route_head *list, const struct route *needle) continue; if (r->prefixlen != needle->prefixlen) continue; + if (r->distance != needle->distance) + continue; /* Compare prefix */ if (r->family == AF_INET) { @@ -289,7 +291,7 @@ static int kernel_read_routes(struct route_head *routes, int family) struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; struct nlmsghdr *nlh; struct rtattr *rta; - struct msghdr msg; + struct msghdr msg = { 0 }; struct rtmsg *rtm; struct iovec iov; struct route *r; @@ -380,6 +382,9 @@ static int kernel_read_routes(struct route_head *routes, int family) break; case RTA_OIF: + /* Gateway routes carry the interface too */ + if (r->nh_type == NH_ADDR) + break; r->nh_type = NH_IFNAME; if_indextoname(*(uint32_t *)RTA_DATA(rta), r->ifname); break; @@ -433,6 +438,9 @@ int linux_backend_apply(struct route_head *routes, struct rip_config *rip, /* Add new routes from config (kernel_routes still has old state) */ TAILQ_FOREACH(r, routes, entries) { + /* Same as FRR: distance 255 means never install the route */ + if (r->distance == 255) + continue; if (!route_exists(&kernel_routes, r)) { DEBUG("Adding new route"); if (netlink_route_add(r) == 0) diff --git a/src/statd/configure.ac b/src/statd/configure.ac index 5794a42fd..b0afa88d5 100644 --- a/src/statd/configure.ac +++ b/src/statd/configure.ac @@ -17,6 +17,10 @@ AC_ARG_ENABLE(containers, AS_HELP_STRING([--enable-containers], [Enable support for containers]),,[ enable_containers=no]) +AC_ARG_ENABLE(frr, + AS_HELP_STRING([--enable-frr], [Enable dynamic routing (OSPF, RIP, BFD) status from FRR]),,[ + enable_frr=no]) + AC_ARG_WITH([yanger-dir], [AS_HELP_STRING([--with-yanger-dir=DIR], [specify the path to yanger])], @@ -30,6 +34,9 @@ AC_DEFINE_UNQUOTED([YANGER_DIR], ["$YANGER_DIR"], [Path to yanger]) AS_IF([test "x$enable_containers" = "xyes"], [ AC_DEFINE(CONTAINERS, 1, [Built with container support])]) +AS_IF([test "x$enable_frr" = "xyes"], [ + AC_DEFINE(HAVE_FRR, 1, [Built with FRR dynamic routing support])]) + # Control build with automake flags AM_CONDITIONAL(CONTAINERS, [test "x$enable_containers" != "xno"]) diff --git a/src/statd/python/yanger/host.py b/src/statd/python/yanger/host.py index ccde7c784..cff4b67f7 100644 --- a/src/statd/python/yanger/host.py +++ b/src/statd/python/yanger/host.py @@ -10,6 +10,10 @@ HOST = None +# statd waits for yanger inside its sysrepo callbacks, so a command that +# never returns blocks every operational read for as long as it hangs. +RUN_TIMEOUT = 30 + class Host(abc.ABC): """Host system API""" @@ -102,9 +106,10 @@ def run(self, cmd, default=None, log=True): result = subprocess.run(cmd, check=True, text=True, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL) + stderr=subprocess.DEVNULL, + timeout=RUN_TIMEOUT) return result.stdout - except subprocess.CalledProcessError as err: + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: if default is not None: return default diff --git a/src/statd/python/yanger/ietf_routing.py b/src/statd/python/yanger/ietf_routing.py index 9c55dfc20..b9bb83c9a 100644 --- a/src/statd/python/yanger/ietf_routing.py +++ b/src/statd/python/yanger/ietf_routing.py @@ -38,6 +38,72 @@ def uptime2datetime(uptime): return str(YangDate.from_delta(uptime_delta)) +# Default route and host prefix length per address family +FAMILY = { + "ipv4": ("0.0.0.0/0", "32"), + "ipv6": ("::/0", "128"), +} + + +def kernel_routes(proto): + """Route table straight from the kernel, for builds without FRR + + Every route here is in the FIB, so it is installed, and of the + routes to the same prefix the one with the lowest metric is the + active one. netd installs static routes with the configured + route preference as metric. + """ + family = '-4' if proto == "ipv4" else '-6' + data = HOST.run_json(['ip', '-j', family, 'route', 'show'], []) + default, host_prefix_length = FAMILY[proto] + + pmap = { + 'kernel': 'direct', + 'static': 'static', + } + + routes = [] + best = {} + for route in data: + dst = route.get('dst', 'default') + if dst == 'default': + dst = default + elif '/' not in dst: + dst = f"{dst}/{host_prefix_length}" + metric = route.get('metric', 0) + best[dst] = min(metric, best.get(dst, metric)) + routes.append((dst, metric, route)) + + out = [] + for dst, metric, route in routes: + new = {} + new[f'ietf-{proto}-unicast-routing:destination-prefix'] = dst + new['source-protocol'] = pmap.get(route.get('protocol'), 'infix-routing:kernel') + new['route-preference'] = metric + if metric == best[dst]: + new['active'] = [None] + + hops = route.get('nexthops', [route]) + next_hops = [] + for hop in hops: + next_hop = {'infix-routing:installed': [None]} + if hop.get('gateway'): + next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['gateway'] + elif hop.get('dev'): + next_hop['outgoing-interface'] = hop['dev'] + next_hops.append(next_hop) + + rtype = route.get('type', 'unicast') + if rtype in ("blackhole", "unreachable", "prohibit"): + new['next-hop'] = {'special-next-hop': "unreachable" if rtype == "prohibit" else rtype} + else: + new['next-hop'] = {'next-hop-list': {'next-hop': next_hops}} + + out.append(new) + + return out + + def add_protocol(routes, proto): """Populate routes from vtysh JSON output""" @@ -57,13 +123,7 @@ def add_protocol(routes, proto): out = {} out["route"] = [] - - if proto == "ipv4": - default = "0.0.0.0/0" - host_prefix_length = "32" - else: - default = "::/0" - host_prefix_length = "128" + default, host_prefix_length = FAMILY[proto] for prefix, entries in data.items(): for route in entries: @@ -184,9 +244,11 @@ def operational(): } } - ipv4routes = out['ietf-routing:routing']['ribs']['rib'][0] - ipv6routes = out['ietf-routing:routing']['ribs']['rib'][1] - add_protocol(ipv4routes, "ipv4") - add_protocol(ipv6routes, "ipv6") + frr = HOST.exists('/usr/bin/vtysh') + for rib in out['ietf-routing:routing']['ribs']['rib']: + if frr: + add_protocol(rib, rib['name']) + else: + insert(rib, 'routes', {"route": kernel_routes(rib['name'])}) return out diff --git a/src/statd/statd.c b/src/statd/statd.c index c3fbef096..34757fd12 100644 --- a/src/statd/statd.c +++ b/src/statd/statd.c @@ -46,9 +46,11 @@ #define XPATH_ROUTING_TABLE "/ietf-routing:routing/ribs" #define XPATH_HARDWARE_BASE "/ietf-hardware:hardware" #define XPATH_SYSTEM_BASE "/ietf-system" +#ifdef HAVE_FRR #define XPATH_ROUTING_OSPF XPATH_ROUTING_BASE "/ospf" #define XPATH_ROUTING_RIP XPATH_ROUTING_BASE "/rip" #define XPATH_ROUTING_BFD XPATH_ROUTING_BASE "/bfd" +#endif #define XPATH_CONTAIN_BASE "/infix-containers:containers" #define XPATH_DHCP_SERVER_BASE "/infix-dhcp-server:dhcp-server" #define XPATH_TFTP_FILES "/infix-services:tftp/files" @@ -242,6 +244,7 @@ static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *model, return err; } +#ifdef HAVE_FRR static int sr_ospf_cb(sr_session_ctx_t *session, uint32_t, const char *, const char *, const char *xpath, uint32_t, struct lyd_node **parent, __attribute__((unused)) void *priv) @@ -349,6 +352,7 @@ static int sr_bfd_cb(sr_session_ctx_t *session, uint32_t, const char *, return err; } +#endif /* HAVE_FRR */ static void sigint_cb(struct ev_loop *loop, struct ev_signal *, int) @@ -440,12 +444,14 @@ static int subscribe_to_all(struct statd *statd) return SR_ERR_INTERNAL; if (subscribe(statd, "ietf-interfaces", XPATH_IFACE_BASE, sr_iface_cb)) return SR_ERR_INTERNAL; +#ifdef HAVE_FRR if (subscribe(statd, "ietf-routing", XPATH_ROUTING_OSPF, sr_ospf_cb)) return SR_ERR_INTERNAL; if (subscribe(statd, "ietf-routing", XPATH_ROUTING_RIP, sr_rip_cb)) return SR_ERR_INTERNAL; if (subscribe(statd, "ietf-routing", XPATH_ROUTING_BFD, sr_bfd_cb)) return SR_ERR_INTERNAL; +#endif if (subscribe(statd, "ietf-hardware", XPATH_HARDWARE_BASE, sr_generic_cb)) return SR_ERR_INTERNAL; if (subscribe(statd, "ietf-system", XPATH_SYSTEM_BASE":system", sr_generic_cb)) diff --git a/test/case/routing/ospf_basic/test.py b/test/case/routing/ospf_basic/test.py index 23e4d7b53..041fcc28e 100755 --- a/test/case/routing/ospf_basic/test.py +++ b/test/case/routing/ospf_basic/test.py @@ -202,6 +202,7 @@ def config_host(target, link, p): R1, R2, HOST = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt"), lambda: env.attach("HOST", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2) with test.step("Configure targets"): _, R1data = env.ltop.xlate("R1", "data") diff --git a/test/case/routing/ospf_bfd/test.py b/test/case/routing/ospf_bfd/test.py index 8935fa9ad..3bb3edbaf 100755 --- a/test/case/routing/ospf_bfd/test.py +++ b/test/case/routing/ospf_bfd/test.py @@ -146,6 +146,8 @@ def ospf_interface(ifname, cost): R1, R2 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2) + route.skip_unless_supported(test, "bfd", R1, R2) with test.step("Setup TPMR between R1fast and R2fast"): breaker = TPMR(env.ltop.xlate("PC", "R1fast")[1], diff --git a/test/case/routing/ospf_debug/test.py b/test/case/routing/ospf_debug/test.py index 8166235fd..08c2f4b26 100755 --- a/test/case/routing/ospf_debug/test.py +++ b/test/case/routing/ospf_debug/test.py @@ -223,6 +223,7 @@ def config_target2(target, link): R1, R1ssh, R2 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R1", "mgmt", "ssh"), lambda: env.attach("R2", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2) with test.step("Clean up old log files from previous test runs"): R1ssh.runsh("sudo rm -f /var/log/ospf-debug") diff --git a/test/case/routing/ospf_default_route_advertise/test.py b/test/case/routing/ospf_default_route_advertise/test.py index 890eaff01..780368589 100755 --- a/test/case/routing/ospf_default_route_advertise/test.py +++ b/test/case/routing/ospf_default_route_advertise/test.py @@ -250,6 +250,7 @@ def set_redistribute_default_always(target, p): R1, R2 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2) with test.step("Configure targets"): _, R1data = env.ltop.xlate("R1", "data") diff --git a/test/case/routing/ospf_multiarea/test.py b/test/case/routing/ospf_multiarea/test.py index 8a39230d1..b48cb9e7b 100755 --- a/test/case/routing/ospf_multiarea/test.py +++ b/test/case/routing/ospf_multiarea/test.py @@ -307,6 +307,7 @@ def lo(name): lambda: env.attach("R2", "mgmt"), lambda: env.attach("R3", "mgmt"), lambda: env.attach("R4", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2, R3, R4) _, R1ring1 = env.ltop.xlate("R1", "ring1") _, R1ring2 = env.ltop.xlate("R1", "ring2") diff --git a/test/case/routing/ospf_point_to_multipoint/test.py b/test/case/routing/ospf_point_to_multipoint/test.py index 57293d1b1..c1b73343e 100755 --- a/test/case/routing/ospf_point_to_multipoint/test.py +++ b/test/case/routing/ospf_point_to_multipoint/test.py @@ -296,6 +296,7 @@ def config_target3(target, link, data): R1, R2, R3 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt"), lambda: env.attach("R3", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2, R3) with test.step("Configure targets"): diff --git a/test/case/routing/ospf_point_to_multipoint_hybrid/test.py b/test/case/routing/ospf_point_to_multipoint_hybrid/test.py index 455692b7e..8a6dc1687 100755 --- a/test/case/routing/ospf_point_to_multipoint_hybrid/test.py +++ b/test/case/routing/ospf_point_to_multipoint_hybrid/test.py @@ -190,6 +190,7 @@ def lo(name): R1, R2, R3 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt"), lambda: env.attach("R3", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2, R3) with test.step("Configure targets"): _, R1link = env.ltop.xlate("R1", "link") diff --git a/test/case/routing/ospf_unnumbered_interface/test.py b/test/case/routing/ospf_unnumbered_interface/test.py index aa86ca290..7c5c91fac 100755 --- a/test/case/routing/ospf_unnumbered_interface/test.py +++ b/test/case/routing/ospf_unnumbered_interface/test.py @@ -159,6 +159,7 @@ def config_target2(target, link): env = infamy.Env() R1, R2 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2) _, R1data = env.ltop.xlate("R1", "data") _, R2link = env.ltop.xlate("R2", "link") diff --git a/test/case/routing/rip_basic/test.py b/test/case/routing/rip_basic/test.py index 71283dfa0..9d6ff147c 100755 --- a/test/case/routing/rip_basic/test.py +++ b/test/case/routing/rip_basic/test.py @@ -164,6 +164,7 @@ def config_target2(target, link, data, p): R1, R2 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt")) + route.skip_unless_supported(test, "rip", R1, R2) with test.step("Configure targets"): _, R1data = env.ltop.xlate("R1", "data") diff --git a/test/case/routing/rip_multihop/test.py b/test/case/routing/rip_multihop/test.py index 1789f97ac..038129333 100755 --- a/test/case/routing/rip_multihop/test.py +++ b/test/case/routing/rip_multihop/test.py @@ -153,6 +153,7 @@ def lo(name): R1, R2, R3 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt"), lambda: env.attach("R3", "mgmt")) + route.skip_unless_supported(test, "rip", R1, R2, R3) with test.step("Configure routers"): _, R1data = env.ltop.xlate("R1", "data") diff --git a/test/case/routing/rip_passive_interface/test.py b/test/case/routing/rip_passive_interface/test.py index 32605234a..6b6a14c36 100755 --- a/test/case/routing/rip_passive_interface/test.py +++ b/test/case/routing/rip_passive_interface/test.py @@ -137,6 +137,7 @@ def config_target2(target, link, p): R1, R2 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt")) + route.skip_unless_supported(test, "rip", R1, R2) with test.step("Configure targets"): _, R1data = env.ltop.xlate("R1", "data") diff --git a/test/case/routing/rip_redistribute/test.py b/test/case/routing/rip_redistribute/test.py index e944be174..6e9236081 100755 --- a/test/case/routing/rip_redistribute/test.py +++ b/test/case/routing/rip_redistribute/test.py @@ -218,6 +218,8 @@ def config_r3_ospf(target, link, p): R1, R2, R3 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt"), lambda: env.attach("R3", "mgmt")) + route.skip_unless_supported(test, "rip", R1, R2) + route.skip_unless_supported(test, "ospf", R1, R3) with test.step("Configure routers"): _, R1rip = env.ltop.xlate("R1", "rip") diff --git a/test/case/routing/route_pref_ospf/test.py b/test/case/routing/route_pref_ospf/test.py index 440056a9f..dd3150e88 100755 --- a/test/case/routing/route_pref_ospf/test.py +++ b/test/case/routing/route_pref_ospf/test.py @@ -169,6 +169,7 @@ def config_target2(target, data, link, ospf, p): R1, R2 = parallel(lambda: env.attach("R1", "mgmt"), lambda: env.attach("R2", "mgmt")) + route.skip_unless_supported(test, "ospf", R1, R2) with test.step("Set up TPMR between R1ospf and R2ospf"): ospf_breaker = TPMR(env.ltop.xlate("PC", "R1_ospf")[1], env.ltop.xlate("PC", "R2_ospf")[1]).start() diff --git a/test/case/statd/containers/system/rootfs/usr/bin/vtysh b/test/case/statd/containers/system/rootfs/usr/bin/vtysh new file mode 100644 index 000000000..e69de29bb diff --git a/test/case/use_case/ospf_container/test.py b/test/case/use_case/ospf_container/test.py index 3815b6840..499a52317 100755 --- a/test/case/use_case/ospf_container/test.py +++ b/test/case/use_case/ospf_container/test.py @@ -584,6 +584,7 @@ def config_abr(target, data, link1, link2, link3): test.skip() if not R3.has_model("infix-containers"): test.skip() + route.skip_unless_supported(test, "ospf", R1, R2, R3, ABR) with test.step("Configure DUTs"): _, R1ring1 = env.ltop.xlate("R1", "ring1") diff --git a/test/infamy/restconf.py b/test/infamy/restconf.py index 63a75b705..b23a90690 100644 --- a/test/infamy/restconf.py +++ b/test/infamy/restconf.py @@ -16,6 +16,11 @@ # We know we have a self-signed certificate, silence warning about it warnings.simplefilter('ignore', InsecureRequestWarning) +# (connect, read) in seconds. nginx gives up on rousette after 60 s +# and replies 504, so only a lost request or reply reaches the read +# timeout. Without one, requests waits forever. +TIMEOUT = (10, 90) + @dataclass class Location: interface: str @@ -58,7 +63,7 @@ def requests_workaround(method, url, json, headers, auth, verify=False, retry=0) prepared_request = session.prepare_request(request) prepared_request.url = re.sub(r'%25', '%', prepared_request.url) prepared_request.url = re.sub(r'%3a', ':', prepared_request.url, flags=re.IGNORECASE) - response = session.send(prepared_request, verify=verify) + response = session.send(prepared_request, verify=verify, timeout=TIMEOUT) try: # Raise exceptions for HTTP errors response.raise_for_status() diff --git a/test/infamy/route.py b/test/infamy/route.py index de23fc57c..093e8bd4e 100644 --- a/test/infamy/route.py +++ b/test/infamy/route.py @@ -3,6 +3,18 @@ """ +def skip_unless_supported(test, feature, *targets): + """Skip the test unless every target advertises the routing feature + + A build without FRR has only static routing, its infix-routing + module leaves the ospf, rip and bfd features off. + """ + for target in targets: + if not target.has_feature("infix-routing", feature): + print(f"DUT does not advertise the '{feature}' routing feature -- skipping") + test.skip() + + def _get_routes(target, protocol): xpath = "/ietf-routing:routing/ribs" rib = target.get_data(xpath)["routing"]["ribs"]["rib"]