+ 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 {
+ send_packet(ofport, pkt);
+ }
+ }
+ 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 = REV_RECONFIGURE;
+ }
+
+ 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 {
+ struct ofport *ofport;
+
+ HMAP_FOR_EACH (ofport, hmap_node, &ofproto->up.ports) {
+ set_stp_port(ofport, NULL);
+ }
+
+ 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) {
+ enum ofputil_port_state 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, &ofproto->revalidate_set);
+ }
+ fwd_change = stp_forward_in_state(ofport->stp_state)
+ != stp_forward_in_state(state);
+
+ ofproto->need_revalidate = REV_STP;
+ ofport->stp_state = state;
+ ofport->stp_state_entered = time_msec();
+
+ if (fwd_change && ofport->bundle) {
+ bundle_update(ofport->bundle);
+ }
+
+ /* Update the STP state bits in the OpenFlow port description. */
+ of_state = ofport->up.pp.state & ~OFPUTIL_PS_STP_MASK;
+ of_state |= (state == STP_LISTENING ? OFPUTIL_PS_STP_LISTEN
+ : state == STP_LEARNING ? OFPUTIL_PS_STP_LEARN
+ : state == STP_FORWARDING ? OFPUTIL_PS_STP_FORWARD
+ : state == STP_BLOCKING ? OFPUTIL_PS_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);
+ update_stp_port_state(ofport);
+ }
+ 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);
+ stp_port_get_counts(sp, &s->tx_count, &s->rx_count, &s->error_count);
+
+ 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);
+ }
+ }
+
+ if (stp_check_and_reset_fdb_flush(ofproto->stp)) {
+ mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+ }
+ }
+}
+
+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 > ntohs(eth->eth_type) + ETH_HEADER_LEN) {
+ payload.size = ntohs(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);
+ }
+}
+\f
+static struct priority_to_dscp *
+get_priority(const struct ofport_dpif *ofport, uint32_t priority)
+{
+ struct priority_to_dscp *pdscp;
+ uint32_t hash;
+
+ hash = hash_int(priority, 0);
+ HMAP_FOR_EACH_IN_BUCKET (pdscp, hmap_node, hash, &ofport->priorities) {
+ if (pdscp->priority == priority) {
+ return pdscp;
+ }
+ }
+ return NULL;
+}
+
+static void
+ofport_clear_priorities(struct ofport_dpif *ofport)
+{
+ struct priority_to_dscp *pdscp, *next;
+
+ HMAP_FOR_EACH_SAFE (pdscp, next, hmap_node, &ofport->priorities) {
+ hmap_remove(&ofport->priorities, &pdscp->hmap_node);
+ free(pdscp);
+ }
+}
+
+static int
+set_queues(struct ofport *ofport_,
+ const struct ofproto_port_queue *qdscp_list,
+ size_t n_qdscp)
+{
+ struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+ struct hmap new = HMAP_INITIALIZER(&new);
+ size_t i;
+
+ for (i = 0; i < n_qdscp; i++) {
+ struct priority_to_dscp *pdscp;
+ uint32_t priority;
+ uint8_t dscp;
+
+ dscp = (qdscp_list[i].dscp << 2) & IP_DSCP_MASK;
+ if (dpif_queue_to_priority(ofproto->dpif, qdscp_list[i].queue,
+ &priority)) {
+ continue;
+ }
+
+ pdscp = get_priority(ofport, priority);
+ if (pdscp) {
+ hmap_remove(&ofport->priorities, &pdscp->hmap_node);
+ } else {
+ pdscp = xmalloc(sizeof *pdscp);
+ pdscp->priority = priority;
+ pdscp->dscp = dscp;
+ ofproto->need_revalidate = REV_RECONFIGURE;
+ }
+
+ if (pdscp->dscp != dscp) {
+ pdscp->dscp = dscp;
+ ofproto->need_revalidate = REV_RECONFIGURE;
+ }
+
+ hmap_insert(&new, &pdscp->hmap_node, hash_int(pdscp->priority, 0));
+ }
+
+ if (!hmap_is_empty(&ofport->priorities)) {
+ ofport_clear_priorities(ofport);
+ ofproto->need_revalidate = REV_RECONFIGURE;
+ }
+
+ hmap_swap(&new, &ofport->priorities);
+ hmap_destroy(&new);
+
+ return 0;
+}
+\f
+/* Bundles. */
+
+/* Expires all MAC learning entries associated with 'bundle' and forces its
+ * ofproto to revalidate every flow.
+ *
+ * Normally MAC learning entries are removed only from the ofproto associated
+ * with 'bundle', but if 'all_ofprotos' is true, then the MAC learning entries
+ * are removed from every ofproto. When patch ports and SLB bonds are in use
+ * and a VM migration happens and the gratuitous ARPs are somehow lost, this
+ * avoids a MAC_ENTRY_IDLE_TIME delay before the migrated VM can communicate
+ * with the host from which it migrated. */
+static void
+bundle_flush_macs(struct ofbundle *bundle, bool all_ofprotos)
+{
+ struct ofproto_dpif *ofproto = bundle->ofproto;
+ struct mac_learning *ml = ofproto->ml;
+ struct mac_entry *mac, *next_mac;
+
+ ofproto->need_revalidate = REV_RECONFIGURE;
+ LIST_FOR_EACH_SAFE (mac, next_mac, lru_node, &ml->lrus) {
+ if (mac->port.p == bundle) {
+ if (all_ofprotos) {
+ struct ofproto_dpif *o;
+
+ HMAP_FOR_EACH (o, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
+ if (o != ofproto) {
+ struct mac_entry *e;
+
+ e = mac_learning_lookup(o->ml, mac->mac, mac->vlan,
+ NULL);
+ if (e) {
+ tag_set_add(&o->revalidate_set, e->tag);
+ mac_learning_expire(o->ml, e);
+ }
+ }
+ }
+ }
+
+ mac_learning_expire(ml, mac);
+ }
+ }
+}
+
+static struct ofbundle *
+bundle_lookup(const struct ofproto_dpif *ofproto, void *aux)
+{
+ struct ofbundle *bundle;
+
+ HMAP_FOR_EACH_IN_BUCKET (bundle, hmap_node, hash_pointer(aux, 0),
+ &ofproto->bundles) {
+ if (bundle->aux == aux) {
+ return bundle;
+ }
+ }
+ return NULL;
+}
+
+/* Looks up each of the 'n_auxes' pointers in 'auxes' as bundles and adds the
+ * ones that are found to 'bundles'. */
+static void
+bundle_lookup_multiple(struct ofproto_dpif *ofproto,
+ void **auxes, size_t n_auxes,
+ struct hmapx *bundles)
+{
+ size_t i;
+
+ hmapx_init(bundles);
+ for (i = 0; i < n_auxes; i++) {
+ struct ofbundle *bundle = bundle_lookup(ofproto, auxes[i]);
+ if (bundle) {
+ hmapx_add(bundles, bundle);
+ }
+ }
+}
+
+static void
+bundle_update(struct ofbundle *bundle)
+{
+ struct ofport_dpif *port;
+
+ bundle->floodable = true;
+ LIST_FOR_EACH (port, bundle_node, &bundle->ports) {
+ if (port->up.pp.config & OFPUTIL_PC_NO_FLOOD
+ || !stp_forward_in_state(port->stp_state)) {
+ bundle->floodable = false;
+ break;
+ }