Merge pull request #867 from kernelkit/stp

STP
This commit is contained in:
Tobias Waldekranz
2025-01-08 11:25:14 +01:00
committed by GitHub
37 changed files with 966 additions and 19 deletions
@@ -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 <!usr/mstpd> \
[S0123456789] mstpd $MSTPD_ARGS -- Spanning Tree daemon
+1
View File
@@ -0,0 +1 @@
../available/mstpd.conf
+1
View File
@@ -76,6 +76,7 @@ BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_NETCALC=y
BR2_PACKAGE_NETCAT_OPENBSD=y
BR2_PACKAGE_NETSNMP=y
+1
View File
@@ -71,6 +71,7 @@ BR2_PACKAGE_FRR=y
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_NETCALC=y
BR2_PACKAGE_NGINX=y
BR2_PACKAGE_NGINX_HTTP_SSL_MODULE=y
+1
View File
@@ -88,6 +88,7 @@ BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_NETCALC=y
BR2_PACKAGE_NETCAT_OPENBSD=y
BR2_PACKAGE_NETSNMP=y
+1
View File
@@ -76,6 +76,7 @@ BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_NETCALC=y
BR2_PACKAGE_NETCAT_OPENBSD=y
BR2_PACKAGE_NETSNMP=y
+1
View File
@@ -71,6 +71,7 @@ BR2_PACKAGE_FRR=y
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
BR2_PACKAGE_MSTPD=y
BR2_PACKAGE_NETCALC=y
BR2_PACKAGE_NGINX=y
BR2_PACKAGE_NGINX_HTTP_SSL_MODULE=y
+1
View File
@@ -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
@@ -0,0 +1,3 @@
# Daemon is managed by finit(1), /sbin/bridge-stp should assume that
# it is already running and simply add/remove bridges as necessary
MANAGE_MSTPD='n'
@@ -0,0 +1,3 @@
# -d: Do _not_ [sic] daemonize
# -s: Log to syslog
MSTPD_ARGS="-d -s"
@@ -1,4 +1,4 @@
# Make sure to configure the bridge to run on before starting mstpd
# Note: all 'sysv' type services are handed an extra 'start' or 'stop'
# argument when starting and stopping.
sysv name:mstpd [0123456] pid:!/run/mstpd.pid bridge-stp br0 -- MSTP daemon
# A single mstpd instance can manage multiple bridges, which are
# dynamically added/removed by the kernel via the /sbin/bridge-stp
# usermode helper.
service [S0123456789] env:-/etc/default/mstpd mstpd $MSTPD_ARGS -- Spanning Tree daemon
+2 -1
View File
@@ -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
+16
View File
@@ -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
+4 -1
View File
@@ -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,
+5
View File
@@ -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;
}
+1
View File
@@ -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);
+76
View File
@@ -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;
}
+89
View File
@@ -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;
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
)
+189 -1
View File
@@ -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;
+5
View File
@@ -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";
+44
View File
@@ -147,6 +147,50 @@ struct lyd_node *lydx_find_by_name(struct lyd_node *from, const char *by, const
return NULL;
}
struct ly_set *lydx_find_vxpathf(struct lyd_node *ctx, const char *xpfmt, va_list ap)
{
struct ly_set *set;
char *xpath;
int err;
vasprintf(&xpath, xpfmt, ap);
err = lyd_find_xpath(ctx, xpath, &set);
free(xpath);
return err ? NULL : set;
}
struct ly_set *lydx_find_xpathf(struct lyd_node *ctx, const char *xpfmt, ...)
{
struct ly_set *set;
va_list ap;
va_start(ap, xpfmt);
set = lydx_find_vxpathf(ctx, xpfmt, ap);
va_end(ap);
return set;
}
struct lyd_node *lydx_get_xpathf(struct lyd_node *ctx, const char *xpfmt, ...)
{
struct lyd_node *node = NULL;
struct ly_set *set;
va_list ap;
va_start(ap, xpfmt);
set = lydx_find_vxpathf(ctx, xpfmt, ap);
va_end(ap);
if (!set)
return NULL;
if (set->count == 1)
node = *set->dnodes;
ly_set_free(set, NULL);
return node;
}
const char *lydx_get_mattr(struct lyd_node *node, const char *name)
{
struct lyd_meta *meta;
+6
View File
@@ -39,6 +39,12 @@ struct lyd_node *lydx_vdescend(struct lyd_node *from, va_list ap);
struct lyd_node *lydx_get_descendant(struct lyd_node *from, ...);
struct lyd_node *lydx_find_by_name(struct lyd_node *from, const char *by, const char *name);
struct ly_set *lydx_find_vxpathf(struct lyd_node *ctx, const char *xpfmt, va_list ap);
struct ly_set *lydx_find_xpathf(struct lyd_node *ctx, const char *xpfmt, ...)
__attribute__ ((format (printf, 2, 3)));
struct lyd_node *lydx_get_xpathf(struct lyd_node *ctx, const char *xpfmt, ...)
__attribute__ ((format (printf, 2, 3)));
const char *lydx_get_mattr(struct lyd_node *node, const char *name);
const char *lydx_get_attr(struct lyd_node *sibling, const char *name);
const char *lydx_get_cattr(struct lyd_node *parent, const char *name);
+2
View File
@@ -23,6 +23,8 @@ include::bridge_fwd_dual_dut/Readme.adoc[]
include::bridge_fwd_sgl_dut/Readme.adoc[]
include::bridge_stp_basic/Readme.adoc[]
include::bridge_veth/Readme.adoc[]
include::bridge_vlan/Readme.adoc[]
@@ -0,0 +1,31 @@
=== Bridge STP Basic
==== Description
Verify that a fully connected mesh of 4 DUTs is pruned to a spanning
tree.
Since the mesh contains 3 redundant paths, can infer that a spanning
tree has been created if all host interfaces can reach each other
while exactly three links are in the blocking state.
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/bridge_stp_basic/topology.svg[Bridge STP Basic topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::bridge_stp_basic/topology.svg[Bridge STP Basic topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[Bridge STP Basic topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUT
. Configure a bridge with spanning tree eneabled on dut a, b, c, and d
. Add an IP address to each host interface in the 10.0.0.0/24 subnet
. Verify that exactly three links are blocking
. Verify that host:a can reach host:{b,c,d}
<<<
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
r"""Bridge STP Basic
Verify that a fully connected mesh of 4 DUTs is pruned to a spanning
tree.
Since the mesh contains 3 redundant paths, can infer that a spanning
tree has been created if all host interfaces can reach each other
while exactly three links are in the blocking state.
"""
import infamy
from infamy.util import parallel, until
def addbr(dut):
ip = {
"A": "10.0.0.101",
"B": "10.0.0.102",
"C": "10.0.0.103",
"D": "10.0.0.104"
}[dut.name]
brports = [
{
"name": dut[n],
"infix-interfaces:bridge-port": {
"bridge": "br0",
}
} for n in ("a", "b", "c", "d", "h") if n != dut.name.lower()
]
dut.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [
{
"name": "br0",
"type": "infix-if-type:bridge",
"enabled": True,
"bridge": {
"stp": {},
},
"ipv4": {
"address": [
{
"ip": ip,
"prefix-length": 24,
}
]
},
}
] + brports,
}
}
})
def num_blocking(dut):
num = 0
for iface in dut.get_data("/ietf-interfaces:interfaces")["interfaces"]["interface"]:
if iface.get("bridge-port", {}).get("stp-state") == "blocking":
num += 1
return num
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
a, b, c, d = parallel(lambda: env.attach("A"), lambda: env.attach("B"),
lambda: env.attach("C"), lambda: env.attach("D"))
host = { p: env.ltop.xlate("host", p)[1] for p in ("a", "b", "c", "d") }
with test.step("Configure a bridge with spanning tree eneabled on dut a, b, c, and d"):
parallel(lambda: addbr(a), lambda: addbr(b), lambda: addbr(c), lambda: addbr(d))
with test.step("Add an IP address to each host interface in the 10.0.0.0/24 subnet"):
ns = { p: infamy.IsolatedMacVlan(host[p]).start() for p in ("a", "b", "c", "d") }
parallel(lambda: ns["a"].addip("10.0.0.1"), lambda: ns["b"].addip("10.0.0.2"),
lambda: ns["c"].addip("10.0.0.3"), lambda: ns["d"].addip("10.0.0.4"))
with test.step("Verify that exactly three links are blocking"):
until(lambda: sum(map(num_blocking, (a, b, c, d))) == 3, 60)
with test.step("Verify that host:a can reach host:{b,c,d}"):
parallel(lambda: ns["a"].must_reach("10.0.0.2"),
lambda: ns["a"].must_reach("10.0.0.3"),
lambda: ns["a"].must_reach("10.0.0.4"))
test.succeed()
@@ -0,0 +1,55 @@
graph "stp" {
layout="neato";
overlap="false";
esep="+80";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{ { <mgmtd> mgmtd | <d> d | <mgmta> mgmta | <a> a | <b> b | <mgmtb> mgmtb | <c> c | <mgmtc> mgmtc } | host }",
color="grey",fontcolor="grey",pos="9,0!",
kind="controller",
];
A [
label="{ A | { <mgmt> mgmt | <h> h } } | { <b> b | <c> c | <d> d }",
pos="6,6!",
kind="infix",
];
B [
label="{ <a> a | <d> d | <c> c } | { B | { <h> h | <mgmt> mgmt } }",
pos="12,6!",
kind="infix",
];
C [
label="{ <b> b | <a> a | <d> d } | { C | { <h> h | <mgmt> mgmt } }",
pos="12,3!",
kind="infix",
];
D [
label="{ D | { <mgmt> mgmt | <h> h } } | { <a> a | <b> b | <c> c }",
pos="6,3!",
kind="infix",
];
host:mgmta -- A:mgmt [kind=mgmt, color="lightgrey"]
host:mgmtb -- B:mgmt [kind=mgmt, color="lightgrey"]
host:mgmtc -- C:mgmt [kind=mgmt, color="lightgrey"]
host:mgmtd -- D:mgmt [kind=mgmt, color="lightgrey"]
host:a -- A:h [color="cornflowerblue"]
host:b -- B:h [color="cornflowerblue"]
host:c -- C:h [color="cornflowerblue"]
host:d -- D:h [color="cornflowerblue"]
# Ring
A:b -- B:a
B:c -- C:b
C:d -- D:c
D:a -- A:d
# Cross-links
A:c -- C:a
B:d -- D:b
}
@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: stp Pages: 1 -->
<svg width="648pt" height="607pt"
viewBox="0.00 0.00 648.05 606.55" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 602.55)">
<title>stp</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-602.55 644.05,-602.55 644.05,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="grey" points="154.03,-0.5 154.03,-46.5 486.03,-46.5 486.03,-0.5 154.03,-0.5"/>
<text text-anchor="middle" x="183.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">mgmtd</text>
<polyline fill="none" stroke="grey" points="212.03,-23.5 212.03,-46.5 "/>
<text text-anchor="middle" x="224.53" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">d</text>
<polyline fill="none" stroke="grey" points="237.03,-23.5 237.03,-46.5 "/>
<text text-anchor="middle" x="266.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">mgmta</text>
<polyline fill="none" stroke="grey" points="295.03,-23.5 295.03,-46.5 "/>
<text text-anchor="middle" x="307.53" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">a</text>
<polyline fill="none" stroke="grey" points="320.03,-23.5 320.03,-46.5 "/>
<text text-anchor="middle" x="332.53" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">b</text>
<polyline fill="none" stroke="grey" points="345.03,-23.5 345.03,-46.5 "/>
<text text-anchor="middle" x="374.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">mgmtb</text>
<polyline fill="none" stroke="grey" points="403.03,-23.5 403.03,-46.5 "/>
<text text-anchor="middle" x="415.53" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">c</text>
<polyline fill="none" stroke="grey" points="428.03,-23.5 428.03,-46.5 "/>
<text text-anchor="middle" x="457.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">mgmtc</text>
<polyline fill="none" stroke="grey" points="154.03,-23.5 486.03,-23.5 "/>
<text text-anchor="middle" x="320.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00" fill="grey">host</text>
</g>
<!-- A -->
<g id="node2" class="node">
<title>A</title>
<polygon fill="none" stroke="black" points="0,-529.05 0,-598.05 100,-598.05 100,-529.05 0,-529.05"/>
<text text-anchor="middle" x="37.5" y="-577.35" font-family="DejaVu Sans Mono, Book" font-size="14.00">A</text>
<polyline fill="none" stroke="black" points="0,-564.05 75,-564.05 "/>
<text text-anchor="middle" x="25" y="-542.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-529.05 50,-564.05 "/>
<text text-anchor="middle" x="62.5" y="-542.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">h</text>
<polyline fill="none" stroke="black" points="75,-529.05 75,-598.05 "/>
<text text-anchor="middle" x="87.5" y="-582.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">b</text>
<polyline fill="none" stroke="black" points="75,-575.05 100,-575.05 "/>
<text text-anchor="middle" x="87.5" y="-559.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">c</text>
<polyline fill="none" stroke="black" points="75,-552.05 100,-552.05 "/>
<text text-anchor="middle" x="87.5" y="-536.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">d</text>
</g>
<!-- host&#45;&#45;A -->
<g id="edge1" class="edge">
<title>host:mgmta&#45;&#45;A:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M266.03,-46.5C266.03,-46.5 25,-528.55 25,-528.55"/>
</g>
<!-- host&#45;&#45;A -->
<g id="edge5" class="edge">
<title>host:a&#45;&#45;A:h</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M307.03,-46.5C307.03,-46.5 63,-528.55 63,-528.55"/>
</g>
<!-- B -->
<g id="node3" class="node">
<title>B</title>
<polygon fill="none" stroke="black" points="540.05,-529.05 540.05,-598.05 640.05,-598.05 640.05,-529.05 540.05,-529.05"/>
<text text-anchor="middle" x="552.55" y="-582.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">a</text>
<polyline fill="none" stroke="black" points="540.05,-575.05 565.05,-575.05 "/>
<text text-anchor="middle" x="552.55" y="-559.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">d</text>
<polyline fill="none" stroke="black" points="540.05,-552.05 565.05,-552.05 "/>
<text text-anchor="middle" x="552.55" y="-536.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">c</text>
<polyline fill="none" stroke="black" points="565.05,-529.05 565.05,-598.05 "/>
<text text-anchor="middle" x="602.55" y="-577.35" font-family="DejaVu Sans Mono, Book" font-size="14.00">B</text>
<polyline fill="none" stroke="black" points="565.05,-564.05 640.05,-564.05 "/>
<text text-anchor="middle" x="577.55" y="-542.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">h</text>
<polyline fill="none" stroke="black" points="590.05,-529.05 590.05,-564.05 "/>
<text text-anchor="middle" x="615.05" y="-542.85" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
</g>
<!-- host&#45;&#45;B -->
<g id="edge2" class="edge">
<title>host:mgmtb&#45;&#45;B:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M374.03,-46.5C374.03,-46.5 615.05,-528.55 615.05,-528.55"/>
</g>
<!-- host&#45;&#45;B -->
<g id="edge6" class="edge">
<title>host:b&#45;&#45;B:h</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M333.03,-46.5C333.03,-46.5 577.05,-528.55 577.05,-528.55"/>
</g>
<!-- C -->
<g id="node4" class="node">
<title>C</title>
<polygon fill="none" stroke="black" points="540.05,-259.03 540.05,-328.03 640.05,-328.03 640.05,-259.03 540.05,-259.03"/>
<text text-anchor="middle" x="552.55" y="-312.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">b</text>
<polyline fill="none" stroke="black" points="540.05,-305.03 565.05,-305.03 "/>
<text text-anchor="middle" x="552.55" y="-289.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">a</text>
<polyline fill="none" stroke="black" points="540.05,-282.03 565.05,-282.03 "/>
<text text-anchor="middle" x="552.55" y="-266.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">d</text>
<polyline fill="none" stroke="black" points="565.05,-259.03 565.05,-328.03 "/>
<text text-anchor="middle" x="602.55" y="-307.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">C</text>
<polyline fill="none" stroke="black" points="565.05,-294.03 640.05,-294.03 "/>
<text text-anchor="middle" x="577.55" y="-272.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">h</text>
<polyline fill="none" stroke="black" points="590.05,-259.03 590.05,-294.03 "/>
<text text-anchor="middle" x="615.05" y="-272.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
</g>
<!-- host&#45;&#45;C -->
<g id="edge3" class="edge">
<title>host:mgmtc&#45;&#45;C:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M486.03,-35.5C486.03,-35.5 615.05,-258.53 615.05,-258.53"/>
</g>
<!-- host&#45;&#45;C -->
<g id="edge7" class="edge">
<title>host:c&#45;&#45;C:h</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M416.03,-46.5C416.03,-46.5 577.05,-258.53 577.05,-258.53"/>
</g>
<!-- D -->
<g id="node5" class="node">
<title>D</title>
<polygon fill="none" stroke="black" points="0,-259.03 0,-328.03 100,-328.03 100,-259.03 0,-259.03"/>
<text text-anchor="middle" x="37.5" y="-307.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">D</text>
<polyline fill="none" stroke="black" points="0,-294.03 75,-294.03 "/>
<text text-anchor="middle" x="25" y="-272.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-259.03 50,-294.03 "/>
<text text-anchor="middle" x="62.5" y="-272.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">h</text>
<polyline fill="none" stroke="black" points="75,-259.03 75,-328.03 "/>
<text text-anchor="middle" x="87.5" y="-312.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">a</text>
<polyline fill="none" stroke="black" points="75,-305.03 100,-305.03 "/>
<text text-anchor="middle" x="87.5" y="-289.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">b</text>
<polyline fill="none" stroke="black" points="75,-282.03 100,-282.03 "/>
<text text-anchor="middle" x="87.5" y="-266.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">c</text>
</g>
<!-- host&#45;&#45;D -->
<g id="edge4" class="edge">
<title>host:mgmtd&#45;&#45;D:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M154.03,-35.5C154.03,-35.5 25,-258.53 25,-258.53"/>
</g>
<!-- host&#45;&#45;D -->
<g id="edge8" class="edge">
<title>host:d&#45;&#45;D:h</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M224.03,-46.5C224.03,-46.5 63,-258.53 63,-258.53"/>
</g>
<!-- A&#45;&#45;B -->
<g id="edge9" class="edge">
<title>A:b&#45;&#45;B:a</title>
<path fill="none" stroke="black" stroke-width="2" d="M100,-586.55C100,-586.55 540.05,-586.55 540.05,-586.55"/>
</g>
<!-- A&#45;&#45;C -->
<g id="edge13" class="edge">
<title>A:c&#45;&#45;C:a</title>
<path fill="none" stroke="black" stroke-width="2" d="M100,-563.55C100,-563.55 540.05,-293.53 540.05,-293.53"/>
</g>
<!-- B&#45;&#45;C -->
<g id="edge10" class="edge">
<title>B:c&#45;&#45;C:b</title>
<path fill="none" stroke="black" stroke-width="2" d="M552.05,-528.55C552.05,-528.55 552.05,-328.53 552.05,-328.53"/>
</g>
<!-- B&#45;&#45;D -->
<g id="edge14" class="edge">
<title>B:d&#45;&#45;D:b</title>
<path fill="none" stroke="black" stroke-width="2" d="M540.05,-563.55C540.05,-563.55 100,-293.53 100,-293.53"/>
</g>
<!-- C&#45;&#45;D -->
<g id="edge11" class="edge">
<title>C:d&#45;&#45;D:c</title>
<path fill="none" stroke="black" stroke-width="2" d="M540.05,-270.53C540.05,-270.53 100,-270.53 100,-270.53"/>
</g>
<!-- D&#45;&#45;A -->
<g id="edge12" class="edge">
<title>D:a&#45;&#45;A:d</title>
<path fill="none" stroke="black" stroke-width="2" d="M88,-328.53C88,-328.53 88,-528.55 88,-528.55"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

@@ -41,6 +41,9 @@
- name: bridge_fwd_dual_dut
case: bridge_fwd_dual_dut/test.py
- name: bridge_stp_basic
case: bridge_stp_basic/test.py
- name: bridge_vlan_separation
case: bridge_vlan_separation/test.py
@@ -0,0 +1,2 @@
# This gets symlinked to whichever bundle we're told to upgrade to
/package
+21 -10
View File
@@ -18,6 +18,20 @@ ENV = NullEnv()
class ArgumentParser(argparse.ArgumentParser):
def DefaultTransport():
"""Pick pseudo-random transport
If the user does not specify a particular transport, make sure
that any (test, $PYTHONHASHSEED) tuple will always map to the
same transport.
"""
name = "/".join(os.path.realpath(sys.argv[0]).split(os.sep)[-2:])
seed = os.environ.get('PYTHONHASHSEED', 0)
random.seed(f"{name}-{seed}")
return random.choice(["netconf", "restconf"])
def __init__(self, top):
super().__init__()
@@ -25,7 +39,7 @@ class ArgumentParser(argparse.ArgumentParser):
self.add_argument("-l", "--logical-topology", dest="ltop", default=top)
self.add_argument("-p", "--package", default=None)
self.add_argument("-y", "--yangdir", default=None)
self.add_argument("-t", "--transport", default=None)
self.add_argument("-t", "--transport", default=ArgumentParser.DefaultTransport())
self.add_argument("ptop", nargs=1, metavar="topology")
@@ -74,7 +88,7 @@ class Env(object):
def get_password(self, node):
return self.ptop.get_password(node)
def attach(self, node, port, protocol=None, test_reset=True, username = None, password = None):
def attach(self, node, port="mgmt", protocol=None, test_reset=True, username = None, password = None):
"""Attach to node on port using protocol."""
name = node
@@ -84,15 +98,12 @@ class Env(object):
else:
mapping = None
# Test protocol always highest prio, followed by command line,
# then environment (detected from defconfig), lastly random.
# Precedence:
# 1. Caller specifies `protocol`
# 2. User specifies `-t` when executing test
# 3. One is pseudo-randomly picked based on $PYTHONHASHSEED
if protocol is None:
if self.args.transport is not None:
protocol = self.args.transport
else:
hseed = os.environ.get('PYTHONHASHSEED', 0)
random.seed(f"{sys.argv[0]}-{hseed}")
protocol = random.choice(["netconf", "restconf"])
protocol = self.args.transport
if password is None:
password = self.get_password(node)
+1
View File
@@ -119,6 +119,7 @@ class Device(Transport):
self.name = name
self.location = location
self.mapping = mapping
self.url_base = f"https://[{location.host}]:{location.port}"
self.restconf_url = f"{self.url_base}/restconf"
self.yang_url = f"{self.url_base}/yang"
+3
View File
@@ -55,6 +55,9 @@ class Transport(ABC):
def call_action(self, xpath):
pass
def __getitem__(self, key):
return self.mapping[key]
def get_iface(self, name):
"""Fetch target dict for iface and extract param from JSON"""
content = self.get_data(infamy.iface.get_xpath(name))
Executable
+127
View File
@@ -0,0 +1,127 @@
#!/bin/sh
# Interact with sysrepo in a Buildroot setup
usage()
{
cat <<EOF
usage:
srop [opt] cmd [arg]
options:
-e FEATURE Enable feature in module
-g GROUP Module group for install/modify, e.g., wheel
-m MODE Module permissions for install/modify, e.g., 0660
-o OWNER Module owner for install/modify, e.g., root
commands:
cat Show default configuration
help This help text
ls List installed modules
modify MOD Modify (change/update) module, may use :ALL
install MOD Install module
uninstall MOD Uninstall module (force)
EOF
}
cleanup()
{
rm -f /dev/shm/${SYSREPO_SHM_PREFIX}*
exit 1
}
# Figure out if we're called from output/ or top directory
if [ -z "$O" ]; then
if [ -f ".config" ] && [ -d "output" ]; then
# Buildroot
O=./output
elif [ -f "output/.config" ]; then
# BR2_EXTERNAL
O=./output
elif [ -f ".config" ] && [ -d "host" ]; then
# Called from inside output/ directory
O=.
else
echo "*** Error: cannot find Buildroot output dir!" >&2
exit 1
fi
fi
if [ -d "$O/host" ]; then
HOST_DIR="$O/host"
else
echo "*** Error: cannot find Buildroot host binaries dir!" >&2
exit 1
fi
MOD="$O/target/usr/share/yang/modules/confd/"
CTL="$HOST_DIR/bin/sysrepoctl"
CFG="$HOST_DIR/bin/sysrepocfg"
while [ "$1" != "" ]; do
case $1 in
-e)
shift
features="$features -e $1"
;;
-g)
shift
group="-g $1"
;;
-o)
shift
owner="-o $1"
;;
-m)
shift
perms="-p $1"
;;
*)
break
;;
esac
shift
done
cmd=$1
if [ -n "$cmd" ]; then
shift
fi
trap cleanup INT TERM EXIT
# Trigger separate namespace for sysrepoctl cmds
export SYSREPO_SHM_PREFIX=sr_buildroot
case $cmd in
cat)
$CFG -X -f json
;;
help)
usage
;;
dump)
$CTL -h
$CFG -h
;;
ls)
$CTL -l
;;
install)
if [ -f "$1" ]; then
M="$1"
else
M="$MOD/$1"
fi
$CTL -i "$M" -s "$MOD" $perms $owner $group $features -v3
;;
modify)
$CTL -c "$1" $perms $owner $group $features -v3
;;
uninstall)
$CTL -u "$1" -f -v3
;;
*)
echo "NOP"
;;
esac