mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-01 05:13:01 +02:00
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
../available/mstpd.conf
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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"
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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--A -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmta--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--A -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>host:a--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--B -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmtb--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--B -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>host:b--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--C -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:mgmtc--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--C -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>host:c--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--D -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>host:mgmtd--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--D -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>host:d--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--B -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>A:b--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--C -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>A:c--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--C -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>B:c--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--D -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>B:d--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--D -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>C:d--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--A -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>D:a--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
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user