ofproto: Optimise OpenFlow flow expiry
[sliver-openvswitch.git] / ofproto / ofproto-dpif.c
index 95fd54e..1d08baf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
 #include "mac-learning.h"
 #include "meta-flow.h"
 #include "multipath.h"
+#include "netdev-vport.h"
 #include "netdev.h"
 #include "netlink.h"
 #include "nx-match.h"
@@ -295,6 +296,8 @@ static void xlate_actions(struct action_xlate_ctx *,
 static void xlate_actions_for_side_effects(struct action_xlate_ctx *,
                                            const struct ofpact *ofpacts,
                                            size_t ofpacts_len);
+static void xlate_table_action(struct action_xlate_ctx *, uint16_t in_port,
+                               uint8_t table_id, bool may_packet_in);
 
 static size_t put_userspace_action(const struct ofproto_dpif *,
                                    struct ofpbuf *odp_actions,
@@ -565,7 +568,7 @@ static uint16_t odp_port_to_ofp_port(const struct ofproto_dpif *,
 static struct ofport_dpif *
 ofport_dpif_cast(const struct ofport *ofport)
 {
-    assert(ofport->ofproto->ofproto_class == &ofproto_dpif_class);
+    ovs_assert(ofport->ofproto->ofproto_class == &ofproto_dpif_class);
     return ofport ? CONTAINER_OF(ofport, struct ofport_dpif, up) : NULL;
 }
 
@@ -619,6 +622,10 @@ struct dpif_backer {
     struct dpif *dpif;
     struct timer next_expiration;
     struct hmap odp_to_ofport_map; /* ODP port to ofport mapping. */
+
+    /* Facet revalidation flags applying to facets which use this backer. */
+    enum revalidate_reason need_revalidate; /* Revalidate every facet. */
+    struct tag_set revalidate_set; /* Revalidate only matching facets. */
 };
 
 /* All existing ofproto_backer instances, indexed by ofproto->up.type. */
@@ -655,8 +662,6 @@ struct ofproto_dpif {
 
     /* Revalidation. */
     struct table_dpif tables[N_TABLES];
-    enum revalidate_reason need_revalidate;
-    struct tag_set revalidate_set;
 
     /* Support for debugging async flow mods. */
     struct list completions;
@@ -674,7 +679,8 @@ struct ofproto_dpif {
     struct hmap vlandev_map;     /* vlandev -> (realdev,vid). */
 
     /* Ports. */
-    struct sset ports;             /* Set of port names. */
+    struct sset ports;             /* Set of standard port names. */
+    struct sset ghost_ports;       /* Ports with no datapath port. */
     struct sset port_poll_set;     /* Queued names for port_poll() reply. */
     int port_poll_errno;           /* Last errno for port_poll() reply. */
 };
@@ -691,7 +697,7 @@ static void ofproto_dpif_unixctl_init(void);
 static struct ofproto_dpif *
 ofproto_dpif_cast(const struct ofproto *ofproto)
 {
-    assert(ofproto->ofproto_class == &ofproto_dpif_class);
+    ovs_assert(ofproto->ofproto_class == &ofproto_dpif_class);
     return CONTAINER_OF(ofproto, struct ofproto_dpif, up);
 }
 
@@ -824,6 +830,41 @@ type_run(const char *type)
 
     dpif_run(backer->dpif);
 
+    if (backer->need_revalidate
+        || !tag_set_is_empty(&backer->revalidate_set)) {
+        struct tag_set revalidate_set = backer->revalidate_set;
+        bool need_revalidate = backer->need_revalidate;
+        struct ofproto_dpif *ofproto;
+
+        switch (backer->need_revalidate) {
+        case REV_RECONFIGURE:   COVERAGE_INC(rev_reconfigure);   break;
+        case REV_STP:           COVERAGE_INC(rev_stp);           break;
+        case REV_PORT_TOGGLED:  COVERAGE_INC(rev_port_toggled);  break;
+        case REV_FLOW_TABLE:    COVERAGE_INC(rev_flow_table);    break;
+        case REV_INCONSISTENCY: COVERAGE_INC(rev_inconsistency); break;
+        }
+
+        HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
+            struct facet *facet;
+
+            if (ofproto->backer != backer) {
+                continue;
+            }
+
+            /* Clear the revalidation flags. */
+            tag_set_init(&backer->revalidate_set);
+            backer->need_revalidate = 0;
+
+            HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
+                if (need_revalidate
+                    || tag_set_intersects(&revalidate_set, facet->tags)) {
+                    facet_revalidate(facet);
+                }
+            }
+        }
+
+    }
+
     if (timer_expired(&backer->next_expiration)) {
         int delay = expire(backer);
         timer_set_duration(&backer->next_expiration, delay);
@@ -836,7 +877,7 @@ type_run(const char *type)
 
         /* Don't report on the datapath's device. */
         if (!strcmp(devname, dpif_base_name(backer->dpif))) {
-            continue;
+            goto next;
         }
 
         ofproto = lookup_ofproto_dpif_by_port_name(devname);
@@ -850,13 +891,14 @@ type_run(const char *type)
                 sset_add(&ofproto->port_poll_set, devname);
                 ofproto->port_poll_errno = 0;
             }
-            dpif_port_destroy(&port);
         } else if (!ofproto) {
             /* The port was added, but we don't know with which
              * ofproto we should associate it.  Delete it. */
             dpif_port_del(backer->dpif, port.port_no);
         }
+        dpif_port_destroy(&port);
 
+    next:
         free(devname);
     }
 
@@ -949,7 +991,7 @@ close_dpif_backer(struct dpif_backer *backer)
 {
     struct shash_node *node;
 
-    assert(backer->refcount > 0);
+    ovs_assert(backer->refcount > 0);
 
     if (--backer->refcount) {
         return;
@@ -1021,6 +1063,7 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
     if (error) {
         VLOG_ERR("failed to open datapath of type %s: %s", type,
                  strerror(error));
+        free(backer);
         return error;
     }
 
@@ -1028,6 +1071,8 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
     backer->refcount = 1;
     hmap_init(&backer->odp_to_ofport_map);
     timer_set_duration(&backer->next_expiration, 1000);
+    backer->need_revalidate = 0;
+    tag_set_init(&backer->revalidate_set);
     *backerp = backer;
 
     dpif_flow_flush(backer->dpif);
@@ -1105,8 +1150,6 @@ construct(struct ofproto *ofproto_)
         table->other_table = NULL;
         table->basis = random_uint32();
     }
-    ofproto->need_revalidate = 0;
-    tag_set_init(&ofproto->revalidate_set);
 
     list_init(&ofproto->completions);
 
@@ -1119,11 +1162,12 @@ construct(struct ofproto *ofproto_)
     hmap_init(&ofproto->realdev_vid_map);
 
     sset_init(&ofproto->ports);
+    sset_init(&ofproto->ghost_ports);
     sset_init(&ofproto->port_poll_set);
     ofproto->port_poll_errno = 0;
 
     SHASH_FOR_EACH_SAFE (node, next, &init_ofp_ports) {
-        const struct iface_hint *iface_hint = node->data;
+        struct iface_hint *iface_hint = node->data;
 
         if (!strcmp(iface_hint->br_name, ofproto->up.name)) {
             /* Check if the datapath already has this port. */
@@ -1133,6 +1177,7 @@ construct(struct ofproto *ofproto_)
 
             free(iface_hint->br_name);
             free(iface_hint->br_type);
+            free(iface_hint);
             shash_delete(&init_ofp_ports, node);
         }
     }
@@ -1179,7 +1224,7 @@ add_internal_flow(struct ofproto_dpif *ofproto, int id,
     }
 
     *rulep = rule_dpif_lookup__(ofproto, &fm.match.flow, TBL_INTERNAL);
-    assert(*rulep != NULL);
+    ovs_assert(*rulep != NULL);
 
     return 0;
 }
@@ -1262,6 +1307,7 @@ destruct(struct ofproto *ofproto_)
     hmap_destroy(&ofproto->realdev_vid_map);
 
     sset_destroy(&ofproto->ports);
+    sset_destroy(&ofproto->ghost_ports);
     sset_destroy(&ofproto->port_poll_set);
 
     close_dpif_backer(ofproto->backer);
@@ -1314,44 +1360,19 @@ run(struct ofproto *ofproto_)
     }
 
     stp_run(ofproto);
-    mac_learning_run(ofproto->ml, &ofproto->revalidate_set);
-
-    /* Now revalidate if there's anything to do. */
-    if (ofproto->need_revalidate
-        || !tag_set_is_empty(&ofproto->revalidate_set)) {
-        struct tag_set revalidate_set = ofproto->revalidate_set;
-        bool revalidate_all = ofproto->need_revalidate;
-        struct facet *facet;
-
-        switch (ofproto->need_revalidate) {
-        case REV_RECONFIGURE:   COVERAGE_INC(rev_reconfigure);   break;
-        case REV_STP:           COVERAGE_INC(rev_stp);           break;
-        case REV_PORT_TOGGLED:  COVERAGE_INC(rev_port_toggled);  break;
-        case REV_FLOW_TABLE:    COVERAGE_INC(rev_flow_table);    break;
-        case REV_INCONSISTENCY: COVERAGE_INC(rev_inconsistency); break;
-        }
-
-        /* Clear the revalidation flags. */
-        tag_set_init(&ofproto->revalidate_set);
-        ofproto->need_revalidate = 0;
-
-        HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
-            if (revalidate_all
-                || tag_set_intersects(&revalidate_set, facet->tags)) {
-                facet_revalidate(facet);
-            }
-        }
-    }
+    mac_learning_run(ofproto->ml, &ofproto->backer->revalidate_set);
 
     /* Check the consistency of a random facet, to aid debugging. */
-    if (!hmap_is_empty(&ofproto->facets) && !ofproto->need_revalidate) {
+    if (!hmap_is_empty(&ofproto->facets)
+        && !ofproto->backer->need_revalidate) {
         struct facet *facet;
 
         facet = CONTAINER_OF(hmap_random_node(&ofproto->facets),
                              struct facet, hmap_node);
-        if (!tag_set_intersects(&ofproto->revalidate_set, facet->tags)) {
+        if (!tag_set_intersects(&ofproto->backer->revalidate_set,
+                                facet->tags)) {
             if (!facet_check_consistency(facet)) {
-                ofproto->need_revalidate = REV_INCONSISTENCY;
+                ofproto->backer->need_revalidate = REV_INCONSISTENCY;
             }
         }
     }
@@ -1393,7 +1414,7 @@ wait(struct ofproto *ofproto_)
     if (ofproto->sflow) {
         dpif_sflow_wait(ofproto->sflow);
     }
-    if (!tag_set_is_empty(&ofproto->revalidate_set)) {
+    if (!tag_set_is_empty(&ofproto->backer->revalidate_set)) {
         poll_immediate_wake();
     }
     HMAP_FOR_EACH (ofport, up.hmap_node, &ofproto->up.ports) {
@@ -1407,7 +1428,7 @@ wait(struct ofproto *ofproto_)
     }
     mac_learning_wait(ofproto->ml);
     stp_wait(ofproto);
-    if (ofproto->need_revalidate) {
+    if (ofproto->backer->need_revalidate) {
         /* Shouldn't happen, but if it does just go around again. */
         VLOG_DBG_RL(&rl, "need revalidate in ofproto_wait_cb()");
         poll_immediate_wake();
@@ -1508,7 +1529,7 @@ port_construct(struct ofport *port_)
     struct dpif_port dpif_port;
     int error;
 
-    ofproto->need_revalidate = REV_RECONFIGURE;
+    ofproto->backer->need_revalidate = REV_RECONFIGURE;
     port->bundle = NULL;
     port->cfm = NULL;
     port->tag = tag_create_random();
@@ -1520,6 +1541,12 @@ port_construct(struct ofport *port_)
     port->vlandev_vid = 0;
     port->carrier_seq = netdev_get_carrier_resets(port->up.netdev);
 
+    if (netdev_vport_is_patch(port->up.netdev)) {
+        /* XXX By bailing out here, we don't do required sFlow work. */
+        port->odp_port = OVSP_NONE;
+        return 0;
+    }
+
     error = dpif_port_query_by_name(ofproto->backer->dpif,
                                     netdev_get_name(port->up.netdev),
                                     &dpif_port);
@@ -1562,9 +1589,13 @@ port_destruct(struct ofport *port_)
         dpif_port_del(ofproto->backer->dpif, port->odp_port);
     }
 
+    if (port->odp_port != OVSP_NONE) {
+        hmap_remove(&ofproto->backer->odp_to_ofport_map, &port->odp_port_node);
+    }
+
     sset_find_and_delete(&ofproto->ports, devname);
-    hmap_remove(&ofproto->backer->odp_to_ofport_map, &port->odp_port_node);
-    ofproto->need_revalidate = REV_RECONFIGURE;
+    sset_find_and_delete(&ofproto->ghost_ports, devname);
+    ofproto->backer->need_revalidate = REV_RECONFIGURE;
     bundle_remove(port_);
     set_cfm(port_, NULL);
     if (ofproto->sflow) {
@@ -1595,7 +1626,7 @@ port_reconfigured(struct ofport *port_, enum ofputil_port_config old_config)
     if (changed & (OFPUTIL_PC_NO_RECV | OFPUTIL_PC_NO_RECV_STP |
                    OFPUTIL_PC_NO_FWD | OFPUTIL_PC_NO_FLOOD |
                    OFPUTIL_PC_NO_PACKET_IN)) {
-        ofproto->need_revalidate = REV_RECONFIGURE;
+        ofproto->backer->need_revalidate = REV_RECONFIGURE;
 
         if (changed & OFPUTIL_PC_NO_FLOOD && port->bundle) {
             bundle_update(port->bundle);
@@ -1618,13 +1649,13 @@ set_sflow(struct ofproto *ofproto_,
             HMAP_FOR_EACH (ofport, up.hmap_node, &ofproto->up.ports) {
                 dpif_sflow_add_port(ds, &ofport->up, ofport->odp_port);
             }
-            ofproto->need_revalidate = REV_RECONFIGURE;
+            ofproto->backer->need_revalidate = REV_RECONFIGURE;
         }
         dpif_sflow_set_options(ds, sflow_options);
     } else {
         if (ds) {
             dpif_sflow_destroy(ds);
-            ofproto->need_revalidate = REV_RECONFIGURE;
+            ofproto->backer->need_revalidate = REV_RECONFIGURE;
             ofproto->sflow = NULL;
         }
     }
@@ -1644,7 +1675,7 @@ set_cfm(struct ofport *ofport_, const struct cfm_settings *s)
             struct ofproto_dpif *ofproto;
 
             ofproto = ofproto_dpif_cast(ofport->up.ofproto);
-            ofproto->need_revalidate = REV_RECONFIGURE;
+            ofproto->backer->need_revalidate = REV_RECONFIGURE;
             ofport->cfm = cfm_create(netdev_get_name(ofport->up.netdev));
         }
 
@@ -1732,7 +1763,7 @@ set_stp(struct ofproto *ofproto_, const struct ofproto_stp_settings *s)
 
     /* Only revalidate flows if the configuration changed. */
     if (!s != !ofproto->stp) {
-        ofproto->need_revalidate = REV_RECONFIGURE;
+        ofproto->backer->need_revalidate = REV_RECONFIGURE;
     }
 
     if (s) {
@@ -1800,12 +1831,13 @@ update_stp_port_state(struct ofport_dpif *ofport)
         if (stp_learn_in_state(ofport->stp_state)
                 != stp_learn_in_state(state)) {
             /* xxx Learning action flows should also be flushed. */
-            mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+            mac_learning_flush(ofproto->ml,
+                               &ofproto->backer->revalidate_set);
         }
         fwd_change = stp_forward_in_state(ofport->stp_state)
                         != stp_forward_in_state(state);
 
-        ofproto->need_revalidate = REV_STP;
+        ofproto->backer->need_revalidate = REV_STP;
         ofport->stp_state = state;
         ofport->stp_state_entered = time_msec();
 
@@ -1905,7 +1937,7 @@ stp_run(struct ofproto_dpif *ofproto)
         }
 
         if (stp_check_and_reset_fdb_flush(ofproto->stp)) {
-            mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+            mac_learning_flush(ofproto->ml, &ofproto->backer->revalidate_set);
         }
     }
 }
@@ -2003,12 +2035,12 @@ set_queues(struct ofport *ofport_,
             pdscp = xmalloc(sizeof *pdscp);
             pdscp->priority = priority;
             pdscp->dscp = dscp;
-            ofproto->need_revalidate = REV_RECONFIGURE;
+            ofproto->backer->need_revalidate = REV_RECONFIGURE;
         }
 
         if (pdscp->dscp != dscp) {
             pdscp->dscp = dscp;
-            ofproto->need_revalidate = REV_RECONFIGURE;
+            ofproto->backer->need_revalidate = REV_RECONFIGURE;
         }
 
         hmap_insert(&new, &pdscp->hmap_node, hash_int(pdscp->priority, 0));
@@ -2016,7 +2048,7 @@ set_queues(struct ofport *ofport_,
 
     if (!hmap_is_empty(&ofport->priorities)) {
         ofport_clear_priorities(ofport);
-        ofproto->need_revalidate = REV_RECONFIGURE;
+        ofproto->backer->need_revalidate = REV_RECONFIGURE;
     }
 
     hmap_swap(&new, &ofport->priorities);
@@ -2043,7 +2075,7 @@ bundle_flush_macs(struct ofbundle *bundle, bool all_ofprotos)
     struct mac_learning *ml = ofproto->ml;
     struct mac_entry *mac, *next_mac;
 
-    ofproto->need_revalidate = REV_RECONFIGURE;
+    ofproto->backer->need_revalidate = REV_RECONFIGURE;
     LIST_FOR_EACH_SAFE (mac, next_mac, lru_node, &ml->lrus) {
         if (mac->port.p == bundle) {
             if (all_ofprotos) {
@@ -2056,7 +2088,6 @@ bundle_flush_macs(struct ofbundle *bundle, bool all_ofprotos)
                         e = mac_learning_lookup(o->ml, mac->mac, mac->vlan,
                                                 NULL);
                         if (e) {
-                            tag_set_add(&o->revalidate_set, e->tag);
                             mac_learning_expire(o->ml, e);
                         }
                     }
@@ -2120,7 +2151,7 @@ bundle_del_port(struct ofport_dpif *port)
 {
     struct ofbundle *bundle = port->bundle;
 
-    bundle->ofproto->need_revalidate = REV_RECONFIGURE;
+    bundle->ofproto->backer->need_revalidate = REV_RECONFIGURE;
 
     list_remove(&port->bundle_node);
     port->bundle = NULL;
@@ -2148,7 +2179,7 @@ bundle_add_port(struct ofbundle *bundle, uint32_t ofp_port,
     }
 
     if (port->bundle != bundle) {
-        bundle->ofproto->need_revalidate = REV_RECONFIGURE;
+        bundle->ofproto->backer->need_revalidate = REV_RECONFIGURE;
         if (port->bundle) {
             bundle_del_port(port);
         }
@@ -2161,7 +2192,7 @@ bundle_add_port(struct ofbundle *bundle, uint32_t ofp_port,
         }
     }
     if (lacp) {
-        port->bundle->ofproto->need_revalidate = REV_RECONFIGURE;
+        bundle->ofproto->backer->need_revalidate = REV_RECONFIGURE;
         lacp_slave_register(bundle->lacp, port, lacp);
     }
 
@@ -2189,7 +2220,7 @@ bundle_destroy(struct ofbundle *bundle)
                 mirror_destroy(m);
             } else if (hmapx_find_and_delete(&m->srcs, bundle)
                        || hmapx_find_and_delete(&m->dsts, bundle)) {
-                ofproto->need_revalidate = REV_RECONFIGURE;
+                ofproto->backer->need_revalidate = REV_RECONFIGURE;
             }
         }
     }
@@ -2225,8 +2256,8 @@ bundle_set(struct ofproto *ofproto_, void *aux,
         return 0;
     }
 
-    assert(s->n_slaves == 1 || s->bond != NULL);
-    assert((s->lacp != NULL) == (s->lacp_slaves != NULL));
+    ovs_assert(s->n_slaves == 1 || s->bond != NULL);
+    ovs_assert((s->lacp != NULL) == (s->lacp_slaves != NULL));
 
     bundle = bundle_lookup(ofproto, aux);
     if (!bundle) {
@@ -2261,7 +2292,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
     /* LACP. */
     if (s->lacp) {
         if (!bundle->lacp) {
-            ofproto->need_revalidate = REV_RECONFIGURE;
+            ofproto->backer->need_revalidate = REV_RECONFIGURE;
             bundle->lacp = lacp_create();
         }
         lacp_configure(bundle->lacp, s->lacp);
@@ -2293,7 +2324,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
         found: ;
         }
     }
-    assert(list_size(&bundle->ports) <= s->n_slaves);
+    ovs_assert(list_size(&bundle->ports) <= s->n_slaves);
 
     if (list_is_empty(&bundle->ports)) {
         bundle_destroy(bundle);
@@ -2367,11 +2398,11 @@ bundle_set(struct ofproto *ofproto_, void *aux,
         bundle->ofproto->has_bonded_bundles = true;
         if (bundle->bond) {
             if (bond_reconfigure(bundle->bond, s->bond)) {
-                ofproto->need_revalidate = REV_RECONFIGURE;
+                ofproto->backer->need_revalidate = REV_RECONFIGURE;
             }
         } else {
             bundle->bond = bond_create(s->bond);
-            ofproto->need_revalidate = REV_RECONFIGURE;
+            ofproto->backer->need_revalidate = REV_RECONFIGURE;
         }
 
         LIST_FOR_EACH (port, bundle_node, &bundle->ports) {
@@ -2491,7 +2522,7 @@ bundle_run(struct ofbundle *bundle)
             bond_slave_set_may_enable(bundle->bond, port, port->may_enable);
         }
 
-        bond_run(bundle->bond, &bundle->ofproto->revalidate_set,
+        bond_run(bundle->bond, &bundle->ofproto->backer->revalidate_set,
                  lacp_status(bundle->lacp));
         if (bond_should_send_learning_packets(bundle->bond)) {
             bundle_send_learning_packets(bundle);
@@ -2676,9 +2707,10 @@ mirror_set(struct ofproto *ofproto_, void *aux,
         }
     }
 
-    ofproto->need_revalidate = REV_RECONFIGURE;
+    ofproto->backer->need_revalidate = REV_RECONFIGURE;
     ofproto->has_mirrors = true;
-    mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+    mac_learning_flush(ofproto->ml,
+                       &ofproto->backer->revalidate_set);
     mirror_update_dups(ofproto);
 
     return 0;
@@ -2697,8 +2729,8 @@ mirror_destroy(struct ofmirror *mirror)
     }
 
     ofproto = mirror->ofproto;
-    ofproto->need_revalidate = REV_RECONFIGURE;
-    mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+    ofproto->backer->need_revalidate = REV_RECONFIGURE;
+    mac_learning_flush(ofproto->ml, &ofproto->backer->revalidate_set);
 
     mirror_bit = MIRROR_MASK_C(1) << mirror->idx;
     HMAP_FOR_EACH (bundle, hmap_node, &ofproto->bundles) {
@@ -2749,7 +2781,7 @@ set_flood_vlans(struct ofproto *ofproto_, unsigned long *flood_vlans)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     if (mac_learning_set_flood_vlans(ofproto->ml, flood_vlans)) {
-        mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+        mac_learning_flush(ofproto->ml, &ofproto->backer->revalidate_set);
     }
     return 0;
 }
@@ -2766,7 +2798,7 @@ static void
 forward_bpdu_changed(struct ofproto *ofproto_)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-    ofproto->need_revalidate = REV_RECONFIGURE;
+    ofproto->backer->need_revalidate = REV_RECONFIGURE;
 }
 
 static void
@@ -2790,7 +2822,8 @@ get_ofp_port(const struct ofproto_dpif *ofproto, uint16_t ofp_port)
 static struct ofport_dpif *
 get_odp_port(const struct ofproto_dpif *ofproto, uint32_t odp_port)
 {
-    return get_ofp_port(ofproto, odp_port_to_ofp_port(ofproto, odp_port));
+    struct ofport_dpif *port = odp_port_to_ofport(ofproto->backer, odp_port);
+    return port && &ofproto->up == port->up.ofproto ? port : NULL;
 }
 
 static void
@@ -2803,6 +2836,28 @@ ofproto_port_from_dpif_port(struct ofproto_dpif *ofproto,
     ofproto_port->ofp_port = odp_port_to_ofp_port(ofproto, dpif_port->port_no);
 }
 
+static struct ofport_dpif *
+ofport_get_peer(const struct ofport_dpif *ofport_dpif)
+{
+    const struct ofproto_dpif *ofproto;
+    const char *peer;
+
+    peer = netdev_vport_patch_peer(ofport_dpif->up.netdev);
+    if (!peer) {
+        return NULL;
+    }
+
+    HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
+        struct ofport *ofport;
+
+        ofport = shash_find_data(&ofproto->up.port_by_name, peer);
+        if (ofport && ofport->ofproto->ofproto_class == &ofproto_dpif_class) {
+            return ofport_dpif_cast(ofport);
+        }
+    }
+    return NULL;
+}
+
 static void
 port_run_fast(struct ofport_dpif *ofport)
 {
@@ -2848,7 +2903,7 @@ port_run(struct ofport_dpif *ofport)
         struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
 
         if (ofproto->has_bundle_action) {
-            ofproto->need_revalidate = REV_PORT_TOGGLED;
+            ofproto->backer->need_revalidate = REV_PORT_TOGGLED;
         }
     }
 
@@ -2871,6 +2926,24 @@ port_query_by_name(const struct ofproto *ofproto_, const char *devname,
     struct dpif_port dpif_port;
     int error;
 
+    if (sset_contains(&ofproto->ghost_ports, devname)) {
+        const char *type = netdev_get_type_from_name(devname);
+
+        /* We may be called before ofproto->up.port_by_name is populated with
+         * the appropriate ofport.  For this reason, we must get the name and
+         * type from the netdev layer directly. */
+        if (type) {
+            const struct ofport *ofport;
+
+            ofport = shash_find_data(&ofproto->up.port_by_name, devname);
+            ofproto_port->ofp_port = ofport ? ofport->ofp_port : OFPP_NONE;
+            ofproto_port->name = xstrdup(devname);
+            ofproto_port->type = xstrdup(type);
+            return 0;
+        }
+        return ENODEV;
+    }
+
     if (!sset_contains(&ofproto->ports, devname)) {
         return ENODEV;
     }
@@ -2889,6 +2962,11 @@ port_add(struct ofproto *ofproto_, struct netdev *netdev)
     uint32_t odp_port = UINT32_MAX;
     int error;
 
+    if (netdev_vport_is_patch(netdev)) {
+        sset_add(&ofproto->ghost_ports, netdev_get_name(netdev));
+        return 0;
+    }
+
     error = dpif_port_add(ofproto->backer->dpif, netdev, &odp_port);
     if (!error) {
         sset_add(&ofproto->ports, netdev_get_name(netdev));
@@ -2927,7 +3005,7 @@ port_get_stats(const struct ofport *ofport_, struct netdev_stats *stats)
 
     error = netdev_get_stats(ofport->up.netdev, stats);
 
-    if (!error && ofport->odp_port == OVSP_LOCAL) {
+    if (!error && ofport_->ofp_port == OFPP_LOCAL) {
         struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
 
         /* ofproto->stats.tx_packets represents packets that we created
@@ -2979,16 +3057,13 @@ ofproto_update_local_port_stats(const struct ofproto *ofproto_,
 struct port_dump_state {
     uint32_t bucket;
     uint32_t offset;
+    bool ghost;
 };
 
 static int
 port_dump_start(const struct ofproto *ofproto_ OVS_UNUSED, void **statep)
 {
-    struct port_dump_state *state;
-
-    *statep = state = xmalloc(sizeof *state);
-    state->bucket = 0;
-    state->offset = 0;
+    *statep = xzalloc(sizeof(struct port_dump_state));
     return 0;
 }
 
@@ -2998,10 +3073,11 @@ port_dump_next(const struct ofproto *ofproto_ OVS_UNUSED, void *state_,
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     struct port_dump_state *state = state_;
+    const struct sset *sset;
     struct sset_node *node;
 
-    while ((node = sset_at_position(&ofproto->ports, &state->bucket,
-                               &state->offset))) {
+    sset = state->ghost ? &ofproto->ghost_ports : &ofproto->ports;
+    while ((node = sset_at_position(sset, &state->bucket, &state->offset))) {
         int error;
 
         error = port_query_by_name(ofproto_, node->name, port);
@@ -3010,6 +3086,13 @@ port_dump_next(const struct ofproto *ofproto_ OVS_UNUSED, void *state_,
         }
     }
 
+    if (!state->ghost) {
+        state->ghost = true;
+        state->bucket = 0;
+        state->offset = 0;
+        return port_dump_next(ofproto_, state_, port);
+    }
+
     return EOF;
 }
 
@@ -3391,32 +3474,68 @@ handle_flow_miss(struct flow_miss *miss, struct flow_miss_op *ops,
     handle_flow_miss_with_facet(miss, facet, now, ops, n_ops);
 }
 
-/* This function does post-processing on data returned from
- * odp_flow_key_to_flow() to help make VLAN splinters transparent to the
- * rest of the upcall processing logic.  In particular, if the extracted
- * in_port is a VLAN splinter port, it replaces flow->in_port by the "real"
- * port, sets flow->vlan_tci correctly for the VLAN of the VLAN splinter
- * port, and pushes a VLAN header onto 'packet' (if it is nonnull). The
- * caller must have called odp_flow_key_to_flow() and supply 'fitness' and
- * 'flow' from its output.  The 'flow' argument must have had the "in_port"
- * member converted to the OpenFlow number.
+/* Given a datpath, packet, and flow metadata ('backer', 'packet', and 'key'
+ * respectively), populates 'flow' with the result of odp_flow_key_to_flow().
+ * Optionally, if nonnull, populates 'fitnessp' with the fitness of 'flow' as
+ * returned by odp_flow_key_to_flow().  Also, optionally populates 'ofproto'
+ * with the ofproto_dpif, and 'odp_in_port' with the datapath in_port, that
+ * 'packet' ingressed.
+ *
+ * If 'ofproto' is nonnull, requires 'flow''s in_port to exist.  Otherwise sets
+ * 'flow''s in_port to OFPP_NONE.
+ *
+ * This function does post-processing on data returned from
+ * odp_flow_key_to_flow() to help make VLAN splinters transparent to the rest
+ * of the upcall processing logic.  In particular, if the extracted in_port is
+ * a VLAN splinter port, it replaces flow->in_port by the "real" port, sets
+ * flow->vlan_tci correctly for the VLAN of the VLAN splinter port, and pushes
+ * a VLAN header onto 'packet' (if it is nonnull).
+ *
+ * Optionally, if nonnull, sets '*initial_tci' to the VLAN TCI with which the
+ * packet was really received, that is, the actual VLAN TCI extracted by
+ * odp_flow_key_to_flow().  (This differs from the value returned in
+ * flow->vlan_tci only for packets received on VLAN splinters.)
  *
- * Sets '*initial_tci' to the VLAN TCI with which the packet was really
- * received, that is, the actual VLAN TCI extracted by odp_flow_key_to_flow().
- * (This differs from the value returned in flow->vlan_tci only for packets
- * received on VLAN splinters.) */
-static enum odp_key_fitness
-ofproto_dpif_vsp_adjust(const struct ofproto_dpif *ofproto,
-                        enum odp_key_fitness fitness,
-                        struct flow *flow, ovs_be16 *initial_tci,
-                        struct ofpbuf *packet)
+ * Returns 0 if successful, ENODEV if the parsed flow has no associated ofport,
+ * or some other positive errno if there are other problems. */
+static int
+ofproto_receive(const struct dpif_backer *backer, struct ofpbuf *packet,
+                const struct nlattr *key, size_t key_len,
+                struct flow *flow, enum odp_key_fitness *fitnessp,
+                struct ofproto_dpif **ofproto, uint32_t *odp_in_port,
+                ovs_be16 *initial_tci)
 {
+    const struct ofport_dpif *port;
+    enum odp_key_fitness fitness;
+    int error;
+
+    fitness = odp_flow_key_to_flow(key, key_len, flow);
     if (fitness == ODP_FIT_ERROR) {
-        return fitness;
+        error = EINVAL;
+        goto exit;
+    }
+
+    if (initial_tci) {
+        *initial_tci = flow->vlan_tci;
+    }
+
+    if (odp_in_port) {
+        *odp_in_port = flow->in_port;
+    }
+
+    port = odp_port_to_ofport(backer, flow->in_port);
+    if (!port) {
+        flow->in_port = OFPP_NONE;
+        error = ofproto ? ENODEV : 0;
+        goto exit;
     }
-    *initial_tci = flow->vlan_tci;
 
-    if (vsp_adjust_flow(ofproto, flow)) {
+    if (ofproto) {
+        *ofproto = ofproto_dpif_cast(port->up.ofproto);
+    }
+
+    flow->in_port = port->up.ofp_port;
+    if (vsp_adjust_flow(ofproto_dpif_cast(port->up.ofproto), flow)) {
         if (packet) {
             /* Make the packet resemble the flow, so that it gets sent to an
              * OpenFlow controller properly, so that it looks correct for
@@ -3440,8 +3559,13 @@ ofproto_dpif_vsp_adjust(const struct ofproto_dpif *ofproto,
             fitness = ODP_FIT_TOO_MUCH;
         }
     }
+    error = 0;
 
-    return fitness;
+exit:
+    if (fitnessp) {
+        *fitnessp = fitness;
+    }
+    return error;
 }
 
 static void
@@ -3472,33 +3596,24 @@ handle_miss_upcalls(struct dpif_backer *backer, struct dpif_upcall *upcalls,
     for (upcall = upcalls; upcall < &upcalls[n_upcalls]; upcall++) {
         struct flow_miss *miss = &misses[n_misses];
         struct flow_miss *existing_miss;
-        enum odp_key_fitness fitness;
         struct ofproto_dpif *ofproto;
-        struct ofport_dpif *port;
         uint32_t odp_in_port;
         struct flow flow;
         uint32_t hash;
+        int error;
 
-        fitness = odp_flow_key_to_flow(upcall->key, upcall->key_len, &flow);
-        port = odp_port_to_ofport(backer, flow.in_port);
-        if (!port) {
+        error = ofproto_receive(backer, upcall->packet, upcall->key,
+                                upcall->key_len, &flow, &miss->key_fitness,
+                                &ofproto, &odp_in_port, &miss->initial_tci);
+        if (error == ENODEV) {
             /* Received packet on port for which we couldn't associate
              * an ofproto.  This can happen if a port is removed while
              * traffic is being received.  Print a rate-limited message
              * in case it happens frequently. */
             VLOG_INFO_RL(&rl, "received packet on unassociated port %"PRIu32,
                          flow.in_port);
-            continue;
         }
-        ofproto = ofproto_dpif_cast(port->up.ofproto);
-        odp_in_port = flow.in_port;
-        flow.in_port = port->up.ofp_port;
-
-        /* Obtain metadata and check userspace/kernel agreement on flow match,
-         * then set 'flow''s header pointers. */
-        miss->key_fitness = ofproto_dpif_vsp_adjust(ofproto, fitness,
-                                &flow, &miss->initial_tci, upcall->packet);
-        if (miss->key_fitness == ODP_FIT_ERROR) {
+        if (error) {
             continue;
         }
         flow_extract(upcall->packet, flow.skb_priority, flow.skb_mark,
@@ -3529,7 +3644,7 @@ handle_miss_upcalls(struct dpif_backer *backer, struct dpif_upcall *upcalls,
     HMAP_FOR_EACH (miss, hmap_node, &todo) {
         handle_flow_miss(miss, flow_miss_ops, &n_ops);
     }
-    assert(n_ops <= ARRAY_SIZE(flow_miss_ops));
+    ovs_assert(n_ops <= ARRAY_SIZE(flow_miss_ops));
 
     /* Execute batch. */
     for (i = 0; i < n_ops; i++) {
@@ -3601,29 +3716,12 @@ handle_sflow_upcall(struct dpif_backer *backer,
 {
     struct ofproto_dpif *ofproto;
     union user_action_cookie cookie;
-    enum odp_key_fitness fitness;
-    struct ofport_dpif *port;
-    ovs_be16 initial_tci;
     struct flow flow;
     uint32_t odp_in_port;
 
-    fitness = odp_flow_key_to_flow(upcall->key, upcall->key_len, &flow);
-
-    port = odp_port_to_ofport(backer, flow.in_port);
-    if (!port) {
-        return;
-    }
-
-    ofproto = ofproto_dpif_cast(port->up.ofproto);
-    if (!ofproto->sflow) {
-        return;
-    }
-
-    odp_in_port = flow.in_port;
-    flow.in_port = port->up.ofp_port;
-    fitness = ofproto_dpif_vsp_adjust(ofproto, fitness, &flow,
-                                      &initial_tci, upcall->packet);
-    if (fitness == ODP_FIT_ERROR) {
+    if (ofproto_receive(backer, upcall->packet, upcall->key, upcall->key_len,
+                        &flow, NULL, &ofproto, &odp_in_port, NULL)
+        || !ofproto->sflow) {
         return;
     }
 
@@ -3642,7 +3740,7 @@ handle_upcalls(struct dpif_backer *backer, unsigned int max_batch)
     int n_misses;
     int i;
 
-    assert(max_batch <= FLOW_MISS_MAX_BATCH);
+    ovs_assert(max_batch <= FLOW_MISS_MAX_BATCH);
 
     n_misses = 0;
     for (n_processed = 0; n_processed < max_batch; n_processed++) {
@@ -3707,8 +3805,7 @@ expire(struct dpif_backer *backer)
     update_stats(backer);
 
     HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
-        struct rule_dpif *rule, *next_rule;
-        struct oftable *table;
+        struct rule *rule, *next_rule;
         int dp_max_idle;
 
         if (ofproto->backer != backer) {
@@ -3723,13 +3820,9 @@ expire(struct dpif_backer *backer)
 
         /* Expire OpenFlow flows whose idle_timeout or hard_timeout
          * has passed. */
-        OFPROTO_FOR_EACH_TABLE (table, &ofproto->up) {
-            struct cls_cursor cursor;
-
-            cls_cursor_init(&cursor, &table->cls, NULL);
-            CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, up.cr, &cursor) {
-                rule_expire(rule);
-            }
+        LIST_FOR_EACH_SAFE (rule, next_rule, expirable,
+                            &ofproto->up.expirable) {
+            rule_expire(rule_dpif_cast(rule));
         }
 
         /* All outstanding data in existing flows has been accounted, so it's a
@@ -3739,7 +3832,7 @@ expire(struct dpif_backer *backer)
 
             HMAP_FOR_EACH (bundle, hmap_node, &ofproto->bundles) {
                 if (bundle->bond) {
-                    bond_rebalance(bundle->bond, &ofproto->revalidate_set);
+                    bond_rebalance(bundle->bond, &backer->revalidate_set);
                 }
             }
         }
@@ -4071,9 +4164,7 @@ facet_free(struct facet *facet)
 }
 
 /* Executes, within 'ofproto', the 'n_actions' actions in 'actions' on
- * 'packet', which arrived on 'in_port'.
- *
- * Takes ownership of 'packet'. */
+ * 'packet', which arrived on 'in_port'. */
 static bool
 execute_odp_actions(struct ofproto_dpif *ofproto, const struct flow *flow,
                     const struct nlattr *odp_actions, size_t actions_len,
@@ -4089,8 +4180,6 @@ execute_odp_actions(struct ofproto_dpif *ofproto, const struct flow *flow,
 
     error = dpif_execute(ofproto->backer->dpif, key.data, key.size,
                          odp_actions, actions_len, packet);
-
-    ofpbuf_delete(packet);
     return !error;
 }
 
@@ -4107,7 +4196,7 @@ facet_remove(struct facet *facet)
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(facet->rule->up.ofproto);
     struct subfacet *subfacet, *next_subfacet;
 
-    assert(!list_is_empty(&facet->subfacets));
+    ovs_assert(!list_is_empty(&facet->subfacets));
 
     /* First uninstall all of the subfacets to get final statistics. */
     LIST_FOR_EACH (subfacet, list_node, &facet->subfacets) {
@@ -4237,8 +4326,8 @@ facet_flush_stats(struct facet *facet)
     struct subfacet *subfacet;
 
     LIST_FOR_EACH (subfacet, list_node, &facet->subfacets) {
-        assert(!subfacet->dp_byte_count);
-        assert(!subfacet->dp_packet_count);
+        ovs_assert(!subfacet->dp_byte_count);
+        ovs_assert(!subfacet->dp_packet_count);
     }
 
     facet_push_stats(facet);
@@ -4303,8 +4392,9 @@ facet_lookup_valid(struct ofproto_dpif *ofproto, const struct flow *flow,
 
     facet = facet_find(ofproto, flow, hash);
     if (facet
-        && (ofproto->need_revalidate
-            || tag_set_intersects(&ofproto->revalidate_set, facet->tags))) {
+        && (ofproto->backer->need_revalidate
+            || tag_set_intersects(&ofproto->backer->revalidate_set,
+                                  facet->tags))) {
         facet_revalidate(facet);
     }
 
@@ -4595,9 +4685,9 @@ facet_push_stats(struct facet *facet)
 {
     struct dpif_flow_stats stats;
 
-    assert(facet->packet_count >= facet->prev_packet_count);
-    assert(facet->byte_count >= facet->prev_byte_count);
-    assert(facet->used >= facet->prev_used);
+    ovs_assert(facet->packet_count >= facet->prev_packet_count);
+    ovs_assert(facet->byte_count >= facet->prev_byte_count);
+    ovs_assert(facet->used >= facet->prev_used);
 
     stats.n_packets = facet->packet_count - facet->prev_packet_count;
     stats.n_bytes = facet->byte_count - facet->prev_byte_count;
@@ -4914,8 +5004,8 @@ subfacet_uninstall(struct subfacet *subfacet)
         }
         subfacet->path = SF_NOT_INSTALLED;
     } else {
-        assert(subfacet->dp_packet_count == 0);
-        assert(subfacet->dp_byte_count == 0);
+        ovs_assert(subfacet->dp_packet_count == 0);
+        ovs_assert(subfacet->dp_byte_count == 0);
     }
 }
 
@@ -5141,11 +5231,10 @@ rule_get_stats(struct rule *rule_, uint64_t *packets, uint64_t *bytes)
     }
 }
 
-static enum ofperr
-rule_execute(struct rule *rule_, const struct flow *flow,
-             struct ofpbuf *packet)
+static void
+rule_dpif_execute(struct rule_dpif *rule, const struct flow *flow,
+                  struct ofpbuf *packet)
 {
-    struct rule_dpif *rule = rule_dpif_cast(rule_);
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
 
     struct dpif_flow_stats stats;
@@ -5167,7 +5256,14 @@ rule_execute(struct rule *rule_, const struct flow *flow,
                         odp_actions.size, packet);
 
     ofpbuf_uninit(&odp_actions);
+}
 
+static enum ofperr
+rule_execute(struct rule *rule, const struct flow *flow,
+             struct ofpbuf *packet)
+{
+    rule_dpif_execute(rule_dpif_cast(rule), flow, packet);
+    ofpbuf_delete(packet);
     return 0;
 }
 
@@ -5193,6 +5289,29 @@ send_packet(const struct ofport_dpif *ofport, struct ofpbuf *packet)
     int error;
 
     flow_extract(packet, 0, 0, NULL, OFPP_LOCAL, &flow);
+    if (netdev_vport_is_patch(ofport->up.netdev)) {
+        struct ofproto_dpif *peer_ofproto;
+        struct dpif_flow_stats stats;
+        struct ofport_dpif *peer;
+        struct rule_dpif *rule;
+
+        peer = ofport_get_peer(ofport);
+        if (!peer) {
+            return ENODEV;
+        }
+
+        dpif_flow_stats_extract(&flow, packet, time_msec(), &stats);
+        netdev_vport_patch_inc_tx(ofport->up.netdev, &stats);
+        netdev_vport_patch_inc_rx(peer->up.netdev, &stats);
+
+        flow.in_port = peer->up.ofp_port;
+        peer_ofproto = ofproto_dpif_cast(peer->up.ofproto);
+        rule = rule_dpif_lookup(peer_ofproto, &flow);
+        rule_dpif_execute(rule, &flow, packet);
+
+        return 0;
+    }
+
     odp_port = vsp_realdev_to_vlandev(ofproto, ofport->odp_port,
                                       flow.vlan_tci);
     if (odp_port != ofport->odp_port) {
@@ -5253,7 +5372,7 @@ compose_slow_path(const struct ofproto_dpif *ofproto, const struct flow *flow,
 
     ofpbuf_use_stack(&buf, stub, stub_size);
     if (slow & (SLOW_CFM | SLOW_LACP | SLOW_STP)) {
-        uint32_t pid = dpif_port_get_pid(ofproto->backer->dpif, UINT16_MAX);
+        uint32_t pid = dpif_port_get_pid(ofproto->backer->dpif, UINT32_MAX);
         odp_put_userspace_action(pid, &cookie, &buf);
     } else {
         put_userspace_action(ofproto, &buf, flow, &cookie);
@@ -5368,7 +5487,7 @@ fix_sflow_action(struct action_xlate_ctx *ctx)
 
     cookie = ofpbuf_at(ctx->odp_actions, ctx->user_cookie_offset,
                        sizeof(*cookie));
-    assert(cookie->type == USER_ACTION_COOKIE_SFLOW);
+    ovs_assert(cookie->type == USER_ACTION_COOKIE_SFLOW);
 
     compose_sflow_cookie(ctx->ofproto, base->vlan_tci,
                          ctx->sflow_odp_port, ctx->sflow_n_outputs, cookie);
@@ -5379,33 +5498,66 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
                         bool check_stp)
 {
     const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port);
-    uint32_t odp_port = ofp_port_to_odp_port(ctx->ofproto, ofp_port);
     ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci;
     uint8_t flow_nw_tos = ctx->flow.nw_tos;
-    uint16_t out_port;
+    struct priority_to_dscp *pdscp;
+    uint32_t out_port, odp_port;
 
-    if (ofport) {
-        struct priority_to_dscp *pdscp;
+    /* If 'struct flow' gets additional metadata, we'll need to zero it out
+     * before traversing a patch port. */
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 18);
 
-        if (ofport->up.pp.config & OFPUTIL_PC_NO_FWD) {
-            xlate_report(ctx, "OFPPC_NO_FWD set, skipping output");
+    if (!ofport) {
+        xlate_report(ctx, "Nonexistent output port");
+        return;
+    } else if (ofport->up.pp.config & OFPUTIL_PC_NO_FWD) {
+        xlate_report(ctx, "OFPPC_NO_FWD set, skipping output");
+        return;
+    } else if (check_stp && !stp_forward_in_state(ofport->stp_state)) {
+        xlate_report(ctx, "STP not in forwarding state, skipping output");
+        return;
+    }
+
+    if (netdev_vport_is_patch(ofport->up.netdev)) {
+        struct ofport_dpif *peer = ofport_get_peer(ofport);
+        struct flow old_flow = ctx->flow;
+        const struct ofproto_dpif *peer_ofproto;
+
+        if (!peer) {
+            xlate_report(ctx, "Nonexistent patch port peer");
             return;
-        } else if (check_stp && !stp_forward_in_state(ofport->stp_state)) {
-            xlate_report(ctx, "STP not in forwarding state, skipping output");
+        }
+
+        peer_ofproto = ofproto_dpif_cast(peer->up.ofproto);
+        if (peer_ofproto->backer != ctx->ofproto->backer) {
+            xlate_report(ctx, "Patch port peer on a different datapath");
             return;
         }
 
-        pdscp = get_priority(ofport, ctx->flow.skb_priority);
-        if (pdscp) {
-            ctx->flow.nw_tos &= ~IP_DSCP_MASK;
-            ctx->flow.nw_tos |= pdscp->dscp;
+        ctx->ofproto = ofproto_dpif_cast(peer->up.ofproto);
+        ctx->flow.in_port = peer->up.ofp_port;
+        ctx->flow.metadata = htonll(0);
+        memset(&ctx->flow.tunnel, 0, sizeof ctx->flow.tunnel);
+        memset(ctx->flow.regs, 0, sizeof ctx->flow.regs);
+        xlate_table_action(ctx, ctx->flow.in_port, 0, true);
+        ctx->flow = old_flow;
+        ctx->ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+
+        if (ctx->resubmit_stats) {
+            netdev_vport_patch_inc_tx(ofport->up.netdev, ctx->resubmit_stats);
+            netdev_vport_patch_inc_rx(peer->up.netdev, ctx->resubmit_stats);
         }
-    } else {
-        /* We may not have an ofport record for this port, but it doesn't hurt
-         * to allow forwarding to it anyhow.  Maybe such a port will appear
-         * later and we're pre-populating the flow table.  */
+
+        return;
     }
 
+    pdscp = get_priority(ofport, ctx->flow.skb_priority);
+    if (pdscp) {
+        ctx->flow.nw_tos &= ~IP_DSCP_MASK;
+        ctx->flow.nw_tos |= pdscp->dscp;
+    }
+
+    odp_port = ofp_port_to_odp_port(ctx->ofproto, ofp_port);
     out_port = vsp_realdev_to_vlandev(ctx->ofproto, odp_port,
                                       ctx->flow.vlan_tci);
     if (out_port != odp_port) {
@@ -5466,7 +5618,7 @@ xlate_table_action(struct action_xlate_ctx *ctx,
         }
 
         if (rule == NULL && may_packet_in) {
-            /* TODO:XXX
+            /* XXX
              * check if table configuration flags
              * OFPTC_TABLE_MISS_CONTROLLER, default.
              * OFPTC_TABLE_MISS_CONTINUE,
@@ -5566,8 +5718,8 @@ execute_controller_action(struct action_xlate_ctx *ctx, int len,
         /* If the Ethernet type is less than ETH_TYPE_MIN, it's likely an 802.2
          * LLC frame.  Calculating the Ethernet type of these frames is more
          * trouble than seems appropriate for a simple assertion. */
-        assert(ntohs(eh->eth_type) < ETH_TYPE_MIN
-               || eh->eth_type == ctx->flow.dl_type);
+        ovs_assert(ntohs(eh->eth_type) < ETH_TYPE_MIN
+                   || eh->eth_type == ctx->flow.dl_type);
 
         memcpy(eh->eth_src, ctx->flow.dl_src, sizeof eh->eth_src);
         memcpy(eh->eth_dst, ctx->flow.dl_dst, sizeof eh->eth_dst);
@@ -5932,7 +6084,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_PUSH_VLAN:
-            /* TODO:XXX 802.1AD(QinQ) */
+            /* XXX 802.1AD(QinQ) */
             ctx->flow.vlan_tci = htons(VLAN_CFI);
             break;
 
@@ -6038,7 +6190,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_CLEAR_ACTIONS:
-            /* TODO:XXX
+            /* XXX
              * Nothing to do because writa-actions is not supported for now.
              * When writa-actions is supported, clear-actions also must
              * be supported at the same time.
@@ -6052,10 +6204,10 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_GOTO_TABLE: {
-            /* TODO:XXX remove recursion */
+            /* XXX remove recursion */
             /* It is assumed that goto-table is last action */
             struct ofpact_goto_table *ogt = ofpact_get_GOTO_TABLE(a);
-            assert(ctx->table_id < ogt->table_id);
+            ovs_assert(ctx->table_id < ogt->table_id);
             xlate_table_action(ctx, ctx->flow.in_port, ogt->table_id, true);
             break;
         }
@@ -6080,11 +6232,35 @@ action_xlate_ctx_init(struct action_xlate_ctx *ctx,
                       ovs_be16 initial_tci, struct rule_dpif *rule,
                       uint8_t tcp_flags, const struct ofpbuf *packet)
 {
+    ovs_be64 initial_tun_id = flow->tunnel.tun_id;
+
+    /* Flow initialization rules:
+     * - 'base_flow' must match the kernel's view of the packet at the
+     *   time that action processing starts.  'flow' represents any
+     *   transformations we wish to make through actions.
+     * - By default 'base_flow' and 'flow' are the same since the input
+     *   packet matches the output before any actions are applied.
+     * - When using VLAN splinters, 'base_flow''s VLAN is set to the value
+     *   of the received packet as seen by the kernel.  If we later output
+     *   to another device without any modifications this will cause us to
+     *   insert a new tag since the original one was stripped off by the
+     *   VLAN device.
+     * - Tunnel 'flow' is largely cleared when transitioning between
+     *   the input and output stages since it does not make sense to output
+     *   a packet with the exact headers that it was received with (i.e.
+     *   the destination IP is us).  The one exception is the tun_id, which
+     *   is preserved to allow use in later resubmit lookups and loads into
+     *   registers.
+     * - Tunnel 'base_flow' is completely cleared since that is what the
+     *   kernel does.  If we wish to maintain the original values an action
+     *   needs to be generated. */
+
     ctx->ofproto = ofproto;
     ctx->flow = *flow;
+    memset(&ctx->flow.tunnel, 0, sizeof ctx->flow.tunnel);
     ctx->base_flow = ctx->flow;
-    memset(&ctx->base_flow.tunnel, 0, sizeof ctx->base_flow.tunnel);
     ctx->base_flow.vlan_tci = initial_tci;
+    ctx->flow.tunnel.tun_id = initial_tun_id;
     ctx->rule = rule;
     ctx->packet = packet;
     ctx->may_learn = packet != NULL;
@@ -6166,6 +6342,7 @@ xlate_actions(struct action_xlate_ctx *ctx,
     } else {
         static struct vlog_rate_limit trace_rl = VLOG_RATE_LIMIT_INIT(1, 1);
         ovs_be16 initial_tci = ctx->base_flow.vlan_tci;
+        uint32_t local_odp_port;
 
         add_sflow_action(ctx);
         do_xlate_actions(ofpacts, ofpacts_len, ctx);
@@ -6186,7 +6363,9 @@ xlate_actions(struct action_xlate_ctx *ctx,
             }
         }
 
+        local_odp_port = ofp_port_to_odp_port(ctx->ofproto, OFPP_LOCAL);
         if (!connmgr_may_set_up_flow(ctx->ofproto->up.connmgr, &ctx->flow,
+                                     local_odp_port,
                                      ctx->odp_actions->data,
                                      ctx->odp_actions->size)) {
             ctx->slow |= SLOW_IN_BAND;
@@ -6573,7 +6752,7 @@ update_learning_table(struct ofproto_dpif *ofproto,
                     in_bundle->name, vlan);
 
         mac->port.p = in_bundle;
-        tag_set_add(&ofproto->revalidate_set,
+        tag_set_add(&ofproto->backer->revalidate_set,
                     mac_learning_changed(ofproto->ml, mac));
     }
 }
@@ -6849,7 +7028,7 @@ table_update_taggable(struct ofproto_dpif *ofproto, uint8_t table_id)
     if (table->catchall_table != catchall || table->other_table != other) {
         table->catchall_table = catchall;
         table->other_table = other;
-        ofproto->need_revalidate = REV_FLOW_TABLE;
+        ofproto->backer->need_revalidate = REV_FLOW_TABLE;
     }
 }
 
@@ -6867,13 +7046,13 @@ rule_invalidate(const struct rule_dpif *rule)
 
     table_update_taggable(ofproto, rule->up.table_id);
 
-    if (!ofproto->need_revalidate) {
+    if (!ofproto->backer->need_revalidate) {
         struct table_dpif *table = &ofproto->tables[rule->up.table_id];
 
         if (table->other_table && rule->tag) {
-            tag_set_add(&ofproto->revalidate_set, rule->tag);
+            tag_set_add(&ofproto->backer->revalidate_set, rule->tag);
         } else {
-            ofproto->need_revalidate = REV_FLOW_TABLE;
+            ofproto->backer->need_revalidate = REV_FLOW_TABLE;
         }
     }
 }
@@ -6883,9 +7062,8 @@ set_frag_handling(struct ofproto *ofproto_,
                   enum ofp_config_flags frag_handling)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-
     if (frag_handling != OFPC_FRAG_REASM) {
-        ofproto->need_revalidate = REV_RECONFIGURE;
+        ofproto->backer->need_revalidate = REV_RECONFIGURE;
         return true;
     } else {
         return false;
@@ -7017,10 +7195,10 @@ ofproto_unixctl_fdb_flush(struct unixctl_conn *conn, int argc,
             unixctl_command_reply_error(conn, "no such bridge");
             return;
         }
-        mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+        mac_learning_flush(ofproto->ml, &ofproto->backer->revalidate_set);
     } else {
         HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
-            mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+            mac_learning_flush(ofproto->ml, &ofproto->backer->revalidate_set);
         }
     }
 
@@ -7184,7 +7362,6 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[],
          * you just say "syntax error" or do you present both error messages?
          * Both choices seem lousy. */
         if (strchr(flow_s, '(')) {
-            enum odp_key_fitness fitness;
             int error;
 
             /* Convert string to datapath key. */
@@ -7195,13 +7372,13 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[],
                 goto exit;
             }
 
-            fitness = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow);
-            flow.in_port = odp_port_to_ofp_port(ofproto, flow.in_port);
-
-            /* Convert odp_key to flow. */
-            error = ofproto_dpif_vsp_adjust(ofproto, fitness, &flow,
-                                            &initial_tci, NULL);
-            if (error == ODP_FIT_ERROR) {
+            /* XXX: Since we allow the user to specify an ofproto, it's
+             * possible they will specify a different ofproto than the one the
+             * port actually belongs too.  Ideally we should simply remove the
+             * ability to specify the ofproto. */
+            if (ofproto_receive(ofproto->backer, NULL, odp_key.data,
+                                odp_key.size, &flow, NULL, NULL, NULL,
+                                &initial_tci)) {
                 unixctl_command_reply_error(conn, "Invalid flow");
                 goto exit;
             }
@@ -7216,7 +7393,6 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[],
             }
 
             initial_tci = flow.vlan_tci;
-            vsp_adjust_flow(ofproto, &flow);
         }
 
         /* Generate a packet, if requested. */
@@ -7390,7 +7566,7 @@ ofproto_dpif_self_check__(struct ofproto_dpif *ofproto, struct ds *reply)
         }
     }
     if (errors) {
-        ofproto->need_revalidate = REV_INCONSISTENCY;
+        ofproto->backer->need_revalidate = REV_INCONSISTENCY;
     }
 
     if (errors) {
@@ -7491,9 +7667,17 @@ show_dp_format(const struct ofproto_dpif *ofproto, struct ds *ds)
         struct ofport *ofport = node->data;
         const char *name = netdev_get_name(ofport->netdev);
         const char *type = netdev_get_type(ofport->netdev);
+        uint32_t odp_port;
+
+        ds_put_format(ds, "\t%s %u/", name, ofport->ofp_port);
+
+        odp_port = ofp_port_to_odp_port(ofproto, ofport->ofp_port);
+        if (odp_port != OVSP_NONE) {
+            ds_put_format(ds, "%"PRIu32":", odp_port);
+        } else {
+            ds_put_cstr(ds, "none:");
+        }
 
-        ds_put_format(ds, "\t%s %u/%u:", name, ofport->ofp_port,
-                      ofp_port_to_odp_port(ofproto, ofport->ofp_port));
         if (strcmp(type, "system")) {
             struct netdev *netdev;
             int error;
@@ -7583,6 +7767,8 @@ ofproto_unixctl_dpif_dump_flows(struct unixctl_conn *conn,
         return;
     }
 
+    update_stats(ofproto->backer);
+
     HMAP_FOR_EACH (subfacet, hmap_node, &ofproto->subfacets) {
         struct odputil_keybuf keybuf;
         struct ofpbuf key;
@@ -7683,7 +7869,7 @@ set_realdev(struct ofport *ofport_, uint16_t realdev_ofp_port, int vid)
         return 0;
     }
 
-    ofproto->need_revalidate = REV_RECONFIGURE;
+    ofproto->backer->need_revalidate = REV_RECONFIGURE;
 
     if (ofport->realdev_ofp_port) {
         vsp_remove(ofport);
@@ -7877,7 +8063,7 @@ odp_port_to_ofp_port(const struct ofproto_dpif *ofproto, uint32_t odp_port)
     struct ofport_dpif *port;
 
     port = odp_port_to_ofport(ofproto->backer, odp_port);
-    if (port && ofproto == ofproto_dpif_cast(port->up.ofproto)) {
+    if (port && &ofproto->up == port->up.ofproto) {
         return port->up.ofp_port;
     } else {
         return OFPP_NONE;