From: Justin Pettit Date: Fri, 14 Oct 2011 16:48:17 +0000 (-0700) Subject: ovs-vswitchd: Add support for 802.1D STP. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=21f7563cef5f3e5ab52c15ade58594c2dfdb7195;p=openvswitch ovs-vswitchd: Add support for 802.1D STP. Still alpha quality, since only tested for interoperability with Linux bridge's STP implementation. --- diff --git a/NEWS b/NEWS index a05c1979..75738d08 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 4629efae..4de5a4e6 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 7f1b110f..8908dc06 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 f6923805..0d80e131 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 9a8f755e..4b37bd78 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 77911fab..73e2b52b 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 f246128f..64255b57 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 1319df8a..3a192350 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, ¤t, 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 5fce9890..3a9c51fb 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 683b27ea..239a9e88 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -435,6 +435,55 @@ + + 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. + + + 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. + + + + The bridge's STP identifier (the lower 48 bits of the bridge-id) + in the form + xx:xx:xx:xx:xx:xx. + By default, the identifier is the MAC address of the bridge. + + + + 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. + + + + The interval between transmissions of hello messages by + designated ports, in seconds. By default the hello interval is + 2 seconds. + + + + 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. + + + + The delay to wait between transitioning root and designated + ports to forwarding, in seconds. By default, the + forwarding delay is 15 seconds. + + + Name of datapath provider. The kernel datapath has @@ -478,12 +527,43 @@ - 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 true 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 + true to enable. + + + + +

+ Status information about bridges. +

+ + Key-value pairs that report bridge status. + + +

+ The bridge-id (in hex) used in spanning tree advertisements. + Configuring the bridge-id is described in the + stp-system-id and stp-priority keys + of the other_config section earlier. +

+
+ +

+ The designated root (in hex) for this spanning tree. +

+
+ +

+ The path cost of reaching the designated bridge. A lower + number is better. +

@@ -807,6 +887,40 @@ + + + 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 false spanning tree is disabled on the + port. + + + + 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. + + + + 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. + + + + 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. + + + Quality of Service configuration for this port. @@ -833,6 +947,45 @@ + +

+ Status information about ports attached to bridges. +

+ + Key-value pairs that report port status. + + +

+ The port-id (in hex) used in spanning tree advertisements for + this port. Configuring the port-id is described in the + stp-port-num and stp-port-priority + keys of the other_config section earlier. +

+
+ +

+ STP state of the port. +

+
+ +

+ The amount of time (in seconds) port has been in the current + STP state. +

+
+ +

+ STP role of the port. +

+
+
+ The overall purpose of these columns is described under Common Columns at the beginning of this document.