packaging: Fix Xen and RH packaging error that was introduced with ovs-test utility
[sliver-openvswitch.git] / ofproto / ofproto-dpif.c
index ff607f7..951ed18 100644 (file)
@@ -121,9 +121,10 @@ struct ofmirror {
     struct hmapx dsts;          /* Contains "struct ofbundle *"s. */
     unsigned long *vlans;       /* Bitmap of chosen VLANs, NULL selects all. */
 
-    /* Output (mutually exclusive). */
+    /* Output (exactly one of out == NULL and out_vlan == -1 is true). */
     struct ofbundle *out;       /* Output port or NULL. */
     int out_vlan;               /* Output VLAN or -1. */
+    mirror_mask_t dup_mirrors;  /* Bitmap of mirrors with the same output. */
 };
 
 static void mirror_destroy(struct ofmirror *);
@@ -144,6 +145,7 @@ struct ofbundle {
                                  * NULL if all VLANs are trunked. */
     struct lacp *lacp;          /* LACP if LACP is enabled, otherwise NULL. */
     struct bond *bond;          /* Nonnull iff more than one port. */
+    bool use_priority_tags;     /* Use 802.1p tag for frames in VLAN 0? */
 
     /* Status. */
     bool floodable;             /* True if no port has OFPPC_NO_FLOOD set. */
@@ -155,11 +157,17 @@ struct ofbundle {
 };
 
 static void bundle_remove(struct ofport *);
+static void bundle_update(struct ofbundle *);
 static void bundle_destroy(struct ofbundle *);
 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);
+
+static bool ofbundle_includes_vlan(const struct ofbundle *, uint16_t vlan);
+
 struct action_xlate_ctx {
 /* action_xlate_ctx_init() initializes these members. */
 
@@ -201,13 +209,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 *,
@@ -313,6 +321,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 *
@@ -373,6 +385,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
@@ -397,12 +413,9 @@ static struct ofport_dpif *get_odp_port(struct ofproto_dpif *,
 static void update_learning_table(struct ofproto_dpif *,
                                   const struct flow *, int vlan,
                                   struct ofbundle *);
-static bool is_admissible(struct ofproto_dpif *, const struct flow *,
-                          bool have_packet, tag_type *, int *vlanp,
-                          struct ofbundle **in_bundlep);
-
 /* Upcalls. */
 #define FLOW_MISS_MAX_BATCH 50
+
 static void handle_upcall(struct ofproto_dpif *, struct dpif_upcall *);
 static void handle_miss_upcalls(struct ofproto_dpif *,
                                 struct dpif_upcall *, size_t n);
@@ -494,6 +507,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++) {
@@ -627,6 +641,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. */
@@ -677,6 +692,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()");
@@ -782,6 +798,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,
@@ -825,6 +843,10 @@ port_reconfigured(struct ofport *port_, ovs_be32 old_config)
     if (changed & htonl(OFPPC_NO_RECV | OFPPC_NO_RECV_STP |
                         OFPPC_NO_FWD | OFPPC_NO_FLOOD)) {
         ofproto->need_revalidate = true;
+
+        if (changed & htonl(OFPPC_NO_FLOOD) && port->bundle) {
+            bundle_update(port->bundle);
+        }
     }
 }
 
@@ -907,6 +929,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 && ofport->bundle) {
+            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
@@ -958,6 +1223,21 @@ bundle_lookup_multiple(struct ofproto_dpif *ofproto,
     }
 }
 
+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.opp.config & htonl(OFPPC_NO_FLOOD)
+                    || !stp_forward_in_state(port->stp_state)) {
+            bundle->floodable = false;
+            break;
+        }
+    }
+}
+
 static void
 bundle_del_port(struct ofport_dpif *port)
 {
@@ -975,12 +1255,7 @@ bundle_del_port(struct ofport_dpif *port)
         bond_slave_unregister(bundle->bond, port);
     }
 
-    bundle->floodable = true;
-    LIST_FOR_EACH (port, bundle_node, &bundle->ports) {
-        if (port->up.opp.config & htonl(OFPPC_NO_FLOOD)) {
-            bundle->floodable = false;
-        }
-    }
+    bundle_update(bundle);
 }
 
 static bool
@@ -1003,7 +1278,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;
         }
     }
@@ -1089,6 +1365,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
         bundle->vlan_mode = PORT_VLAN_TRUNK;
         bundle->vlan = -1;
         bundle->trunks = NULL;
+        bundle->use_priority_tags = s->use_priority_tags;
         bundle->lacp = NULL;
         bundle->bond = NULL;
 
@@ -1147,8 +1424,10 @@ bundle_set(struct ofproto *ofproto_, void *aux,
     }
 
     /* Set VLAN tagging mode */
-    if (s->vlan_mode != bundle->vlan_mode) {
+    if (s->vlan_mode != bundle->vlan_mode
+        || s->use_priority_tags != bundle->use_priority_tags) {
         bundle->vlan_mode = s->vlan_mode;
+        bundle->use_priority_tags = s->use_priority_tags;
         need_flush = true;
     }
 
@@ -1271,12 +1550,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 "
@@ -1295,7 +1570,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++;
@@ -1377,6 +1661,39 @@ mirror_lookup(struct ofproto_dpif *ofproto, void *aux)
     return NULL;
 }
 
+/* Update the 'dup_mirrors' member of each of the ofmirrors in 'ofproto'. */
+static void
+mirror_update_dups(struct ofproto_dpif *ofproto)
+{
+    int i;
+
+    for (i = 0; i < MAX_MIRRORS; i++) {
+        struct ofmirror *m = ofproto->mirrors[i];
+
+        if (m) {
+            m->dup_mirrors = MIRROR_MASK_C(1) << i;
+        }
+    }
+
+    for (i = 0; i < MAX_MIRRORS; i++) {
+        struct ofmirror *m1 = ofproto->mirrors[i];
+        int j;
+
+        if (!m1) {
+            continue;
+        }
+
+        for (j = i + 1; j < MAX_MIRRORS; j++) {
+            struct ofmirror *m2 = ofproto->mirrors[j];
+
+            if (m2 && m1->out == m2->out && m1->out_vlan == m2->out_vlan) {
+                m1->dup_mirrors |= MIRROR_MASK_C(1) << j;
+                m2->dup_mirrors |= m1->dup_mirrors;
+            }
+        }
+    }
+}
+
 static int
 mirror_set(struct ofproto *ofproto_, void *aux,
            const struct ofproto_mirror_settings *s)
@@ -1482,6 +1799,7 @@ mirror_set(struct ofproto *ofproto_, void *aux,
 
     ofproto->need_revalidate = true;
     mac_learning_flush(ofproto->ml);
+    mirror_update_dups(ofproto);
 
     return 0;
 }
@@ -1515,6 +1833,8 @@ mirror_destroy(struct ofmirror *mirror)
     ofproto->mirrors[mirror->idx] = NULL;
     free(mirror->name);
     free(mirror);
+
+    mirror_update_dups(ofproto);
 }
 
 static int
@@ -1529,7 +1849,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);
@@ -1824,6 +2144,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;
 }
@@ -1973,9 +2298,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++;
@@ -2600,10 +2926,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) {
@@ -2612,20 +2938,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;
         }
     }
@@ -2955,13 +3274,26 @@ static struct rule_dpif *
 rule_dpif_lookup(struct ofproto_dpif *ofproto, const struct flow *flow,
                  uint8_t table_id)
 {
+    struct cls_rule *cls_rule;
+    struct classifier *cls;
+
     if (table_id >= N_TABLES) {
         return NULL;
     }
 
-    return rule_dpif_cast(rule_from_cls_rule(
-                              classifier_lookup(&ofproto->up.tables[table_id],
-                                                flow)));
+    cls = &ofproto->up.tables[table_id];
+    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. */
+        struct flow ofpc_normal_flow = *flow;
+        ofpc_normal_flow.tp_src = htons(0);
+        ofpc_normal_flow.tp_dst = htons(0);
+        cls_rule = classifier_lookup(cls, &ofpc_normal_flow);
+    } else {
+        cls_rule = classifier_lookup(cls, flow);
+    }
+    return rule_dpif_cast(rule_from_cls_rule(cls_rule));
 }
 
 static void
@@ -3152,7 +3484,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);
 
@@ -3185,19 +3517,12 @@ put_userspace_action(const struct ofproto_dpif *ofproto,
                      const struct flow *flow,
                      const struct user_action_cookie *cookie)
 {
-    size_t offset;
     uint32_t pid;
 
     pid = dpif_port_get_pid(ofproto->dpif,
                             ofp_port_to_odp_port(flow->in_port));
 
-    offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_USERSPACE);
-    nl_msg_put_u32(odp_actions, OVS_USERSPACE_ATTR_PID, pid);
-    nl_msg_put_unspec(odp_actions, OVS_USERSPACE_ATTR_USERDATA,
-                      cookie, sizeof *cookie);
-    nl_msg_end_nested(odp_actions, offset);
-
-    return odp_actions->size - NLA_ALIGN(sizeof *cookie);
+    return odp_put_userspace_action(pid, cookie, odp_actions);
 }
 
 /* Compose SAMPLE action for sFlow. */
@@ -3288,13 +3613,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);
 }
@@ -3308,8 +3630,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
@@ -3329,8 +3651,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
@@ -3343,18 +3665,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;
 }
@@ -3372,19 +3692,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->nw_tos == flow->nw_tos) {
+        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_tos = base->nw_tos = flow->nw_tos;
-
     ipv4_key.ipv4_proto = base->nw_proto;
+    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
@@ -3406,8 +3730,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;
@@ -3415,25 +3739,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
@@ -3448,7 +3769,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
@@ -3466,7 +3787,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;
         }
@@ -3559,7 +3881,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);
         }
     }
@@ -3654,7 +3978,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),
@@ -3675,10 +3999,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) {
@@ -3703,7 +4027,7 @@ xlate_set_queue_action(struct action_xlate_ctx *ctx,
         return;
     }
 
-    ctx->priority = priority;
+    ctx->flow.priority = priority;
 }
 
 struct xlate_reg_state {
@@ -3743,7 +4067,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;
@@ -3773,6 +4097,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)
@@ -3782,11 +4127,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;
     }
@@ -3803,6 +4144,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:
@@ -3843,7 +4188,8 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
             break;
 
         case OFPUTIL_OFPAT_SET_NW_TOS:
-            ctx->flow.nw_tos = 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:
@@ -3879,7 +4225,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:
@@ -3937,8 +4283,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
@@ -3967,11 +4324,31 @@ xlate_actions(struct action_xlate_ctx *ctx,
     ctx->has_normal = false;
     ctx->nf_output_iface = NF_OUT_DROP;
     ctx->recurse = 0;
-    ctx->priority = 0;
-    ctx->base_priority = 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.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 = ctx->base_flow.tp_src = htons(0);
+            ctx->flow.tp_dst = ctx->base_flow.tp_dst = htons(0);
+            break;
+
+        case OFPC_FRAG_DROP:
+            return ctx->odp_actions;
+
+        case OFPC_FRAG_REASM:
+            NOT_REACHED();
+
+        case OFPC_FRAG_NX_MATCH:
+            /* Nothing to do. */
+            break;
+        }
+    }
 
     if (process_special(ctx->ofproto, &ctx->flow, ctx->packet)) {
         ctx->may_set_up_flow = false;
@@ -3998,21 +4375,6 @@ xlate_actions(struct action_xlate_ctx *ctx,
 \f
 /* OFPP_NORMAL implementation. */
 
-struct dst {
-    struct ofport_dpif *port;
-    uint16_t vid;
-};
-
-struct dst_set {
-    struct dst builtin[32];
-    struct dst *dsts;
-    size_t n, allocated;
-};
-
-static void dst_set_init(struct dst_set *);
-static void dst_set_add(struct dst_set *, const struct dst *);
-static void dst_set_free(struct dst_set *);
-
 static struct ofport_dpif *ofbundle_get_a_port(const struct ofbundle *);
 
 /* Given 'vid', the VID obtained from the 802.1Q header that was received as
@@ -4041,6 +4403,58 @@ input_vid_to_vlan(const struct ofbundle *in_bundle, uint16_t vid)
     }
 }
 
+/* Checks whether a packet with the given 'vid' may ingress on 'in_bundle'.
+ * If so, returns true.  Otherwise, returns false and, if 'warn' is true, logs
+ * a warning.
+ *
+ * 'vid' should be the VID obtained from the 802.1Q header that was received as
+ * part of a packet (specify 0 if there was no 802.1Q header), in the range
+ * 0...4095. */
+static bool
+input_vid_is_valid(uint16_t vid, struct ofbundle *in_bundle, bool warn)
+{
+    switch (in_bundle->vlan_mode) {
+    case PORT_VLAN_ACCESS:
+        if (vid) {
+            if (warn) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %"PRIu16" tagged "
+                             "packet received on port %s configured as VLAN "
+                             "%"PRIu16" access port",
+                             in_bundle->ofproto->up.name, vid,
+                             in_bundle->name, in_bundle->vlan);
+            }
+            return false;
+        }
+        return true;
+
+    case PORT_VLAN_NATIVE_UNTAGGED:
+    case PORT_VLAN_NATIVE_TAGGED:
+        if (!vid) {
+            /* Port must always carry its native VLAN. */
+            return true;
+        }
+        /* Fall through. */
+    case PORT_VLAN_TRUNK:
+        if (!ofbundle_includes_vlan(in_bundle, vid)) {
+            if (warn) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %"PRIu16" packet "
+                             "received on port %s not configured for trunking "
+                             "VLAN %"PRIu16,
+                             in_bundle->ofproto->up.name, vid,
+                             in_bundle->name, vid);
+            }
+            return false;
+        }
+        return true;
+
+    default:
+        NOT_REACHED();
+    }
+
+}
+
 /* Given 'vlan', the VLAN that a packet belongs to, and
  * 'out_bundle', a bundle on which the packet is to be output, returns the VID
  * that should be included in the 802.1Q header.  (If the return value is 0,
@@ -4067,20 +4481,37 @@ output_vlan_to_vid(const struct ofbundle *out_bundle, uint16_t vlan)
     }
 }
 
-static bool
-set_dst(struct action_xlate_ctx *ctx, struct dst *dst,
-        const struct ofbundle *in_bundle, const struct ofbundle *out_bundle)
+static void
+output_normal(struct action_xlate_ctx *ctx, const struct ofbundle *out_bundle,
+              uint16_t vlan)
 {
-    uint16_t vlan;
+    struct ofport_dpif *port;
+    uint16_t vid;
+    ovs_be16 tci;
+
+    vid = output_vlan_to_vid(out_bundle, vlan);
+    if (!out_bundle->bond) {
+        port = ofbundle_get_a_port(out_bundle);
+    } else {
+        port = bond_choose_output_slave(out_bundle->bond, &ctx->flow,
+                                        vid, &ctx->tags);
+        if (!port) {
+            /* No slaves enabled, so drop packet. */
+            return;
+        }
+    }
 
-    vlan = input_vid_to_vlan(in_bundle, vlan_tci_to_vid(ctx->flow.vlan_tci));
-    dst->vid = output_vlan_to_vid(out_bundle, vlan);
+    tci = htons(vid);
+    if (tci || out_bundle->use_priority_tags) {
+        tci |= ctx->flow.vlan_tci & htons(VLAN_PCP_MASK);
+        if (tci) {
+            tci |= htons(VLAN_CFI);
+        }
+    }
+    commit_vlan_action(ctx, tci);
 
-    dst->port = (!out_bundle->bond
-                 ? ofbundle_get_a_port(out_bundle)
-                 : bond_choose_output_slave(out_bundle->bond, &ctx->flow,
-                                            dst->vid, &ctx->tags));
-    return dst->port != NULL;
+    compose_output_action(ctx, port->odp_port);
+    ctx->nf_output_iface = port->odp_port;
 }
 
 static int
@@ -4090,54 +4521,6 @@ mirror_mask_ffs(mirror_mask_t mask)
     return ffs(mask);
 }
 
-static void
-dst_set_init(struct dst_set *set)
-{
-    set->dsts = set->builtin;
-    set->n = 0;
-    set->allocated = ARRAY_SIZE(set->builtin);
-}
-
-static void
-dst_set_add(struct dst_set *set, const struct dst *dst)
-{
-    if (set->n >= set->allocated) {
-        size_t new_allocated;
-        struct dst *new_dsts;
-
-        new_allocated = set->allocated * 2;
-        new_dsts = xmalloc(new_allocated * sizeof *new_dsts);
-        memcpy(new_dsts, set->dsts, set->n * sizeof *new_dsts);
-
-        dst_set_free(set);
-
-        set->dsts = new_dsts;
-        set->allocated = new_allocated;
-    }
-    set->dsts[set->n++] = *dst;
-}
-
-static void
-dst_set_free(struct dst_set *set)
-{
-    if (set->dsts != set->builtin) {
-        free(set->dsts);
-    }
-}
-
-static bool
-dst_is_duplicate(const struct dst_set *set, const struct dst *test)
-{
-    size_t i;
-    for (i = 0; i < set->n; i++) {
-        if (set->dsts[i].vid == test->vid
-            && set->dsts[i].port == test->port) {
-            return true;
-        }
-    }
-    return false;
-}
-
 static bool
 ofbundle_trunks_vlan(const struct ofbundle *bundle, uint16_t vlan)
 {
@@ -4159,12 +4542,12 @@ ofbundle_get_a_port(const struct ofbundle *bundle)
                         struct ofport_dpif, bundle_node);
 }
 
-static void
+static mirror_mask_t
 compose_dsts(struct action_xlate_ctx *ctx, uint16_t vlan,
              const struct ofbundle *in_bundle,
-             const struct ofbundle *out_bundle, struct dst_set *set)
+             const struct ofbundle *out_bundle)
 {
-    struct dst dst;
+    mirror_mask_t dst_mirrors = 0;
 
     if (out_bundle == OFBUNDLE_FLOOD) {
         struct ofbundle *bundle;
@@ -4173,16 +4556,18 @@ compose_dsts(struct action_xlate_ctx *ctx, uint16_t vlan,
             if (bundle != in_bundle
                 && ofbundle_includes_vlan(bundle, vlan)
                 && bundle->floodable
-                && !bundle->mirror_out
-                && set_dst(ctx, &dst, in_bundle, bundle)) {
-                dst_set_add(set, &dst);
+                && !bundle->mirror_out) {
+                output_normal(ctx, bundle, vlan);
+                dst_mirrors |= bundle->dst_mirrors;
             }
         }
         ctx->nf_output_iface = NF_OUT_FLOOD;
-    } else if (out_bundle && set_dst(ctx, &dst, in_bundle, out_bundle)) {
-        dst_set_add(set, &dst);
-        ctx->nf_output_iface = dst.port->odp_port;
+    } else if (out_bundle) {
+        output_normal(ctx, out_bundle, vlan);
+        dst_mirrors = out_bundle->dst_mirrors;
     }
+
+    return dst_mirrors;
 }
 
 static bool
@@ -4233,153 +4618,41 @@ eth_dst_may_rspan(const uint8_t dst[ETH_ADDR_LEN])
 }
 
 static void
-compose_mirror_dsts(struct action_xlate_ctx *ctx,
-                    uint16_t vlan, const struct ofbundle *in_bundle,
-                    struct dst_set *set)
+output_mirrors(struct action_xlate_ctx *ctx,
+               uint16_t vlan, const struct ofbundle *in_bundle,
+               mirror_mask_t dst_mirrors)
 {
     struct ofproto_dpif *ofproto = ctx->ofproto;
     mirror_mask_t mirrors;
-    uint16_t flow_vid;
-    size_t i;
-
-    mirrors = in_bundle->src_mirrors;
-    for (i = 0; i < set->n; i++) {
-        mirrors |= set->dsts[i].port->bundle->dst_mirrors;
-    }
 
+    mirrors = in_bundle->src_mirrors | dst_mirrors;
     if (!mirrors) {
         return;
     }
 
-    flow_vid = vlan_tci_to_vid(ctx->flow.vlan_tci);
     while (mirrors) {
-        struct ofmirror *m = ofproto->mirrors[mirror_mask_ffs(mirrors) - 1];
-        if (vlan_is_mirrored(m, vlan)) {
-            struct dst dst;
-
-            if (m->out) {
-                if (set_dst(ctx, &dst, in_bundle, m->out)
-                    && !dst_is_duplicate(set, &dst)) {
-                    dst_set_add(set, &dst);
-                }
-            } else if (eth_dst_may_rspan(ctx->flow.dl_dst)) {
-                struct ofbundle *bundle;
-
-                HMAP_FOR_EACH (bundle, hmap_node, &ofproto->bundles) {
-                    if (ofbundle_includes_vlan(bundle, m->out_vlan)
-                        && set_dst(ctx, &dst, in_bundle, bundle))
-                    {
-                        /* set_dst() got dst->vid from the input packet's VLAN,
-                         * not from m->out_vlan, so recompute it. */
-                        dst.vid = output_vlan_to_vid(bundle, m->out_vlan);
-
-                        if (dst_is_duplicate(set, &dst)) {
-                            continue;
-                        }
-
-                        if (bundle == in_bundle && dst.vid == flow_vid) {
-                            /* Don't send out input port on same VLAN. */
-                            continue;
-                        }
-                        dst_set_add(set, &dst);
-                    }
-                }
-            }
-        }
-        mirrors &= mirrors - 1;
-    }
-}
+        struct ofmirror *m;
 
-static void
-compose_actions(struct action_xlate_ctx *ctx, uint16_t vlan,
-                const struct ofbundle *in_bundle,
-                const struct ofbundle *out_bundle)
-{
-    uint16_t initial_vid, cur_vid;
-    const struct dst *dst;
-    struct dst_set set;
-
-    dst_set_init(&set);
-    compose_dsts(ctx, vlan, in_bundle, out_bundle, &set);
-    compose_mirror_dsts(ctx, vlan, in_bundle, &set);
-    if (!set.n) {
-        dst_set_free(&set);
-        return;
-    }
+        m = ofproto->mirrors[mirror_mask_ffs(mirrors) - 1];
 
-    /* Output all the packets we can without having to change the VLAN. */
-    commit_odp_actions(ctx);
-    initial_vid = vlan_tci_to_vid(ctx->flow.vlan_tci);
-    for (dst = set.dsts; dst < &set.dsts[set.n]; dst++) {
-        if (dst->vid != initial_vid) {
-            continue;
-        }
-        compose_output_action(ctx, dst->port->odp_port);
-    }
-
-    /* Then output the rest. */
-    cur_vid = initial_vid;
-    for (dst = set.dsts; dst < &set.dsts[set.n]; dst++) {
-        if (dst->vid == initial_vid) {
+        if (!vlan_is_mirrored(m, vlan)) {
+            mirrors &= mirrors - 1;
             continue;
         }
-        if (dst->vid != cur_vid) {
-            ovs_be16 tci;
 
-            tci = htons(dst->vid);
-            tci |= ctx->flow.vlan_tci & htons(VLAN_PCP_MASK);
-            if (tci) {
-                tci |= htons(VLAN_CFI);
-            }
-            commit_vlan_action(ctx, tci);
-
-            cur_vid = dst->vid;
-        }
-        compose_output_action(ctx, dst->port->odp_port);
-    }
-
-    dst_set_free(&set);
-}
-
-/* Returns the effective vlan of a packet, taking into account both the
- * 802.1Q header and implicitly tagged ports.  A value of 0 indicates that
- * the packet is untagged and -1 indicates it has an invalid header and
- * should be dropped. */
-static int
-flow_get_vlan(struct ofproto_dpif *ofproto, const struct flow *flow,
-              struct ofbundle *in_bundle, bool have_packet)
-{
-    int vlan = vlan_tci_to_vid(flow->vlan_tci);
-    if (vlan) {
-        if (in_bundle->vlan_mode == PORT_VLAN_ACCESS) {
-            /* Drop tagged packet on access port */
-            if (have_packet) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-                VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %d tagged "
-                             "packet received on port %s configured with "
-                             "implicit VLAN %"PRIu16,
-                             ofproto->up.name, vlan,
-                             in_bundle->name, in_bundle->vlan);
-            }
-            return -1;
-        } else if (ofbundle_includes_vlan(in_bundle, vlan)) {
-            return vlan;
-        } else {
-            /* Drop packets from a VLAN not member of the trunk */
-            if (have_packet) {
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-                VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %d tagged "
-                             "packet received on port %s not configured for "
-                             "trunking VLAN %d",
-                             ofproto->up.name, vlan, in_bundle->name, vlan);
+        mirrors &= ~m->dup_mirrors;
+        if (m->out) {
+            output_normal(ctx, m->out, vlan);
+        } else if (eth_dst_may_rspan(ctx->flow.dl_dst)
+                   && vlan != m->out_vlan) {
+            struct ofbundle *bundle;
+
+            HMAP_FOR_EACH (bundle, hmap_node, &ofproto->bundles) {
+                if (ofbundle_includes_vlan(bundle, m->out_vlan)
+                    && !bundle->mirror_out) {
+                    output_normal(ctx, bundle, m->out_vlan);
+                }
             }
-            return -1;
-        }
-    } else {
-        if (in_bundle->vlan_mode != PORT_VLAN_TRUNK) {
-            return in_bundle->vlan;
-        } else {
-            return ofbundle_includes_vlan(in_bundle, 0) ? 0 : -1;
         }
     }
 }
@@ -4434,81 +4707,59 @@ update_learning_table(struct ofproto_dpif *ofproto,
     }
 }
 
-/* Determines whether packets in 'flow' within 'br' should be forwarded or
+static struct ofport_dpif *
+lookup_input_bundle(struct ofproto_dpif *ofproto, uint16_t in_port, bool warn)
+{
+    struct ofport_dpif *ofport;
+
+    /* Find the port and bundle for the received packet. */
+    ofport = get_ofp_port(ofproto, in_port);
+    if (ofport && ofport->bundle) {
+        return ofport;
+    }
+
+    /* Odd.  A few possible reasons here:
+     *
+     * - We deleted a port but there are still a few packets queued up
+     *   from it.
+     *
+     * - Someone externally added a port (e.g. "ovs-dpctl add-if") that
+     *   we don't know about.
+     *
+     * - The ofproto client didn't configure the port as part of a bundle.
+     */
+    if (warn) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+        VLOG_WARN_RL(&rl, "bridge %s: received packet on unknown "
+                     "port %"PRIu16, ofproto->up.name, in_port);
+    }
+    return NULL;
+}
+
+/* Determines whether packets in 'flow' within 'ofproto' should be forwarded or
  * dropped.  Returns true if they may be forwarded, false if they should be
  * dropped.
  *
- * If 'have_packet' is true, it indicates that the caller is processing a
- * received packet.  If 'have_packet' is false, then the caller is just
- * revalidating an existing flow because configuration has changed.  Either
- * way, 'have_packet' only affects logging (there is no point in logging errors
- * during revalidation).
- *
- * Sets '*in_portp' to the input port.  This will be a null pointer if
- * flow->in_port does not designate a known input port (in which case
- * is_admissible() returns false).
+ * 'in_port' must be the ofport_dpif that corresponds to flow->in_port.
+ * 'in_port' must be part of a bundle (e.g. in_port->bundle must be nonnull).
  *
- * When returning true, sets '*vlanp' to the effective VLAN of the input
- * packet, as returned by flow_get_vlan().
+ * 'vlan' must be the VLAN that corresponds to flow->vlan_tci on 'in_port', as
+ * returned by input_vid_to_vlan().  It must be a valid VLAN for 'in_port', as
+ * checked by input_vid_is_valid().
  *
  * May also add tags to '*tags', although the current implementation only does
  * so in one special case.
  */
 static bool
 is_admissible(struct ofproto_dpif *ofproto, const struct flow *flow,
-              bool have_packet,
-              tag_type *tags, int *vlanp, struct ofbundle **in_bundlep)
+              struct ofport_dpif *in_port, uint16_t vlan, tag_type *tags)
 {
-    struct ofport_dpif *in_port;
-    struct ofbundle *in_bundle;
-    int vlan;
-
-    /* Find the port and bundle for the received packet. */
-    in_port = get_ofp_port(ofproto, flow->in_port);
-    *in_bundlep = in_bundle = in_port ? in_port->bundle : NULL;
-    if (!in_port || !in_bundle) {
-        /* No interface?  Something fishy... */
-        if (have_packet) {
-            /* Odd.  A few possible reasons here:
-             *
-             * - We deleted a port but there are still a few packets queued up
-             *   from it.
-             *
-             * - Someone externally added a port (e.g. "ovs-dpctl add-if") that
-             *   we don't know about.
-             *
-             * - Packet arrived on the local port but the local port is not
-             *   part of a bundle.
-             */
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-
-            VLOG_WARN_RL(&rl, "bridge %s: received packet on unknown "
-                         "port %"PRIu16,
-                         ofproto->up.name, flow->in_port);
-        }
-        *vlanp = -1;
-        return false;
-    }
-    *vlanp = vlan = flow_get_vlan(ofproto, flow, in_bundle, have_packet);
-    if (vlan < 0) {
-        return false;
-    }
+    struct ofbundle *in_bundle = in_port->bundle;
 
     /* 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;
-    }
-
-    /* Drop frames on bundles reserved for mirroring. */
-    if (in_bundle->mirror_out) {
-        if (have_packet) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl, "bridge %s: dropping packet received on port "
-                         "%s, which is reserved exclusively for mirroring",
-                         ofproto->up.name, in_bundle->name);
-        }
+    if (eth_addr_is_reserved(flow->dl_dst) && !ofproto->up.forward_bpdu) {
         return false;
     }
 
@@ -4540,18 +4791,60 @@ is_admissible(struct ofproto_dpif *ofproto, const struct flow *flow,
 static void
 xlate_normal(struct action_xlate_ctx *ctx)
 {
+    mirror_mask_t dst_mirrors = 0;
+    struct ofport_dpif *in_port;
     struct ofbundle *in_bundle;
     struct ofbundle *out_bundle;
     struct mac_entry *mac;
-    int vlan;
+    uint16_t vlan;
+    uint16_t vid;
 
     ctx->has_normal = true;
 
-    /* Check whether we should drop packets in this flow. */
-    if (!is_admissible(ctx->ofproto, &ctx->flow, ctx->packet != NULL,
-                       &ctx->tags, &vlan, &in_bundle)) {
-        out_bundle = NULL;
-        goto done;
+    /* Obtain in_port from ctx->flow.in_port.
+     *
+     * lookup_input_bundle() also ensures that in_port belongs to a bundle. */
+    in_port = lookup_input_bundle(ctx->ofproto, ctx->flow.in_port,
+                                  ctx->packet != NULL);
+    if (!in_port) {
+        return;
+    }
+    in_bundle = in_port->bundle;
+
+    /* Drop malformed frames. */
+    if (ctx->flow.dl_type == htons(ETH_TYPE_VLAN) &&
+        !(ctx->flow.vlan_tci & htons(VLAN_CFI))) {
+        if (ctx->packet != NULL) {
+            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",
+                         ctx->ofproto->up.name, in_bundle->name);
+        }
+        return;
+    }
+
+    /* Drop frames on bundles reserved for mirroring. */
+    if (in_bundle->mirror_out) {
+        if (ctx->packet != NULL) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "bridge %s: dropping packet received on port "
+                         "%s, which is reserved exclusively for mirroring",
+                         ctx->ofproto->up.name, in_bundle->name);
+        }
+        return;
+    }
+
+    /* Check VLAN. */
+    vid = vlan_tci_to_vid(ctx->flow.vlan_tci);
+    if (!input_vid_is_valid(vid, in_bundle, ctx->packet != NULL)) {
+        return;
+    }
+    vlan = input_vid_to_vlan(in_bundle, vid);
+
+    /* Check other admissibility requirements. */
+    if (!is_admissible(ctx->ofproto, &ctx->flow, in_port, vlan, &ctx->tags)) {
+        output_mirrors(ctx, vlan, in_bundle, 0);
+        return;
     }
 
     /* Learn source MAC. */
@@ -4577,14 +4870,10 @@ xlate_normal(struct action_xlate_ctx *ctx)
     }
 
     /* Don't send packets out their input bundles. */
-    if (in_bundle == out_bundle) {
-        out_bundle = NULL;
-    }
-
-done:
-    if (in_bundle) {
-        compose_actions(ctx, vlan, in_bundle, out_bundle);
+    if (in_bundle != out_bundle) {
+        dst_mirrors = compose_dsts(ctx, vlan, in_bundle, out_bundle);
     }
+    output_mirrors(ctx, vlan, in_bundle, dst_mirrors);
 }
 \f
 /* Optimized flow revalidation.
@@ -4702,21 +4991,17 @@ rule_invalidate(const struct rule_dpif *rule)
 }
 \f
 static bool
-get_drop_frags(struct ofproto *ofproto_)
+set_frag_handling(struct ofproto *ofproto_,
+                  enum ofp_config_flags frag_handling)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-    bool drop_frags;
-
-    dpif_get_drop_frags(ofproto->dpif, &drop_frags);
-    return drop_frags;
-}
 
-static void
-set_drop_frags(struct ofproto *ofproto_, bool drop_frags)
-{
-    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-
-    dpif_set_drop_frags(ofproto->dpif, drop_frags);
+    if (frag_handling != OFPC_FRAG_REASM) {
+        ofproto->need_revalidate = true;
+        return true;
+    } else {
+        return false;
+    }
 }
 
 static int
@@ -4727,6 +5012,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) {
@@ -4765,6 +5054,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)
@@ -4862,7 +5167,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;
@@ -4880,14 +5185,15 @@ 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;
 
         /* Convert string to datapath key. */
         ofpbuf_init(&odp_key, 0);
-        error = odp_flow_key_from_string(arg1, &odp_key);
+        error = odp_flow_key_from_string(arg1, NULL, &odp_key);
         if (error) {
             unixctl_command_reply(conn, 501, "Bad flow syntax");
             goto exit;
@@ -4905,18 +5211,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;
         }
@@ -4931,7 +5239,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;
@@ -5014,6 +5322,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);
@@ -5056,8 +5366,7 @@ const struct ofproto_class ofproto_dpif_class = {
     rule_get_stats,
     rule_execute,
     rule_modify_actions,
-    get_drop_frags,
-    set_drop_frags,
+    set_frag_handling,
     packet_out,
     set_netflow,
     get_netflow_ids,
@@ -5065,6 +5374,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,