stp: Track BPDU tx and rx counts.
[openvswitch] / ofproto / ofproto-dpif.c
index 38f5be700913636449a096fb4a0533fcd25c3b02..4cc720667d143dff167fd4ae42bdcb8c95fbe46b 100644 (file)
@@ -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. */
 
@@ -202,13 +205,13 @@ struct action_xlate_ctx {
  * reason to look at them. */
 
     int recurse;                /* Recursion level, via xlate_table_action. */
-    uint32_t priority;          /* Current flow priority. 0 if none. */
     struct flow base_flow;      /* Flow at the last commit. */
-    uint32_t base_priority;     /* Priority at the last commit. */
+    uint32_t original_priority; /* Priority when packet arrived. */
     uint8_t table_id;           /* OpenFlow table ID where flow was found. */
     uint32_t sflow_n_outputs;   /* Number of output ports. */
     uint16_t sflow_odp_port;    /* Output port for composing sFlow action. */
     uint16_t user_cookie_offset;/* Used for user_action_cookie fixup. */
+    bool exit;                  /* No further actions should be processed. */
 };
 
 static void action_xlate_ctx_init(struct action_xlate_ctx *,
@@ -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,249 @@ get_cfm_remote_mpids(const struct ofport *ofport_, const uint64_t **rmps,
     }
 }
 \f
+/* 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 {
+            send_packet(ofproto_dpif_cast(ofport->up.ofproto),
+                        ofport->odp_port, 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 = 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);
+            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);
+            }
+        }
+    }
+}
+
+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
 /* Bundles. */
 
 /* Expires all MAC learning entries associated with 'port' and forces ofproto
@@ -970,7 +1229,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 +1277,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;
         }
     }
@@ -1285,12 +1546,8 @@ send_pdu_cb(void *port_, const void *pdu, size_t pdu_size)
                                  pdu_size);
         memcpy(packet_pdu, pdu, pdu_size);
 
-        error = netdev_send(port->up.netdev, &packet);
-        if (error) {
-            VLOG_WARN_RL(&rl, "port %s: sending LACP PDU on iface %s failed "
-                         "(%s)", port->bundle->name,
-                         netdev_get_name(port->up.netdev), strerror(error));
-        }
+        send_packet(ofproto_dpif_cast(port->up.ofproto), port->odp_port,
+                    &packet);
         ofpbuf_uninit(&packet);
     } else {
         VLOG_ERR_RL(&rl, "port %s: cannot obtain Ethernet address of iface "
@@ -1309,7 +1566,16 @@ bundle_send_learning_packets(struct ofbundle *bundle)
     error = n_packets = n_errors = 0;
     LIST_FOR_EACH (e, lru_node, &ofproto->ml->lrus) {
         if (e->port.p != bundle) {
-            int ret = bond_send_learning_packet(bundle->bond, e->mac, e->vlan);
+            struct ofpbuf *learning_packet;
+            struct ofport_dpif *port;
+            int ret;
+
+            learning_packet = bond_compose_learning_packet(bundle->bond, e->mac,
+                                                           e->vlan,
+                                                           (void **)&port);
+            ret = send_packet(ofproto_dpif_cast(port->up.ofproto),
+                              port->odp_port, learning_packet);
+            ofpbuf_delete(learning_packet);
             if (ret) {
                 error = ret;
                 n_errors++;
@@ -1543,7 +1809,7 @@ set_flood_vlans(struct ofproto *ofproto_, unsigned long *flood_vlans)
 }
 
 static bool
-is_mirror_output_bundle(struct ofproto *ofproto_, void *aux)
+is_mirror_output_bundle(const struct ofproto *ofproto_, void *aux)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     struct ofbundle *bundle = bundle_lookup(ofproto, aux);
@@ -1838,6 +2104,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;
 }
@@ -1987,9 +2258,10 @@ handle_miss_upcalls(struct ofproto_dpif *ofproto, struct dpif_upcall *upcalls,
         /* Obtain in_port and tun_id, at least, then set 'flow''s header
          * pointers. */
         odp_flow_key_to_flow(upcall->key, upcall->key_len, &flow);
-        flow_extract(upcall->packet, flow.tun_id, flow.in_port, &flow);
+        flow_extract(upcall->packet, flow.priority, 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++;
@@ -2614,10 +2886,10 @@ facet_account(struct ofproto_dpif *ofproto, struct facet *facet)
      * hash bucket.) */
     vlan_tci = facet->flow.vlan_tci;
     NL_ATTR_FOR_EACH_UNSAFE (a, left, facet->actions, facet->actions_len) {
+        const struct ovs_action_push_vlan *vlan;
         struct ofport_dpif *port;
 
         switch (nl_attr_type(a)) {
-        const struct nlattr *nested;
         case OVS_ACTION_ATTR_OUTPUT:
             port = get_odp_port(ofproto, nl_attr_get_u32(a));
             if (port && port->bundle && port->bundle->bond) {
@@ -2626,20 +2898,13 @@ facet_account(struct ofproto_dpif *ofproto, struct facet *facet)
             }
             break;
 
-        case OVS_ACTION_ATTR_POP:
-            if (nl_attr_get_u16(a) == OVS_KEY_ATTR_8021Q) {
-                vlan_tci = htons(0);
-            }
+        case OVS_ACTION_ATTR_POP_VLAN:
+            vlan_tci = htons(0);
             break;
 
-        case OVS_ACTION_ATTR_PUSH:
-            nested = nl_attr_get(a);
-            if (nl_attr_type(nested) == OVS_KEY_ATTR_8021Q) {
-                const struct ovs_key_8021q *q_key;
-
-                q_key = nl_attr_get_unspec(nested, sizeof(*q_key));
-                vlan_tci = q_key->q_tci;
-            }
+        case OVS_ACTION_ATTR_PUSH_VLAN:
+            vlan = nl_attr_get(a);
+            vlan_tci = vlan->vlan_tci;
             break;
         }
     }
@@ -2977,7 +3242,7 @@ rule_dpif_lookup(struct ofproto_dpif *ofproto, const struct flow *flow,
     }
 
     cls = &ofproto->up.tables[table_id];
-    if (flow->tos_frag & FLOW_FRAG_ANY
+    if (flow->nw_frag & FLOW_NW_FRAG_ANY
         && ofproto->up.frag_handling == OFPC_FRAG_NORMAL) {
         /* For OFPC_NORMAL frag_handling, we must pretend that transport ports
          * are unavailable. */
@@ -3179,7 +3444,7 @@ send_packet(struct ofproto_dpif *ofproto, uint32_t odp_port,
     struct flow flow;
     int error;
 
-    flow_extract((struct ofpbuf *) packet, 0, 0, &flow);
+    flow_extract((struct ofpbuf *) packet, 0, 0, 0, &flow);
     ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
     odp_flow_key_from_flow(&key, &flow);
 
@@ -3315,13 +3580,10 @@ fix_sflow_action(struct action_xlate_ctx *ctx)
 }
 
 static void
-commit_action__(struct ofpbuf *odp_actions,
-                enum ovs_action_attr act_type,
-                enum ovs_key_attr key_type,
-                const void *key, size_t key_size)
+commit_set_action(struct ofpbuf *odp_actions, enum ovs_key_attr key_type,
+                  const void *key, size_t key_size)
 {
-    size_t offset = nl_msg_start_nested(odp_actions, act_type);
-
+    size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
     nl_msg_put_unspec(odp_actions, key_type, key, key_size);
     nl_msg_end_nested(odp_actions, offset);
 }
@@ -3335,8 +3597,8 @@ commit_set_tun_id_action(const struct flow *flow, struct flow *base,
     }
     base->tun_id = flow->tun_id;
 
-    commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
-             OVS_KEY_ATTR_TUN_ID, &base->tun_id, sizeof(base->tun_id));
+    commit_set_action(odp_actions, OVS_KEY_ATTR_TUN_ID,
+                      &base->tun_id, sizeof(base->tun_id));
 }
 
 static void
@@ -3356,8 +3618,8 @@ commit_set_ether_addr_action(const struct flow *flow, struct flow *base,
     memcpy(eth_key.eth_src, base->dl_src, ETH_ADDR_LEN);
     memcpy(eth_key.eth_dst, base->dl_dst, ETH_ADDR_LEN);
 
-    commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
-             OVS_KEY_ATTR_ETHERNET, &eth_key, sizeof(eth_key));
+    commit_set_action(odp_actions, OVS_KEY_ATTR_ETHERNET,
+                      &eth_key, sizeof(eth_key));
 }
 
 static void
@@ -3370,18 +3632,16 @@ commit_vlan_action(struct action_xlate_ctx *ctx, ovs_be16 new_tci)
     }
 
     if (base->vlan_tci & htons(VLAN_CFI)) {
-        nl_msg_put_u16(ctx->odp_actions, OVS_ACTION_ATTR_POP,
-                                       OVS_KEY_ATTR_8021Q);
+        nl_msg_put_flag(ctx->odp_actions, OVS_ACTION_ATTR_POP_VLAN);
     }
 
     if (new_tci & htons(VLAN_CFI)) {
-        struct ovs_key_8021q q_key;
-
-        q_key.q_tpid = htons(ETH_TYPE_VLAN);
-        q_key.q_tci = new_tci & ~htons(VLAN_CFI);
+        struct ovs_action_push_vlan vlan;
 
-        commit_action__(ctx->odp_actions, OVS_ACTION_ATTR_PUSH,
-                            OVS_KEY_ATTR_8021Q, &q_key, sizeof(q_key));
+        vlan.vlan_tpid = htons(ETH_TYPE_VLAN);
+        vlan.vlan_tci = new_tci;
+        nl_msg_put_unspec(ctx->odp_actions, OVS_ACTION_ATTR_PUSH_VLAN,
+                          &vlan, sizeof vlan);
     }
     base->vlan_tci = new_tci;
 }
@@ -3390,7 +3650,6 @@ static void
 commit_set_nw_action(const struct flow *flow, struct flow *base,
                      struct ofpbuf *odp_actions)
 {
-    int frag = base->tos_frag & FLOW_FRAG_MASK;
     struct ovs_key_ipv4 ipv4_key;
 
     if (base->dl_type != htons(ETH_TYPE_IP) ||
@@ -3400,22 +3659,23 @@ commit_set_nw_action(const struct flow *flow, struct flow *base,
 
     if (base->nw_src == flow->nw_src &&
         base->nw_dst == flow->nw_dst &&
-        base->tos_frag == flow->tos_frag) {
+        base->nw_tos == flow->nw_tos &&
+        base->nw_ttl == flow->nw_ttl &&
+        base->nw_frag == flow->nw_frag) {
         return;
     }
 
-
-    memset(&ipv4_key, 0, sizeof(ipv4_key));
     ipv4_key.ipv4_src = base->nw_src = flow->nw_src;
     ipv4_key.ipv4_dst = base->nw_dst = flow->nw_dst;
     ipv4_key.ipv4_proto = base->nw_proto;
-    ipv4_key.ipv4_tos = flow->tos_frag & IP_DSCP_MASK;
-    ipv4_key.ipv4_frag = (frag == 0 ? OVS_FRAG_TYPE_NONE
-                          : frag == FLOW_FRAG_ANY ? OVS_FRAG_TYPE_FIRST
-                          : OVS_FRAG_TYPE_LATER);
+    ipv4_key.ipv4_tos = flow->nw_tos;
+    ipv4_key.ipv4_ttl = flow->nw_ttl;
+    ipv4_key.ipv4_frag = (base->nw_frag == 0 ? OVS_FRAG_TYPE_NONE
+                          : base->nw_frag == FLOW_NW_FRAG_ANY
+                          ? OVS_FRAG_TYPE_FIRST : OVS_FRAG_TYPE_LATER);
 
-    commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
-             OVS_KEY_ATTR_IPV4, &ipv4_key, sizeof(ipv4_key));
+    commit_set_action(odp_actions, OVS_KEY_ATTR_IPV4,
+                      &ipv4_key, sizeof(ipv4_key));
 }
 
 static void
@@ -3437,8 +3697,8 @@ commit_set_port_action(const struct flow *flow, struct flow *base,
         port_key.tcp_src = base->tp_src = flow->tp_src;
         port_key.tcp_dst = base->tp_dst = flow->tp_dst;
 
-        commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
-             OVS_KEY_ATTR_TCP, &port_key, sizeof(port_key));
+        commit_set_action(odp_actions, OVS_KEY_ATTR_TCP,
+                          &port_key, sizeof(port_key));
 
     } else if (flow->nw_proto == IPPROTO_UDP) {
         struct ovs_key_udp port_key;
@@ -3446,25 +3706,22 @@ commit_set_port_action(const struct flow *flow, struct flow *base,
         port_key.udp_src = base->tp_src = flow->tp_src;
         port_key.udp_dst = base->tp_dst = flow->tp_dst;
 
-        commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
-             OVS_KEY_ATTR_UDP, &port_key, sizeof(port_key));
+        commit_set_action(odp_actions, OVS_KEY_ATTR_UDP,
+                          &port_key, sizeof(port_key));
     }
 }
 
 static void
-commit_priority_action(struct action_xlate_ctx *ctx)
+commit_set_priority_action(const struct flow *flow, struct flow *base,
+                           struct ofpbuf *odp_actions)
 {
-    if (ctx->base_priority == ctx->priority) {
+    if (base->priority == flow->priority) {
         return;
     }
+    base->priority = flow->priority;
 
-    if (ctx->priority) {
-        nl_msg_put_u32(ctx->odp_actions,
-                        OVS_ACTION_ATTR_SET_PRIORITY, ctx->priority);
-    } else {
-        nl_msg_put_flag(ctx->odp_actions, OVS_ACTION_ATTR_POP_PRIORITY);
-    }
-    ctx->base_priority = ctx->priority;
+    commit_set_action(odp_actions, OVS_KEY_ATTR_PRIORITY,
+                      &base->priority, sizeof(base->priority));
 }
 
 static void
@@ -3479,7 +3736,7 @@ commit_odp_actions(struct action_xlate_ctx *ctx)
     commit_vlan_action(ctx, flow->vlan_tci);
     commit_set_nw_action(flow, base, odp_actions);
     commit_set_port_action(flow, base, odp_actions);
-    commit_priority_action(ctx);
+    commit_set_priority_action(flow, base, odp_actions);
 }
 
 static void
@@ -3497,7 +3754,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 +3848,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);
         }
     }
@@ -3685,7 +3945,7 @@ xlate_enqueue_action(struct action_xlate_ctx *ctx,
                      const struct ofp_action_enqueue *oae)
 {
     uint16_t ofp_port, odp_port;
-    uint32_t ctx_priority, priority;
+    uint32_t flow_priority, priority;
     int error;
 
     error = dpif_queue_to_priority(ctx->ofproto->dpif, ntohl(oae->queue_id),
@@ -3706,10 +3966,10 @@ xlate_enqueue_action(struct action_xlate_ctx *ctx,
     odp_port = ofp_port_to_odp_port(ofp_port);
 
     /* Add datapath actions. */
-    ctx_priority = ctx->priority;
-    ctx->priority = priority;
+    flow_priority = ctx->flow.priority;
+    ctx->flow.priority = priority;
     add_output_action(ctx, odp_port);
-    ctx->priority = ctx_priority;
+    ctx->flow.priority = flow_priority;
 
     /* Update NetFlow output port. */
     if (ctx->nf_output_iface == NF_OUT_DROP) {
@@ -3734,7 +3994,7 @@ xlate_set_queue_action(struct action_xlate_ctx *ctx,
         return;
     }
 
-    ctx->priority = priority;
+    ctx->flow.priority = priority;
 }
 
 struct xlate_reg_state {
@@ -3774,7 +4034,7 @@ slave_enabled_cb(uint16_t ofp_port, void *ofproto_)
     case OFPP_NORMAL:
     case OFPP_FLOOD:
     case OFPP_ALL:
-    case OFPP_LOCAL:
+    case OFPP_NONE:
         return true;
     case OFPP_CONTROLLER: /* Not supported by the bundle action. */
         return false;
@@ -3804,6 +4064,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 +4094,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;
     }
@@ -3834,6 +4111,10 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
         enum ofputil_action_code code;
         ovs_be64 tun_id;
 
+        if (ctx->exit) {
+            break;
+        }
+
         code = ofputil_decode_action_unsafe(ia);
         switch (code) {
         case OFPUTIL_OFPAT_OUTPUT:
@@ -3874,8 +4155,8 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
             break;
 
         case OFPUTIL_OFPAT_SET_NW_TOS:
-            ctx->flow.tos_frag &= ~IP_DSCP_MASK;
-            ctx->flow.tos_frag |= ia->nw_tos.nw_tos & IP_DSCP_MASK;
+            ctx->flow.nw_tos &= ~IP_DSCP_MASK;
+            ctx->flow.nw_tos |= ia->nw_tos.nw_tos & IP_DSCP_MASK;
             break;
 
         case OFPUTIL_OFPAT_SET_TP_SRC:
@@ -3911,7 +4192,7 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
             break;
 
         case OFPUTIL_NXAST_POP_QUEUE:
-            ctx->priority = 0;
+            ctx->flow.priority = ctx->original_priority;
             break;
 
         case OFPUTIL_NXAST_REG_MOVE:
@@ -3969,8 +4250,19 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
                 xlate_learn_action(ctx, (const struct nx_action_learn *) ia);
             }
             break;
+
+        case OFPUTIL_NXAST_EXIT:
+            ctx->exit = true;
+            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
@@ -3993,13 +4285,24 @@ xlate_actions(struct action_xlate_ctx *ctx,
 
     ctx->odp_actions = ofpbuf_new(512);
     ofpbuf_reserve(ctx->odp_actions, NL_A_U32_SIZE);
+    ctx->tags = 0;
+    ctx->may_set_up_flow = true;
+    ctx->has_learn = false;
+    ctx->has_normal = false;
+    ctx->nf_output_iface = NF_OUT_DROP;
+    ctx->recurse = 0;
+    ctx->original_priority = ctx->flow.priority;
+    ctx->base_flow = ctx->flow;
+    ctx->base_flow.tun_id = 0;
+    ctx->table_id = 0;
+    ctx->exit = false;
 
-    if (ctx->flow.tos_frag & FLOW_FRAG_ANY) {
+    if (ctx->flow.nw_frag & FLOW_NW_FRAG_ANY) {
         switch (ctx->ofproto->up.frag_handling) {
         case OFPC_FRAG_NORMAL:
             /* We must pretend that transport ports are unavailable. */
-            ctx->flow.tp_src = htons(0);
-            ctx->flow.tp_dst = htons(0);
+            ctx->flow.tp_src = ctx->base_flow.tp_src = htons(0);
+            ctx->flow.tp_dst = ctx->base_flow.tp_dst = htons(0);
             break;
 
         case OFPC_FRAG_DROP:
@@ -4014,18 +4317,6 @@ xlate_actions(struct action_xlate_ctx *ctx,
         }
     }
 
-    ctx->tags = 0;
-    ctx->may_set_up_flow = true;
-    ctx->has_learn = false;
-    ctx->has_normal = false;
-    ctx->nf_output_iface = NF_OUT_DROP;
-    ctx->recurse = 0;
-    ctx->priority = 0;
-    ctx->base_priority = 0;
-    ctx->base_flow = ctx->flow;
-    ctx->base_flow.tun_id = 0;
-    ctx->table_id = 0;
-
     if (process_special(ctx->ofproto, &ctx->flow, ctx->packet)) {
         ctx->may_set_up_flow = false;
         return ctx->odp_actions;
@@ -4429,6 +4720,14 @@ flow_get_vlan(struct ofproto_dpif *ofproto, const struct flow *flow,
             return -1;
         }
     } else {
+        if (flow->dl_type == htons(ETH_TYPE_VLAN) &&
+            !(flow->vlan_tci & htons(VLAN_CFI))) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "bridge %s: dropping packet with partial "
+                         "VLAN tag received on port %s",
+                         ofproto->up.name, in_bundle->name);
+            return -1;
+        }
         if (in_bundle->vlan_mode != PORT_VLAN_TRUNK) {
             return in_bundle->vlan;
         } else {
@@ -4547,10 +4846,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;
     }
 
@@ -4776,6 +5074,10 @@ packet_out(struct ofproto *ofproto_, struct ofpbuf *packet,
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     int error;
 
+    if (flow->in_port >= ofproto->max_ports && flow->in_port < OFPP_MAX) {
+        return ofp_mkerr_nicira(OFPET_BAD_REQUEST, NXBRC_BAD_IN_PORT);
+    }
+
     error = validate_actions(ofp_actions, n_ofp_actions, flow,
                              ofproto->max_ports);
     if (!error) {
@@ -4814,6 +5116,22 @@ ofproto_dpif_lookup(const char *name)
             : NULL);
 }
 
+static void
+ofproto_unixctl_fdb_flush(struct unixctl_conn *conn,
+                         const char *args, void *aux OVS_UNUSED)
+{
+    const struct ofproto_dpif *ofproto;
+
+    ofproto = ofproto_dpif_lookup(args);
+    if (!ofproto) {
+        unixctl_command_reply(conn, 501, "no such bridge");
+        return;
+    }
+    mac_learning_flush(ofproto->ml);
+
+    unixctl_command_reply(conn, 200, "table successfully flushed");
+}
+
 static void
 ofproto_unixctl_fdb_show(struct unixctl_conn *conn,
                          const char *args, void *aux OVS_UNUSED)
@@ -4911,7 +5229,7 @@ static void
 ofproto_unixctl_trace(struct unixctl_conn *conn, const char *args_,
                       void *aux OVS_UNUSED)
 {
-    char *dpname, *arg1, *arg2, *arg3;
+    char *dpname, *arg1, *arg2, *arg3, *arg4;
     char *args = xstrdup(args_);
     char *save_ptr = NULL;
     struct ofproto_dpif *ofproto;
@@ -4929,7 +5247,8 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, const char *args_,
     dpname = strtok_r(args, " ", &save_ptr);
     arg1 = strtok_r(NULL, " ", &save_ptr);
     arg2 = strtok_r(NULL, " ", &save_ptr);
-    arg3 = strtok_r(NULL, "", &save_ptr); /* Get entire rest of line. */
+    arg3 = strtok_r(NULL, " ", &save_ptr);
+    arg4 = strtok_r(NULL, "", &save_ptr); /* Get entire rest of line. */
     if (dpname && arg1 && (!arg2 || !strcmp(arg2, "-generate")) && !arg3) {
         /* ofproto/trace dpname flow [-generate] */
         int error;
@@ -4954,18 +5273,20 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, const char *args_,
             packet = ofpbuf_new(0);
             flow_compose(packet, &flow);
         }
-    } else if (dpname && arg1 && arg2 && arg3) {
-        /* ofproto/trace dpname tun_id in_port packet */
+    } else if (dpname && arg1 && arg2 && arg3 && arg4) {
+        /* ofproto/trace dpname priority tun_id in_port packet */
         uint16_t in_port;
         ovs_be64 tun_id;
+        uint32_t priority;
 
-        tun_id = htonll(strtoull(arg1, NULL, 0));
-        in_port = ofp_port_to_odp_port(atoi(arg2));
+        priority = atoi(arg1);
+        tun_id = htonll(strtoull(arg2, NULL, 0));
+        in_port = ofp_port_to_odp_port(atoi(arg3));
 
         packet = ofpbuf_new(strlen(args) / 2);
-        arg3 = ofpbuf_put_hex(packet, arg3, NULL);
-        arg3 += strspn(arg3, " ");
-        if (*arg3 != '\0') {
+        arg4 = ofpbuf_put_hex(packet, arg4, NULL);
+        arg4 += strspn(arg4, " ");
+        if (*arg4 != '\0') {
             unixctl_command_reply(conn, 501, "Trailing garbage in command");
             goto exit;
         }
@@ -4980,7 +5301,7 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, const char *args_,
         ds_put_cstr(&result, s);
         free(s);
 
-        flow_extract(packet, tun_id, in_port, &flow);
+        flow_extract(packet, priority, tun_id, in_port, &flow);
     } else {
         unixctl_command_reply(conn, 501, "Bad command syntax");
         goto exit;
@@ -5063,6 +5384,8 @@ ofproto_dpif_unixctl_init(void)
     unixctl_command_register("ofproto/trace",
                       "bridge {tun_id in_port packet | odp_flow [-generate]}",
                       ofproto_unixctl_trace, NULL);
+    unixctl_command_register("fdb/flush", "bridge", ofproto_unixctl_fdb_flush,
+                             NULL);
     unixctl_command_register("fdb/show", "bridge", ofproto_unixctl_fdb_show,
                              NULL); 
     unixctl_command_register("ofproto/clog", "", ofproto_dpif_clog, NULL);
@@ -5113,6 +5436,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,