diff --git a/board/common/rootfs/etc/finit.d/available/mstpd.conf b/board/common/rootfs/etc/finit.d/available/mstpd.conf new file mode 100644 index 00000000..211aaa8e --- /dev/null +++ b/board/common/rootfs/etc/finit.d/available/mstpd.conf @@ -0,0 +1,8 @@ +# A single mstpd instance can manage multiple bridges, which are +# dynamically added/removed by the kernel via the /sbin/bridge-stp +# usermode helper. We depend on usr/mstpd so that confd can +# enable/disable the service without an initctl barrier, since it +# needs to already be running when a bridge interface with spanning +# tree enabled is created. +service env:-/etc/default/mstpd \ + [S0123456789] mstpd $MSTPD_ARGS -- Spanning Tree daemon diff --git a/board/common/rootfs/etc/finit.d/enabled/mstpd.conf b/board/common/rootfs/etc/finit.d/enabled/mstpd.conf new file mode 120000 index 00000000..e55e364d --- /dev/null +++ b/board/common/rootfs/etc/finit.d/enabled/mstpd.conf @@ -0,0 +1 @@ +../available/mstpd.conf \ No newline at end of file diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 7507412e..f176a5d1 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -18,6 +18,7 @@ All notable changes to the project are documented in this file. - SSH Server is now configurable, issue #441 SSH Server and NETCONF Server now uses the same SSH hostkey in factory-config - Add support for GRE/GRETAP tunnels + - Support for STP/RSTP on bridges ### Fixes diff --git a/src/confd/bin/Makefile.am b/src/confd/bin/Makefile.am index fd7c12d3..1748757e 100644 --- a/src/confd/bin/Makefile.am +++ b/src/confd/bin/Makefile.am @@ -1,3 +1,4 @@ pkglibexec_SCRIPTS = bootstrap error load gen-service gen-hostname \ - gen-interfaces gen-motd gen-hardware gen-version + gen-interfaces gen-motd gen-hardware gen-version \ + mstpd-wait-online sbin_SCRIPTS = dagger migrate diff --git a/src/confd/bin/mstpd-wait-online b/src/confd/bin/mstpd-wait-online new file mode 100644 index 00000000..cb413996 --- /dev/null +++ b/src/confd/bin/mstpd-wait-online @@ -0,0 +1,16 @@ +#!/bin/sh +# +# Block until mstpd has opened its control socket + +set -e + +timeout=${1:-10} + +for _ in $(seq $((timeout * 10))); do + ss -Haxn src '@.mstp_server*' | grep -q mstp_server && exit 0 + sleep .1 +done + +logger -I $$ -p user.error -t mstpd-wait-online \ + "Timeout waiting for mstpd to start" +exit 1 diff --git a/src/confd/src/dagger.h b/src/confd/src/dagger.h index f60408bf..4720657b 100644 --- a/src/confd/src/dagger.h +++ b/src/confd/src/dagger.h @@ -73,8 +73,11 @@ enum netdag_init { /* Interface specific setup (bridge VLAN PVID) of lowers */ NETDAG_INIT_LOWERS_PROTO = 65, - /* Start daemons running on interface (zcip etc.) */ + /* Start/configure daemons on interface (mstpd, zcip etc.) */ NETDAG_INIT_DAEMON = 70, + + /* Inject lower-specific configuration to daemon (mstpd) */ + NETDAG_INIT_DAEMON_LOWERS = 75, }; FILE *dagger_fopen_net_init(struct dagger *d, const char *node, enum netdag_init order, diff --git a/src/confd/src/ietf-interfaces.c b/src/confd/src/ietf-interfaces.c index 4cd6028f..2ced2d96 100644 --- a/src/confd/src/ietf-interfaces.c +++ b/src/confd/src/ietf-interfaces.c @@ -662,6 +662,11 @@ static sr_error_t ifchange_post(sr_session_ctx_t *session, struct dagger *net, */ err = bridge_mcd_gen(cifs); + /* Whenever at least one bridge has spanning tree enabled, + * start mstpd; otherwise, stop it. + */ + err = err ? : bridge_mstpd_gen(cifs); + return err ? SR_ERR_INTERNAL : SR_ERR_OK; } diff --git a/src/confd/src/ietf-interfaces.h b/src/confd/src/ietf-interfaces.h index 7c733950..4b74a2be 100644 --- a/src/confd/src/ietf-interfaces.h +++ b/src/confd/src/ietf-interfaces.h @@ -57,6 +57,7 @@ int netdag_gen_ip_addrs(struct dagger *net, FILE *ip, const char *proto, struct lyd_node *cif, struct lyd_node *dif); /* infix-if-bridge.c */ +int bridge_mstpd_gen(struct lyd_node *cifs); int bridge_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, int add); /* infix-if-bridge-mcd.c */ int bridge_mcd_gen(struct lyd_node *cifs); diff --git a/src/confd/src/infix-if-bridge-port.c b/src/confd/src/infix-if-bridge-port.c index 08103b76..42f77f2a 100644 --- a/src/confd/src/infix-if-bridge-port.c +++ b/src/confd/src/infix-if-bridge-port.c @@ -12,6 +12,78 @@ #include "ietf-interfaces.h" +static const char *mstpd_cost_str(struct lyd_node *cost) +{ + const char *val = lyd_get_value(cost); + + if (!strcmp(val, "auto")) + return "0"; + + return val; +} + +static int gen_stp_tree(FILE *mstpctl, const char *iface, + const char *brname, struct lyd_node *tree) +{ + int mstid = 0; + + fprintf(mstpctl, "settreeportcost %s %s %d %s\n", brname, iface, mstid, + mstpd_cost_str(lydx_get_child(tree, "internal-path-cost"))); + + fprintf(mstpctl, "settreeportprio %s %s %d %s\n", brname, iface, mstid, + lydx_get_cattr(tree, "priority")); + + return 0; +} + +static int gen_stp(struct lyd_node *cif) +{ + const char *brname, *edge, *iface; + struct lyd_node *stp, *cist; + FILE *mstpctl; + int err; + + brname = lydx_get_cattr(lydx_get_child(cif, "bridge-port"), "bridge"); + if (!brname) + return 0; + + if (!lydx_get_xpathf(cif, "../interface[name='%s']/bridge/stp", brname)) + return 0; + + iface = lydx_get_cattr(cif, "name"); + stp = lydx_get_descendant(lyd_child(cif), "bridge-port", "stp", NULL); + + mstpctl = dagger_fopen_net_init(&confd.netdag, brname, + NETDAG_INIT_DAEMON_LOWERS, "init-ports.mstpctl"); + if (!mstpctl) + return -EIO; + + fputs("#!/sbin/mstpctl -b\n", mstpctl); + + edge = lydx_get_cattr(stp, "edge"); + if (!strcmp(edge, "true")) { + fprintf(mstpctl, "setportautoedge %s %s no\n", brname, iface); + fprintf(mstpctl, "setportadminedge %s %s yes\n", brname, iface); + } else if (!strcmp(edge, "false")) { + fprintf(mstpctl, "setportautoedge %s %s no\n", brname, iface); + fprintf(mstpctl, "setportadminedge %s %s no\n", brname, iface); + } else if (!strcmp(edge, "auto")) { + fprintf(mstpctl, "setportautoedge %s %s yes\n", brname, iface); + } + + cist = lydx_get_child(stp, "cist"); + err = gen_stp_tree(mstpctl, iface, brname, cist); + if (err) + goto out_close; + + fprintf(mstpctl, "setportpathcost %s %s %s\n", brname, iface, + mstpd_cost_str(lydx_get_child(cist, "external-path-cost"))); + +out_close: + fclose(mstpctl); + return err; +} + static const char *get_port_egress_mode(const char *iface, int vid, const char *brname) { static const char *modes[] = { "tagged", "untagged", NULL }; @@ -237,5 +309,9 @@ int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip) if (err) return err; + err = gen_stp(cif); + if (err) + return err; + return 0; } diff --git a/src/confd/src/infix-if-bridge.c b/src/confd/src/infix-if-bridge.c index cbe3cacf..cc9d2e6f 100644 --- a/src/confd/src/infix-if-bridge.c +++ b/src/confd/src/infix-if-bridge.c @@ -308,6 +308,91 @@ static int gen_vlan(struct ixif_br *br) /* VLAN */ +/* STP */ + +int bridge_mstpd_gen(struct lyd_node *cifs) +{ + struct ly_set *stp_brs; + int n, err = 0; + FILE *cond; + + if (lyd_find_xpath(cifs, "../interface/bridge/stp", &stp_brs)) + return -EINVAL; + + n = stp_brs->count; + ly_set_free(stp_brs, NULL); + + if (n) { + cond = dagger_fopen_next(&confd.netdag, "init", "@pre", 50, "enable-mstpd.sh"); + if (!cond) + return -EIO; + + fputs("initctl -bnq cond set mstpd\n" + "/usr/libexec/confd/mstpd-wait-online || exit 1\n", cond); + fclose(cond); + } else if (!dagger_is_bootstrap(&confd.netdag)) { + cond = dagger_fopen_current(&confd.netdag, "exit", "@post", 50, "disable-mstpd.sh"); + if (!cond) + return -EIO; + + fputs("initctl -bnq cond clear mstpd\n", cond); + fclose(cond); + } + + return err; +} + +static int gen_stp(struct ixif_br *br) +{ + struct lyd_node *stp; + FILE *mstpctl; + + stp = lydx_get_descendant(lyd_child(br->cif), "bridge", "stp", NULL); + if (!stp) { + fputs(" stp_state 0", br->bropts.fp); + return 0; + } + + fputs(" stp_state 1", br->bropts.fp); + + if (lydx_get_descendant(lyd_child(br->cif), "bridge", "vlans", NULL)) + /* Use the MSTP compatible mode of managing port + * states, rather then the legacy (and default) + * per-VLAN mode. */ + fputs(" mst_enabled 1", br->bropts.fp); + + mstpctl = dagger_fopen_net_init(&confd.netdag, br->name, + NETDAG_INIT_DAEMON, "init-br.mstpctl"); + if (!mstpctl) + return -EIO; + + fputs("#!/sbin/mstpctl -b\n", mstpctl); + + fprintf(mstpctl, "setforcevers %s %s\n", br->name, + lydx_get_cattr(stp, "force-protocol")); + + fprintf(mstpctl, "setfdelay %s %s\n", br->name, + lydx_get_cattr(stp, "forward-delay")); + + fprintf(mstpctl, "setmaxage %s %s\n", br->name, + lydx_get_cattr(stp, "max-age")); + + fprintf(mstpctl, "settxholdcount %s %s\n", br->name, + lydx_get_cattr(stp, "transmit-hold-count")); + + fprintf(mstpctl, "setmaxhops %s %s\n", br->name, + lydx_get_cattr(stp, "max-hops")); + + fprintf(mstpctl, "settreeprio %s 0 %s\n", br->name, + lydx_get_cattr(lydx_get_child(stp, "cist"), "priority")); + + fclose(mstpctl); + return 0; + +} + +/* STP */ + /* BR */ static void gen_phys_address(struct ixif_br *br) @@ -420,6 +505,10 @@ int bridge_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, int add) fputs(" mcast_flood_always 1", br.bropts.fp); fputs(" vlan_default_pvid 0", br.bropts.fp); + err = gen_stp(&br); + if (err) + goto out; + err = gen_vlan(&br); if (err) goto out; diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index b0a0c77b..4f87d649 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -37,7 +37,7 @@ MODULES=( "ieee802-ethernet-interface@2019-06-21.yang" "infix-ethernet-interface@2024-02-27.yang" "infix-factory-default@2023-06-28.yang" - "infix-interfaces@2024-11-27.yang -e vlan-filtering" + "infix-interfaces@2025-01-08.yang -e vlan-filtering" # from rousette "ietf-restconf@2017-01-26.yang" diff --git a/src/confd/yang/containers.inc b/src/confd/yang/containers.inc index 6ef7e607..6c863c96 100644 --- a/src/confd/yang/containers.inc +++ b/src/confd/yang/containers.inc @@ -1,6 +1,6 @@ # -*- sh -*- # REMEMBER TO UPDATE infix-interfaces ALSO IN confd.inc MODULES=( - "infix-interfaces@2024-11-27.yang -e vlan-filtering -e containers" + "infix-interfaces@2025-01-08.yang -e vlan-filtering -e containers" "infix-containers@2024-11-15.yang" ) diff --git a/src/confd/yang/infix-if-bridge.yang b/src/confd/yang/infix-if-bridge.yang index 4a594b41..11adce17 100644 --- a/src/confd/yang/infix-if-bridge.yang +++ b/src/confd/yang/infix-if-bridge.yang @@ -26,6 +26,11 @@ submodule infix-if-bridge { contact "kernelkit@googlegroups.com"; description "Linux bridge extension for ietf-interfaces."; + revision 2025-01-08 { + description "Add Spanning Tree Protocol (STP) support."; + reference "internal"; + } + revision 2024-11-28 { description "Drop must() expressions for IP addres on VLAN bridges. If a bridge is untagged member of a VLAN it should be @@ -148,6 +153,26 @@ submodule infix-if-bridge { reserved link-local multicast groups, in 01:80:C2:00:00:0X."; } + typedef stp-protocol { + description "Spanning Tree Protocol version."; + type enumeration { + enum stp { + value 0; + description "Spanning Tree Protocol."; + } + enum rstp { + value 2; + description "Rapid Spanning Tree Protocol."; + } + // enum mstp { + // value 3; + // description "Multiple Spanning Tree Protocol"; + // } + } + + reference "IEEE 802.1Q-2018 13.7.2"; + } + typedef stp-state { description "User-friendly enumeration of different bridge port operational states."; type enumeration { @@ -165,7 +190,7 @@ submodule infix-if-bridge { } enum forwarding { value 3; - description "Port is in STP FORWARDING state. This is the default vlan state."; + description "Port is in STP FORWARDING state."; } enum blocking { value 4; @@ -174,6 +199,36 @@ submodule infix-if-bridge { } } + typedef stp-priority { + type uint8 { + range "0..15"; + } + + default 8; + } + + typedef stp-bool-or-auto { + type union { + type enumeration { + enum auto; + } + type boolean; + } + default auto; + } + + typedef stp-path-cost { + type union { + type enumeration { + enum auto; + } + type uint32 { + range "0..200000000"; + } + } + default auto; + } + typedef querier-mode { description "Type of IGMP/MLD querier, recommend using 'auto'."; type enumeration { @@ -224,6 +279,7 @@ submodule infix-if-bridge { } } + /* * Shared settings */ @@ -321,6 +377,34 @@ submodule infix-if-bridge { } } + grouping stp-tree { + leaf priority { + description "Priority."; + type stp-priority; + reference "IEEE 802.1Q-2018 13.18"; + } + } + + grouping stp-tree-port { + leaf internal-path-cost { + description "Internal path cost."; + type stp-path-cost; + reference "IEEE 802.1Q-2018 13.27.33"; + } + + leaf priority { + description "Priority."; + type stp-priority; + reference "IEEE 802.1Q-2018 13.18"; + } + + leaf state { + description "Operational state."; + type stp-state; + config false; + } + } + /* * Data Nodes */ @@ -392,6 +476,77 @@ submodule infix-if-bridge { } } } + + container stp { + presence stp; + description "Spanning Tree Protocol"; + + leaf force-protocol { + description "Protocol version."; + type stp-protocol; + default rstp; + reference "IEEE 802.1Q-2018 13.7.2"; + } + + leaf forward-delay { + description "Forward delay."; + type uint8 { + range "4..30"; + } + default 15; + reference "IEEE 802.1Q-2018 13.25"; + } + + leaf max-age { + description "Max age."; + type uint8 { + range "6..40"; + } + default 20; + reference "IEEE 802.1Q-2018 13.25"; + } + + leaf transmit-hold-count { + description "Transmit hold count."; + type uint8 { + range "1..10"; + } + default 6; + reference "IEEE 802.1Q-2018 13.25"; + } + + leaf max-hops { + description "Max hops."; + type uint8 { + range "6..40"; + } + default 20; + reference "IEEE 802.1Q-2018 13.25"; + } + + container cist { + description "Common and Internal Spanning Tree."; + uses stp-tree; + } + + // list mst { + // description "Multiple Spanning Tree Instance."; + // key "mstid"; + + // leaf mstid { + // description "The MSTI identifier to which this entry applies."; + // type dot1q-types:mstid-type; + // } + + // uses stp-tree; + + // leaf-list vlan { + // type leafref { + // path "/if:interfaces/interface/bridge/vlans/vlan/vid"; + // } + // } + // } + } } } deviation "/if:interfaces/if:interface/infix-if:bridge/type/ieee8021d/multicast-filters/multicast-filter/ports/port" { @@ -506,6 +661,39 @@ submodule infix-if-bridge { } } + container stp { + description "Spanning Tree Protocol"; + reference "IEEE 802.1Q-2018 13.27"; + + leaf edge { + description "Edge."; + type stp-bool-or-auto; + } + + container cist { + description "Common and Internal Spanning Tree."; + uses stp-tree-port; + + leaf external-path-cost { + description "External path cost."; + type stp-path-cost; + reference "IEEE 802.1Q-2018 13.27.25"; + } + } + + // list mst { + // description "Multiple Spanning Tree Instance."; + // key "mstid"; + + // leaf mstid { + // description "The MSTI identifier to which this entry applies."; + // type dot1q-types:mstid-type; + // } + + // uses stp-tree-port; + // } + } + leaf stp-state { type stp-state; config false; diff --git a/src/confd/yang/infix-if-bridge@2024-11-28.yang b/src/confd/yang/infix-if-bridge@2025-01-08.yang similarity index 100% rename from src/confd/yang/infix-if-bridge@2024-11-28.yang rename to src/confd/yang/infix-if-bridge@2025-01-08.yang diff --git a/src/confd/yang/infix-interfaces.yang b/src/confd/yang/infix-interfaces.yang index 90978e07..f339091a 100644 --- a/src/confd/yang/infix-interfaces.yang +++ b/src/confd/yang/infix-interfaces.yang @@ -27,6 +27,11 @@ module infix-interfaces { contact "kernelkit@googlegroups.com"; description "Linux bridge and lag extensions for ietf-interfaces."; + revision 2025-01-08 { + description "Add Spanning Tree Protocol (STP) support to bridges."; + reference "internal"; + } + revision 2024-11-27 { description "Allow IP addresses directly on VLAN filtering bridges."; reference "internal"; diff --git a/src/confd/yang/infix-interfaces@2024-11-27.yang b/src/confd/yang/infix-interfaces@2025-01-08.yang similarity index 100% rename from src/confd/yang/infix-interfaces@2024-11-27.yang rename to src/confd/yang/infix-interfaces@2025-01-08.yang