[ovs-dev] [STP 9/9] ovs-vswitchd: Add support for 802.1D STP.

Justin Pettit jpettit at nicira.com
Tue Oct 18 05:31:49 UTC 2011


Add 802.1D spanning tree protocol support to ovs-vswitchd.  Still needs
improvements in the following areas:

    * Does not work over bonds.
    * Does not expire entries generated by the learning action when STP
      state changes occur.
    * Needs more interoperability testing.  Only tested against the
      Linux bridge.
---
 NEWS                       |    1 +
 ofproto/ofproto-dpif.c     |  336 ++++++++++++++++++++++++++++++++++++++++++--
 ofproto/ofproto-provider.h |   47 ++++++
 ofproto/ofproto.c          |   73 ++++++++++
 ofproto/ofproto.h          |   37 +++++
 tests/ovs-vsctl.at         |    4 +
 utilities/ovs-vsctl.8.in   |   18 +++
 vswitchd/bridge.c          |  278 ++++++++++++++++++++++++++++++++++++
 vswitchd/vswitch.ovsschema |   10 ++-
 vswitchd/vswitch.xml       |  140 ++++++++++++++++++
 10 files changed, 933 insertions(+), 11 deletions(-)

diff --git a/NEWS b/NEWS
index ff3bc44..61cfc20 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).
     - CAPWAP tunneling now supports an extension to transport a 64-key.  By
       default it remains compatible with the old version and other
       standards-based implementations.
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index d6bd2a5..8ad4d10 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. */
+
+    int stp_port_num;           /* -1 indicates disabled. */
+    enum stp_state stp_state;   /* Always STP_DISABLED if STP not in use. */
+    long long int stp_msec_in_state;
 };
 
 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_num = -1;
+    port->stp_state = STP_DISABLED;
 
     if (ofproto->sflow) {
         dpif_sflow_add_port(ofproto->sflow, port->odp_port,
@@ -912,6 +928,268 @@ 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_);
+
+    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;
+    }
+    ofproto->need_revalidate = true;
+
+    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
+stp_update_port_state(struct ofport_dpif *ofport)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+    enum stp_state state;
+
+    /* Figure out new state. */
+    if (ofport->stp_port_num != -1) {
+        struct stp_port *sp;
+
+        sp = stp_get_port(ofproto->stp, ofport->stp_port_num);
+        state = stp_port_get_state(sp);
+    } else {
+        /* STP is disabled on the port. */
+        state = 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_msec_in_state = 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(&ofproto->up, ofport->up.ofp_port, 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 = NULL;
+
+    if (ofport->stp_port_num != -1) {
+        sp = stp_get_port(ofproto->stp, ofport->stp_port_num);
+    }
+
+    if (!s || !s->enable) {
+        if (sp) {
+            ofport->stp_port_num = -1;
+            stp_port_disable(sp);
+        }
+        return 0;
+    } else if (ofport->stp_port_num != s->port_num
+            && sp && 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);
+    }
+
+    ofport->stp_port_num = s->port_num;
+    sp = stp_get_port(ofproto->stp, ofport->stp_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);
+
+    stp_update_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;
+
+    if (!ofproto->stp || ofport->stp_port_num == -1) {
+        s->enabled = false;
+        return 0;
+    }
+
+    sp = stp_get_port(ofproto->stp, ofport->stp_port_num);
+
+    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_msec_in_state) / 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) {
+                stp_update_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 ofproto_dpif *ofproto,
+                   const struct ofport_dpif *ofport,
+                   const struct ofpbuf *packet)
+{
+    struct ofpbuf payload = *packet;
+    struct eth_header *eth = payload.data;
+    struct stp_port *sp;
+
+    /* Sink packets on ports that have STP disabled when the bridge has
+     * STP enabled. */
+    if (ofport->stp_port_num == -1) {
+        return;
+    }
+
+    sp = stp_get_port(ofproto->stp, ofport->stp_port_num);
+    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 +1248,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 +1296,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 +2118,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(ofproto, ofport, packet);
+        }
+        return true;
     }
     return false;
 }
@@ -1989,7 +2274,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++;
@@ -3389,7 +3674,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;
         }
@@ -3482,7 +3768,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);
         }
     }
@@ -3694,6 +3982,28 @@ 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 & 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))) {
+        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)
@@ -3703,11 +4013,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;
     }
@@ -3860,6 +4166,12 @@ 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);
+    }
 }
 
 static void
@@ -4987,6 +5299,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 c571f52..0b37689 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -894,6 +894,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 ea54df5..1ffadc1 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -529,6 +529,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 fbd4701..3188197 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. */
 
@@ -181,6 +214,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 77911fa..73e2b52 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 f246128..f2607ee 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 0x8100:
+.IP
+.B "ovs\-vsctl set Bridge br0 other_config:stp-priority=0x8100"
+.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 d4335c1..29eb87e 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,197 @@ 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_size(&port->ifaces) != 1) {
+        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 +1561,82 @@ 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;
+    struct shash sh;
+    size_t n;
+    char **keys, **values;
+
+    if (ofproto_get_stp_status(ofproto, &status)) {
+        return;
+    }
+
+    if (!status.enabled) {
+        ovsrec_bridge_set_status(br->cfg, NULL, NULL, 0);
+        return;
+    }
+
+    shash_init(&sh);
+    shash_add(&sh, "stp_bridge_id",
+              xasprintf(STP_ID_FMT, STP_ID_ARGS(status.bridge_id)));
+    shash_add(&sh, "stp_designated_root",
+              xasprintf(STP_ID_FMT, STP_ID_ARGS(status.designated_root)));
+    shash_add(&sh, "stp_root_path_cost",
+              xasprintf("%d", status.root_path_cost));
+
+    shash_to_ovs_idl_map(&sh, &keys, &values, &n);
+    ovsrec_bridge_set_status(br->cfg, keys, values, n);
+
+    shash_destroy_free_data(&sh);
+    free(keys);
+    free(values);
+}
+
+static void
+port_refresh_stp_status(struct port *port)
+{
+    struct ofproto *ofproto = port->bridge->ofproto;
+    struct iface *iface;
+    struct ofproto_port_stp_status status;
+    struct shash sh;
+    size_t n;
+    char **keys, **values;
+
+
+    /* STP doesn't currently support bonds. */
+    if (!list_is_singleton(&port->ifaces)) {
+        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;
+    }
+
+    shash_init(&sh);
+    shash_add(&sh, "stp_port_id",
+              xasprintf(STP_PORT_ID_FMT, STP_PORT_ID_ARGS(status.port_id)));
+    shash_add(&sh, "stp_state", xstrdup(stp_state_name(status.state)));
+    shash_add(&sh, "stp_sec_in_state", xasprintf("%u", status.sec_in_state));
+    shash_add(&sh, "stp_role", xstrdup(stp_role_name(status.role)));
+
+    shash_to_ovs_idl_map(&sh, &keys, &values, &n);
+    ovsrec_port_set_status(port->cfg, keys, values, n);
+
+    shash_destroy_free_data(&sh);
+    free(keys);
+    free(values);
+}
+
 static bool
 enable_system_stats(const struct ovsrec_open_vswitch *cfg)
 {
@@ -1548,9 +1824,11 @@ bridge_run(void)
             HMAP_FOR_EACH (br, node, &all_bridges) {
                 struct port *port;
 
+                br_refresh_stp_status(br);
                 HMAP_FOR_EACH (port, hmap_node, &br->ports) {
                     struct iface *iface;
 
+                    port_refresh_stp_status(port);
                     LIST_FOR_EACH (iface, port_elem, &port->ifaces) {
                         iface_refresh_stats(iface);
                         iface_refresh_status(iface);
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index 5fce989..3a9c51f 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 683b27e..c427d50 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -368,6 +368,12 @@
           a different type of mirror instead.
         </p>
       </column>
+      <column name="stp_enable">
+        Enable 802.1D spanning tree protocol (STP) on the bridge.  By
+        default, STP is disabled on the bridge.  Bond, internal, and
+        mirror ports are not supported and will not participate in the
+        spanning tree.
+      </column>
     </group>
 
     <group title="OpenFlow Configuration">
@@ -485,6 +491,70 @@
         node does not run STP, then this option should be enabled.  Default is
         disabled, set to <code>true</code> to enable.
       </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="Bridge Status">
+      <p>
+        Status information about bridges, updated every second.
+      </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>
 
     <group title="Common Columns">
@@ -781,6 +851,37 @@
           changed, all flows will be assigned different hash values possibly
           causing slave selection decisions to change.
         </column>
+ 
+        <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).  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="SLB Configuration">
@@ -833,6 +934,45 @@
       </column>
     </group>
 
+    <group title="Port Status">
+      <p>
+        Status information about ports attached to bridges, updated every
+        second.
+      </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">
+        <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.
-- 
1.7.1




More information about the dev mailing list