]> git.proxmox.com Git - mirror_ovs.git/commitdiff
ofproto-dpif: Restore metadata and registers on recirculation.
authorJarno Rajahalme <jrajahalme@nicira.com>
Thu, 26 Mar 2015 18:18:16 +0000 (11:18 -0700)
committerJarno Rajahalme <jrajahalme@nicira.com>
Thu, 26 Mar 2015 18:18:16 +0000 (11:18 -0700)
xlate_actions() now considers an optional recirculation context (via
'xin') and restores OpenFlow pipeline metadata (registers, 'metadata',
etc.) based on it.  The recirculation context may contain an action
set and stack to be restored and further actions to be executed upon
recirculation.  It also contains a table_id number to be used for rule
lookup in cases where no post-recirculation actions are used.

The translation context internal metadata is restored using a new
internal action: UNROLL_XLATE action stores the translation context
data visible to OpenFlow controllers via PACKET_IN messages.  This
includes the current table number and the current rule cookie.
UNROLL_XLATE actions are inserted only when the remaining actions may
generate PACKET_IN messages.

These changes allow the post-MPLS recirculation to properly continue
with the pipeline metadata that existed at the time of recirculation.

The internal table is still consulted for bonds.

Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ethan Jackson <ethan@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
12 files changed:
lib/ofp-actions.c
lib/ofp-actions.h
ofproto/bond.c
ofproto/ofproto-dpif-rid.c
ofproto/ofproto-dpif-rid.h
ofproto/ofproto-dpif-upcall.c
ofproto/ofproto-dpif-xlate.c
ofproto/ofproto-dpif-xlate.h
ofproto/ofproto-dpif.c
ofproto/ofproto-dpif.h
tests/mpls-xlate.at
tests/ofproto-dpif.at

index 260d854744784ce06e37d8eb96d04d6e60cdceb9..65fa64d08d24c77d6d8db4ee787e98949ef796ca 100644 (file)
@@ -4237,6 +4237,31 @@ format_EXIT(const struct ofpact_null *a OVS_UNUSED, struct ds *s)
     ds_put_cstr(s, "exit");
 }
 \f
+/* Unroll xlate action. */
+
+static void
+encode_UNROLL_XLATE(const struct ofpact_unroll_xlate *unroll OVS_UNUSED,
+                    enum ofp_version ofp_version OVS_UNUSED,
+                    struct ofpbuf *out OVS_UNUSED)
+{
+    OVS_NOT_REACHED();
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_UNROLL_XLATE(char *arg OVS_UNUSED, struct ofpbuf *ofpacts OVS_UNUSED,
+                   enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+    OVS_NOT_REACHED();
+    return NULL;
+}
+
+static void
+format_UNROLL_XLATE(const struct ofpact_unroll_xlate *a OVS_UNUSED,
+                    struct ds *s)
+{
+    ds_put_cstr(s, "unroll_xlate");
+}
+\f
 /* Action structure for NXAST_SAMPLE.
  *
  * Samples matching packets with the given probability and sends them
@@ -4726,6 +4751,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
     case OFPACT_DEC_TTL:
     case OFPACT_ENQUEUE:
     case OFPACT_EXIT:
+    case OFPACT_UNROLL_XLATE:
     case OFPACT_FIN_TIMEOUT:
     case OFPACT_GOTO_TABLE:
     case OFPACT_GROUP:
@@ -4795,6 +4821,7 @@ ofpact_is_allowed_in_actions_set(const struct ofpact *a)
     case OFPACT_CONTROLLER:
     case OFPACT_ENQUEUE:
     case OFPACT_EXIT:
+    case OFPACT_UNROLL_XLATE:
     case OFPACT_FIN_TIMEOUT:
     case OFPACT_LEARN:
     case OFPACT_CONJUNCTION:
@@ -4868,7 +4895,7 @@ ofpacts_copy_all(struct ofpbuf *out, const struct ofpbuf *in,
  * "Action Set" and "Action List" terms used in OpenFlow 1.1+.)
  *
  * In general this involves appending the last instance of each action that is
- * adimissible in the action set in the order described in the OpenFlow
+ * admissible in the action set in the order described in the OpenFlow
  * specification.
  *
  * Exceptions:
@@ -5017,6 +5044,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
     case OFPACT_MULTIPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_UNROLL_XLATE:
     case OFPACT_SAMPLE:
     default:
         return OVSINST_OFPIT11_APPLY_ACTIONS;
@@ -5607,6 +5635,11 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     case OFPACT_GROUP:
         return 0;
 
+    case OFPACT_UNROLL_XLATE:
+        /* UNROLL is an internal action that should never be seen via
+         * OpenFlow. */
+        return OFPERR_OFPBAC_BAD_TYPE;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -5998,6 +6031,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
     case OFPACT_MULTIPATH:
     case OFPACT_NOTE:
     case OFPACT_EXIT:
+    case OFPACT_UNROLL_XLATE:
     case OFPACT_PUSH_MPLS:
     case OFPACT_POP_MPLS:
     case OFPACT_SAMPLE:
index a1a5bb12231c167a719233f3e29b5bdbdaae0316..785c8140c7ed0ff8f93be82e73a3966da6f249a4 100644 (file)
     OFPACT(NOTE,            ofpact_note,        data, "note")           \
     OFPACT(EXIT,            ofpact_null,        ofpact, "exit")         \
     OFPACT(SAMPLE,          ofpact_sample,      ofpact, "sample")       \
+    OFPACT(UNROLL_XLATE,    ofpact_unroll_xlate, ofpact, "unroll_xlate") \
                                                                         \
     /* Instructions. */                                                 \
     OFPACT(METER,           ofpact_meter,       ofpact, "meter")        \
@@ -715,6 +716,17 @@ struct ofpact_group {
     uint32_t group_id;
 };
 
+/* OFPACT_UNROLL_XLATE.
+ *
+ * Used only internally. */
+struct ofpact_unroll_xlate {
+    struct ofpact ofpact;
+
+    /* Metadata in xlate context, visible to controller via PACKET_INs. */
+    uint8_t  rule_table_id;       /* 0xFF if none. */
+    ovs_be64 rule_cookie;         /* OVS_BE64_MAX if none. */
+};
+
 /* Converting OpenFlow to ofpacts. */
 enum ofperr ofpacts_pull_openflow_actions(struct ofpbuf *openflow,
                                           unsigned int actions_len,
index 7831fa41e882ff9aba14589b183967d374024fcd..2e3ad2957f615fe6b850358206005fd0794e0abf 100644 (file)
@@ -28,6 +28,7 @@
 #include "ofpbuf.h"
 #include "ofproto/ofproto-provider.h"
 #include "ofproto/ofproto-dpif.h"
+#include "ofproto/ofproto-dpif-rid.h"
 #include "connectivity.h"
 #include "coverage.h"
 #include "dynamic-string.h"
@@ -289,7 +290,7 @@ bond_unref(struct bond *bond)
     hmap_destroy(&bond->pr_rule_ops);
 
     if (bond->recirc_id) {
-        ofproto_dpif_free_recirc_id(bond->ofproto, bond->recirc_id);
+        recirc_free_id(bond->recirc_id);
     }
 
     free(bond);
@@ -446,10 +447,10 @@ bond_reconfigure(struct bond *bond, const struct bond_settings *s)
 
     if (bond->balance != BM_AB) {
         if (!bond->recirc_id) {
-            bond->recirc_id = ofproto_dpif_alloc_recirc_id(bond->ofproto);
+            bond->recirc_id = recirc_alloc_id(bond->ofproto);
         }
     } else if (bond->recirc_id) {
-        ofproto_dpif_free_recirc_id(bond->ofproto, bond->recirc_id);
+        recirc_free_id(bond->recirc_id);
         bond->recirc_id = 0;
     }
 
index afad3ce347da750d4b9040aab68acc48342a7b68..17bcede6244b4e7aad27e2407cb401053df87031 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Nicira, Inc.
+ * Copyright (c) 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include <config.h>
 
-#include "id-pool.h"
-#include "ovs-thread.h"
+#include "ofpbuf.h"
+#include "ofproto-dpif.h"
 #include "ofproto-dpif-rid.h"
+#include "ofproto-provider.h"
+#include "openvswitch/vlog.h"
 
-struct recirc_id_pool {
-    struct ovs_mutex lock;
-    struct id_pool *rids;
-};
+VLOG_DEFINE_THIS_MODULE(ofproto_dpif_rid);
 
-#define RECIRC_ID_BASE  300
-#define RECIRC_ID_N_IDS  1024
+static struct ovs_mutex mutex;
 
-struct recirc_id_pool *
-recirc_id_pool_create(void)
+static struct cmap id_map;
+static struct cmap metadata_map;
+
+static struct ovs_list expiring OVS_GUARDED_BY(mutex);
+static struct ovs_list expired OVS_GUARDED_BY(mutex);
+
+static uint32_t next_id OVS_GUARDED_BY(mutex); /* Possible next free id. */
+
+#define RECIRC_POOL_STATIC_IDS 1024
+
+void
+recirc_init(void)
 {
-    struct recirc_id_pool *pool;
+    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
 
-    pool = xmalloc(sizeof *pool);
-    pool->rids = id_pool_create(RECIRC_ID_BASE, RECIRC_ID_N_IDS);
-    ovs_mutex_init(&pool->lock);
+    if (ovsthread_once_start(&once)) {
+        ovs_mutex_init(&mutex);
+        ovs_mutex_lock(&mutex);
+        next_id = 1; /* 0 is not a valid ID. */
+        cmap_init(&id_map);
+        cmap_init(&metadata_map);
+        list_init(&expiring);
+        list_init(&expired);
+        ovs_mutex_unlock(&mutex);
+
+        ovsthread_once_done(&once);
+    }
 
-    return pool;
 }
 
+/* This should be called by the revalidator once at each round (every 500ms or
+ * more). */
 void
-recirc_id_pool_destroy(struct recirc_id_pool *pool)
+recirc_run(void)
+{
+    static long long int last = 0;
+    long long int now = time_msec();
+
+    /* Do maintenance at most 4 times / sec. */
+    ovs_mutex_lock(&mutex);
+    if (now - last > 250) {
+        struct recirc_id_node *node, *next;
+
+        last = now;
+
+        /* Nodes in 'expiring' and 'expired' lists have the refcount of zero,
+         * which means that while they can still be found (by id), no new
+         * references can be taken on them.  We have removed the entry from the
+         * 'metadata_map', at the time when refcount reached zero, causing any
+         * new translations to allocate a new ID.  This allows the expiring
+         * entry to be safely deleted while any sudden new use of the similar
+         * recirculation will safely start using a new recirculation ID.  When
+         * the refcount gets to zero, the node is also added to the 'expiring'
+         * list.  At any time after that the nodes in the 'expiring' list can
+         * be moved to the 'expired' list, from which they are deleted at least
+         * 250ms afterwards. */
+
+        /* Delete the expired.  These have been lingering for at least 250 ms,
+         * which should be enough for any ongoing recirculations to be
+         * finished. */
+        LIST_FOR_EACH_SAFE (node, next, exp_node, &expired) {
+            list_remove(&node->exp_node);
+            cmap_remove(&id_map, &node->id_node, node->id);
+            ovsrcu_postpone(free, node);
+        }
+
+        if (!list_is_empty(&expiring)) {
+            /* 'expired' is now empty, move nodes in 'expiring' to it. */
+            list_splice(&expired, list_front(&expiring), &expiring);
+        }
+    }
+    ovs_mutex_unlock(&mutex);
+}
+
+/* We use the id as the hash value, which works due to cmap internal rehashing.
+ * We also only insert nodes with unique IDs, so all possible hash collisions
+ * remain internal to the cmap. */
+static struct recirc_id_node *
+recirc_find__(uint32_t id)
+    OVS_REQUIRES(mutex)
+{
+    struct cmap_node *node = cmap_find_protected(&id_map, id);
+
+    return node ? CONTAINER_OF(node, struct recirc_id_node, id_node) : NULL;
+}
+
+/* Lockless RCU protected lookup.  If node is needed accross RCU quiescent
+ * state, caller should copy the contents. */
+const struct recirc_id_node *
+recirc_id_node_find(uint32_t id)
+{
+    const struct cmap_node *node = cmap_find(&id_map, id);
+
+    return node
+        ? CONTAINER_OF(node, const struct recirc_id_node, id_node)
+        : NULL;
+}
+
+static uint32_t
+recirc_metadata_hash(struct ofproto_dpif *ofproto, uint8_t table_id,
+                     struct recirc_metadata *md, struct ofpbuf *stack,
+                     uint32_t action_set_len, uint32_t ofpacts_len,
+                     const struct ofpact *ofpacts)
+{
+    uint32_t hash;
+
+    BUILD_ASSERT(OFPACT_ALIGNTO == sizeof(uint64_t));
+
+    hash = hash_pointer(ofproto, 0);
+    hash = hash_int(table_id, hash);
+    hash = hash_words64((const uint64_t *)md, sizeof *md / sizeof(uint64_t),
+                        hash);
+    if (stack && stack->size != 0) {
+        hash = hash_words64((const uint64_t *)stack->data,
+                            stack->size / sizeof(uint64_t), hash);
+    }
+    hash = hash_int(action_set_len, hash);
+    if (ofpacts_len) {
+        hash = hash_words64(ALIGNED_CAST(const uint64_t *, ofpacts),
+                            OFPACT_ALIGN(ofpacts_len) / sizeof(uint64_t),
+                            hash);
+    }
+    return hash;
+}
+
+static bool
+recirc_metadata_equal(const struct recirc_id_node *node,
+                      struct ofproto_dpif *ofproto, uint8_t table_id,
+                      struct recirc_metadata *md, struct ofpbuf *stack,
+                      uint32_t action_set_len, uint32_t ofpacts_len,
+                      const struct ofpact *ofpacts)
+{
+    return node->ofproto == ofproto
+        && node->table_id == table_id
+        && !memcmp(&node->metadata, md, sizeof *md)
+        && ((!node->stack && (!stack || stack->size == 0))
+            || (node->stack && stack && ofpbuf_equal(node->stack, stack)))
+        && node->action_set_len == action_set_len
+        && node->ofpacts_len == ofpacts_len
+        && (ofpacts_len == 0 || !memcmp(node->ofpacts, ofpacts, ofpacts_len));
+}
+
+/* Lockless RCU protected lookup.  If node is needed accross RCU quiescent
+ * state, caller should take a reference. */
+static struct recirc_id_node *
+recirc_find_equal(struct ofproto_dpif *ofproto, uint8_t table_id,
+                  struct recirc_metadata *md, struct ofpbuf *stack,
+                  uint32_t action_set_len, uint32_t ofpacts_len,
+                  const struct ofpact *ofpacts, uint32_t hash)
+{
+    struct recirc_id_node *node;
+
+    CMAP_FOR_EACH_WITH_HASH(node, metadata_node, hash, &metadata_map) {
+        if (recirc_metadata_equal(node, ofproto, table_id, md, stack,
+                                  action_set_len, ofpacts_len, ofpacts)) {
+            return node;
+        }
+    }
+    return NULL;
+}
+
+static struct recirc_id_node *
+recirc_ref_equal(struct ofproto_dpif *ofproto, uint8_t table_id,
+                 struct recirc_metadata *md, struct ofpbuf *stack,
+                 uint32_t action_set_len, uint32_t ofpacts_len,
+                 const struct ofpact *ofpacts, uint32_t hash)
+{
+    struct recirc_id_node *node;
+
+    do {
+        node = recirc_find_equal(ofproto, table_id, md, stack, action_set_len,
+                                 ofpacts_len, ofpacts, hash);
+
+        /* Try again if the node was released before we get the reference. */
+    } while (node && !ovs_refcount_try_ref_rcu(&node->refcount));
+
+    return node;
+}
+
+/* Allocate a unique recirculation id for the given set of flow metadata.
+ * The ID space is 2^^32, so there should never be a situation in which all
+ * the IDs are used up.  We loop until we find a free one.
+ * hash is recomputed if it is passed in as 0. */
+static struct recirc_id_node *
+recirc_alloc_id__(struct ofproto_dpif *ofproto, uint8_t table_id,
+                  struct recirc_metadata *md, struct ofpbuf *stack,
+                  uint32_t action_set_len, uint32_t ofpacts_len,
+                  const struct ofpact *ofpacts, uint32_t hash)
+{
+    struct recirc_id_node *node = xzalloc(sizeof *node +
+                                          OFPACT_ALIGN(ofpacts_len));
+    node->hash = hash;
+    ovs_refcount_init(&node->refcount);
+
+    node->ofproto = ofproto;
+    node->table_id = table_id;
+    memcpy(&node->metadata, md, sizeof node->metadata);
+    node->stack = (stack && stack->size) ? ofpbuf_clone(stack) : NULL;
+    node->action_set_len = action_set_len;
+    node->ofpacts_len = ofpacts_len;
+    if (ofpacts_len) {
+        memcpy(node->ofpacts, ofpacts, ofpacts_len);
+    }
+
+    ovs_mutex_lock(&mutex);
+    for (;;) {
+        /* Claim the next ID.  The ID space should be sparse enough for the
+           allocation to succeed at the first try.  We do skip the first
+           RECIRC_POOL_STATIC_IDS IDs on the later rounds, though, as some of
+           the initial allocations may be for long term uses (like bonds). */
+        node->id = next_id++;
+        if (OVS_UNLIKELY(!node->id)) {
+            next_id = RECIRC_POOL_STATIC_IDS + 1;
+            node->id = next_id++;
+        }
+        /* Find if the id is free. */
+        if (OVS_LIKELY(!recirc_find__(node->id))) {
+            break;
+        }
+    }
+    cmap_insert(&id_map, &node->id_node, node->id);
+    cmap_insert(&metadata_map, &node->metadata_node, node->hash);
+    ovs_mutex_unlock(&mutex);
+    return node;
+}
+
+/* Look up an existing ID for the given flow's metadata and optional actions.
+ */
+uint32_t
+recirc_find_id(struct ofproto_dpif *ofproto, uint8_t table_id,
+               struct recirc_metadata *md, struct ofpbuf *stack,
+               uint32_t action_set_len, uint32_t ofpacts_len,
+               const struct ofpact *ofpacts)
+{
+    /* Check if an ID with the given metadata already exists. */
+    struct recirc_id_node *node;
+    uint32_t hash;
+
+    hash = recirc_metadata_hash(ofproto, table_id, md, stack, action_set_len,
+                                ofpacts_len, ofpacts);
+    node = recirc_find_equal(ofproto, table_id, md, stack, action_set_len,
+                             ofpacts_len, ofpacts, hash);
+
+    return node ? node->id : 0;
+}
+
+/* Allocate a unique recirculation id for the given set of flow metadata and
+   optional actions. */
+uint32_t
+recirc_alloc_id_ctx(struct ofproto_dpif *ofproto, uint8_t table_id,
+                    struct recirc_metadata *md, struct ofpbuf *stack,
+                    uint32_t action_set_len, uint32_t ofpacts_len,
+                    const struct ofpact *ofpacts)
 {
-    id_pool_destroy(pool->rids);
-    ovs_mutex_destroy(&pool->lock);
-    free(pool);
+    struct recirc_id_node *node;
+    uint32_t hash;
+
+    /* Look up an existing ID. */
+    hash = recirc_metadata_hash(ofproto, table_id, md, stack, action_set_len,
+                                ofpacts_len, ofpacts);
+    node = recirc_ref_equal(ofproto, table_id, md, stack, action_set_len,
+                            ofpacts_len, ofpacts, hash);
+
+    /* Allocate a new recirc ID if needed. */
+    if (!node) {
+        ovs_assert(action_set_len <= ofpacts_len);
+
+        node = recirc_alloc_id__(ofproto, table_id, md, stack, action_set_len,
+                                 ofpacts_len, ofpacts, hash);
+    }
+
+    return node->id;
 }
 
+/* Allocate a unique recirculation id. */
 uint32_t
-recirc_id_alloc(struct recirc_id_pool *pool)
+recirc_alloc_id(struct ofproto_dpif *ofproto)
 {
-    uint32_t id;
-    bool ret;
+    struct recirc_metadata md;
+    struct recirc_id_node *node;
+    uint32_t hash;
 
-    ovs_mutex_lock(&pool->lock);
-    ret = id_pool_alloc_id(pool->rids, &id);
-    ovs_mutex_unlock(&pool->lock);
+    memset(&md, 0, sizeof md);
+    md.in_port = OFPP_NONE;
+    hash = recirc_metadata_hash(ofproto, TBL_INTERNAL, &md, NULL, 0, 0, NULL);
+    node = recirc_alloc_id__(ofproto, TBL_INTERNAL, &md, NULL, 0, 0, NULL,
+                             hash);
+    return node->id;
+}
 
-    if (!ret) {
-        return 0;
+void
+recirc_id_node_unref(const struct recirc_id_node *node_)
+    OVS_EXCLUDED(mutex)
+{
+    struct recirc_id_node *node = CONST_CAST(struct recirc_id_node *, node_);
+
+    if (node && ovs_refcount_unref(&node->refcount) == 1) {
+        ovs_mutex_lock(&mutex);
+        /* Prevent re-use of this node by removing the node from 'metadata_map'
+         */
+        cmap_remove(&metadata_map, &node->metadata_node, node->hash);
+        /* We keep the node in the 'id_map' so that it can be found as long
+         * as it lingers, and add it to the 'expiring' list. */
+        list_insert(&expiring, &node->exp_node);
+        ovs_mutex_unlock(&mutex);
     }
+}
 
-    return id;
+void
+recirc_free_id(uint32_t id)
+{
+    const struct recirc_id_node *node;
+
+    node = recirc_id_node_find(id);
+    if (node) {
+        recirc_id_node_unref(node);
+    } else {
+        VLOG_ERR("Freeing nonexistent recirculation ID: %"PRIu32, id);
+    }
 }
 
+/* Called when 'ofproto' is destructed.  Checks for and clears any
+ * recirc_id leak.
+ * No other thread may have access to the 'ofproto' being destructed.
+ * All related datapath flows must be deleted before calling this. */
 void
-recirc_id_free(struct recirc_id_pool *pool, uint32_t id)
+recirc_free_ofproto(struct ofproto_dpif *ofproto, const char *ofproto_name)
 {
-    ovs_mutex_lock(&pool->lock);
-    id_pool_free_id(pool->rids, id);
-    ovs_mutex_unlock(&pool->lock);
+    struct recirc_id_node *n;
+
+    CMAP_FOR_EACH (n, metadata_node, &metadata_map) {
+        if (n->ofproto == ofproto) {
+            VLOG_ERR("recirc_id %"PRIu32
+                     " left allocated when ofproto (%s)"
+                     " is destructed", n->id, ofproto_name);
+        }
+    }
 }
index 3344e2a53573dd0f4238d6ccb08b64a37c42606f..81a61a2368bb1b99916c09ac6c2fdaf53a9b7c48 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Nicira, Inc.
+ * Copyright (c) 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <stddef.h>
 #include <stdint.h>
 
-struct recirc_id_pool;
+#include "cmap.h"
+#include "list.h"
+#include "ofp-actions.h"
+#include "ovs-thread.h"
+
+struct ofproto_dpif;
+struct rule;
 
 /*
- * Recirculation ID pool.
- * ======================
+ * Recirculation
+ * =============
+ *
+ * Recirculation is a technique to allow a frame to re-enter the datapath
+ * packet processing path for one or multiple times to achieve more flexible
+ * packet processing, such modifying header fields after MPLS POP action and
+ * selecting bond a slave port for bond ports.
+ *
+ * Data path and user space interface
+ * -----------------------------------
+ *
+ * Recirculation uses two uint32_t fields, recirc_id and dp_hash, and a RECIRC
+ * action.  The value recirc_id is used to select the next packet processing
+ * steps among multiple instances of recirculation.  When a packet initially
+ * enters the data path it is assigned with recirc_id 0, which indicates no
+ * recirculation.  Recirc_ids are managed by the user space, opaque to the
+ * data path.
+ *
+ * On the other hand, dp_hash can only be computed by the data path, opaque to
+ * the user space.  In fact, user space may not able to recompute the hash
+ * value.  The dp_hash value should be wildcarded for a newly received
+ * packet.  HASH action specifies whether the hash is computed, and if
+ * computed, how many fields are to be included in the hash computation.  The
+ * computed hash value is stored into the dp_hash field prior to recirculation.
+ *
+ * The RECIRC action sets the recirc_id field and then reprocesses the packet
+ * as if it was received on the same input port.  RECIRC action works like a
+ * function call; actions listed behind the RECIRC action will be executed
+ * after its execution.  RECIRC action can be nested, data path implementation
+ * limits the number of recirculation executed to prevent unreasonable nesting
+ * depth or infinite loop.
+ *
+ * User space recirculation context
+ * ---------------------------------
  *
- * Recirculation ID needs to be unique for each datapath. Recirculation
- * ID pool keeps track recirculation ids.
+ * Recirculation is hidden from the OpenFlow controllers.  Action translation
+ * code deduces when recirculation is necessary and issues a data path
+ * recirculation action.  All OpenFlow actions to be performed after
+ * recirculation are derived from the OpenFlow pipeline and are stored with the
+ * recirculation ID.  When the OpenFlow tables are changed in a way affecting
+ * the recirculation flows, new recirculation ID with new metadata and actions
+ * is allocated and the old one is timed out.
  *
- * Typically, there is one recirculation ID pool for each backer.
+ * Recirculation ID pool
+ * ----------------------
  *
- * In theory, Recirculation ID can be any uint32_t value, except 0.
- * The implementation usually limits it to a smaller range to ease
- * debugging.
+ * Recirculation ID needs to be unique for all data paths.  Recirculation ID
+ * pool keeps track recirculation ids and stores OpenFlow pipeline translation
+ * context so that flow processing may continue after recirculation.
+ *
+ * A Recirculation ID can be any uint32_t value, except for that the value 0 is
+ * reserved for 'no recirculation' case.
  *
  * Thread-safety
- * =============
+ * --------------
  *
  * All APIs are thread safe.
- *
  */
-struct recirc_id_pool *recirc_id_pool_create(void);
-void  recirc_id_pool_destroy(struct recirc_id_pool *pool);
-uint32_t recirc_id_alloc(struct recirc_id_pool *pool);
-void recirc_id_free(struct recirc_id_pool *pool, uint32_t recirc_id);
+
+/* Metadata for restoring pipeline context after recirculation.  Helpers
+ * are inlined below to keep them together with the definition for easier
+ * updates. */
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 31);
+
+struct recirc_metadata {
+    /* Metadata in struct flow. */
+    struct flow_tnl tunnel;       /* Encapsulating tunnel parameters. */
+    ovs_be64 metadata;            /* OpenFlow Metadata. */
+    uint64_t regs[FLOW_N_XREGS];  /* Registers. */
+    ofp_port_t in_port;           /* Incoming port. */
+    ofp_port_t actset_output;     /* Output port in action set. */
+};
+
+static inline void
+recirc_metadata_from_flow(struct recirc_metadata *md,
+                          const struct flow *flow)
+{
+    memset(md, 0, sizeof *md);
+    md->tunnel = flow->tunnel;
+    md->metadata = flow->metadata;
+    memcpy(md->regs, flow->regs, sizeof md->regs);
+    md->in_port = flow->in_port.ofp_port;
+    md->actset_output = flow->actset_output;
+}
+
+static inline void
+recirc_metadata_to_flow(const struct recirc_metadata *md,
+                        struct flow *flow)
+{
+    flow->tunnel = md->tunnel;
+    flow->metadata = md->metadata;
+    memcpy(flow->regs, md->regs, sizeof flow->regs);
+    flow->in_port.ofp_port = md->in_port;
+    flow->actset_output = md->actset_output;
+}
+
+/* Pool node fields should NOT be modified after placing the node in the pool.
+ */
+struct recirc_id_node {
+    struct ovs_list exp_node OVS_GUARDED;
+    struct cmap_node id_node;
+    struct cmap_node metadata_node;
+    uint32_t id;
+    uint32_t hash;
+    struct ovs_refcount refcount;
+
+    /* Initial table for post-recirculation processing. */
+    uint8_t table_id;
+
+    /* Pipeline context for post-recirculation processing. */
+    struct ofproto_dpif *ofproto; /* Post-recirculation bridge. */
+    struct recirc_metadata metadata; /* Flow metadata. */
+    struct ofpbuf *stack;         /* Stack if any. */
+
+    /* Actions to be translated on recirculation. */
+    uint32_t action_set_len;      /* How much of 'ofpacts' consists of an
+                                   * action set? */
+    uint32_t ofpacts_len;         /* Size of 'ofpacts', in bytes. */
+    struct ofpact ofpacts[];      /* Sequence of "struct ofpacts". */
+};
+
+void recirc_init(void);
+
+/* This is only used for bonds and will go away when bonds implementation is
+ * updated to use this mechanism instead of internal rules. */
+uint32_t recirc_alloc_id(struct ofproto_dpif *);
+
+uint32_t recirc_alloc_id_ctx(struct ofproto_dpif *, uint8_t table_id,
+                             struct recirc_metadata *, struct ofpbuf *stack,
+                             uint32_t action_set_len, uint32_t ofpacts_len,
+                             const struct ofpact *);
+uint32_t recirc_find_id(struct ofproto_dpif *, uint8_t table_id,
+                        struct recirc_metadata *, struct ofpbuf *stack,
+                        uint32_t action_set_len, uint32_t ofpacts_len,
+                        const struct ofpact *);
+void recirc_free_id(uint32_t recirc_id);
+void recirc_free_ofproto(struct ofproto_dpif *, const char *ofproto_name);
+
+const struct recirc_id_node *recirc_id_node_find(uint32_t recirc_id);
+
+static inline bool recirc_id_node_try_ref_rcu(const struct recirc_id_node *n_)
+{
+    struct recirc_id_node *node = CONST_CAST(struct recirc_id_node *, n_);
+
+    return node ? ovs_refcount_try_ref_rcu(&node->refcount) : false;
+}
+
+void recirc_id_node_unref(const struct recirc_id_node *);
+
+void recirc_run(void);
+
 #endif
index 3c80a0c0367c207840b73cd9cfae2c253c12ac32..01bc382b58ba7d98f16dff7c07eab742b824ae29 100644 (file)
@@ -152,6 +152,8 @@ enum upcall_type {
 
 struct upcall {
     struct ofproto_dpif *ofproto;  /* Parent ofproto. */
+    const struct recirc_id_node *recirc; /* Recirculation context. */
+    bool have_recirc_ref;                /* Reference held on recirc ctx? */
 
     /* The flow and packet are only required to be constant when using
      * dpif-netdev.  If a modification is absolutely necessary, a const cast
@@ -229,6 +231,10 @@ struct udpif_key {
         struct odputil_keybuf buf;
         struct nlattr nla;
     } keybuf, maskbuf;
+
+    /* Recirculation IDs with references held by the ukey. */
+    unsigned n_recircs;
+    uint32_t recircs[];   /* 'n_recircs' id's for which references are held. */
 };
 
 /* Datapath operation with optional ukey attached. */
@@ -271,7 +277,7 @@ static void upcall_unixctl_dump_wait(struct unixctl_conn *conn, int argc,
 static void upcall_unixctl_purge(struct unixctl_conn *conn, int argc,
                                  const char *argv[], void *aux);
 
-static struct udpif_key *ukey_create_from_upcall(const struct upcall *);
+static struct udpif_key *ukey_create_from_upcall(struct upcall *);
 static int ukey_create_from_dpif_flow(const struct udpif *,
                                       const struct dpif_flow *,
                                       struct udpif_key **);
@@ -737,6 +743,8 @@ udpif_revalidator(void *arg)
         if (leader) {
             uint64_t reval_seq;
 
+            recirc_run(); /* Recirculation cleanup. */
+
             reval_seq = seq_read(udpif->reval_seq);
             last_reval_seq = reval_seq;
 
@@ -903,6 +911,8 @@ upcall_receive(struct upcall *upcall, const struct dpif_backer *backer,
         return error;
     }
 
+    upcall->recirc = NULL;
+    upcall->have_recirc_ref = false;
     upcall->flow = flow;
     upcall->packet = packet;
     upcall->ufid = ufid;
@@ -942,9 +952,21 @@ upcall_xlate(struct udpif *udpif, struct upcall *upcall,
 
     if (upcall->type == DPIF_UC_MISS) {
         xin.resubmit_stats = &stats;
+
+        if (xin.recirc) {
+            /* We may install a datapath flow only if we get a reference to the
+             * recirculation context (otherwise we could have recirculation
+             * upcalls using recirculation ID for which no context can be
+             * found).  We may still execute the flow's actions even if we
+             * don't install the flow. */
+            upcall->recirc = xin.recirc;
+            upcall->have_recirc_ref = recirc_id_node_try_ref_rcu(xin.recirc);
+        }
     } else {
-        /* For non-miss upcalls, there's a flow in the datapath which this
-         * packet was accounted to.  Presumably the revalidators will deal
+        /* For non-miss upcalls, we are either executing actions (one of which
+         * is an userspace action) for an upcall, in which case the stats have
+         * already been taken care of, or there's a flow in the datapath which
+         * this packet was accounted to.  Presumably the revalidators will deal
          * with pushing its stats eventually. */
     }
 
@@ -1005,8 +1027,13 @@ upcall_uninit(struct upcall *upcall)
             xlate_out_uninit(&upcall->xout);
         }
         ofpbuf_uninit(&upcall->put_actions);
-        if (!upcall->ukey_persists) {
-            ukey_delete__(upcall->ukey);
+        if (upcall->ukey) {
+            if (!upcall->ukey_persists) {
+                ukey_delete__(upcall->ukey);
+            }
+        } else if (upcall->have_recirc_ref) {
+            /* The reference was transferred to the ukey if one was created. */
+            recirc_id_node_unref(upcall->recirc);
         }
     }
 }
@@ -1056,10 +1083,16 @@ upcall_cb(const struct dp_packet *packet, const struct flow *flow, ovs_u128 *ufi
         goto out;
     }
 
-    if (upcall.ukey && !ukey_install(udpif, upcall.ukey)) {
+    /* Prevent miss flow installation if the key has recirculation ID but we
+     * were not able to get a reference on it. */
+    if (type == DPIF_UC_MISS && upcall.recirc && !upcall.have_recirc_ref) {
         error = ENOSPC;
+        goto out;
     }
 
+    if (upcall.ukey && !ukey_install(udpif, upcall.ukey)) {
+        error = ENOSPC;
+    }
 out:
     if (!error) {
         upcall.ukey_persists = true;
@@ -1189,8 +1222,12 @@ handle_upcalls(struct udpif *udpif, struct upcall *upcalls,
          *    - The datapath already has too many flows.
          *
          *    - We received this packet via some flow installed in the kernel
-         *      already. */
-        if (may_put && upcall->type == DPIF_UC_MISS) {
+         *      already.
+         *
+         *    - Upcall was a recirculation but we do not have a reference to
+         *      to the recirculation ID. */
+        if (may_put && upcall->type == DPIF_UC_MISS &&
+            (!upcall->recirc || upcall->have_recirc_ref)) {
             struct udpif_key *ukey = upcall->ukey;
 
             upcall->ukey_persists = true;
@@ -1277,10 +1314,13 @@ ukey_create__(const struct nlattr *key, size_t key_len,
               const struct nlattr *mask, size_t mask_len,
               bool ufid_present, const ovs_u128 *ufid,
               const int pmd_id, const struct ofpbuf *actions,
-              uint64_t dump_seq, uint64_t reval_seq, long long int used)
+              uint64_t dump_seq, uint64_t reval_seq, long long int used,
+              const struct recirc_id_node *key_recirc, struct xlate_out *xout)
     OVS_NO_THREAD_SAFETY_ANALYSIS
 {
-    struct udpif_key *ukey = xmalloc(sizeof *ukey);
+    unsigned n_recircs = (key_recirc ? 1 : 0) + (xout ? xout->n_recircs : 0);
+    struct udpif_key *ukey = xmalloc(sizeof *ukey +
+                                     n_recircs * sizeof *ukey->recircs);
 
     memcpy(&ukey->keybuf, key, key_len);
     ukey->key = &ukey->keybuf.nla;
@@ -1303,11 +1343,22 @@ ukey_create__(const struct nlattr *key, size_t key_len,
     ukey->stats.used = used;
     ukey->xcache = NULL;
 
+    ukey->n_recircs = n_recircs;
+    if (key_recirc) {
+        ukey->recircs[0] = key_recirc->id;
+    }
+    if (xout && xout->n_recircs) {
+        const uint32_t *act_recircs = xlate_out_get_recircs(xout);
+
+        memcpy(ukey->recircs + (key_recirc ? 1 : 0), act_recircs,
+               xout->n_recircs * sizeof *ukey->recircs);
+        xlate_out_take_recircs(xout);
+    }
     return ukey;
 }
 
 static struct udpif_key *
-ukey_create_from_upcall(const struct upcall *upcall)
+ukey_create_from_upcall(struct upcall *upcall)
 {
     struct odputil_keybuf keystub, maskstub;
     struct ofpbuf keybuf, maskbuf;
@@ -1337,7 +1388,9 @@ ukey_create_from_upcall(const struct upcall *upcall)
     return ukey_create__(keybuf.data, keybuf.size, maskbuf.data, maskbuf.size,
                          true, upcall->ufid, upcall->pmd_id,
                          &upcall->put_actions, upcall->dump_seq,
-                         upcall->reval_seq, 0);
+                         upcall->reval_seq, 0,
+                         upcall->have_recirc_ref ? upcall->recirc : NULL,
+                         &upcall->xout);
 }
 
 static int
@@ -1349,12 +1402,15 @@ ukey_create_from_dpif_flow(const struct udpif *udpif,
     struct ofpbuf actions;
     uint64_t dump_seq, reval_seq;
     uint64_t stub[DPIF_FLOW_BUFSIZE / 8];
+    const struct nlattr *a;
+    unsigned int left;
 
-    if (!flow->key_len) {
+    if (!flow->key_len || !flow->actions_len) {
         struct ofpbuf buf;
         int err;
 
-        /* If the key was not provided by the datapath, fetch the full flow. */
+        /* If the key or actions were not provided by the datapath, fetch the
+         * full flow. */
         ofpbuf_use_stack(&buf, &stub, sizeof stub);
         err = dpif_flow_get(udpif->dpif, NULL, 0, &flow->ufid,
                             flow->pmd_id, &buf, &full_flow);
@@ -1363,13 +1419,23 @@ ukey_create_from_dpif_flow(const struct udpif *udpif,
         }
         flow = &full_flow;
     }
+
+    /* Check the flow actions for recirculation action.  As recirculation
+     * relies on OVS userspace internal state, we need to delete all old
+     * datapath flows with recirculation upon OVS restart. */
+    NL_ATTR_FOR_EACH_UNSAFE (a, left, flow->actions, flow->actions_len) {
+        if (nl_attr_type(a) == OVS_ACTION_ATTR_RECIRC) {
+            return EINVAL;
+        }
+    }
+
     dump_seq = seq_read(udpif->dump_seq);
     reval_seq = seq_read(udpif->reval_seq);
     ofpbuf_use_const(&actions, &flow->actions, flow->actions_len);
     *ukey = ukey_create__(flow->key, flow->key_len,
                           flow->mask, flow->mask_len, flow->ufid_present,
                           &flow->ufid, flow->pmd_id, &actions, dump_seq,
-                          reval_seq, flow->stats.used);
+                          reval_seq, flow->stats.used, NULL, NULL);
 
     return 0;
 }
@@ -1512,6 +1578,9 @@ ukey_delete__(struct udpif_key *ukey)
     OVS_NO_THREAD_SAFETY_ANALYSIS
 {
     if (ukey) {
+        for (int i = 0; i < ukey->n_recircs; i++) {
+            recirc_free_id(ukey->recircs[i]);
+        }
         xlate_cache_delete(ukey->xcache);
         ofpbuf_delete(ukey->actions);
         ovs_mutex_destroy(&ukey->mutex);
@@ -1776,8 +1845,8 @@ push_ukey_ops__(struct udpif *udpif, struct ukey_op *ops, size_t n_ops)
                 continue;
             }
 
-            error = xlate_lookup(udpif->backer, &flow, &ofproto,
-                                 NULL, NULL, &netflow, &ofp_in_port);
+            error = xlate_lookup(udpif->backer, &flow, &ofproto, NULL, NULL,
+                                 &netflow, &ofp_in_port);
             if (!error) {
                 struct xlate_in xin;
 
index bdb9d538e5168b9ca75ec70dc66d05ec1621dd11..3e22a5fa0d8168d48699e3a378192593ca815445 100644 (file)
@@ -71,9 +71,6 @@ VLOG_DEFINE_THIS_MODULE(ofproto_dpif_xlate);
 #define MAX_INTERNAL_RESUBMITS 1   /* Max resbmits allowed using rules in
                                       internal table. */
 
-/* Timeout for internal rules created to handle recirculation */
-#define RECIRC_TIMEOUT 60
-
 /* Maximum number of resubmit actions in a flow translation, whether they are
  * recursive or not. */
 #define MAX_RESUBMITS (MAX_RESUBMIT_RECURSION * MAX_RESUBMIT_RECURSION)
@@ -208,6 +205,91 @@ struct xlate_ctx {
     uint16_t user_cookie_offset;/* Used for user_action_cookie fixup. */
     bool exit;                  /* No further actions should be processed. */
 
+   /* These are used for non-bond recirculation.  The recirculation IDs are
+    * stored in xout and must be associated with a datapath flow (ukey),
+    * otherwise they will be freed when the xout is uninitialized.
+    *
+    *
+    * Steps in Recirculation Translation
+    * ==================================
+    *
+    * At some point during translation, the code recognizes the need for
+    * recirculation.  For example, recirculation is necessary when, after
+    * popping the last MPLS label, an action or a match tries to examine or
+    * modify a field that has been newly revealed following the MPLS label.
+    *
+    * The simplest part of the work to be done is to commit existing changes to
+    * the packet, which produces datapath actions corresponding to the changes,
+    * and after this, add an OVS_ACTION_ATTR_RECIRC datapath action.
+    *
+    * The main problem here is preserving state.  When the datapath executes
+    * OVS_ACTION_ATTR_RECIRC, it will upcall to userspace to get a translation
+    * for the post-recirculation actions.  At this point userspace has to
+    * resume the translation where it left off, which means that it has to
+    * execute the following:
+    *
+    *     - The action that prompted recirculation, and any actions following
+    *       it within the same flow.
+    *
+    *     - If the action that prompted recirculation was invoked within a
+    *       NXAST_RESUBMIT, then any actions following the resubmit.  These
+    *       "resubmit"s can be nested, so this has to go all the way up the
+    *       control stack.
+    *
+    *     - The OpenFlow 1.1+ action set.
+    *
+    * State that actions and flow table lookups can depend on, such as the
+    * following, must also be preserved:
+    *
+    *     - Metadata fields (input port, registers, OF1.1+ metadata, ...).
+    *
+    *     - Action set, stack
+    *
+    *     - The table ID and cookie of the flow being translated at each level
+    *       of the control stack (since OFPAT_CONTROLLER actions send these to
+    *       the controller).
+    *
+    * Translation allows for the control of this state preservation via these
+    * members.  When a need for recirculation is identified, the translation
+    * process:
+    *
+    * 1. Sets 'recirc_action_offset' to the current size of 'action_set'.  The
+    *    action set is part of what needs to be preserved, so this allows the
+    *    action set and the additional state to share the 'action_set' buffer.
+    *    Later steps can tell that setup for recirculation is in progress from
+    *    the nonnegative value of 'recirc_action_offset'.
+    *
+    * 2. Sets 'exit' to true to tell later steps that we're exiting from the
+    *    translation process.
+    *
+    * 3. Adds an OFPACT_UNROLL_XLATE action to 'action_set'.  This action
+    *    holds the current table ID and cookie so that they can be restored
+    *    during a post-recirculation upcall translation.
+    *
+    * 4. Adds the action that prompted recirculation and any actions following
+    *    it within the same flow to 'action_set', so that they can be executed
+    *    during a post-recirculation upcall translation.
+    *
+    * 5. Returns.
+    *
+    * 6. The action that prompted recirculation might be nested in a stack of
+    *    nested "resubmit"s that have actions remaining.  Each of these notices
+    *    that we're exiting (from 'exit') and that recirculation setup is in
+    *    progress (from 'recirc_action_offset') and responds by adding more
+    *    OFPACT_UNROLL_XLATE actions to 'action_set', as necessary, and any
+    *    actions that were yet unprocessed.
+    *
+    * The caller stores all the state produced by this process associated with
+    * the recirculation ID.  For post-recirculation upcall translation, the
+    * caller passes it back in for the new translation to execute.  The
+    * process yielded a set of ofpacts that can be translated directly, so it
+    * is not much of a special case at that point.
+    */
+    int recirc_action_offset;   /* Offset in 'action_set' to actions to be
+                                 * executed after recirculation, or -1. */
+    int last_unroll_offset;     /* Offset in 'action_set' to the latest unroll
+                                 * action, or -1. */
+
     /* True if a packet was but is no longer MPLS (due to an MPLS pop action).
      * This is a trigger for recirculation in cases where translating an action
      * or looking up a flow requires access to the fields of the packet after
@@ -230,6 +312,16 @@ struct xlate_ctx {
 
 static void xlate_action_set(struct xlate_ctx *ctx);
 
+static inline bool
+exit_recirculates(const struct xlate_ctx *ctx)
+{
+    /* When recirculating the 'recirc_action_offset' has a non-negative value.
+     */
+    return ctx->recirc_action_offset >= 0;
+}
+
+static void compose_recirculate_action(struct xlate_ctx *ctx);
+
 /* A controller may use OFPP_NONE as the ingress port to indicate that
  * it did not arrive on a "real" port.  'ofpp_none_bundle' exists for
  * when an input bundle is needed for validation (e.g., mirroring or
@@ -985,64 +1077,24 @@ xlate_ofport_remove(struct ofport_dpif *ofport)
     xlate_xport_remove(new_xcfg, xport);
 }
 
-/* Given a datapath and flow metadata ('backer', and 'flow' respectively)
- * returns the corresponding struct xport, or NULL if none is found. */
-static struct xport *
-xlate_lookup_xport(const struct dpif_backer *backer, const struct flow *flow)
-{
-    struct xlate_cfg *xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp);
-
-    return xport_lookup(xcfg, tnl_port_should_receive(flow)
-                         ? tnl_port_receive(flow)
-                         : odp_port_to_ofport(backer, flow->in_port.odp_port));
-}
-
 static struct ofproto_dpif *
 xlate_lookup_ofproto_(const struct dpif_backer *backer, const struct flow *flow,
                       ofp_port_t *ofp_in_port, const struct xport **xportp)
 {
-    struct ofproto_dpif *recv_ofproto = NULL;
-    struct ofproto_dpif *recirc_ofproto = NULL;
+    struct xlate_cfg *xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp);
     const struct xport *xport;
-    ofp_port_t in_port = OFPP_NONE;
-
-    *xportp = xport = xlate_lookup_xport(backer, flow);
-
-    if (xport) {
-        recv_ofproto = xport->xbridge->ofproto;
-        in_port = xport->ofp_port;
-    }
 
-    /* When recirc_id is set in 'flow', checks whether the ofproto_dpif that
-     * corresponds to the recirc_id is same as the receiving bridge.  If they
-     * are the same, uses the 'recv_ofproto' and keeps the 'ofp_in_port' as
-     * assigned.  Otherwise, uses the 'recirc_ofproto' that owns recirc_id and
-     * assigns OFPP_NONE to 'ofp_in_port'.  Doing this is in that, the
-     * recirculated flow must be processced by the ofproto which originates
-     * the recirculation, and as bridges can only see their own ports, the
-     * in_port of the 'recv_ofproto' should not be passed to the
-     * 'recirc_ofproto'.
-     *
-     * Admittedly, setting the 'ofp_in_port' to OFPP_NONE limits the
-     * 'recirc_ofproto' from meaningfully matching on in_port of recirculated
-     * flow, and should be fixed in the near future.
-     *
-     * TODO: Restore the original patch port.
-     */
-    if (recv_ofproto && flow->recirc_id) {
-        recirc_ofproto = ofproto_dpif_recirc_get_ofproto(backer,
-                                                         flow->recirc_id);
-        if (recv_ofproto != recirc_ofproto) {
-            *xportp = xport = NULL;
-            in_port = OFPP_NONE;
-        }
+    xport = xport_lookup(xcfg, tnl_port_should_receive(flow)
+                         ? tnl_port_receive(flow)
+                         : odp_port_to_ofport(backer, flow->in_port.odp_port));
+    if (OVS_UNLIKELY(!xport)) {
+        return NULL;
     }
-
+    *xportp = xport;
     if (ofp_in_port) {
-        *ofp_in_port = in_port;
+        *ofp_in_port = xport->ofp_port;
     }
-
-    return xport ? recv_ofproto : recirc_ofproto;
+    return xport->xbridge->ofproto;
 }
 
 /* Given a datapath and flow metadata ('backer', and 'flow' respectively)
@@ -2741,7 +2793,6 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
         struct flow old_flow = ctx->xin->flow;
         bool old_was_mpls = ctx->was_mpls;
         enum slow_path_reason special;
-        uint8_t table_id = rule_dpif_lookup_get_init_table_id(&ctx->xin->flow);
         struct ofpbuf old_stack = ctx->stack;
         union mf_subvalue new_stack[1024 / sizeof(union mf_subvalue)];
         struct ofpbuf old_action_set = ctx->action_set;
@@ -2762,11 +2813,17 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
             ctx->xout->slow |= special;
         } else if (may_receive(peer, ctx)) {
             if (xport_stp_forward_state(peer) && xport_rstp_forward_state(peer)) {
-                xlate_table_action(ctx, flow->in_port.ofp_port, table_id,
-                                   true, true);
+                xlate_table_action(ctx, flow->in_port.ofp_port, 0, true, true);
                 if (ctx->action_set.size) {
-                    /* Translate action set only if not dropping the packet. */
-                    xlate_action_set(ctx);
+                    /* Translate action set only if not dropping the packet and
+                     * not recirculating. */
+                    if (!exit_recirculates(ctx)) {
+                        xlate_action_set(ctx);
+                    }
+                }
+                /* Check if need to recirculate. */
+                if (exit_recirculates(ctx)) {
+                    compose_recirculate_action(ctx);
                 }
             } else {
                 /* Forwarding is disabled by STP and RSTP.  Let OFPP_NORMAL and
@@ -2775,11 +2832,17 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
                 size_t old_size = ctx->xout->odp_actions->size;
                 mirror_mask_t old_mirrors = ctx->xout->mirrors;
 
-                xlate_table_action(ctx, flow->in_port.ofp_port, table_id,
-                                   true, true);
+                xlate_table_action(ctx, flow->in_port.ofp_port, 0, true, true);
                 ctx->xout->mirrors = old_mirrors;
                 ctx->base_flow = old_base_flow;
                 ctx->xout->odp_actions->size = old_size;
+
+                /* Undo changes that may have been done for recirculation. */
+                if (exit_recirculates(ctx)) {
+                    ctx->action_set.size = ctx->recirc_action_offset;
+                    ctx->recirc_action_offset = -1;
+                    ctx->last_unroll_offset = -1;
+                }
             }
         }
 
@@ -3071,6 +3134,11 @@ xlate_group_bucket(struct xlate_ctx *ctx, struct ofputil_bucket *bucket)
     ofpbuf_uninit(&action_set);
     ofpbuf_uninit(&action_list);
 
+    /* Check if need to recirculate. */
+    if (exit_recirculates(ctx)) {
+        compose_recirculate_action(ctx);
+    }
+
     /* Roll back flow to previous state.
      * This is equivalent to cloning the packet for each bucket.
      *
@@ -3392,69 +3460,53 @@ execute_controller_action(struct xlate_ctx *ctx, int len,
     dp_packet_delete(packet);
 }
 
+/* Called only when ctx->recirc_action_offset is set. */
 static void
-compose_recirculate_action(struct xlate_ctx *ctx,
-                           const struct ofpact *ofpacts_base,
-                           const struct ofpact *ofpact_current,
-                           size_t ofpacts_base_len)
+compose_recirculate_action(struct xlate_ctx *ctx)
 {
+    struct recirc_metadata md;
     uint32_t id;
-    int error;
-    unsigned ofpacts_len;
-    struct match match;
-    struct rule *rule;
-    struct ofpbuf ofpacts;
 
-    ctx->exit = true;
+    ctx->xout->slow |= commit_odp_actions(&ctx->xin->flow, &ctx->base_flow,
+                                          ctx->xout->odp_actions,
+                                          &ctx->xout->wc,
+                                          ctx->xbridge->masked_set_action);
 
-    ofpacts_len = ofpacts_base_len -
-        ((uint8_t *)ofpact_current - (uint8_t *)ofpacts_base);
+    recirc_metadata_from_flow(&md, &ctx->xin->flow);
 
-    if (ctx->rule) {
-        id = rule_dpif_get_recirc_id(ctx->rule);
-    } else {
-        /* In the case where ctx has no rule then allocate a recirc id.
-         * The life-cycle of this recirc id is managed by associating it
-         * with the internal rule that is created to to handle
-         * recirculation below.
-         *
-         * The known use-case of this is packet_out which
-         * translates actions without a rule */
-        id = ofproto_dpif_alloc_recirc_id(ctx->xbridge->ofproto);
-    }
-    if (!id) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_ERR_RL(&rl, "Failed to allocate recirculation id");
-        return;
-    }
+    ovs_assert(ctx->recirc_action_offset >= 0);
 
-    match_init_catchall(&match);
-    match_set_recirc_id(&match, id);
-    ofpbuf_use_const(&ofpacts, ofpact_current, ofpacts_len);
-    error = ofproto_dpif_add_internal_flow(ctx->xbridge->ofproto, &match,
-                                           RECIRC_RULE_PRIORITY,
-                                           RECIRC_TIMEOUT, &ofpacts, &rule);
-    if (error) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_ERR_RL(&rl, "Failed to add post recirculation flow %s",
-                    match_to_string(&match, 0));
-        if (!ctx->rule) {
-            ofproto_dpif_free_recirc_id(ctx->xbridge->ofproto, id);
+    /* Only allocate recirculation ID if we have a packet. */
+    if (ctx->xin->packet) {
+        /* Allocate a unique recirc id for the given metadata state in the
+         * flow.  The life-cycle of this recirc id is managed by associating it
+         * with the udpif key ('ukey') created for each new datapath flow. */
+        id = recirc_alloc_id_ctx(ctx->xbridge->ofproto, 0, &md, &ctx->stack,
+                                 ctx->recirc_action_offset,
+                                 ctx->action_set.size, ctx->action_set.data);
+        if (!id) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_ERR_RL(&rl, "Failed to allocate recirculation id");
+            return;
         }
-        return;
-    }
-    /* If ctx has no rule then associate the recirc id, which
-     * was allocated above, with the internal rule. This allows
-     * the recirc id to be released when the internal rule times out. */
-    if (!ctx->rule) {
-        rule_set_recirc_id(rule, id);
+        xlate_out_add_recirc(ctx->xout, id);
+    } else {
+        /* Look up an existing recirc id for the given metadata state in the
+         * flow.  No new reference is taken, as the ID is RCU protected and is
+         * only required temporarily for verification. */
+        id = recirc_find_id(ctx->xbridge->ofproto, 0, &md, &ctx->stack,
+                            ctx->recirc_action_offset,
+                            ctx->action_set.size, ctx->action_set.data);
+        /* We let zero 'id' to be used in the RECIRC action below, which will
+         * fail all revalidations as zero is not a valid recirculation ID. */
     }
 
-    ctx->xout->slow |= commit_odp_actions(&ctx->xin->flow, &ctx->base_flow,
-                                          ctx->xout->odp_actions,
-                                          &ctx->xout->wc,
-                                          ctx->xbridge->masked_set_action);
     nl_msg_put_u32(ctx->xout->odp_actions, OVS_ACTION_ATTR_RECIRC, id);
+
+    /* Undo changes done by recirculation. */
+    ctx->action_set.size = ctx->recirc_action_offset;
+    ctx->recirc_action_offset = -1;
+    ctx->last_unroll_offset = -1;
 }
 
 static void
@@ -3935,6 +3987,7 @@ ofpact_needs_recirculation_after_mpls(const struct ofpact *a, struct xlate_ctx *
     case OFPACT_POP_QUEUE:
     case OFPACT_CONJUNCTION:
     case OFPACT_NOTE:
+    case OFPACT_UNROLL_XLATE:
     case OFPACT_OUTPUT_REG:
     case OFPACT_EXIT:
     case OFPACT_METER:
@@ -3987,6 +4040,104 @@ ofpact_needs_recirculation_after_mpls(const struct ofpact *a, struct xlate_ctx *
     OVS_NOT_REACHED();
 }
 
+static void
+recirc_put_unroll_xlate(struct xlate_ctx *ctx)
+{
+    struct ofpact_unroll_xlate *unroll;
+
+    unroll = ctx->last_unroll_offset < 0
+        ? NULL
+        : ALIGNED_CAST(struct ofpact_unroll_xlate *,
+                       (char *)ctx->action_set.data + ctx->last_unroll_offset);
+
+    /* Restore the table_id and rule cookie for a potential PACKET
+     * IN if needed. */
+    if (!unroll ||
+        (ctx->table_id != unroll->rule_table_id
+         || ctx->rule_cookie != unroll->rule_cookie)) {
+
+        ctx->last_unroll_offset = ctx->action_set.size;
+        unroll = ofpact_put_UNROLL_XLATE(&ctx->action_set);
+        unroll->rule_table_id = ctx->table_id;
+        unroll->rule_cookie = ctx->rule_cookie;
+    }
+}
+
+
+/* Copy remaining actions to the action_set to be executed after recirculation.
+ * UNROLL_XLATE action is inserted, if not already done so, before actions that
+ * may generate PACKET_INs from the current table and without matching another
+ * rule. */
+static void
+recirc_unroll_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
+                      struct xlate_ctx *ctx)
+{
+    const struct ofpact *a;
+
+    OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
+        switch (a->type) {
+            /* May generate PACKET INs. */
+        case OFPACT_OUTPUT_REG:
+        case OFPACT_GROUP:
+        case OFPACT_OUTPUT:
+        case OFPACT_CONTROLLER:
+        case OFPACT_DEC_MPLS_TTL:
+        case OFPACT_DEC_TTL:
+            recirc_put_unroll_xlate(ctx);
+            break;
+
+            /* These may not generate PACKET INs. */
+        case OFPACT_SET_TUNNEL:
+        case OFPACT_REG_MOVE:
+        case OFPACT_SET_FIELD:
+        case OFPACT_STACK_PUSH:
+        case OFPACT_STACK_POP:
+        case OFPACT_LEARN:
+        case OFPACT_WRITE_METADATA:
+        case OFPACT_RESUBMIT:        /* May indirectly generate PACKET INs, */
+        case OFPACT_GOTO_TABLE:      /* but from a different table and rule. */
+        case OFPACT_ENQUEUE:
+        case OFPACT_SET_VLAN_VID:
+        case OFPACT_SET_VLAN_PCP:
+        case OFPACT_STRIP_VLAN:
+        case OFPACT_PUSH_VLAN:
+        case OFPACT_SET_ETH_SRC:
+        case OFPACT_SET_ETH_DST:
+        case OFPACT_SET_IPV4_SRC:
+        case OFPACT_SET_IPV4_DST:
+        case OFPACT_SET_IP_DSCP:
+        case OFPACT_SET_IP_ECN:
+        case OFPACT_SET_IP_TTL:
+        case OFPACT_SET_L4_SRC_PORT:
+        case OFPACT_SET_L4_DST_PORT:
+        case OFPACT_SET_QUEUE:
+        case OFPACT_POP_QUEUE:
+        case OFPACT_PUSH_MPLS:
+        case OFPACT_POP_MPLS:
+        case OFPACT_SET_MPLS_LABEL:
+        case OFPACT_SET_MPLS_TC:
+        case OFPACT_SET_MPLS_TTL:
+        case OFPACT_MULTIPATH:
+        case OFPACT_BUNDLE:
+        case OFPACT_EXIT:
+        case OFPACT_UNROLL_XLATE:
+        case OFPACT_FIN_TIMEOUT:
+        case OFPACT_CLEAR_ACTIONS:
+        case OFPACT_WRITE_ACTIONS:
+        case OFPACT_METER:
+        case OFPACT_SAMPLE:
+            break;
+
+            /* These need not be copied for restoration. */
+        case OFPACT_NOTE:
+        case OFPACT_CONJUNCTION:
+            continue;
+        }
+        /* Copy the action over. */
+        ofpbuf_put(&ctx->action_set, a, OFPACT_ALIGN(a->len));
+    }
+}
+
 static void
 do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
                  struct xlate_ctx *ctx)
@@ -4006,13 +4157,21 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
         const struct ofpact_set_field *set_field;
         const struct mf_field *mf;
 
-        if (ctx->exit) {
-            break;
+        if (!ctx->exit && ofpact_needs_recirculation_after_mpls(a, ctx)) {
+            ctx->exit = true;
+            ctx->recirc_action_offset = ctx->action_set.size;
         }
 
-        if (ofpact_needs_recirculation_after_mpls(a, ctx)) {
-            compose_recirculate_action(ctx, ofpacts, a, ofpacts_len);
-            return;
+        if (ctx->exit) {
+            /* Check if need to store the remaining actions for later
+             * execution. */
+            if (exit_recirculates(ctx)) {
+                recirc_unroll_actions(a, OFPACT_ALIGN(ofpacts_len -
+                                                      ((uint8_t *)a -
+                                                       (uint8_t *)ofpacts)),
+                                      ctx);
+            }
+            break;
         }
 
         switch (a->type) {
@@ -4261,6 +4420,14 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             ctx->exit = true;
             break;
 
+        case OFPACT_UNROLL_XLATE: {
+            struct ofpact_unroll_xlate *unroll = ofpact_get_UNROLL_XLATE(a);
+
+            /* Restore translation context data that was stored earlier. */
+            ctx->table_id = unroll->rule_table_id;
+            ctx->rule_cookie = unroll->rule_cookie;
+            break;
+        }
         case OFPACT_FIN_TIMEOUT:
             memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
             ctx->xout->has_fin_timeout = true;
@@ -4330,13 +4497,21 @@ xlate_in_init(struct xlate_in *xin, struct ofproto_dpif *ofproto,
     xin->resubmit_stats = NULL;
     xin->skip_wildcards = false;
     xin->odp_actions = NULL;
+
+    /* Do recirc lookup. */
+    xin->recirc = flow->recirc_id
+        ? recirc_id_node_find(flow->recirc_id)
+        : NULL;
 }
 
 void
 xlate_out_uninit(struct xlate_out *xout)
 {
-    if (xout && xout->odp_actions == &xout->odp_actions_buf) {
-        ofpbuf_uninit(xout->odp_actions);
+    if (xout) {
+        if (xout->odp_actions == &xout->odp_actions_buf) {
+            ofpbuf_uninit(xout->odp_actions);
+        }
+        xlate_out_free_recircs(xout);
     }
 }
 
@@ -4498,9 +4673,8 @@ too_many_output_actions(const struct ofpbuf *odp_actions OVS_UNUSED)
 #endif
 }
 
-/* Translates the 'ofpacts_len' bytes of "struct ofpacts" starting at 'ofpacts'
- * into datapath actions in 'odp_actions', using 'ctx'.
- *
+/* Translates the flow, actions, or rule in 'xin' into datapath actions in
+ * 'xout'.
  * The caller must take responsibility for eventually freeing 'xout', with
  * xlate_out_uninit(). */
 void
@@ -4513,6 +4687,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
 
     enum slow_path_reason special;
     const struct ofpact *ofpacts;
+    struct xbridge *xbridge;
     struct xport *in_port;
     struct flow orig_flow;
     struct xlate_ctx ctx;
@@ -4551,6 +4726,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
     ctx.xout->has_fin_timeout = false;
     ctx.xout->nf_output_iface = NF_OUT_DROP;
     ctx.xout->mirrors = 0;
+    ctx.xout->n_recircs = 0;
 
     xout->odp_actions = xin->odp_actions;
     if (!xout->odp_actions) {
@@ -4560,10 +4736,13 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
     }
     ofpbuf_reserve(xout->odp_actions, NL_A_U32_SIZE);
 
-    ctx.xbridge = xbridge_lookup(xcfg, xin->ofproto);
-    if (!ctx.xbridge) {
+    xbridge = xbridge_lookup(xcfg, xin->ofproto);
+    if (!xbridge) {
         return;
     }
+    /* 'ctx.xbridge' may be changed by action processing, whereas 'xbridge'
+     * will remain set on the original input bridge. */
+    ctx.xbridge = xbridge;
     ctx.rule = xin->rule;
 
     ctx.base_flow = *flow;
@@ -4578,12 +4757,12 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         if (is_ip_any(flow)) {
             wc->masks.nw_frag |= FLOW_NW_FRAG_MASK;
         }
-        if (ctx.xbridge->enable_recirc) {
+        if (xbridge->enable_recirc) {
             /* Always exactly match recirc_id when datapath supports
              * recirculation.  */
             wc->masks.recirc_id = UINT32_MAX;
         }
-        if (ctx.xbridge->netflow) {
+        if (xbridge->netflow) {
             netflow_mask_wc(flow, wc);
         }
     }
@@ -4600,11 +4779,96 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
     ctx.rule_cookie = OVS_BE64_MAX;
     ctx.exit = false;
     ctx.was_mpls = false;
+    ctx.recirc_action_offset = -1;
+    ctx.last_unroll_offset = -1;
+
+    ctx.action_set_has_group = false;
+    ofpbuf_use_stub(&ctx.action_set,
+                    ctx.action_set_stub, sizeof ctx.action_set_stub);
+
+    ofpbuf_use_stub(&ctx.stack, ctx.init_stack, sizeof ctx.init_stack);
+
+    /* The in_port of the original packet before recirculation. */
+    in_port = get_ofp_port(xbridge, flow->in_port.ofp_port);
+
+    if (xin->recirc) {
+        const struct recirc_id_node *recirc = xin->recirc;
+
+        if (xin->ofpacts_len > 0 || ctx.rule) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+
+            VLOG_WARN_RL(&rl, "Recirculation conflict (%s)!",
+                         xin->ofpacts_len > 0
+                         ? "actions"
+                         : "rule");
+            return;
+        }
+
+        /* Set the bridge for post-recirculation processing if needed. */
+        if (ctx.xbridge->ofproto != recirc->ofproto) {
+            struct xlate_cfg *xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp);
+            const struct xbridge *new_bridge = xbridge_lookup(xcfg,
+                                                              recirc->ofproto);
+
+            if (OVS_UNLIKELY(!new_bridge)) {
+                /* Drop the packet if the bridge cannot be found. */
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_WARN_RL(&rl, "Recirculation bridge no longer exists.");
+                return;
+            }
+            ctx.xbridge = new_bridge;
+        }
+
+        /* Set the post-recirculation table id.  Note: A table lookup is done
+         * only if there are no post-recirculation actions. */
+        ctx.table_id = recirc->table_id;
+
+        /* Restore pipeline metadata. May change flow's in_port and other
+         * metadata to the values that existed when recirculation was
+         * triggered. */
+        recirc_metadata_to_flow(&recirc->metadata, flow);
+
+        /* Restore stack, if any. */
+        if (recirc->stack) {
+            ofpbuf_put(&ctx.stack, recirc->stack->data, recirc->stack->size);
+        }
+
+        /* Restore action set, if any. */
+        if (recirc->action_set_len) {
+            const struct ofpact *a;
+
+            ofpbuf_put(&ctx.action_set, recirc->ofpacts,
+                       recirc->action_set_len);
+
+            OFPACT_FOR_EACH(a, recirc->ofpacts, recirc->action_set_len) {
+                if (a->type == OFPACT_GROUP) {
+                    ctx.action_set_has_group = true;
+                    break;
+                }
+            }
+        }
+
+        /* Restore recirculation actions.  If there are no actions, processing
+         * will start with a lookup in the table set above. */
+        if (recirc->ofpacts_len > recirc->action_set_len) {
+            xin->ofpacts_len = recirc->ofpacts_len - recirc->action_set_len;
+            xin->ofpacts = recirc->ofpacts +
+                recirc->action_set_len / sizeof *recirc->ofpacts;
+        }
+    } else if (OVS_UNLIKELY(flow->recirc_id)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+
+        VLOG_WARN_RL(&rl, "Recirculation context not found for ID %"PRIx32,
+                     flow->recirc_id);
+        return;
+    }
 
     if (!xin->ofpacts && !ctx.rule) {
-        rule = rule_dpif_lookup(ctx.xbridge->ofproto, flow, wc,
-                                ctx.xin->xcache != NULL,
-                                ctx.xin->resubmit_stats, &ctx.table_id);
+        rule = rule_dpif_lookup_from_table(ctx.xbridge->ofproto, flow, wc,
+                                           ctx.xin->xcache != NULL,
+                                           ctx.xin->resubmit_stats,
+                                           &ctx.table_id,
+                                           flow->in_port.ofp_port, true, true);
         if (ctx.xin->resubmit_stats) {
             rule_dpif_credit_stats(rule, ctx.xin->resubmit_stats);
         }
@@ -4636,20 +4900,14 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         OVS_NOT_REACHED();
     }
 
-    ofpbuf_use_stub(&ctx.stack, ctx.init_stack, sizeof ctx.init_stack);
-
-    ctx.action_set_has_group = false;
-    ofpbuf_use_stub(&ctx.action_set,
-                    ctx.action_set_stub, sizeof ctx.action_set_stub);
-
-    if (mbridge_has_mirrors(ctx.xbridge->mbridge)) {
+    if (mbridge_has_mirrors(xbridge->mbridge)) {
         /* Do this conditionally because the copy is expensive enough that it
          * shows up in profiles. */
         orig_flow = *flow;
     }
 
-    in_port = get_ofp_port(ctx.xbridge, flow->in_port.ofp_port);
-    if (in_port && in_port->is_tunnel) {
+    /* Tunnel stats only for non-recirculated packets. */
+    if (!xin->recirc && in_port && in_port->is_tunnel) {
         if (ctx.xin->resubmit_stats) {
             netdev_vport_inc_rx(in_port->netdev, ctx.xin->resubmit_stats);
             if (in_port->bfd) {
@@ -4665,22 +4923,29 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         }
     }
 
-    special = process_special(&ctx, flow, in_port, ctx.xin->packet);
-    if (special) {
+    /* Do not perform special processing on recirculated packets,
+     * as recirculated packets are not really received by the bridge. */
+    if (!xin->recirc &&
+        (special = process_special(&ctx, flow, in_port, ctx.xin->packet))) {
         ctx.xout->slow |= special;
     } else {
         size_t sample_actions_len;
 
         if (flow->in_port.ofp_port
-            != vsp_realdev_to_vlandev(ctx.xbridge->ofproto,
+            != vsp_realdev_to_vlandev(xbridge->ofproto,
                                       flow->in_port.ofp_port,
                                       flow->vlan_tci)) {
             ctx.base_flow.vlan_tci = 0;
         }
 
-        add_sflow_action(&ctx);
-        add_ipfix_action(&ctx);
-        sample_actions_len = ctx.xout->odp_actions->size;
+        /* Sampling is done only for packets really received by the bridge. */
+        if (!xin->recirc) {
+            add_sflow_action(&ctx);
+            add_ipfix_action(&ctx);
+            sample_actions_len = ctx.xout->odp_actions->size;
+        } else {
+            sample_actions_len = 0;
+        }
 
         if (tnl_may_send && (!in_port || may_receive(in_port, &ctx))) {
             do_xlate_actions(ofpacts, ofpacts_len, &ctx);
@@ -4691,21 +4956,40 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
                             !xport_rstp_forward_state(in_port))) {
                 /* Drop all actions added by do_xlate_actions() above. */
                 ctx.xout->odp_actions->size = sample_actions_len;
+
+                /* Undo changes that may have been done for recirculation. */
+                if (exit_recirculates(&ctx)) {
+                    ctx.action_set.size = ctx.recirc_action_offset;
+                    ctx.recirc_action_offset = -1;
+                    ctx.last_unroll_offset = -1;
+                }
             } else if (ctx.action_set.size) {
-                /* Translate action set only if not dropping the packet. */
-                xlate_action_set(&ctx);
+                /* Translate action set only if not dropping the packet and
+                 * not recirculating. */
+                if (!exit_recirculates(&ctx)) {
+                    xlate_action_set(&ctx);
+                }
+            }
+            /* Check if need to recirculate. */
+            if (exit_recirculates(&ctx)) {
+                compose_recirculate_action(&ctx);
             }
         }
 
-        if (ctx.xbridge->has_in_band
+        /* Output only fully processed packets. */
+        if (!exit_recirculates(&ctx)
+            && xbridge->has_in_band
             && in_band_must_output_to_local_port(flow)
             && !actions_output_to_local_port(&ctx)) {
             compose_output_action(&ctx, OFPP_LOCAL, NULL);
         }
 
-        fix_sflow_action(&ctx);
-
-        if (mbridge_has_mirrors(ctx.xbridge->mbridge)) {
+        if (!xin->recirc) {
+            fix_sflow_action(&ctx);
+        }
+        /* Only mirror fully processed packets. */
+        if (!exit_recirculates(&ctx)
+            && mbridge_has_mirrors(xbridge->mbridge)) {
             add_mirror_actions(&ctx, &orig_flow);
         }
     }
@@ -4722,9 +5006,10 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         ctx.xout->slow |= SLOW_ACTION;
     }
 
-    if (mbridge_has_mirrors(ctx.xbridge->mbridge)) {
+    /* Update mirror stats only for packets really received by the bridge. */
+    if (!xin->recirc && mbridge_has_mirrors(xbridge->mbridge)) {
         if (ctx.xin->resubmit_stats) {
-            mirror_update_stats(ctx.xbridge->mbridge, xout->mirrors,
+            mirror_update_stats(xbridge->mbridge, xout->mirrors,
                                 ctx.xin->resubmit_stats->n_packets,
                                 ctx.xin->resubmit_stats->n_bytes);
         }
@@ -4732,12 +5017,13 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
             struct xc_entry *entry;
 
             entry = xlate_cache_add_entry(ctx.xin->xcache, XC_MIRROR);
-            entry->u.mirror.mbridge = mbridge_ref(ctx.xbridge->mbridge);
+            entry->u.mirror.mbridge = mbridge_ref(xbridge->mbridge);
             entry->u.mirror.mirrors = xout->mirrors;
         }
     }
 
-    if (ctx.xbridge->netflow) {
+    /* Do netflow only for packets really received by the bridge. */
+    if (!xin->recirc && xbridge->netflow) {
         /* Only update netflow if we don't have controller flow.  We don't
          * report NetFlow expiration messages for such facets because they
          * are just part of the control logic for the network, not real
@@ -4746,7 +5032,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
             || ofpacts->type != OFPACT_CONTROLLER
             || ofpact_next(ofpacts) < ofpact_end(ofpacts, ofpacts_len)) {
             if (ctx.xin->resubmit_stats) {
-                netflow_flow_update(ctx.xbridge->netflow, flow,
+                netflow_flow_update(xbridge->netflow, flow,
                                     xout->nf_output_iface,
                                     ctx.xin->resubmit_stats);
             }
@@ -4754,7 +5040,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
                 struct xc_entry *entry;
 
                 entry = xlate_cache_add_entry(ctx.xin->xcache, XC_NETFLOW);
-                entry->u.nf.netflow = netflow_ref(ctx.xbridge->netflow);
+                entry->u.nf.netflow = netflow_ref(xbridge->netflow);
                 entry->u.nf.flow = xmemdup(flow, sizeof *flow);
                 entry->u.nf.iface = xout->nf_output_iface;
             }
index 3e596fb5401c828e5fa15f2847b052a889572f97..6c8ade308c77b9df45af6c2489e4dd0904f696d0 100644 (file)
@@ -21,6 +21,7 @@
 #include "odp-util.h"
 #include "ofpbuf.h"
 #include "ofproto-dpif-mirror.h"
+#include "ofproto-dpif-rid.h"
 #include "ofproto-dpif.h"
 #include "ofproto.h"
 #include "stp.h"
@@ -54,11 +55,72 @@ struct xlate_out {
     ofp_port_t nf_output_iface; /* Output interface index for NetFlow. */
     mirror_mask_t mirrors;      /* Bitmap of associated mirrors. */
 
+    /* Recirculation IDs on which references are held. */
+    unsigned n_recircs;
+    union {
+        uint32_t recirc[2];   /* When n_recircs == 1 or 2 */
+        uint32_t *recircs;    /* When 'n_recircs' > 2 */
+    };
+
     uint64_t odp_actions_stub[256 / 8];
     struct ofpbuf odp_actions_buf;
     struct ofpbuf *odp_actions;
 };
 
+/* Helpers to abstract the recirculation union away. */
+static inline void
+xlate_out_add_recirc(struct xlate_out *xout, uint32_t id)
+{
+    if (OVS_LIKELY(xout->n_recircs < ARRAY_SIZE(xout->recirc))) {
+        xout->recirc[xout->n_recircs++] = id;
+    } else {
+        if (xout->n_recircs == ARRAY_SIZE(xout->recirc)) {
+            uint32_t *recircs = xmalloc(sizeof xout->recirc + sizeof id);
+
+            memcpy(recircs, xout->recirc, sizeof xout->recirc);
+            xout->recircs = recircs;
+        } else {
+            xout->recircs = xrealloc(xout->recircs,
+                                     (xout->n_recircs + 1) * sizeof id);
+        }
+        xout->recircs[xout->n_recircs++] = id;
+    }
+}
+
+static inline const uint32_t *
+xlate_out_get_recircs(const struct xlate_out *xout)
+{
+    if (OVS_LIKELY(xout->n_recircs <= ARRAY_SIZE(xout->recirc))) {
+        return xout->recirc;
+    } else {
+        return xout->recircs;
+    }
+}
+
+static inline void
+xlate_out_take_recircs(struct xlate_out *xout)
+{
+    if (OVS_UNLIKELY(xout->n_recircs > ARRAY_SIZE(xout->recirc))) {
+        free(xout->recircs);
+    }
+    xout->n_recircs = 0;
+}
+
+static inline void
+xlate_out_free_recircs(struct xlate_out *xout)
+{
+    if (OVS_LIKELY(xout->n_recircs <= ARRAY_SIZE(xout->recirc))) {
+        for (int i = 0; i < xout->n_recircs; i++) {
+            recirc_free_id(xout->recirc[i]);
+        }
+    } else {
+        for (int i = 0; i < xout->n_recircs; i++) {
+            recirc_free_id(xout->recircs[i]);
+        }
+        free(xout->recircs);
+    }
+}
+
 struct xlate_in {
     struct ofproto_dpif *ofproto;
 
@@ -137,6 +199,10 @@ struct xlate_in {
      * odp_actions stored in xlate_out.  If NULL, the default buffer will be
      * used. */
     struct ofpbuf *odp_actions;
+
+    /* The recirculation context related to this translation, as returned by
+     * xlate_lookup. */
+    const struct recirc_id_node *recirc;
 };
 
 void xlate_ofproto_set(struct ofproto_dpif *, const char *name, struct dpif *,
index d6a4d8ccb512a573602a252578fb5f30c9b64095..64024044a2679d03d488c5e0eeba4dfb66a3752a 100644 (file)
@@ -255,13 +255,6 @@ COVERAGE_DEFINE(rev_flow_table);
 COVERAGE_DEFINE(rev_mac_learning);
 COVERAGE_DEFINE(rev_mcast_snooping);
 
-/* Stores mapping between 'recirc_id' and 'ofproto-dpif'. */
-struct dpif_backer_recirc_node {
-    struct cmap_node cmap_node;
-    struct ofproto_dpif *ofproto;
-    uint32_t recirc_id;
-};
-
 /* All datapaths of a given type share a single dpif backer instance. */
 struct dpif_backer {
     char *type;
@@ -279,9 +272,6 @@ struct dpif_backer {
     bool recv_set_enable; /* Enables or disables receiving packets. */
 
     /* Recirculation. */
-    struct recirc_id_pool *rid_pool;       /* Recirculation ID pool. */
-    struct cmap recirc_map;         /* Map of 'recirc_id's to 'ofproto's. */
-    struct ovs_mutex recirc_mutex;  /* Protects 'recirc_map'. */
     bool enable_recirc;   /* True if the datapath supports recirculation */
 
     /* True if the datapath supports unique flow identifiers */
@@ -395,8 +385,6 @@ ofproto_dpif_get_enable_ufid(struct dpif_backer *backer)
     return backer->enable_ufid;
 }
 
-static struct ofport_dpif *get_ofp_port(const struct ofproto_dpif *ofproto,
-                                        ofp_port_t ofp_port);
 static void ofproto_trace(struct ofproto_dpif *, struct flow *,
                           const struct dp_packet *packet,
                           const struct ofpact[], size_t ofpacts_len,
@@ -857,27 +845,6 @@ dealloc(struct ofproto *ofproto_)
     free(ofproto);
 }
 
-/* Called when 'ofproto' is destructed.  Checks for and clears any
- * recirc_id leak. */
-static void
-dpif_backer_recirc_clear_ofproto(struct dpif_backer *backer,
-                                 struct ofproto_dpif *ofproto)
-{
-    struct dpif_backer_recirc_node *node;
-
-    ovs_mutex_lock(&backer->recirc_mutex);
-    CMAP_FOR_EACH (node, cmap_node, &backer->recirc_map) {
-        if (node->ofproto == ofproto) {
-            VLOG_ERR("recirc_id %"PRIu32", not freed when ofproto (%s) "
-                     "is destructed", node->recirc_id, ofproto->up.name);
-            cmap_remove(&backer->recirc_map, &node->cmap_node,
-                        node->recirc_id);
-            ovsrcu_postpone(free, node);
-        }
-    }
-    ovs_mutex_unlock(&backer->recirc_mutex);
-}
-
 static void
 close_dpif_backer(struct dpif_backer *backer)
 {
@@ -893,9 +860,6 @@ close_dpif_backer(struct dpif_backer *backer)
     ovs_rwlock_destroy(&backer->odp_to_ofport_lock);
     hmap_destroy(&backer->odp_to_ofport_map);
     shash_find_and_delete(&all_dpif_backers, backer->type);
-    recirc_id_pool_destroy(backer->rid_pool);
-    cmap_destroy(&backer->recirc_map);
-    ovs_mutex_destroy(&backer->recirc_mutex);
     free(backer->type);
     free(backer->dp_version_string);
     dpif_close(backer->dpif);
@@ -929,6 +893,8 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
     const char *name;
     int error;
 
+    recirc_init();
+
     backer = shash_find_data(&all_dpif_backers, type);
     if (backer) {
         backer->refcount++;
@@ -1010,9 +976,6 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
     backer->max_mpls_depth = check_max_mpls_depth(backer);
     backer->masked_set_action = check_masked_set_action(backer);
     backer->enable_ufid = check_ufid(backer);
-    backer->rid_pool = recirc_id_pool_create();
-    ovs_mutex_init(&backer->recirc_mutex);
-    cmap_init(&backer->recirc_map);
 
     backer->enable_tnl_push_pop = dpif_supports_tnl_push_pop(backer->dpif);
     atomic_count_init(&backer->tnl_count, 0);
@@ -1365,7 +1328,6 @@ add_internal_flows(struct ofproto_dpif *ofproto)
     uint64_t ofpacts_stub[128 / 8];
     struct ofpbuf ofpacts;
     struct rule *unused_rulep OVS_UNUSED;
-    struct ofpact_resubmit *resubmit;
     struct match match;
     int error;
     int id;
@@ -1408,22 +1370,6 @@ add_internal_flows(struct ofproto_dpif *ofproto)
     match_set_recirc_id(&match, 0);
     error = ofproto_dpif_add_internal_flow(ofproto, &match, 2, 0, &ofpacts,
                                            &unused_rulep);
-    if (error) {
-        return error;
-    }
-
-    /* Continue rule lookups for not-matched recirc rules from table 0.
-     *
-     * (priority=1), actions=resubmit(, 0)
-     */
-    resubmit = ofpact_put_RESUBMIT(&ofpacts);
-    resubmit->in_port = OFPP_IN_PORT;
-    resubmit->table_id = 0;
-
-    match_init_catchall(&match);
-    error = ofproto_dpif_add_internal_flow(ofproto, &match, 1, 0, &ofpacts,
-                                           &unused_rulep);
-
     return error;
 }
 
@@ -1461,7 +1407,7 @@ destruct(struct ofproto *ofproto_)
     }
     guarded_list_destroy(&ofproto->pins);
 
-    dpif_backer_recirc_clear_ofproto(ofproto->backer, ofproto);
+    recirc_free_ofproto(ofproto, ofproto->up.name);
 
     mbridge_unref(ofproto->mbridge);
 
@@ -2743,7 +2689,7 @@ bundle_add_port(struct ofbundle *bundle, ofp_port_t ofp_port,
 {
     struct ofport_dpif *port;
 
-    port = get_ofp_port(bundle->ofproto, ofp_port);
+    port = ofp_port_to_ofport(bundle->ofproto, ofp_port);
     if (!port) {
         return false;
     }
@@ -3249,8 +3195,8 @@ set_mcast_snooping_port(struct ofproto *ofproto_, void *aux,
 \f
 /* Ports. */
 
-static struct ofport_dpif *
-get_ofp_port(const struct ofproto_dpif *ofproto, ofp_port_t ofp_port)
+struct ofport_dpif *
+ofp_port_to_ofport(const struct ofproto_dpif *ofproto, ofp_port_t ofp_port)
 {
     struct ofport *ofport = ofproto_get_port(&ofproto->up, ofp_port);
     return ofport ? ofport_dpif_cast(ofport) : NULL;
@@ -3444,7 +3390,7 @@ static int
 port_del(struct ofproto *ofproto_, ofp_port_t ofp_port)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-    struct ofport_dpif *ofport = get_ofp_port(ofproto, ofp_port);
+    struct ofport_dpif *ofport = ofp_port_to_ofport(ofproto, ofp_port);
     int error = 0;
 
     if (!ofport) {
@@ -3756,21 +3702,13 @@ static void
 rule_dpif_set_recirc_id(struct rule_dpif *rule, uint32_t id)
     OVS_REQUIRES(rule->up.mutex)
 {
-    ovs_assert(!rule->recirc_id);
-    rule->recirc_id = id;
-}
-
-/* Returns 'rule''s recirculation id. */
-uint32_t
-rule_dpif_get_recirc_id(struct rule_dpif *rule)
-    OVS_REQUIRES(rule->up.mutex)
-{
-    if (!rule->recirc_id) {
-        struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
-
-        rule_dpif_set_recirc_id(rule, ofproto_dpif_alloc_recirc_id(ofproto));
+    ovs_assert(!rule->recirc_id || rule->recirc_id == id);
+    if (rule->recirc_id == id) {
+        /* Release the new reference to the same id. */
+        recirc_free_id(id);
+    } else {
+        rule->recirc_id = id;
     }
-    return rule->recirc_id;
 }
 
 /* Sets 'rule''s recirculation id. */
@@ -3784,32 +3722,6 @@ rule_set_recirc_id(struct rule *rule_, uint32_t id)
     ovs_mutex_unlock(&rule->up.mutex);
 }
 
-/* Lookup 'flow' in table 0 of 'ofproto''s classifier.
- * If 'wc' is non-null, sets the fields that were relevant as part of
- * the lookup. Returns the table id where a match or miss occurred via
- * 'table_id'.  This will be zero unless there was a miss and
- * OFPTC11_TABLE_MISS_CONTINUE is in effect for the sequence of tables
- * where misses occur, or TBL_INTERNAL if the rule has a non-zero
- * recirculation ID, and a match was found in the internal table, or if
- * there was no match and one of the special rules (drop_frags_rule,
- * miss_rule, or no_packet_in_rule) was returned.
- *
- * The return value is the found rule, which is valid at least until the next
- * RCU quiescent period.  If the rule needs to stay around longer,
- * a non-zero 'take_ref' must be passed in to cause a reference to be taken
- * on it before this returns. */
-struct rule_dpif *
-rule_dpif_lookup(struct ofproto_dpif *ofproto, struct flow *flow,
-                 struct flow_wildcards *wc, bool take_ref,
-                 const struct dpif_flow_stats *stats, uint8_t *table_id)
-{
-    *table_id = rule_dpif_lookup_get_init_table_id(flow);
-
-    return rule_dpif_lookup_from_table(ofproto, flow, wc, take_ref, stats,
-                                       table_id, flow->in_port.ofp_port, true,
-                                       true);
-}
-
 /* The returned rule (if any) is valid at least until the next RCU quiescent
  * period.  If the rule needs to stay around longer, a non-zero 'take_ref'
  * must be passed in to cause a reference to be taken on it.
@@ -3943,7 +3855,7 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto, struct flow *flow,
             || miss_config == OFPUTIL_TABLE_MISS_CONTROLLER) {
             struct ofport_dpif *port;
 
-            port = get_ofp_port(ofproto, old_in_port);
+            port = ofp_port_to_ofport(ofproto, old_in_port);
             if (!port) {
                 VLOG_WARN_RL(&rl, "packet-in on unknown OpenFlow port %"PRIu16,
                              old_in_port);
@@ -4034,9 +3946,7 @@ rule_destruct(struct rule *rule_)
 
     ovs_mutex_destroy(&rule->stats_mutex);
     if (rule->recirc_id) {
-        struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
-
-        ofproto_dpif_free_recirc_id(ofproto, rule->recirc_id);
+        recirc_free_id(rule->recirc_id);
     }
 }
 
@@ -5476,7 +5386,7 @@ vsp_add(struct ofport_dpif *port, ofp_port_t realdev_ofp_port, int vid)
 static odp_port_t
 ofp_port_to_odp_port(const struct ofproto_dpif *ofproto, ofp_port_t ofp_port)
 {
-    const struct ofport_dpif *ofport = get_ofp_port(ofproto, ofp_port);
+    const struct ofport_dpif *ofport = ofp_port_to_ofport(ofproto, ofp_port);
     return ofport ? ofport->odp_port : ODPP_NONE;
 }
 
@@ -5511,61 +5421,6 @@ odp_port_to_ofp_port(const struct ofproto_dpif *ofproto, odp_port_t odp_port)
     }
 }
 
-struct ofproto_dpif *
-ofproto_dpif_recirc_get_ofproto(const struct dpif_backer *backer,
-                                uint32_t recirc_id)
-{
-    struct dpif_backer_recirc_node *node;
-
-    node = CONTAINER_OF(cmap_find(&backer->recirc_map, recirc_id),
-                        struct dpif_backer_recirc_node, cmap_node);
-
-    return node ? node->ofproto : NULL;
-}
-
-uint32_t
-ofproto_dpif_alloc_recirc_id(struct ofproto_dpif *ofproto)
-{
-    struct dpif_backer *backer = ofproto->backer;
-    uint32_t recirc_id = recirc_id_alloc(backer->rid_pool);
-
-    if (recirc_id) {
-        struct dpif_backer_recirc_node *node = xmalloc(sizeof *node);
-
-        node->recirc_id = recirc_id;
-        node->ofproto = ofproto;
-
-        ovs_mutex_lock(&backer->recirc_mutex);
-        cmap_insert(&backer->recirc_map, &node->cmap_node, node->recirc_id);
-        ovs_mutex_unlock(&backer->recirc_mutex);
-    }
-
-    return recirc_id;
-}
-
-void
-ofproto_dpif_free_recirc_id(struct ofproto_dpif *ofproto, uint32_t recirc_id)
-{
-    struct dpif_backer *backer = ofproto->backer;
-    struct dpif_backer_recirc_node *node;
-
-    node = CONTAINER_OF(cmap_find(&backer->recirc_map, recirc_id),
-                        struct dpif_backer_recirc_node, cmap_node);
-    if (node) {
-        ovs_mutex_lock(&backer->recirc_mutex);
-        cmap_remove(&backer->recirc_map, &node->cmap_node, node->recirc_id);
-        ovs_mutex_unlock(&backer->recirc_mutex);
-        recirc_id_free(backer->rid_pool, node->recirc_id);
-
-        /* 'recirc_id' should never be freed by non-owning 'ofproto'. */
-        ovs_assert(node->ofproto == ofproto);
-
-        /* RCU postpone the free, since other threads may be referring
-         * to 'node' at same time. */
-        ovsrcu_postpone(free, node);
-    }
-}
-
 int
 ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
                                const struct match *match, int priority,
index 19c91f0a0ebbe8b409c96e824f772d93d196c0aa..9566924229b9dc13a952a9f4f2cb13d6e96f3326 100644 (file)
@@ -77,11 +77,6 @@ size_t ofproto_dpif_get_max_mpls_depth(const struct ofproto_dpif *);
 bool ofproto_dpif_get_enable_recirc(const struct ofproto_dpif *);
 bool ofproto_dpif_get_enable_ufid(struct dpif_backer *backer);
 
-struct rule_dpif *rule_dpif_lookup(struct ofproto_dpif *, struct flow *,
-                                   struct flow_wildcards *, bool take_ref,
-                                   const struct dpif_flow_stats *,
-                                   uint8_t *table_id);
-
 struct rule_dpif *rule_dpif_lookup_from_table(struct ofproto_dpif *,
                                               struct flow *,
                                               struct flow_wildcards *,
@@ -92,14 +87,6 @@ struct rule_dpif *rule_dpif_lookup_from_table(struct ofproto_dpif *,
                                               bool may_packet_in,
                                               bool honor_table_miss);
 
-/* If 'recirc_id' is set, starts looking up from internal table for
- * post recirculation flows or packets.  Otherwise, starts from table 0. */
-static inline uint8_t
-rule_dpif_lookup_get_init_table_id(const struct flow *flow)
-{
-    return flow->recirc_id ? TBL_INTERNAL : 0;
-}
-
 static inline void rule_dpif_ref(struct rule_dpif *);
 static inline void rule_dpif_unref(struct rule_dpif *);
 
@@ -115,7 +102,6 @@ uint8_t rule_dpif_get_table(const struct rule_dpif *);
 bool table_is_internal(uint8_t table_id);
 
 const struct rule_actions *rule_dpif_get_actions(const struct rule_dpif *);
-uint32_t rule_dpif_get_recirc_id(struct rule_dpif *);
 void rule_set_recirc_id(struct rule *, uint32_t id);
 
 ovs_be64 rule_dpif_get_flow_cookie(const struct rule_dpif *rule);
@@ -159,83 +145,9 @@ void ofproto_dpif_flow_mod(struct ofproto_dpif *, struct ofputil_flow_mod *);
 struct rule_dpif *ofproto_dpif_refresh_rule(struct rule_dpif *);
 
 struct ofport_dpif *odp_port_to_ofport(const struct dpif_backer *, odp_port_t);
+struct ofport_dpif *ofp_port_to_ofport(const struct ofproto_dpif *,
+                                       ofp_port_t);
 
-/*
- * Recirculation
- * =============
- *
- * Recirculation is a technique to allow a frame to re-enter the packet
- * processing path for one or multiple times to achieve more flexible packet
- * processing in the data path. MPLS handling and selecting bond slave port
- * of a bond ports.
- *
- * Data path and user space interface
- * -----------------------------------
- *
- * Two new fields, recirc_id and dp_hash, are added to the current flow data
- * structure. They are both of type uint32_t. In addition, a new action,
- * RECIRC, are added.
- *
- * The value recirc_id is used to distinguish a packet from multiple
- * iterations of recirculation. A packet initially received is considered of
- * having recirc_id of 0. Recirc_id is managed by the user space, opaque to
- * the data path.
- *
- * On the other hand, dp_hash can only be computed by the data path, opaque to
- * the user space. In fact, user space may not able to recompute the hash
- * value. The dp_hash value should be wildcarded when for a newly received
- * packet. RECIRC action specifies whether the hash is computed. If computed,
- * how many fields to be included in the hash computation. The computed hash
- * value is stored into the dp_hash field prior to recirculation.
- *
- * The RECIRC action computes and set the dp_hash field, set the recirc_id
- * field and then reprocess the packet as if it was received on the same input
- * port. RECIRC action works like a function call; actions listed behind the
- * RECIRC action will be executed after its execution.  RECIRC action can be
- * nested, data path implementation limits the number of recirculation executed
- * to prevent unreasonable nesting depth or infinite loop.
- *
- * Both flow fields and the RECIRC action are exposed as OpenFlow fields via
- * Nicira extensions.
- *
- * Post recirculation flow
- * ------------------------
- *
- * At the OpenFlow level, post recirculation rules are always hidden from the
- * controller.  They are installed in table 254 which is set up as a hidden
- * table during boot time. Those rules are managed by the local user space
- * program only.
- *
- * To speed up the classifier look up process, recirc_id is always reflected
- * into the metadata field, since recirc_id is required to be exactly matched.
- *
- * Classifier look up always starts with table 254. A post recirculation flow
- * lookup should find its hidden rule within this table. On the other hand, A
- * newly received packet should miss all post recirculation rules because its
- * recirc_id is zero, then hit a pre-installed lower priority rule to redirect
- * classifier to look up starting from table 0:
- *
- *       * , actions=resubmit(,0)
- *
- * Post recirculation data path flows are managed like other data path flows.
- * They are created on demand. Miss handling, stats collection and revalidation
- * work the same way as regular flows.
- *
- * If the bridge which originates the recirculation is different from the bridge
- * that receives the post recirculation packet (e.g. when patch port is used),
- * the packet will be processed directly by the recirculation bridge with
- * in_port set to OFPP_NONE.  Admittedly, doing this limits the recirculation
- * bridge from matching on in_port of post recirculation packets, and will be
- * fixed in the near future.
- *
- * TODO: Always restore the correct in_port.
- *
- */
-
-struct ofproto_dpif *ofproto_dpif_recirc_get_ofproto(const struct dpif_backer *ofproto,
-                                                     uint32_t recirc_id);
-uint32_t ofproto_dpif_alloc_recirc_id(struct ofproto_dpif *ofproto);
-void ofproto_dpif_free_recirc_id(struct ofproto_dpif *ofproto, uint32_t recirc_id);
 int ofproto_dpif_add_internal_flow(struct ofproto_dpif *,
                                    const struct match *, int priority,
                                    uint16_t idle_timeout,
index e2ef2e7c6aed9ab18738403f8facde7461fa99cc..571b8ce459a9a54313b94f616ab7237af8868933 100644 (file)
@@ -31,8 +31,12 @@ dnl Setup multiple MPLS tags.
 AT_CHECK([ovs-ofctl del-flows br0])
 
 AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 in_port=local,dl_type=0x0800,action=push_mpls:0x8847,set_field:10-\>mpls_label,push_mpls:0x8847,set_field:20-\>mpls_label,output:1])
-AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 table=0,dl_type=0x8847,in_port=1,mpls_label=60,action=pop_mpls:0x8847,goto_table:1])
-AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 table=1,dl_type=0x8847,in_port=1,mpls_label=50,action=pop_mpls:0x0800,output:LOCAL])
+# The resubmits will be executed after recirculation, which preserves the
+# register values.
+AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 cookie=0xa,table=0,dl_type=0x8847,in_port=1,mpls_label=60,action=set_field:10-\>reg0,pop_mpls:0x8847,goto_table:1])
+# The pop_mpls below recirculates from within a resubmit
+# After recirculation the (restored) register value is moved to IP ttl.
+AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 cookie=0xb,table=1,dl_type=0x8847,in_port=1,mpls_label=50,action=push:NXM_NX_REG0[[0..7]],pop_mpls:0x0800,set_field:0-\>nw_ttl,pop:NXM_NX_REG1[[0..7]],move:NXM_NX_REG1[[0..7]]-\>NXM_NX_IP_TTL[[]],output:LOCAL])
 
 dnl Double MPLS push
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(100),eth(src=f8:bc:12:44:34:b6,dst=f8:bc:12:46:58:e0),eth_type(0x0800),ipv4(src=1.1.2.92,dst=1.1.2.88,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
@@ -41,9 +45,19 @@ AT_CHECK([tail -1 stdout], [0],
 ])
 
 dnl Double MPLS pop
-AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=f8:bc:12:46:58:e0),eth_type(0x8847),mpls(label=60,tc=0/0,ttl=64,bos=0)'], [0], [stdout])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=f8:bc:12:46:58:e0),eth_type(0x8847),mpls(label=60,tc=0/0,ttl=64,bos=0)' -generate], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: pop_mpls(eth_type=0x8847),recirc(300)
+  [Datapath actions: pop_mpls(eth_type=0x8847),recirc(1)
+])
+
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(1),in_port(1),eth(src=f8:bc:12:44:34:b6,dst=f8:bc:12:46:58:e0),eth_type(0x8847),mpls(label=50,tc=0/0,ttl=64,bos=0)' -generate], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: pop_mpls(eth_type=0x800),recirc(2)
+])
+
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(2),in_port(1),eth(src=f8:bc:12:44:34:b6,dst=f8:bc:12:46:58:e0),eth_type(0x0800),ipv4(src=1.1.2.92,dst=1.1.2.88,proto=47,tos=0,ttl=64,frag=no)' -generate], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: set(ipv4(ttl=10)),100
 ])
 
 OVS_VSWITCHD_STOP
index b4d7a23da6822f3a170f04d1b966ae33aa6c0941..f3d886f1a9b2d07c49abca1599c191e0d98dc0bb 100644 (file)
@@ -186,7 +186,7 @@ table=0 priority=2 in_port=5 dl_vlan=1 actions=drop
 AT_CHECK([ovs-ofctl add-flows br-int flows.txt])
 
 # Sends a packet to trigger recirculation.
-# Should generate recirc_id(0x12d),dp_hash(0xc1261ba2/0xff).
+# Should generate recirc_id(0x2),dp_hash(0xc1261ba2/0xff).
 AT_CHECK([ovs-appctl netdev-dummy/receive p5 "in_port(5),eth(src=50:54:00:00:00:05,dst=50:54:00:00:01:00),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1)"])
 
 # Collects flow stats.
@@ -195,7 +195,7 @@ AT_CHECK([ovs-appctl revalidator/purge], [0])
 # Checks the flow stats in br1, should only be one flow with non-zero
 # 'n_packets' from internal table.
 AT_CHECK([ovs-appctl bridge/dump-flows br1 | ofctl_strip | grep -- "n_packets" | grep -- "table_id" | sed -e 's/dp_hash=0x[[0-9a-f]][[0-9a-f]]*/dp_hash=0x0/' -e 's/output:[[0-9]][[0-9]]*/output/'], [0], [dnl
-table_id=254, n_packets=1, n_bytes=64, priority=20,recirc_id=0x12d,dp_hash=0x0/0xff,actions=output
+table_id=254, n_packets=1, n_bytes=64, priority=20,recirc_id=0x2,dp_hash=0x0/0xff,actions=output
 ])
 
 # Checks the flow stats in br-int, should be only one match.
@@ -1571,7 +1571,8 @@ cookie=0xd dl_src=60:66:66:66:00:07 actions=pop_mpls:0x0800,learn(table=1,hard_t
 cookie=0xd dl_src=60:66:66:66:00:08 actions=pop_mpls:0x0806,resubmit(1,1)
 cookie=0xd table=1 arp actions=controller
 
-cookie=0xd dl_src=60:66:66:66:00:09 actions=pop_mpls:0x0800,mod_nw_tos:48,controller
+cookie=0xdeadbeef table=2 dl_src=60:66:66:66:00:09 actions=pop_mpls:0x0800,mod_nw_tos:48
+cookie=0xd dl_src=60:66:66:66:00:09 actions=resubmit(,2),controller
 cookie=0xd dl_src=60:66:66:66:00:0a actions=pop_mpls:0x0800,mod_nw_dst:10.0.0.1,controller
 cookie=0xd dl_src=60:66:66:66:00:0b actions=pop_mpls:0x0800,mod_nw_src:10.0.0.1,controller
 
@@ -1580,20 +1581,20 @@ cookie=0xd dl_src=60:66:66:66:01:01 actions=pop_mpls:0x8847,dec_mpls_ttl,control
 cookie=0xd dl_src=60:66:66:66:01:02 actions=pop_mpls:0x8848,load:3->OXM_OF_MPLS_TC[[]],controller
 
 cookie=0xd dl_src=60:66:66:66:02:00 actions=pop_mpls:0x8847,pop_mpls:0x0800,controller
-cookie=0xd dl_src=60:66:66:66:02:01 actions=pop_mpls:0x8848,pop_mpls:0x0800,dec_ttl,controller
-cookie=0xd dl_src=60:66:66:66:02:10 actions=pop_mpls:0x8847,dec_mpls_ttl,pop_mpls:0x0800,dec_ttl,controller
+cookie=0xe dl_src=60:66:66:66:02:01 actions=pop_mpls:0x8848,pop_mpls:0x0800,dec_ttl,controller
+cookie=0xe dl_src=60:66:66:66:02:10 actions=pop_mpls:0x8847,dec_mpls_ttl,pop_mpls:0x0800,dec_ttl,controller
 
-cookie=0xd dl_src=60:66:66:66:03:00 actions=pop_mpls:0x8848,pop_mpls:0x8848,controller
-cookie=0xd dl_src=60:66:66:66:03:01 actions=pop_mpls:0x8847,pop_mpls:0x8847,dec_mpls_ttl,controller
-cookie=0xd dl_src=60:66:66:66:03:10 actions=pop_mpls:0x8848,dec_mpls_ttl,pop_mpls:0x8848,dec_mpls_ttl,controller
+cookie=0xe dl_src=60:66:66:66:03:00 actions=pop_mpls:0x8848,pop_mpls:0x8848,controller
+cookie=0xe dl_src=60:66:66:66:03:01 actions=pop_mpls:0x8847,pop_mpls:0x8847,dec_mpls_ttl,controller
+cookie=0xe dl_src=60:66:66:66:03:10 actions=pop_mpls:0x8848,dec_mpls_ttl,pop_mpls:0x8848,dec_mpls_ttl,controller
 
-cookie=0xd dl_src=60:66:66:66:04:00 actions=pop_mpls:0x0800,push_mpls:0x8847,controller
-cookie=0xd dl_src=60:66:66:66:04:01 actions=pop_mpls:0x0800,push_mpls:0x8848,dec_mpls_ttl,controller
-cookie=0xd dl_src=60:66:66:66:04:10 actions=pop_mpls:0x0800,dec_ttl,push_mpls:0x8848,dec_mpls_ttl,controller
+cookie=0xf dl_src=60:66:66:66:04:00 actions=pop_mpls:0x0800,push_mpls:0x8847,controller
+cookie=0xf dl_src=60:66:66:66:04:01 actions=pop_mpls:0x0800,push_mpls:0x8848,dec_mpls_ttl,controller
+cookie=0xf dl_src=60:66:66:66:04:10 actions=pop_mpls:0x0800,dec_ttl,push_mpls:0x8848,dec_mpls_ttl,controller
 
-cookie=0xd dl_src=60:66:66:66:05:00 actions=push_mpls:0x8848,pop_mpls:0x8847,controller
-cookie=0xd dl_src=60:66:66:66:05:01 actions=push_mpls:0x8847,pop_mpls:0x8848,dec_mpls_ttl,controller
-cookie=0xd dl_src=60:66:66:66:05:10 actions=push_mpls:0x8848,dec_mpls_ttl,pop_mpls:0x8847,dec_mpls_ttl,controller
+cookie=0x5 dl_src=60:66:66:66:05:00 actions=push_mpls:0x8848,pop_mpls:0x8847,controller
+cookie=0x5 dl_src=60:66:66:66:05:01 actions=push_mpls:0x8847,pop_mpls:0x8848,dec_mpls_ttl,controller
+cookie=0x5 dl_src=60:66:66:66:05:10 actions=push_mpls:0x8848,dec_mpls_ttl,pop_mpls:0x8847,dec_mpls_ttl,controller
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
@@ -1867,13 +1868,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:01,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:01,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:01,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 ])
 
@@ -1893,13 +1894,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:02,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2dee
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:02,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2dee
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:02,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2dee
 ])
 
@@ -1919,13 +1920,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:03,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.2,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7743
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:03,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.2,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7743
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:03,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.2,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7743
 ])
 
@@ -1945,13 +1946,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:04,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.2,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7743
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:04,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.2,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7743
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:04,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.2,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7743
 ])
 
@@ -1971,13 +1972,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:05,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.106,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:76db
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:05,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.106,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:76db
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:05,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.106,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:76db
 ])
 
@@ -1997,13 +1998,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:06,dl_dst=50:54:00:00:00:07,nw_src=192.168.255.255,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7745
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:06,dl_dst=50:54:00:00:00:07,nw_src=192.168.255.255,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7745
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:06,dl_dst=50:54:00:00:00:07,nw_src=192.168.255.255,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7745
 ])
 
@@ -2023,13 +2024,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:07,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:07,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:07,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 ])
 
@@ -2073,13 +2074,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:09,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=48,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:09,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=48,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:09,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=48,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 ])
 
@@ -2099,13 +2100,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:0a,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2dee
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:0a,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2dee
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:0a,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2dee
 ])
 
@@ -2125,13 +2126,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:0b,dl_dst=50:54:00:00:00:07,nw_src=10.0.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2ded
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:0b,dl_dst=50:54:00:00:00:07,nw_src=10.0.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2ded
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:0b,dl_dst=50:54:00:00:00:07,nw_src=10.0.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:2ded
 ])
 
@@ -2179,13 +2180,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:01:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=30,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:01:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=30,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:01:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=30,mpls_bos=1
 ])
 
@@ -2233,13 +2234,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:00,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:00,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:00,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=255,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 ])
 
@@ -2256,17 +2257,18 @@ AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor
 for i in 1 2 3; do
     ovs-appctl netdev-dummy/receive p1 '50 54 00 00 00 07 60 66 66 66 02 01 88 48 00 01 40 20 00 01 41 1f 45 00 00 2c 00 00 00 00 ff 06 3b 78 c0 a8 00 01 c0 a8 00 02 00 50 00 00 00 00 00 2a 00 00 00 2a 50 00 27 10 77 44 00 00 48 4f 47 45'
 done
+
 OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:01,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:01,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:01,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 ])
 
@@ -2274,7 +2276,7 @@ AT_CHECK([ovs-appctl time/warp 5000], [0], [ignore])
 
 dnl Modified MPLS pop action.
 dnl The input is a frame with two MPLS label stack entries which tcpdump -vve shows as:
-dnl 60:66:66:66:02:10 > 50:54:00:00:02:10, ethertype MPLS unicast (0x8847), length 66: MPLS (label 20, exp 0, ttl 32)
+dnl 60:66:66:66:02:10 > 50:54:00:00:00:07, ethertype MPLS unicast (0x8847), length 66: MPLS (label 20, exp 0, ttl 32)
 dnl            (label 20, exp 0, [S], ttl 31)
 dnl            (tos 0x0, ttl 254, id 0, offset 0, flags [none], proto TCP (6), length 44)
 dnl        192.168.0.1.80 > 192.168.0.2.0: Flags [none], cksum 0x7744 (correct), seq 42:46, win 10000, length 4
@@ -2287,13 +2289,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:10,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:10,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:02:10,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 ])
 
@@ -2301,7 +2303,7 @@ AT_CHECK([ovs-appctl time/warp 5000], [0], [ignore])
 
 dnl Modified MPLS pop action.
 dnl The input is a frame with three MPLS label stack entries which tcpdump -vve shows as:
-dnl 60:66:66:66:03:00 > 50:54:00:00:00:00, ethertype MPLS unicast (0x8847), length 66: MPLS (label 20, exp 0, ttl 32)
+dnl 60:66:66:66:03:00 > 50:54:00:00:00:07, ethertype MPLS unicast (0x8847), length 66: MPLS (label 20, exp 0, ttl 32)
 dnl            (label 20, exp 0, ttl 31)
 dnl            (label 20, exp 0, [S], ttl 30)
 dnl            (tos 0x0, ttl 254, id 0, offset 0, flags [none], proto TCP (6), length 44)
@@ -2315,13 +2317,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:00,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=30,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:00,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=30,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:00,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=30,mpls_bos=1
 ])
 
@@ -2343,13 +2345,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=29,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=29,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=29,mpls_bos=1
 ])
 
@@ -2371,13 +2373,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:10,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=29,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:10,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=29,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xe total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:03:10,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=29,mpls_bos=1
 ])
 
@@ -2397,13 +2399,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:00,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=255,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:00,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=255,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:00,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=255,mpls_bos=1
 ])
 
@@ -2423,13 +2425,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:01,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=254,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:01,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=254,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:01,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=254,mpls_bos=1
 ])
 
@@ -2449,13 +2451,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA  ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:10,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=253,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:10,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=253,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0xf total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:04:10,dl_dst=50:54:00:00:00:07,mpls_label=0,mpls_tc=0,mpls_ttl=253,mpls_bos=1
 ])
 
@@ -2471,17 +2473,18 @@ AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor
 for i in 1 2 3; do
     ovs-appctl netdev-dummy/receive p1 '50 54 00 00 00 07 60 66 66 66 05 00 88 47 00 01 41 20 45 00 00 2c 00 00 00 00 ff 06 3a 78 c0 a8 00 01 c0 a8 00 02 00 50 00 00 00 00 00 2a 00 00 00 2a 50 00 27 10 77 44 00 00 48 4f 47 45'
 done
+
 OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:00,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:00,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): cookie=0xd total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:00,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=1
 ])
 
@@ -2501,13 +2504,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=31,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=31,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mplsm,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:01,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=31,mpls_bos=1
 ])
 
@@ -2523,22 +2526,26 @@ AT_CHECK([ovs-ofctl monitor br0 65534 -P nxm --detach --pidfile 2> ofctl_monitor
 for i in 1 2 3; do
     ovs-appctl netdev-dummy/receive p1 '50 54 00 00 00 07 60 66 66 66 05 10 88 47 00 01 41 20 45 00 00 2c 00 00 00 00 ff 06 3a 78 c0 a8 00 01 c0 a8 00 02 00 50 00 00 00 00 00 2a 00 00 00 2a 50 00 27 10 77 44 00 00 48 4f 47 45'
 done
+
 OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:10,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=31,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:10,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=31,mpls_bos=1
 dnl
-NXT_PACKET_IN (xid=0x0): table_id=254 cookie=0x0 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
+NXT_PACKET_IN (xid=0x0): cookie=0x5 total_len=62 in_port=1 (via action) data_len=62 (unbuffered)
 mpls,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:05:10,dl_dst=50:54:00:00:00:07,mpls_label=20,mpls_tc=0,mpls_ttl=31,mpls_bos=1
 ])
 
 AT_CHECK([ovs-appctl revalidator/purge], [0])
 AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x5, n_packets=3, n_bytes=186, dl_src=60:66:66:66:05:00 actions=push_mpls:0x8848,pop_mpls:0x8847,CONTROLLER:65535
+ cookie=0x5, n_packets=3, n_bytes=186, dl_src=60:66:66:66:05:01 actions=push_mpls:0x8847,pop_mpls:0x8848,dec_mpls_ttl,CONTROLLER:65535
+ cookie=0x5, n_packets=3, n_bytes=186, dl_src=60:66:66:66:05:10 actions=push_mpls:0x8848,dec_mpls_ttl,pop_mpls:0x8847,dec_mpls_ttl,CONTROLLER:65535
  cookie=0xa, n_packets=3, n_bytes=180, dl_src=40:44:44:44:44:42 actions=push_mpls:0x8847,load:0xa->OXM_OF_MPLS_LABEL[[]],load:0x3->OXM_OF_MPLS_TC[[]],CONTROLLER:65535
  cookie=0xa, n_packets=3, n_bytes=180, dl_src=40:44:44:44:44:43 actions=push_mpls:0x8847,load:0xa->OXM_OF_MPLS_LABEL[[]],load:0x3->OXM_OF_MPLS_TC[[]],CONTROLLER:65535
  cookie=0xa, n_packets=3, n_bytes=180, dl_src=40:44:44:44:44:44 actions=push_mpls:0x8847,load:0xa->OXM_OF_MPLS_LABEL[[]],load:0x3->OXM_OF_MPLS_TC[[]],CONTROLLER:65535
@@ -2558,26 +2565,24 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
  cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:00:05 actions=pop_mpls:0x0800,multipath(eth_src,50,modulo_n,256,0,NXM_OF_IP_SRC[[0..7]]),CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:00:06 actions=pop_mpls:0x0800,bundle_load(eth_src,50,hrw,ofport,NXM_OF_IP_SRC[[0..15]],slaves:1,2),CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:00:07 actions=pop_mpls:0x0800,learn(table=1,hard_timeout=60,eth_type=0x800,nw_proto=6,NXM_OF_IP_SRC[[]]=NXM_OF_IP_DST[[]]),CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:00:09 actions=pop_mpls:0x0800,mod_nw_tos:48,CONTROLLER:65535
+ cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:00:09 actions=resubmit(,2),CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:00:0a actions=pop_mpls:0x0800,mod_nw_dst:10.0.0.1,CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:00:0b actions=pop_mpls:0x0800,mod_nw_src:10.0.0.1,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:04:00 actions=pop_mpls:0x0800,push_mpls:0x8847,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:04:01 actions=pop_mpls:0x0800,push_mpls:0x8848,dec_mpls_ttl,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:04:10 actions=pop_mpls:0x0800,dec_ttl,push_mpls:0x8848,dec_mpls_ttl,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:05:00 actions=push_mpls:0x8848,pop_mpls:0x8847,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:05:01 actions=push_mpls:0x8847,pop_mpls:0x8848,dec_mpls_ttl,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:05:10 actions=push_mpls:0x8848,dec_mpls_ttl,pop_mpls:0x8847,dec_mpls_ttl,CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=186, dl_src=60:66:66:66:66:66 actions=pop_mpls:0x0800,CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=198, dl_src=60:66:66:66:01:00 actions=pop_mpls:0x8848,CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=198, dl_src=60:66:66:66:01:01 actions=pop_mpls:0x8847,dec_mpls_ttl,CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=198, dl_src=60:66:66:66:01:02 actions=pop_mpls:0x8848,load:0x3->OXM_OF_MPLS_TC[[]],CONTROLLER:65535
  cookie=0xd, n_packets=3, n_bytes=198, dl_src=60:66:66:66:02:00 actions=pop_mpls:0x8847,pop_mpls:0x0800,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=198, dl_src=60:66:66:66:02:01 actions=pop_mpls:0x8848,pop_mpls:0x0800,dec_ttl,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=198, dl_src=60:66:66:66:02:10 actions=pop_mpls:0x8847,dec_mpls_ttl,pop_mpls:0x0800,dec_ttl,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=210, dl_src=60:66:66:66:03:00 actions=pop_mpls:0x8848,pop_mpls:0x8848,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=210, dl_src=60:66:66:66:03:01 actions=pop_mpls:0x8847,pop_mpls:0x8847,dec_mpls_ttl,CONTROLLER:65535
- cookie=0xd, n_packets=3, n_bytes=210, dl_src=60:66:66:66:03:10 actions=pop_mpls:0x8848,dec_mpls_ttl,pop_mpls:0x8848,dec_mpls_ttl,CONTROLLER:65535
  cookie=0xd, table=1, n_packets=3, n_bytes=168, arp actions=CONTROLLER:65535
+ cookie=0xdeadbeef, table=2, n_packets=3, n_bytes=186, dl_src=60:66:66:66:00:09 actions=pop_mpls:0x0800,mod_nw_tos:48
+ cookie=0xe, n_packets=3, n_bytes=198, dl_src=60:66:66:66:02:01 actions=pop_mpls:0x8848,pop_mpls:0x0800,dec_ttl,CONTROLLER:65535
+ cookie=0xe, n_packets=3, n_bytes=198, dl_src=60:66:66:66:02:10 actions=pop_mpls:0x8847,dec_mpls_ttl,pop_mpls:0x0800,dec_ttl,CONTROLLER:65535
+ cookie=0xe, n_packets=3, n_bytes=210, dl_src=60:66:66:66:03:00 actions=pop_mpls:0x8848,pop_mpls:0x8848,CONTROLLER:65535
+ cookie=0xe, n_packets=3, n_bytes=210, dl_src=60:66:66:66:03:01 actions=pop_mpls:0x8847,pop_mpls:0x8847,dec_mpls_ttl,CONTROLLER:65535
+ cookie=0xe, n_packets=3, n_bytes=210, dl_src=60:66:66:66:03:10 actions=pop_mpls:0x8848,dec_mpls_ttl,pop_mpls:0x8848,dec_mpls_ttl,CONTROLLER:65535
+ cookie=0xf, n_packets=3, n_bytes=186, dl_src=60:66:66:66:04:00 actions=pop_mpls:0x0800,push_mpls:0x8847,CONTROLLER:65535
+ cookie=0xf, n_packets=3, n_bytes=186, dl_src=60:66:66:66:04:01 actions=pop_mpls:0x0800,push_mpls:0x8848,dec_mpls_ttl,CONTROLLER:65535
+ cookie=0xf, n_packets=3, n_bytes=186, dl_src=60:66:66:66:04:10 actions=pop_mpls:0x0800,dec_ttl,push_mpls:0x8848,dec_mpls_ttl,CONTROLLER:65535
 NXST_FLOW reply:
 ])
 
@@ -2661,13 +2666,13 @@ OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 6])
 OVS_APP_EXIT_AND_WAIT(ovs-ofctl)
 
 AT_CHECK([STRIP_METADATA ofctl_monitor.log], [0], [dnl
-OFPT_PACKET_IN (OF1.2) (xid=0x0): table_id=254 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+OFPT_PACKET_IN (OF1.2) (xid=0x0): total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:08,dl_dst=50:54:00:00:00:01,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=32,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-OFPT_PACKET_IN (OF1.2) (xid=0x0): table_id=254 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+OFPT_PACKET_IN (OF1.2) (xid=0x0): total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:08,dl_dst=50:54:00:00:00:01,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=32,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 dnl
-OFPT_PACKET_IN (OF1.2) (xid=0x0): table_id=254 total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
+OFPT_PACKET_IN (OF1.2) (xid=0x0): total_len=58 in_port=1 (via action) data_len=58 (unbuffered)
 tcp,in_port=0,vlan_tci=0x0000,dl_src=60:66:66:66:00:08,dl_dst=50:54:00:00:00:01,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=32,nw_ecn=0,nw_ttl=254,tp_src=80,tp_dst=0,tcp_flags=0 tcp_csum:7744
 ])