summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--ofproto/ofproto-dpif.c327
-rw-r--r--ofproto/ofproto-provider.h47
-rw-r--r--ofproto/ofproto.c73
-rw-r--r--ofproto/ofproto.h37
-rw-r--r--tests/ovs-vsctl.at4
-rw-r--r--utilities/ovs-vsctl.8.in18
-rw-r--r--vswitchd/bridge.c279
-rw-r--r--vswitchd/vswitch.ovsschema10
-rw-r--r--vswitchd/vswitch.xml165
10 files changed, 940 insertions, 21 deletions
diff --git a/NEWS b/NEWS
index a05c19795..75738d08b 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,7 @@ Post-v1.2.0
new NXAST_RESUBMIT_TABLE action can look up in additional
tables. Tables 128 and above are reserved for use by the
switch itself; please use only tables 0 through 127.
+ - Add support for 802.1D spanning tree (STP).
- Fragment handling extensions:
- New OFPC_FRAG_NX_MATCH fragment handling mode, in which L4
fields are made available for matching in fragments with
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 4629efae6..4de5a4e63 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -161,6 +161,9 @@ static void bundle_del_port(struct ofport_dpif *);
static void bundle_run(struct ofbundle *);
static void bundle_wait(struct ofbundle *);
+static void stp_run(struct ofproto_dpif *ofproto);
+static void stp_wait(struct ofproto_dpif *ofproto);
+
struct action_xlate_ctx {
/* action_xlate_ctx_init() initializes these members. */
@@ -314,6 +317,10 @@ struct ofport_dpif {
tag_type tag; /* Tag associated with this port. */
uint32_t bond_stable_id; /* stable_id to use as bond slave, or 0. */
bool may_enable; /* May be enabled in bonds. */
+
+ struct stp_port *stp_port; /* Spanning Tree Protocol, if any. */
+ enum stp_state stp_state; /* Always STP_DISABLED if STP not in use. */
+ long long int stp_state_entered;
};
static struct ofport_dpif *
@@ -374,6 +381,10 @@ struct ofproto_dpif {
struct list completions;
bool has_bundle_action; /* True when the first bundle action appears. */
+
+ /* Spanning tree. */
+ struct stp *stp;
+ long long int stp_last_tick;
};
/* Defer flow mod completion until "ovs-appctl ofproto/unclog"? (Useful only
@@ -495,6 +506,7 @@ construct(struct ofproto *ofproto_, int *n_tablesp)
ofproto->netflow = NULL;
ofproto->sflow = NULL;
+ ofproto->stp = NULL;
hmap_init(&ofproto->bundles);
ofproto->ml = mac_learning_create();
for (i = 0; i < MAX_MIRRORS; i++) {
@@ -628,6 +640,7 @@ run(struct ofproto *ofproto_)
bundle_run(bundle);
}
+ stp_run(ofproto);
mac_learning_run(ofproto->ml, &ofproto->revalidate_set);
/* Now revalidate if there's anything to do. */
@@ -678,6 +691,7 @@ wait(struct ofproto *ofproto_)
bundle_wait(bundle);
}
mac_learning_wait(ofproto->ml);
+ stp_wait(ofproto);
if (ofproto->need_revalidate) {
/* Shouldn't happen, but if it does just go around again. */
VLOG_DBG_RL(&rl, "need revalidate in ofproto_wait_cb()");
@@ -783,6 +797,8 @@ port_construct(struct ofport *port_)
port->cfm = NULL;
port->tag = tag_create_random();
port->may_enable = true;
+ port->stp_port = NULL;
+ port->stp_state = STP_DISABLED;
if (ofproto->sflow) {
dpif_sflow_add_port(ofproto->sflow, port->odp_port,
@@ -912,6 +928,252 @@ get_cfm_remote_mpids(const struct ofport *ofport_, const uint64_t **rmps,
}
}
+/* Spanning Tree. */
+
+static void
+send_bpdu_cb(struct ofpbuf *pkt, int port_num, void *ofproto_)
+{
+ struct ofproto_dpif *ofproto = ofproto_;
+ struct stp_port *sp = stp_get_port(ofproto->stp, port_num);
+ struct ofport_dpif *ofport;
+
+ ofport = stp_port_get_aux(sp);
+ if (!ofport) {
+ VLOG_WARN_RL(&rl, "%s: cannot send BPDU on unknown port %d",
+ ofproto->up.name, port_num);
+ } else {
+ struct eth_header *eth = pkt->l2;
+
+ netdev_get_etheraddr(ofport->up.netdev, eth->eth_src);
+ if (eth_addr_is_zero(eth->eth_src)) {
+ VLOG_WARN_RL(&rl, "%s: cannot send BPDU on port %d "
+ "with unknown MAC", ofproto->up.name, port_num);
+ } else {
+ int error = netdev_send(ofport->up.netdev, pkt);
+ if (error) {
+ VLOG_WARN_RL(&rl, "%s: sending BPDU on port %s failed (%s)",
+ ofproto->up.name,
+ netdev_get_name(ofport->up.netdev),
+ strerror(error));
+ }
+ }
+ }
+ ofpbuf_delete(pkt);
+}
+
+/* Configures STP on 'ofproto_' using the settings defined in 's'. */
+static int
+set_stp(struct ofproto *ofproto_, const struct ofproto_stp_settings *s)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+
+ /* Only revalidate flows if the configuration changed. */
+ if (!s != !ofproto->stp) {
+ ofproto->need_revalidate = true;
+ }
+
+ if (s) {
+ if (!ofproto->stp) {
+ ofproto->stp = stp_create(ofproto_->name, s->system_id,
+ send_bpdu_cb, ofproto);
+ ofproto->stp_last_tick = time_msec();
+ }
+
+ stp_set_bridge_id(ofproto->stp, s->system_id);
+ stp_set_bridge_priority(ofproto->stp, s->priority);
+ stp_set_hello_time(ofproto->stp, s->hello_time);
+ stp_set_max_age(ofproto->stp, s->max_age);
+ stp_set_forward_delay(ofproto->stp, s->fwd_delay);
+ } else {
+ stp_destroy(ofproto->stp);
+ ofproto->stp = NULL;
+ }
+
+ return 0;
+}
+
+static int
+get_stp_status(struct ofproto *ofproto_, struct ofproto_stp_status *s)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+
+ if (ofproto->stp) {
+ s->enabled = true;
+ s->bridge_id = stp_get_bridge_id(ofproto->stp);
+ s->designated_root = stp_get_designated_root(ofproto->stp);
+ s->root_path_cost = stp_get_root_path_cost(ofproto->stp);
+ } else {
+ s->enabled = false;
+ }
+
+ return 0;
+}
+
+static void
+update_stp_port_state(struct ofport_dpif *ofport)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+ enum stp_state state;
+
+ /* Figure out new state. */
+ state = ofport->stp_port ? stp_port_get_state(ofport->stp_port)
+ : STP_DISABLED;
+
+ /* Update state. */
+ if (ofport->stp_state != state) {
+ ovs_be32 of_state;
+ bool fwd_change;
+
+ VLOG_DBG_RL(&rl, "port %s: STP state changed from %s to %s",
+ netdev_get_name(ofport->up.netdev),
+ stp_state_name(ofport->stp_state),
+ stp_state_name(state));
+ if (stp_learn_in_state(ofport->stp_state)
+ != stp_learn_in_state(state)) {
+ /* xxx Learning action flows should also be flushed. */
+ mac_learning_flush(ofproto->ml);
+ }
+ fwd_change = stp_forward_in_state(ofport->stp_state)
+ != stp_forward_in_state(state);
+
+ ofproto->need_revalidate = true;
+ ofport->stp_state = state;
+ ofport->stp_state_entered = time_msec();
+
+ if (fwd_change) {
+ bundle_update(ofport->bundle);
+ }
+
+ /* Update the STP state bits in the OpenFlow port description. */
+ of_state = (ofport->up.opp.state & htonl(~OFPPS_STP_MASK))
+ | htonl(state == STP_LISTENING ? OFPPS_STP_LISTEN
+ : state == STP_LEARNING ? OFPPS_STP_LEARN
+ : state == STP_FORWARDING ? OFPPS_STP_FORWARD
+ : state == STP_BLOCKING ? OFPPS_STP_BLOCK
+ : 0);
+ ofproto_port_set_state(&ofport->up, of_state);
+ }
+}
+
+/* Configures STP on 'ofport_' using the settings defined in 's'. The
+ * caller is responsible for assigning STP port numbers and ensuring
+ * there are no duplicates. */
+static int
+set_stp_port(struct ofport *ofport_,
+ const struct ofproto_port_stp_settings *s)
+{
+ struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+ struct stp_port *sp = ofport->stp_port;
+
+ if (!s || !s->enable) {
+ if (sp) {
+ ofport->stp_port = NULL;
+ stp_port_disable(sp);
+ }
+ return 0;
+ } else if (sp && stp_port_no(sp) != s->port_num
+ && ofport == stp_port_get_aux(sp)) {
+ /* The port-id changed, so disable the old one if it's not
+ * already in use by another port. */
+ stp_port_disable(sp);
+ }
+
+ sp = ofport->stp_port = stp_get_port(ofproto->stp, s->port_num);
+ stp_port_enable(sp);
+
+ stp_port_set_aux(sp, ofport);
+ stp_port_set_priority(sp, s->priority);
+ stp_port_set_path_cost(sp, s->path_cost);
+
+ update_stp_port_state(ofport);
+
+ return 0;
+}
+
+static int
+get_stp_port_status(struct ofport *ofport_,
+ struct ofproto_port_stp_status *s)
+{
+ struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+ struct stp_port *sp = ofport->stp_port;
+
+ if (!ofproto->stp || !sp) {
+ s->enabled = false;
+ return 0;
+ }
+
+ s->enabled = true;
+ s->port_id = stp_port_get_id(sp);
+ s->state = stp_port_get_state(sp);
+ s->sec_in_state = (time_msec() - ofport->stp_state_entered) / 1000;
+ s->role = stp_port_get_role(sp);
+
+ return 0;
+}
+
+static void
+stp_run(struct ofproto_dpif *ofproto)
+{
+ if (ofproto->stp) {
+ long long int now = time_msec();
+ long long int elapsed = now - ofproto->stp_last_tick;
+ struct stp_port *sp;
+
+ if (elapsed > 0) {
+ stp_tick(ofproto->stp, MIN(INT_MAX, elapsed));
+ ofproto->stp_last_tick = now;
+ }
+ while (stp_get_changed_port(ofproto->stp, &sp)) {
+ struct ofport_dpif *ofport = stp_port_get_aux(sp);
+
+ if (ofport) {
+ update_stp_port_state(ofport);
+ }
+ }
+ }
+}
+
+static void
+stp_wait(struct ofproto_dpif *ofproto)
+{
+ if (ofproto->stp) {
+ poll_timer_wait(1000);
+ }
+}
+
+/* Returns true if STP should process 'flow'. */
+static bool
+stp_should_process_flow(const struct flow *flow)
+{
+ return eth_addr_equals(flow->dl_dst, eth_addr_stp);
+}
+
+static void
+stp_process_packet(const struct ofport_dpif *ofport,
+ const struct ofpbuf *packet)
+{
+ struct ofpbuf payload = *packet;
+ struct eth_header *eth = payload.data;
+ struct stp_port *sp = ofport->stp_port;
+
+ /* Sink packets on ports that have STP disabled when the bridge has
+ * STP enabled. */
+ if (!sp || stp_port_get_state(sp) == STP_DISABLED) {
+ return;
+ }
+
+ /* Trim off padding on payload. */
+ if (payload.size > htons(eth->eth_type) + ETH_HEADER_LEN) {
+ payload.size = htons(eth->eth_type) + ETH_HEADER_LEN;
+ }
+
+ if (ofpbuf_try_pull(&payload, ETH_HEADER_LEN + LLC_HEADER_LEN)) {
+ stp_received_bpdu(sp, payload.data, payload.size);
+ }
+}
+
/* Bundles. */
/* Expires all MAC learning entries associated with 'port' and forces ofproto
@@ -970,7 +1232,8 @@ bundle_update(struct ofbundle *bundle)
bundle->floodable = true;
LIST_FOR_EACH (port, bundle_node, &bundle->ports) {
- if (port->up.opp.config & htonl(OFPPC_NO_FLOOD)) {
+ if (port->up.opp.config & htonl(OFPPC_NO_FLOOD)
+ || !stp_forward_in_state(port->stp_state)) {
bundle->floodable = false;
break;
}
@@ -1017,7 +1280,8 @@ bundle_add_port(struct ofbundle *bundle, uint32_t ofp_port,
port->bundle = bundle;
list_push_back(&bundle->ports, &port->bundle_node);
- if (port->up.opp.config & htonl(OFPPC_NO_FLOOD)) {
+ if (port->up.opp.config & htonl(OFPPC_NO_FLOOD)
+ || !stp_forward_in_state(port->stp_state)) {
bundle->floodable = false;
}
}
@@ -1838,6 +2102,11 @@ process_special(struct ofproto_dpif *ofproto, const struct flow *flow,
lacp_process_packet(ofport->bundle->lacp, ofport, packet);
}
return true;
+ } else if (ofproto->stp && stp_should_process_flow(flow)) {
+ if (packet) {
+ stp_process_packet(ofport, packet);
+ }
+ return true;
}
return false;
}
@@ -1989,7 +2258,7 @@ handle_miss_upcalls(struct ofproto_dpif *ofproto, struct dpif_upcall *upcalls,
odp_flow_key_to_flow(upcall->key, upcall->key_len, &flow);
flow_extract(upcall->packet, flow.tun_id, flow.in_port, &flow);
- /* Handle 802.1ag and LACP specially. */
+ /* Handle 802.1ag, LACP, and STP specially. */
if (process_special(ofproto, &flow, upcall->packet)) {
ofpbuf_delete(upcall->packet);
ofproto->n_matches++;
@@ -3497,7 +3766,8 @@ add_output_action(struct action_xlate_ctx *ctx, uint16_t ofp_port)
uint16_t odp_port = ofp_port_to_odp_port(ofp_port);
if (ofport) {
- if (ofport->up.opp.config & htonl(OFPPC_NO_FWD)) {
+ if (ofport->up.opp.config & htonl(OFPPC_NO_FWD)
+ || !stp_forward_in_state(ofport->stp_state)) {
/* Forwarding disabled on port. */
return;
}
@@ -3590,7 +3860,9 @@ flood_packets(struct action_xlate_ctx *ctx, ovs_be32 mask)
commit_odp_actions(ctx);
HMAP_FOR_EACH (ofport, up.hmap_node, &ctx->ofproto->up.ports) {
uint16_t ofp_port = ofport->up.ofp_port;
- if (ofp_port != ctx->flow.in_port && !(ofport->up.opp.config & mask)) {
+ if (ofp_port != ctx->flow.in_port
+ && !(ofport->up.opp.config & mask)
+ && stp_forward_in_state(ofport->stp_state)) {
compose_output_action(ctx, ofport->odp_port);
}
}
@@ -3804,6 +4076,27 @@ xlate_learn_action(struct action_xlate_ctx *ctx,
free(fm.actions);
}
+static bool
+may_receive(const struct ofport_dpif *port, struct action_xlate_ctx *ctx)
+{
+ if (port->up.opp.config & (eth_addr_equals(ctx->flow.dl_dst, eth_addr_stp)
+ ? htonl(OFPPC_NO_RECV_STP)
+ : htonl(OFPPC_NO_RECV))) {
+ return false;
+ }
+
+ /* Only drop packets here if both forwarding and learning are
+ * disabled. If just learning is enabled, we need to have
+ * OFPP_NORMAL and the learning action have a look at the packet
+ * before we can drop it. */
+ if (!stp_forward_in_state(port->stp_state)
+ && !stp_learn_in_state(port->stp_state)) {
+ return false;
+ }
+
+ return true;
+}
+
static void
do_xlate_actions(const union ofp_action *in, size_t n_in,
struct action_xlate_ctx *ctx)
@@ -3813,11 +4106,7 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
size_t left;
port = get_ofp_port(ctx->ofproto, ctx->flow.in_port);
- if (port
- && port->up.opp.config & htonl(OFPPC_NO_RECV | OFPPC_NO_RECV_STP) &&
- port->up.opp.config & (eth_addr_equals(ctx->flow.dl_dst, eth_addr_stp)
- ? htonl(OFPPC_NO_RECV_STP)
- : htonl(OFPPC_NO_RECV))) {
+ if (port && !may_receive(port, ctx)) {
/* Drop this flow. */
return;
}
@@ -3971,6 +4260,13 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
break;
}
}
+
+ /* We've let OFPP_NORMAL and the learning action look at the packet,
+ * so drop it now if forwarding is disabled. */
+ if (port && !stp_forward_in_state(port->stp_state)) {
+ ofpbuf_clear(ctx->odp_actions);
+ add_sflow_action(ctx);
+ }
}
static void
@@ -4547,10 +4843,9 @@ is_admissible(struct ofproto_dpif *ofproto, const struct flow *flow,
return false;
}
- /* Drop frames for reserved multicast addresses
- * only if forward_bpdu option is absent. */
- if (eth_addr_is_reserved(flow->dl_dst) &&
- !ofproto->up.forward_bpdu) {
+ /* Drop frames for reserved multicast addresses only if forward_bpdu
+ * option is absent. */
+ if (eth_addr_is_reserved(flow->dl_dst) && !ofproto->up.forward_bpdu) {
return false;
}
@@ -5113,6 +5408,10 @@ const struct ofproto_class ofproto_dpif_class = {
set_cfm,
get_cfm_fault,
get_cfm_remote_mpids,
+ set_stp,
+ get_stp_status,
+ set_stp_port,
+ get_stp_port_status,
bundle_set,
bundle_remove,
mirror_set,
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 7f1b110fa..8908dc069 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -918,6 +918,53 @@ struct ofproto_class {
int (*get_cfm_remote_mpids)(const struct ofport *ofport,
const uint64_t **rmps, size_t *n_rmps);
+ /* Configures spanning tree protocol (STP) on 'ofproto' using the
+ * settings defined in 's'.
+ *
+ * If 's' is nonnull, configures STP according to its members.
+ *
+ * If 's' is null, removes any STP configuration from 'ofproto'.
+ *
+ * EOPNOTSUPP as a return value indicates that this ofproto_class does not
+ * support STP, as does a null pointer. */
+ int (*set_stp)(struct ofproto *ofproto,
+ const struct ofproto_stp_settings *s);
+
+ /* Retrieves state of spanning tree protocol (STP) on 'ofproto'.
+ *
+ * Stores STP state for 'ofproto' in 's'. If the 'enabled' member
+ * is false, the other member values are not meaningful.
+ *
+ * EOPNOTSUPP as a return value indicates that this ofproto_class does not
+ * support STP, as does a null pointer. */
+ int (*get_stp_status)(struct ofproto *ofproto,
+ struct ofproto_stp_status *s);
+
+ /* Configures spanning tree protocol (STP) on 'ofport' using the
+ * settings defined in 's'.
+ *
+ * If 's' is nonnull, configures STP according to its members. The
+ * caller is responsible for assigning STP port numbers (using the
+ * 'port_num' member in the range of 1 through 255, inclusive) and
+ * ensuring there are no duplicates.
+ *
+ * If 's' is null, removes any STP configuration from 'ofport'.
+ *
+ * EOPNOTSUPP as a return value indicates that this ofproto_class does not
+ * support STP, as does a null pointer. */
+ int (*set_stp_port)(struct ofport *ofport,
+ const struct ofproto_port_stp_settings *s);
+
+ /* Retrieves spanning tree protocol (STP) port status of 'ofport'.
+ *
+ * Stores STP state for 'ofport' in 's'. If the 'enabled' member is
+ * false, the other member values are not meaningful.
+ *
+ * EOPNOTSUPP as a return value indicates that this ofproto_class does not
+ * support STP, as does a null pointer. */
+ int (*get_stp_port_status)(struct ofport *ofport,
+ struct ofproto_port_stp_status *s);
+
/* If 's' is nonnull, this function registers a "bundle" associated with
* client data pointer 'aux' in 'ofproto'. A bundle is the same concept as
* a Port in OVSDB, that is, it consists of one or more "slave" devices
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index f6923805a..0d80e1318 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -530,6 +530,79 @@ ofproto_set_sflow(struct ofproto *ofproto,
}
}
+/* Spanning Tree Protocol (STP) configuration. */
+
+/* Configures STP on 'ofproto' using the settings defined in 's'. If
+ * 's' is NULL, disables STP.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+int
+ofproto_set_stp(struct ofproto *ofproto,
+ const struct ofproto_stp_settings *s)
+{
+ return (ofproto->ofproto_class->set_stp
+ ? ofproto->ofproto_class->set_stp(ofproto, s)
+ : EOPNOTSUPP);
+}
+
+/* Retrieves STP status of 'ofproto' and stores it in 's'. If the
+ * 'enabled' member of 's' is false, then the other members are not
+ * meaningful.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+int
+ofproto_get_stp_status(struct ofproto *ofproto,
+ struct ofproto_stp_status *s)
+{
+ return (ofproto->ofproto_class->get_stp_status
+ ? ofproto->ofproto_class->get_stp_status(ofproto, s)
+ : EOPNOTSUPP);
+}
+
+/* Configures STP on 'ofp_port' of 'ofproto' using the settings defined
+ * in 's'. The caller is responsible for assigning STP port numbers
+ * (using the 'port_num' member in the range of 1 through 255, inclusive)
+ * and ensuring there are no duplicates. If the 's' is NULL, then STP
+ * is disabled on the port.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.*/
+int
+ofproto_port_set_stp(struct ofproto *ofproto, uint16_t ofp_port,
+ const struct ofproto_port_stp_settings *s)
+{
+ struct ofport *ofport = ofproto_get_port(ofproto, ofp_port);
+ if (!ofport) {
+ VLOG_WARN("%s: cannot configure STP on nonexistent port %"PRIu16,
+ ofproto->name, ofp_port);
+ return ENODEV;
+ }
+
+ return (ofproto->ofproto_class->set_stp_port
+ ? ofproto->ofproto_class->set_stp_port(ofport, s)
+ : EOPNOTSUPP);
+}
+
+/* Retrieves STP port status of 'ofp_port' on 'ofproto' and stores it in
+ * 's'. If the 'enabled' member in 's' is false, then the other members
+ * are not meaningful.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.*/
+int
+ofproto_port_get_stp_status(struct ofproto *ofproto, uint16_t ofp_port,
+ struct ofproto_port_stp_status *s)
+{
+ struct ofport *ofport = ofproto_get_port(ofproto, ofp_port);
+ if (!ofport) {
+ VLOG_WARN("%s: cannot get STP status on nonexistent port %"PRIu16,
+ ofproto->name, ofp_port);
+ return ENODEV;
+ }
+
+ return (ofproto->ofproto_class->get_stp_port_status
+ ? ofproto->ofproto_class->get_stp_port_status(ofport, s)
+ : EOPNOTSUPP);
+}
+
/* Connectivity Fault Management configuration. */
/* Clears the CFM configuration from 'ofp_port' on 'ofproto'. */
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index 9a8f755e3..4b37bd78f 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -26,6 +26,7 @@
#include "flow.h"
#include "netflow.h"
#include "sset.h"
+#include "stp.h"
#include "tag.h"
#ifdef __cplusplus
@@ -65,6 +66,36 @@ struct ofproto_sflow_options {
char *control_ip;
};
+struct ofproto_stp_settings {
+ stp_identifier system_id;
+ uint16_t priority;
+ uint16_t hello_time;
+ uint16_t max_age;
+ uint16_t fwd_delay;
+};
+
+struct ofproto_stp_status {
+ bool enabled; /* If false, ignore other members. */
+ stp_identifier bridge_id;
+ stp_identifier designated_root;
+ int root_path_cost;
+};
+
+struct ofproto_port_stp_settings {
+ bool enable;
+ uint8_t port_num; /* In the range 1-255, inclusive. */
+ uint8_t priority;
+ uint16_t path_cost;
+};
+
+struct ofproto_port_stp_status {
+ bool enabled; /* If false, ignore other members. */
+ int port_id;
+ enum stp_state state;
+ unsigned int sec_in_state;
+ enum stp_role role;
+};
+
/* How the switch should act if the controller cannot be contacted. */
enum ofproto_fail_mode {
OFPROTO_FAIL_SECURE, /* Preserve flow table. */
@@ -170,6 +201,8 @@ int ofproto_set_snoops(struct ofproto *, const struct sset *snoops);
int ofproto_set_netflow(struct ofproto *,
const struct netflow_options *nf_options);
int ofproto_set_sflow(struct ofproto *, const struct ofproto_sflow_options *);
+int ofproto_set_stp(struct ofproto *, const struct ofproto_stp_settings *);
+int ofproto_get_stp_status(struct ofproto *, struct ofproto_stp_status *);
/* Configuration of ports. */
@@ -179,6 +212,10 @@ void ofproto_port_clear_cfm(struct ofproto *, uint16_t ofp_port);
void ofproto_port_set_cfm(struct ofproto *, uint16_t ofp_port,
const struct cfm_settings *);
int ofproto_port_is_lacp_current(struct ofproto *, uint16_t ofp_port);
+int ofproto_port_set_stp(struct ofproto *, uint16_t ofp_port,
+ const struct ofproto_port_stp_settings *);
+int ofproto_port_get_stp_status(struct ofproto *, uint16_t ofp_port,
+ struct ofproto_port_stp_status *);
/* The behaviour of the port regarding VLAN handling */
enum port_vlan_mode {
diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
index 77911fab9..73e2b52bb 100644
--- a/tests/ovs-vsctl.at
+++ b/tests/ovs-vsctl.at
@@ -579,6 +579,8 @@ netflow : []
other_config : {}
ports : []
sflow : []
+status : {}
+stp_enable : false
<0>
]], [ignore], [test ! -e pid || kill `cat pid`])
AT_CHECK(
@@ -889,6 +891,8 @@ netflow : []
other_config : {}
ports : []
sflow : []
+status : {}
+stp_enable : false
]], [ignore], [test ! -e pid || kill `cat pid`])
OVS_VSCTL_CLEANUP
AT_CLEANUP
diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in
index f246128ff..64255b578 100644
--- a/utilities/ovs-vsctl.8.in
+++ b/utilities/ovs-vsctl.8.in
@@ -829,6 +829,24 @@ Deconfigure sFlow from \fBbr0\fR, which also destroys the sFlow record
(since it is now unreferenced):
.IP
.B "ovs\-vsctl \-\- clear Bridge br0 sflow"
+.SS "802.1D Spanning Tree Protocol (STP)"
+.PP
+Configure bridge \fBbr0\fR to participate in an 802.1D spanning tree:
+.IP
+.B "ovs\-vsctl set Bridge br0 stp_enable=true"
+.PP
+Set the bridge priority of \fBbr0\fR to 0x7800:
+.IP
+.B "ovs\-vsctl set Bridge br0 other_config:stp-priority=0x7800"
+.PP
+Set the path cost of port \fBeth0\fR to 10:
+.IP
+.B "ovs\-vsctl set Port eth0 other_config:stp-path-cost=10"
+.PP
+Deconfigure STP from above:
+.IP
+.B "ovs\-vsctl clear Bridge br0 stp_enable"
+.PP
.SH "EXIT STATUS"
.IP "0"
Successful program execution.
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 1319df8a7..3a1923501 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -152,6 +152,7 @@ static void bridge_configure_flow_eviction_threshold(struct bridge *);
static void bridge_configure_netflow(struct bridge *);
static void bridge_configure_forward_bpdu(struct bridge *);
static void bridge_configure_sflow(struct bridge *, int *sflow_bridge_number);
+static void bridge_configure_stp(struct bridge *);
static void bridge_configure_remotes(struct bridge *,
const struct sockaddr_in *managers,
size_t n_managers);
@@ -161,6 +162,11 @@ static void bridge_pick_local_hw_addr(struct bridge *,
static uint64_t bridge_pick_datapath_id(struct bridge *,
const uint8_t bridge_ea[ETH_ADDR_LEN],
struct iface *hw_addr_iface);
+static const char *bridge_get_other_config(const struct ovsrec_bridge *,
+ const char *key);
+static const char *get_port_other_config(const struct ovsrec_port *,
+ const char *key,
+ const char *default_value);
static uint64_t dpid_from_hash(const void *, size_t nbytes);
static bool bridge_has_bond_fake_iface(const struct bridge *,
const char *name);
@@ -229,8 +235,10 @@ bridge_init(const char *remote)
ovsdb_idl_omit(idl, &ovsrec_open_vswitch_col_system_version);
ovsdb_idl_omit_alert(idl, &ovsrec_bridge_col_datapath_id);
+ ovsdb_idl_omit_alert(idl, &ovsrec_bridge_col_status);
ovsdb_idl_omit(idl, &ovsrec_bridge_col_external_ids);
+ ovsdb_idl_omit_alert(idl, &ovsrec_port_col_status);
ovsdb_idl_omit(idl, &ovsrec_port_col_external_ids);
ovsdb_idl_omit(idl, &ovsrec_port_col_fake_bridge);
@@ -423,6 +431,7 @@ bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
bridge_configure_remotes(br, managers, n_managers);
bridge_configure_netflow(br);
bridge_configure_sflow(br, &sflow_bridge_number);
+ bridge_configure_stp(br);
}
free(managers);
@@ -716,6 +725,196 @@ bridge_configure_sflow(struct bridge *br, int *sflow_bridge_number)
sset_destroy(&oso.targets);
}
+static void
+port_configure_stp(const struct ofproto *ofproto, struct port *port,
+ struct ofproto_port_stp_settings *port_s,
+ int *port_num_counter, unsigned long *port_num_bitmap)
+{
+ const char *config_str;
+ struct iface *iface;
+
+ config_str = get_port_other_config(port->cfg, "stp-enable", NULL);
+ if (config_str && !strcmp(config_str, "false")) {
+ port_s->enable = false;
+ return;
+ } else {
+ port_s->enable = true;
+ }
+
+ /* STP over bonds is not supported. */
+ if (!list_is_singleton(&port->ifaces)) {
+ VLOG_ERR("port %s: cannot enable STP on bonds, disabling",
+ port->name);
+ port_s->enable = false;
+ return;
+ }
+
+ iface = CONTAINER_OF(list_front(&port->ifaces), struct iface, port_elem);
+
+ /* Internal ports shouldn't participate in spanning tree, so
+ * skip them. */
+ if (!strcmp(iface->type, "internal")) {
+ VLOG_DBG("port %s: disable STP on internal ports", port->name);
+ port_s->enable = false;
+ return;
+ }
+
+ /* STP on mirror output ports is not supported. */
+ if (ofproto_is_mirror_output_bundle(ofproto, port)) {
+ VLOG_DBG("port %s: disable STP on mirror ports", port->name);
+ port_s->enable = false;
+ return;
+ }
+
+ config_str = get_port_other_config(port->cfg, "stp-port-num", NULL);
+ if (config_str) {
+ unsigned long int port_num = strtoul(config_str, NULL, 0);
+ int port_idx = port_num - 1;
+
+ if (port_num < 1 || port_num > STP_MAX_PORTS) {
+ VLOG_ERR("port %s: invalid stp-port-num", port->name);
+ port_s->enable = false;
+ return;
+ }
+
+ if (bitmap_is_set(port_num_bitmap, port_idx)) {
+ VLOG_ERR("port %s: duplicate stp-port-num %lu, disabling",
+ port->name, port_num);
+ port_s->enable = false;
+ return;
+ }
+ bitmap_set1(port_num_bitmap, port_idx);
+ port_s->port_num = port_idx;
+ } else {
+ if (*port_num_counter > STP_MAX_PORTS) {
+ VLOG_ERR("port %s: too many STP ports, disabling", port->name);
+ port_s->enable = false;
+ return;
+ }
+
+ port_s->port_num = (*port_num_counter)++;
+ }
+
+ config_str = get_port_other_config(port->cfg, "stp-path-cost", NULL);
+ if (config_str) {
+ port_s->path_cost = strtoul(config_str, NULL, 10);
+ } else {
+ uint32_t current;
+
+ if (netdev_get_features(iface->netdev, &current, NULL, NULL, NULL)) {
+ /* Couldn't get speed, so assume 100Mb/s. */
+ port_s->path_cost = 19;
+ } else {
+ unsigned int mbps;
+
+ mbps = netdev_features_to_bps(current) / 1000000;
+ port_s->path_cost = stp_convert_speed_to_cost(mbps);
+ }
+ }
+
+ config_str = get_port_other_config(port->cfg, "stp-port-priority", NULL);
+ if (config_str) {
+ port_s->priority = strtoul(config_str, NULL, 0);
+ } else {
+ port_s->priority = STP_DEFAULT_PORT_PRIORITY;
+ }
+}
+
+/* Set spanning tree configuration on 'br'. */
+static void
+bridge_configure_stp(struct bridge *br)
+{
+ if (!br->cfg->stp_enable) {
+ ofproto_set_stp(br->ofproto, NULL);
+ } else {
+ struct ofproto_stp_settings br_s;
+ const char *config_str;
+ struct port *port;
+ int port_num_counter;
+ unsigned long *port_num_bitmap;
+
+ config_str = bridge_get_other_config(br->cfg, "stp-system-id");
+ if (config_str) {
+ uint8_t ea[ETH_ADDR_LEN];
+
+ if (eth_addr_from_string(config_str, ea)) {
+ br_s.system_id = eth_addr_to_uint64(ea);
+ } else {
+ br_s.system_id = eth_addr_to_uint64(br->ea);
+ VLOG_ERR("bridge %s: invalid stp-system-id, defaulting "
+ "to "ETH_ADDR_FMT, br->name, ETH_ADDR_ARGS(br->ea));
+ }
+ } else {
+ br_s.system_id = eth_addr_to_uint64(br->ea);
+ }
+
+ config_str = bridge_get_other_config(br->cfg, "stp-priority");
+ if (config_str) {
+ br_s.priority = strtoul(config_str, NULL, 0);
+ } else {
+ br_s.priority = STP_DEFAULT_BRIDGE_PRIORITY;
+ }
+
+ config_str = bridge_get_other_config(br->cfg, "stp-hello-time");
+ if (config_str) {
+ br_s.hello_time = strtoul(config_str, NULL, 10) * 1000;
+ } else {
+ br_s.hello_time = STP_DEFAULT_HELLO_TIME;
+ }
+
+ config_str = bridge_get_other_config(br->cfg, "stp-max-age");
+ if (config_str) {
+ br_s.max_age = strtoul(config_str, NULL, 10) * 1000;
+ } else {
+ br_s.max_age = STP_DEFAULT_MAX_AGE;
+ }
+
+ config_str = bridge_get_other_config(br->cfg, "stp-forward-delay");
+ if (config_str) {
+ br_s.fwd_delay = strtoul(config_str, NULL, 10) * 1000;
+ } else {
+ br_s.fwd_delay = STP_DEFAULT_FWD_DELAY;
+ }
+
+ /* Configure STP on the bridge. */
+ if (ofproto_set_stp(br->ofproto, &br_s)) {
+ VLOG_ERR("bridge %s: could not enable STP", br->name);
+ return;
+ }
+
+ /* Users must either set the port number with the "stp-port-num"
+ * configuration on all ports or none. If manual configuration
+ * is not done, then we allocate them sequentially. */
+ port_num_counter = 0;
+ port_num_bitmap = bitmap_allocate(STP_MAX_PORTS);
+ HMAP_FOR_EACH (port, hmap_node, &br->ports) {
+ struct ofproto_port_stp_settings port_s;
+ struct iface *iface;
+
+ port_configure_stp(br->ofproto, port, &port_s,
+ &port_num_counter, port_num_bitmap);
+
+ /* As bonds are not supported, just apply configuration to
+ * all interfaces. */
+ LIST_FOR_EACH (iface, port_elem, &port->ifaces) {
+ if (ofproto_port_set_stp(br->ofproto, iface->ofp_port,
+ &port_s)) {
+ VLOG_ERR("port %s: could not enable STP", port->name);
+ continue;
+ }
+ }
+ }
+
+ if (bitmap_scan(port_num_bitmap, 0, STP_MAX_PORTS) != STP_MAX_PORTS
+ && port_num_counter) {
+ VLOG_ERR("bridge %s: must manually configure all STP port "
+ "IDs or none, disabling", br->name);
+ ofproto_set_stp(br->ofproto, NULL);
+ }
+ bitmap_free(port_num_bitmap);
+ }
+}
+
static bool
bridge_has_bond_fake_iface(const struct bridge *br, const char *name)
{
@@ -1361,6 +1560,79 @@ iface_refresh_stats(struct iface *iface)
#undef IFACE_STATS
}
+static void
+br_refresh_stp_status(struct bridge *br)
+{
+ struct ofproto *ofproto = br->ofproto;
+ struct ofproto_stp_status status;
+ char *keys[3], *values[3];
+ size_t i;
+
+ if (ofproto_get_stp_status(ofproto, &status)) {
+ return;
+ }
+
+ if (!status.enabled) {
+ ovsrec_bridge_set_status(br->cfg, NULL, NULL, 0);
+ return;
+ }
+
+ keys[0] = "stp_bridge_id",
+ values[0] = xasprintf(STP_ID_FMT, STP_ID_ARGS(status.bridge_id));
+ keys[1] = "stp_designated_root",
+ values[1] = xasprintf(STP_ID_FMT, STP_ID_ARGS(status.designated_root));
+ keys[2] = "stp_root_path_cost",
+ values[2] = xasprintf("%d", status.root_path_cost);
+
+ ovsrec_bridge_set_status(br->cfg, keys, values, ARRAY_SIZE(values));
+
+ for (i = 0; i < ARRAY_SIZE(values); i++) {
+ free(values[i]);
+ }
+}
+
+static void
+port_refresh_stp_status(struct port *port)
+{
+ struct ofproto *ofproto = port->bridge->ofproto;
+ struct iface *iface;
+ struct ofproto_port_stp_status status;
+ char *keys[4], *values[4];
+ size_t i;
+
+ /* STP doesn't currently support bonds. */
+ if (!list_is_singleton(&port->ifaces)) {
+ ovsrec_port_set_status(port->cfg, NULL, NULL, 0);
+ return;
+ }
+
+ iface = CONTAINER_OF(list_front(&port->ifaces), struct iface, port_elem);
+
+ if (ofproto_port_get_stp_status(ofproto, iface->ofp_port, &status)) {
+ return;
+ }
+
+ if (!status.enabled) {
+ ovsrec_port_set_status(port->cfg, NULL, NULL, 0);
+ return;
+ }
+
+ keys[0] = "stp_port_id";
+ values[0] = xasprintf(STP_PORT_ID_FMT, status.port_id);
+ keys[1] = "stp_state";
+ values[1] = xstrdup(stp_state_name(status.state));
+ keys[2] = "stp_sec_in_state";
+ values[2] = xasprintf("%u", status.sec_in_state);
+ keys[3] = "stp_role";
+ values[3] = xstrdup(stp_role_name(status.role));
+
+ ovsrec_port_set_status(port->cfg, keys, values, ARRAY_SIZE(values));
+
+ for (i = 0; i < ARRAY_SIZE(values); i++) {
+ free(values[i]);
+ }
+}
+
static bool
enable_system_stats(const struct ovsrec_open_vswitch *cfg)
{
@@ -1572,6 +1844,13 @@ bridge_run(void)
txn = ovsdb_idl_txn_create(idl);
HMAP_FOR_EACH (br, node, &all_bridges) {
struct iface *iface;
+ struct port *port;
+
+ br_refresh_stp_status(br);
+
+ HMAP_FOR_EACH (port, hmap_node, &br->ports) {
+ port_refresh_stp_status(port);
+ }
HMAP_FOR_EACH (iface, name_node, &br->iface_by_name) {
const char *link_state;
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index 5fce98905..3a9c51fb0 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@
{"name": "Open_vSwitch",
"version": "6.2.0",
- "cksum": "212779557 14885",
+ "cksum": "145151998 15203",
"tables": {
"Open_vSwitch": {
"columns": {
@@ -62,6 +62,8 @@
"datapath_id": {
"type": {"key": "string", "min": 0, "max": 1},
"ephemeral": true},
+ "stp_enable": {
+ "type": "boolean"},
"ports": {
"type": {"key": {"type": "uuid",
"refTable": "Port"},
@@ -86,6 +88,9 @@
"type": {"key": {"type": "string",
"enum": ["set", ["standalone", "secure"]]},
"min": 0, "max": 1}},
+ "status": {
+ "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"},
+ "ephemeral": true},
"other_config": {
"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
"external_ids": {
@@ -142,6 +147,9 @@
"type": "boolean"},
"fake_bridge": {
"type": "boolean"},
+ "status": {
+ "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"},
+ "ephemeral": true},
"other_config": {
"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
"external_ids": {
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 683b27ea6..239a9e884 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -435,6 +435,55 @@
</column>
</group>
+ <group title="Spanning Tree Configuration">
+ The IEEE 802.1D Spanning Tree Protocol (STP) is a network protocol
+ that ensures loop-free topologies. It allows redundant links to
+ be included in the network to provide automatic backup paths if
+ the active links fails.
+
+ <column name="stp_enable">
+ Enable spanning tree on the bridge. By default, STP is disabled
+ on bridges. Bond, internal, and mirror ports are not supported
+ and will not participate in the spanning tree.
+ </column>
+
+ <column name="other_config" key="stp-system-id">
+ The bridge's STP identifier (the lower 48 bits of the bridge-id)
+ in the form
+ <var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>.
+ By default, the identifier is the MAC address of the bridge.
+ </column>
+
+ <column name="other_config" key="stp-priority"
+ type='{"type": "integer", "minInteger": 0, "maxInteger": 65535}'>
+ The bridge's relative priority value for determining the root
+ bridge (the upper 16 bits of the bridge-id). A bridge with the
+ lowest bridge-id is elected the root. By default, the priority
+ is 0x8000.
+ </column>
+
+ <column name="other_config" key="stp-hello-time"
+ type='{"type": "integer", "minInteger": 1, "maxInteger": 10}'>
+ The interval between transmissions of hello messages by
+ designated ports, in seconds. By default the hello interval is
+ 2 seconds.
+ </column>
+
+ <column name="other_config" key="stp-max-age"
+ type='{"type": "integer", "minInteger": 6, "maxInteger": 40}'>
+ The maximum age of the information transmitted by the bridge
+ when it is the root bridge, in seconds. By default, the maximum
+ age is 20 seconds.
+ </column>
+
+ <column name="other_config" key="stp-forward-delay"
+ type='{"type": "integer", "minInteger": 4, "maxInteger": 30}'>
+ The delay to wait between transitioning root and designated
+ ports to <code>forwarding</code>, in seconds. By default, the
+ forwarding delay is 15 seconds.
+ </column>
+ </group>
+
<group title="Other Features">
<column name="datapath_type">
Name of datapath provider. The kernel datapath has
@@ -478,12 +527,43 @@
<column name="other_config" key="forward-bpdu"
type='{"type": "boolean"}'>
- Option to allow forwarding of BPDU frames when NORMAL action if
- invoked. Frames with reserved Ethernet addresses (e.g. STP BPDU) will
- be forwarded when this option is enabled. If the Open vSwitch bridge
- is used to connect different Ethernet networks, and if Open vSwitch
- node does not run STP, then this option should be enabled. Default is
- disabled, set to <code>true</code> to enable.
+ Option to allow forwarding of BPDU frames when NORMAL action is
+ invoked. Frames with reserved Ethernet addresses (e.g. STP
+ BPDU) will be forwarded when this option is enabled and the
+ switch is not providing that functionality. If STP is enabled
+ on the port, STP BPDUs will never be forwarded. If the Open
+ vSwitch bridge is used to connect different Ethernet networks,
+ and if Open vSwitch node does not run STP, then this option
+ should be enabled. Default is disabled, set to
+ <code>true</code> to enable.
+ </column>
+ </group>
+
+ <group title="Bridge Status">
+ <p>
+ Status information about bridges.
+ </p>
+ <column name="status">
+ Key-value pairs that report bridge status.
+ </column>
+ <column name="status" key="stp_bridge_id">
+ <p>
+ The bridge-id (in hex) used in spanning tree advertisements.
+ Configuring the bridge-id is described in the
+ <code>stp-system-id</code> and <code>stp-priority</code> keys
+ of the <code>other_config</code> section earlier.
+ </p>
+ </column>
+ <column name="status" key="stp_designated_root">
+ <p>
+ The designated root (in hex) for this spanning tree.
+ </p>
+ </column>
+ <column name="status" key="stp_root_path_cost">
+ <p>
+ The path cost of reaching the designated bridge. A lower
+ number is better.
+ </p>
</column>
</group>
@@ -807,6 +887,40 @@
</column>
</group>
+ <group title="Spanning Tree Configuration">
+ <column name="other_config" key="stp-enable"
+ type='{"type": "boolean"}'>
+ If spanning tree is enabled on the bridge, member ports are
+ enabled by default (with the exception of bond, internal, and
+ mirror ports which do not work with STP). If this column's
+ value is <code>false</code> spanning tree is disabled on the
+ port.
+ </column>
+
+ <column name="other_config" key="stp-port-num"
+ type='{"type": "integer", "minInteger": 1, "maxInteger": 255}'>
+ The port number used for the lower 8 bits of the port-id. By
+ default, the numbers will be assigned automatically. If any
+ port's number is manually configured on a bridge, then they
+ must all be.
+ </column>
+
+ <column name="other_config" key="stp-port-priority"
+ type='{"type": "integer", "minInteger": 0, "maxInteger": 255}'>
+ The port's relative priority value for determining the root
+ port (the upper 8 bits of the port-id). A port with a lower
+ port-id will be chosen as the root port. By default, the
+ priority is 0x80.
+ </column>
+
+ <column name="other_config" key="stp-path-cost"
+ type='{"type": "integer", "minInteger": 0, "maxInteger": 65535}'>
+ Spanning tree path cost for the port. A lower number indicates
+ a faster link. By default, the cost is based on the maximum
+ speed of the link.
+ </column>
+ </group>
+
<group title="Other Features">
<column name="qos">
Quality of Service configuration for this port.
@@ -833,6 +947,45 @@
</column>
</group>
+ <group title="Port Status">
+ <p>
+ Status information about ports attached to bridges.
+ </p>
+ <column name="status">
+ Key-value pairs that report port status.
+ </column>
+ <column name="status" key="stp_port_id">
+ <p>
+ The port-id (in hex) used in spanning tree advertisements for
+ this port. Configuring the port-id is described in the
+ <code>stp-port-num</code> and <code>stp-port-priority</code>
+ keys of the <code>other_config</code> section earlier.
+ </p>
+ </column>
+ <column name="status" key="stp_state"
+ type='{"type": "string", "enum": ["set",
+ ["disabled", "listening", "learning",
+ "forwarding", "blocking"]]}'>
+ <p>
+ STP state of the port.
+ </p>
+ </column>
+ <column name="status" key="stp_sec_in_state"
+ type='{"type": "integer", "minInteger": 0}'>
+ <p>
+ The amount of time (in seconds) port has been in the current
+ STP state.
+ </p>
+ </column>
+ <column name="status" key="stp_role"
+ type='{"type": "string", "enum": ["set",
+ ["root", "designated", "alternate"]]}'>
+ <p>
+ STP role of the port.
+ </p>
+ </column>
+ </group>
+
<group title="Common Columns">
The overall purpose of these columns is described under <code>Common
Columns</code> at the beginning of this document.