[ovs-dev] [PATCH v4 07/14] Implement serializing the state of packet traversal in "continuations".

Ben Pfaff blp at ovn.org
Fri Feb 19 08:34:17 UTC 2016


One purpose of OpenFlow packet-in messages is to allow a controller to
interpose on the path of a packet through the flow tables.  If, for
example, the controller needs to modify a packet in some way that the
switch doesn't directly support, the controller should be able to
program the switch to send it the packet, then modify the packet and
send it back to the switch to continue through the flow table.

That's the theory.  In practice, this doesn't work with any but the
simplest flow tables.  Packet-in messages simply don't include enough
context to allow the flow table traversal to continue.  For example:

    * Via "resubmit" actions, an Open vSwitch packet can have an
      effective "call stack", but a packet-in can't describe it, and
      so it would be lost.

    * Via "patch ports", an Open vSwitch packet can traverse multiple
      OpenFlow logical switches.  A packet-in can't describe or resume
      this context.

    * A packet-in can't preserve the stack used by NXAST_PUSH and
      NXAST_POP actions.

    * A packet-in can't preserve the OpenFlow 1.1+ action set.

    * A packet-in can't preserve the state of Open vSwitch mirroring
      or connection tracking.

This commit introduces a solution called "continuations".  A continuation
is the state of a packet's traversal through OpenFlow flow tables.  A
"controller" action with the "pause" flag, which is newly implemented in
this comit, generates a continuation and sends it to the OpenFlow
controller in a packet-in asynchronous message (only NXT_PACKET_IN2
supports continuations, so the controller must configure them with
NXT_SET_PACKET_IN_FORMAT).  The controller processes the packet-in,
possibly modifying some of its data, and sends it back to the switch with
an NXT_RESUME request, which causes flow table traversal to continue.  In
principle, a single packet can be paused and resumed multiple times.

Another way to look at it is:

    - "pause" is an extension of the existing OFPAT_CONTROLLER
      action.  It sends the packet to the controller, with full
      pipeline context (some of which is switch implementation
      dependent, and may thus vary from switch to switch).

    - A continuation is an extension of OFPT_PACKET_IN, allowing for
      implementation dependent metadata.

    - NXT_RESUME is an extension of OFPT_PACKET_OUT, with the
      semantics that the pipeline processing is continued with the
      original translation context from where it was left at the time
      it was paused.

Signed-off-by: Ben Pfaff <blp at ovn.org>
Acked-by: Jarno Rajahalme <jarno at ovn.org>
---
 NEWS                          |   5 +-
 include/openflow/nicira-ext.h |  96 ++++++++-
 lib/learning-switch.c         |   3 +-
 lib/meta-flow.c               |   9 +-
 lib/meta-flow.h               |   3 +-
 lib/ofp-actions.c             |  28 ++-
 lib/ofp-actions.h             |   5 +
 lib/ofp-errors.h              |  16 +-
 lib/ofp-msgs.h                |   4 +
 lib/ofp-print.c               |  78 +++++--
 lib/ofp-util.c                | 470 ++++++++++++++++++++++++++++++++++++------
 lib/ofp-util.h                |  57 ++++-
 lib/rconn.c                   |   3 +-
 ofproto/connmgr.c             |  23 ++-
 ofproto/connmgr.h             |   2 +-
 ofproto/fail-open.c           |  16 +-
 ofproto/ofproto-dpif-xlate.c  | 199 ++++++++++++++----
 ofproto/ofproto-dpif-xlate.h  |   4 +
 ofproto/ofproto-dpif.c        |  34 +++
 ofproto/ofproto-provider.h    |   3 +
 ofproto/ofproto.c             |  24 +++
 ovn/TODO                      |  57 -----
 ovn/controller/pinctrl.c      |   4 +-
 tests/ofp-actions.at          |  13 +-
 tests/ofp-print.at            |  12 ++
 tests/ofproto-dpif.at         | 172 ++++++++++++++++
 tests/ofproto-macros.at       |  35 +++-
 utilities/ovs-ofctl.8.in      |  11 +-
 utilities/ovs-ofctl.c         | 109 +++++++---
 29 files changed, 1239 insertions(+), 256 deletions(-)

diff --git a/NEWS b/NEWS
index 9ab6cae..ba4b7f7 100644
--- a/NEWS
+++ b/NEWS
@@ -6,7 +6,10 @@ Post-v2.5.0
      * OpenFlow 1.1+ OFPT_QUEUE_GET_CONFIG_REQUEST now supports OFPP_ANY.
      * OpenFlow 1.4+ OFPMP_QUEUE_DESC is now supported.
      * New property-based packet-in message format NXT_PACKET_IN2 with support
-       for arbitrary user-provided data.
+       for arbitrary user-provided data and for serializing flow table
+       traversal into a continuation for later resumption.
+     * New extension message NXT_SET_ASYNC_CONFIG2 to allow OpenFlow 1.4-like
+       control over asynchronous messages in earlier versions of OpenFlow.
    - ovs-ofctl:
      * queue-get-config command now allows a queue ID to be specified.
    - DPDK:
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 7e56066..77a735d 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -260,12 +260,103 @@ struct nx_packet_in {
 };
 OFP_ASSERT(sizeof(struct nx_packet_in) == 24);
 
-/* NXT_PACKET_IN2.
+/* NXT_PACKET_IN2
+ * ==============
  *
  * NXT_PACKET_IN2 is conceptually similar to OFPT_PACKET_IN but it is expressed
  * as an extensible set of properties instead of using a fixed structure.
  *
- * Added in Open vSwitch 2.6. */
+ * Added in Open vSwitch 2.6
+ *
+ *
+ * Continuations
+ * -------------
+ *
+ * When a "controller" action specifies the "pause" flag, the controller action
+ * freezes the packet's trip through Open vSwitch flow tables and serializes
+ * that state into the packet-in message as a "continuation".  The controller
+ * can later send the continuation back to the switch, which will restart the
+ * packet's traversal from the point where it was interrupted.  This permits an
+ * OpenFlow controller to interpose on a packet midway through processing in
+ * Open vSwitch.
+ *
+ * Continuations fit into packet processing this way:
+ *
+ * 1. A packet ingresses into Open vSwitch, which runs it through the OpenFlow
+ *    tables.
+ *
+ * 2. An OpenFlow flow executes a "controller" action that includes the "pause"
+ *    flag.  Open vSwitch serializes the packet processing state and sends it,
+ *    as an NXT_PACKET_IN2 that includes an additional NXPINT_CONTINUATION
+ *    property (the continuation), to the OpenFlow controller.
+ *
+ *    (The controller must use NXAST_CONTROLLER2 to generate the packet-in,
+ *    because only this form of the "controller" action has a "pause" flag.
+ *    Similarly, the controller must use NXT_SET_PACKET_IN_FORMAT to select
+ *    NXT_PACKET_IN2 as the packet-in format, because this is the only format
+ *    that supports continuation passing.)
+ *
+ * 3. The controller receives the NXT_PACKET_IN2 and processes it.  The
+ *    controller can interpret and, if desired, modify some of the contents of
+ *    the packet-in, such as the packet and the metadata being processed.
+ *
+ * 4. The controller sends the continuation back to the switch, using an
+ *    NXT_RESUME message.  Packet processing resumes where it left off.
+ *
+ * The controller might change the pipeline configuration concurrently with
+ * steps 2 through 4.  For example, it might add or remove OpenFlow flows.  If
+ * that happens, then the packet will experience a mix of processing from the
+ * two configurations, that is, the initial processing (before
+ * NXAST_CONTROLLER2) uses the initial flow table, and the later processing
+ * (after NXT_RESUME) uses the later flow table.
+ *
+ * External side effects (e.g. "output") of OpenFlow actions processed before
+ * NXAST_CONTROLLER2 is encountered might be executed during step 2 or step 4,
+ * and the details may vary among Open vSwitch features and versions.  Thus, a
+ * controller that wants to make sure that side effects are executed must pass
+ * the continuation back to the switch, that is, must not skip step 4.
+ *
+ * Architecturally, continuations may be "stateful" or "stateless", that is,
+ * they may or may not refer to buffered state maintained in Open vSwitch.
+ * This means that a controller should not attempt to resume a given
+ * continuations more than once (because the switch might have discarded the
+ * buffered state after the first use).  For the same reason, continuations
+ * might become "stale" if the controller takes too long to resume them
+ * (because the switch might have discarded old buffered state).  Taken
+ * together with the previous note, this means that a controller should resume
+ * each continuation exactly once (and promptly).
+ *
+ * Without the information in NXPINT_CONTINUATION, the controller can (with
+ * careful design, and help from the flow cookie) determine where the packet is
+ * in the pipeline, but in the general case it can't determine what nested
+ * "resubmit"s that may be in progress, or what data is on the stack maintained
+ * by NXAST_STACK_PUSH and NXAST_STACK_POP actions, what is in the OpenFlow
+ * action set, etc.
+ *
+ * Continuations are expensive because they require a round trip between the
+ * switch and the controller.  Thus, they should not be used to implement
+ * processing that needs to happen at "line rate".
+ *
+ * The contents of NXPINT_CONTINUATION are private to the switch, may change
+ * unpredictably from one version of Open vSwitch to another, and are not
+ * documented here.  The contents are also tied to a given Open vSwitch process
+ * and bridge, so that restarting Open vSwitch or deleting and recreating a
+ * bridge will cause the corresponding NXT_RESUME to be rejected.
+ *
+ * In the current implementation, Open vSwitch forks the packet processing
+ * pipeline across patch ports.  Suppose, for example, that the pipeline for
+ * br0 outputs to a patch port whose peer belongs to br1, and that the pipeline
+ * for br1 executes a controller action with the "pause" flag.  This only
+ * pauses processing within br1, and processing in br0 continues and possibly
+ * completes with visible side effects, such as outputting to ports, before
+ * br1's controller receives or processes the continuation.  This
+ * implementation maintains the independence of separate bridges and, since
+ * processing in br1 cannot affect the behavior of br0 anyway, should not cause
+ * visible behavioral changes.
+ *
+ * A packet-in that includes a continuation always includes the entire packet
+ * and is never buffered.
+ */
 enum nx_packet_in2_prop_type {
     /* Packet. */
     NXPINT_PACKET,              /* Raw packet data. */
@@ -280,6 +371,7 @@ enum nx_packet_in2_prop_type {
     NXPINT_REASON,              /* uint8_t, one of OFPR_*. */
     NXPINT_METADATA,            /* NXM or OXM for metadata fields. */
     NXPINT_USERDATA,            /* From NXAST_CONTROLLER2 userdata. */
+    NXPINT_CONTINUATION,        /* Private data for continuing processing. */
 };
 
 /* Configures the "role" of the sending controller.  The default role is:
diff --git a/lib/learning-switch.c b/lib/learning-switch.c
index 8f194b3..19a90db 100644
--- a/lib/learning-switch.c
+++ b/lib/learning-switch.c
@@ -511,7 +511,6 @@ static void
 process_packet_in(struct lswitch *sw, const struct ofp_header *oh)
 {
     struct ofputil_packet_in pi;
-    size_t total_len;
     uint32_t buffer_id;
     uint32_t queue_id;
     ofp_port_t out_port;
@@ -525,7 +524,7 @@ process_packet_in(struct lswitch *sw, const struct ofp_header *oh)
     struct dp_packet pkt;
     struct flow flow;
 
-    error = ofputil_decode_packet_in(oh, &pi, &total_len, &buffer_id);
+    error = ofputil_decode_packet_in(oh, true, &pi, NULL, &buffer_id, NULL);
     if (error) {
         VLOG_WARN_RL(&rl, "failed to decode packet-in: %s",
                      ofperr_to_string(error));
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 6bd0b99..16b9c92 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -171,6 +171,13 @@ mf_subvalue_shift(union mf_subvalue *value, int n)
     }
 }
 
+/* Appends a formatted representation of 'sv' to 's'. */
+void
+mf_subvalue_format(const union mf_subvalue *sv, struct ds *s)
+{
+    ds_put_hex(s, sv, sizeof *sv);
+}
+
 /* Returns true if 'wc' wildcards all the bits in field 'mf', false if 'wc'
  * specifies at least one bit in the field.
  *
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 53bbc9e..cb4e22d 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1940,6 +1940,7 @@ bool mf_subvalue_intersect(const union mf_subvalue *a_value,
                            union mf_subvalue *dst_mask);
 int mf_subvalue_width(const union mf_subvalue *);
 void mf_subvalue_shift(union mf_subvalue *, int n);
+void mf_subvalue_format(const union mf_subvalue *, struct ds *);
 
 /* An array of fields with values */
 struct field_array {
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 38011c3..36b5526 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
+ * Copyright (c) 2008-2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -628,12 +628,16 @@ struct nx_action_controller {
 };
 OFP_ASSERT(sizeof(struct nx_action_controller) == 16);
 
-/* Properties for NXAST_CONTROLLER2. */
+/* Properties for NXAST_CONTROLLER2.
+ *
+ * For more information on the effect of NXAC2PT_PAUSE, see the large comment
+ * on NXT_PACKET_IN2 in nicira-ext.h */
 enum nx_action_controller2_prop_type {
     NXAC2PT_MAX_LEN,            /* ovs_be16 max length to send controller. */
     NXAC2PT_CONTROLLER_ID,      /* ovs_be16 controller ID of destination. */
     NXAC2PT_REASON,             /* uint8_t reason (OFPR_*). */
     NXAC2PT_USERDATA,           /* Data to copy into NXPINT_USERDATA. */
+    NXAC2PT_PAUSE,              /* Flag to pause pipeline to resume later. */
 };
 
 /* Action structure for NXAST_CONTROLLER2.
@@ -717,6 +721,10 @@ decode_NXAST_RAW_CONTROLLER2(const struct nx_action_controller2 *nac2,
             oc->userdata_len = ofpbuf_msgsize(&payload);
             break;
 
+        case NXAC2PT_PAUSE:
+            oc->pause = true;
+            break;
+
         default:
             error = OFPPROP_UNKNOWN(false, "NXAST_RAW_CONTROLLER2", type);
             break;
@@ -737,6 +745,7 @@ encode_CONTROLLER(const struct ofpact_controller *controller,
                   struct ofpbuf *out)
 {
     if (controller->userdata_len
+        || controller->pause
         || controller->ofpact.raw == NXAST_RAW_CONTROLLER2) {
         size_t start_ofs = out->size;
         put_NXAST_CONTROLLER2(out);
@@ -754,6 +763,9 @@ encode_CONTROLLER(const struct ofpact_controller *controller,
             ofpprop_put(out, NXAC2PT_USERDATA, controller->userdata,
                         controller->userdata_len);
         }
+        if (controller->pause) {
+            ofpprop_put_flag(out, NXAC2PT_PAUSE);
+        }
         pad_ofpat(out, start_ofs);
     } else {
         struct nx_action_controller *nac;
@@ -773,6 +785,7 @@ parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts,
     uint16_t controller_id = 0;
     uint16_t max_len = UINT16_MAX;
     const char *userdata = NULL;
+    bool pause = false;
 
     if (!arg[0]) {
         /* Use defaults. */
@@ -801,6 +814,8 @@ parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts,
                 }
             } else if (!strcmp(name, "userdata")) {
                 userdata = value;
+            } else if (!strcmp(name, "pause")) {
+                pause = true;
             } else {
                 return xasprintf("unknown key \"%s\" parsing controller "
                                  "action", name);
@@ -808,7 +823,7 @@ parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts,
         }
     }
 
-    if (reason == OFPR_ACTION && controller_id == 0 && !userdata) {
+    if (reason == OFPR_ACTION && controller_id == 0 && !userdata && !pause) {
         struct ofpact_output *output;
 
         output = ofpact_put_OUTPUT(ofpacts);
@@ -821,6 +836,7 @@ parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts,
         controller->max_len = max_len;
         controller->reason = reason;
         controller->controller_id = controller_id;
+        controller->pause = pause;
 
         if (userdata) {
             size_t start_ofs = ofpacts->size;
@@ -853,7 +869,8 @@ format_hex_arg(struct ds *s, const uint8_t *data, size_t len)
 static void
 format_CONTROLLER(const struct ofpact_controller *a, struct ds *s)
 {
-    if (a->reason == OFPR_ACTION && !a->controller_id && !a->userdata_len) {
+    if (a->reason == OFPR_ACTION && !a->controller_id && !a->userdata_len
+        && !a->pause) {
         ds_put_format(s, "CONTROLLER:%"PRIu16, a->max_len);
     } else {
         enum ofp_packet_in_reason reason = a->reason;
@@ -877,6 +894,9 @@ format_CONTROLLER(const struct ofpact_controller *a, struct ds *s)
             format_hex_arg(s, a->userdata, a->userdata_len);
             ds_put_char(s, ',');
         }
+        if (a->pause) {
+            ds_put_cstr(s, "pause,");
+        }
         ds_chomp(s, ',');
         ds_put_char(s, ')');
     }
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index d4125e6..58d7857 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -246,6 +246,11 @@ struct ofpact_controller {
     uint16_t controller_id;     /* Controller ID to send packet-in. */
     enum ofp_packet_in_reason reason; /* Reason to put in packet-in. */
 
+    /* If true, this action freezes packet traversal of the OpenFlow tables and
+     * adds a continuation to the packet-in message, that a controller can use
+     * to resume that traversal. */
+    bool pause;
+
     /* Arbitrary data to include in the packet-in message (currently, only in
      * NXT_PACKET_IN2). */
     uint16_t userdata_len;
diff --git a/lib/ofp-errors.h b/lib/ofp-errors.h
index 8e13873..a9abc05 100644
--- a/lib/ofp-errors.h
+++ b/lib/ofp-errors.h
@@ -764,9 +764,19 @@ enum ofperr {
      * to be mapped is the same as one assigned to a different field. */
     OFPERR_NXTTMFC_DUP_ENTRY,
 
-/* ## ------------------ ## */
-/* ## OFPET_EXPERIMENTER ## */
-/* ## ------------------ ## */
+/* ## ---------- ## */
+/* ## NXT_RESUME ## */
+/* ## ---------- ## */
+
+    /* NX1.0-1.1(1,533), NX1.2+(34).  This datapath doesn't support
+     * NXT_RESUME. */
+    OFPERR_NXR_NOT_SUPPORTED,
+
+    /* NX1.0-1.1(1,534), NX1.2+(35).  Continuation is stale: Open vSwitch
+     * process has been restarted or bridge has been destroyed since
+     * continuation was generated, or continuation was not generated by this
+     * Open vSwitch instance. */
+    OFPERR_NXR_STALE,
 };
 
 const char *ofperr_domain_get_name(enum ofp_version);
diff --git a/lib/ofp-msgs.h b/lib/ofp-msgs.h
index 8ce1f35..e2d55b7 100644
--- a/lib/ofp-msgs.h
+++ b/lib/ofp-msgs.h
@@ -452,6 +452,9 @@ enum ofpraw {
 
     /* NXT 1.0+ (26): struct nx_tlv_table_reply, struct nx_tlv_map[]. */
     OFPRAW_NXT_TLV_TABLE_REPLY,
+
+    /* NXT 1.0+ (28): uint8_t[8][]. */
+    OFPRAW_NXT_RESUME,
 };
 
 /* Decoding messages into OFPRAW_* values. */
@@ -670,6 +673,7 @@ enum ofptype {
     OFPTYPE_NXT_TLV_TABLE_MOD, /* OFPRAW_NXT_TLV_TABLE_MOD. */
     OFPTYPE_NXT_TLV_TABLE_REQUEST, /* OFPRAW_NXT_TLV_TABLE_REQUEST. */
     OFPTYPE_NXT_TLV_TABLE_REPLY, /* OFPRAW_NXT_TLV_TABLE_REPLY. */
+    OFPTYPE_NXT_RESUME,          /* OFPRAW_NXT_RESUME. */
 
     /* Flow monitor extension. */
     OFPTYPE_FLOW_MONITOR_CANCEL,        /* OFPRAW_NXT_FLOW_MONITOR_CANCEL. */
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 39e1c8b..ada0d2f 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -99,67 +99,108 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
                     int verbosity)
 {
     char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE];
-    struct ofputil_packet_in pin;
+    struct ofputil_packet_in_private pin;
+    const struct ofputil_packet_in *public = &pin.public;
     uint32_t buffer_id;
     size_t total_len;
-    int error;
+    enum ofperr error;
 
-    error = ofputil_decode_packet_in(oh, &pin, &total_len, &buffer_id);
+    error = ofputil_decode_packet_in_private(oh, true,
+                                             &pin, &total_len, &buffer_id);
     if (error) {
         ofp_print_error(string, error);
         return;
     }
 
-    if (pin.table_id) {
-        ds_put_format(string, " table_id=%"PRIu8, pin.table_id);
+    if (public->table_id) {
+        ds_put_format(string, " table_id=%"PRIu8, public->table_id);
     }
 
-    if (pin.cookie != OVS_BE64_MAX) {
-        ds_put_format(string, " cookie=0x%"PRIx64, ntohll(pin.cookie));
+    if (public->cookie != OVS_BE64_MAX) {
+        ds_put_format(string, " cookie=0x%"PRIx64, ntohll(public->cookie));
     }
 
     ds_put_format(string, " total_len=%"PRIuSIZE" ", total_len);
 
-    match_format(&pin.flow_metadata, string, OFP_DEFAULT_PRIORITY);
+    match_format(&public->flow_metadata, string, OFP_DEFAULT_PRIORITY);
 
     ds_put_format(string, " (via %s)",
-                  ofputil_packet_in_reason_to_string(pin.reason,
+                  ofputil_packet_in_reason_to_string(public->reason,
                                                      reasonbuf,
                                                      sizeof reasonbuf));
 
-    ds_put_format(string, " data_len=%"PRIuSIZE, pin.packet_len);
+    ds_put_format(string, " data_len=%"PRIuSIZE, public->packet_len);
     if (buffer_id == UINT32_MAX) {
         ds_put_format(string, " (unbuffered)");
-        if (total_len != pin.packet_len) {
+        if (total_len != public->packet_len) {
             ds_put_format(string, " (***total_len != data_len***)");
         }
     } else {
         ds_put_format(string, " buffer=0x%08"PRIx32, buffer_id);
-        if (total_len < pin.packet_len) {
+        if (total_len < public->packet_len) {
             ds_put_format(string, " (***total_len < data_len***)");
         }
     }
     ds_put_char(string, '\n');
 
-    if (pin.userdata_len) {
+    if (public->userdata_len) {
         ds_put_cstr(string, " userdata=");
-        for (size_t i = 0; i < pin.userdata_len; i++) {
+        for (size_t i = 0; i < public->userdata_len; i++) {
             if (i) {
                 ds_put_char(string, '.');
             }
-            ds_put_format(string, "%02x", pin.userdata[i]);
+            ds_put_format(string, "%02x", public->userdata[i]);
+        }
+        ds_put_char(string, '\n');
+    }
+
+    if (!uuid_is_zero(&pin.bridge)) {
+        ds_put_format(string, " continuation.bridge="UUID_FMT"\n",
+                      UUID_ARGS(&pin.bridge));
+    }
+
+    if (pin.n_stack) {
+        ds_put_cstr(string, " continuation.stack=");
+        for (size_t i = 0; i < pin.n_stack; i++) {
+            if (i) {
+                ds_put_char(string, ' ');
+            }
+            mf_subvalue_format(&pin.stack[i], string);
         }
+    }
+
+    if (pin.mirrors) {
+        ds_put_format(string, " continuation.mirrors=0x%"PRIx32"\n",
+                      pin.mirrors);
+    }
+
+    if (pin.conntracked) {
+        ds_put_cstr(string, " continuation.conntracked=true\n");
+    }
+
+    if (pin.actions_len) {
+        ds_put_cstr(string, " continuation.actions=");
+        ofpacts_format(pin.actions, pin.actions_len, string);
+        ds_put_char(string, '\n');
+    }
+
+    if (pin.action_set_len) {
+        ds_put_cstr(string, " continuation.action_set=");
+        ofpacts_format(pin.action_set, pin.action_set_len, string);
         ds_put_char(string, '\n');
     }
 
     if (verbosity > 0) {
-        char *packet = ofp_packet_to_string(pin.packet, pin.packet_len);
+        char *packet = ofp_packet_to_string(public->packet,
+                                            public->packet_len);
         ds_put_cstr(string, packet);
         free(packet);
     }
     if (verbosity > 2) {
-        ds_put_hex_dump(string, pin.packet, pin.packet_len, 0, false);
+        ds_put_hex_dump(string, public->packet, public->packet_len, 0, false);
     }
+
+    ofputil_packet_in_private_destroy(&pin);
 }
 
 static void
@@ -3333,6 +3374,9 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
         ofp_print_tlv_table_reply(string, msg);
         break;
 
+    case OFPTYPE_NXT_RESUME:
+        ofp_print_packet_in(string, msg, verbosity);
+        break;
     }
 }
 
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 085bc04..af028a1 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -3302,9 +3302,10 @@ ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
 }
 
 static enum ofperr
-decode_nx_packet_in2(const struct ofp_header *oh,
+decode_nx_packet_in2(const struct ofp_header *oh, bool loose,
                      struct ofputil_packet_in *pin,
-                     size_t *total_len, uint32_t *buffer_id)
+                     size_t *total_len, uint32_t *buffer_id,
+                     struct ofpbuf *continuation)
 {
     *total_len = 0;
     *buffer_id = UINT32_MAX;
@@ -3364,8 +3365,14 @@ decode_nx_packet_in2(const struct ofp_header *oh,
             pin->userdata_len = ofpbuf_msgsize(&payload);
             break;
 
+        case NXPINT_CONTINUATION:
+            if (continuation) {
+                error = ofpprop_parse_nested(&payload, continuation);
+            }
+            break;
+
         default:
-            error = OFPPROP_UNKNOWN(false, "NX_PACKET_IN2", type);
+            error = OFPPROP_UNKNOWN(loose, "NX_PACKET_IN2", type);
             break;
         }
         if (error) {
@@ -3387,20 +3394,38 @@ decode_nx_packet_in2(const struct ofp_header *oh,
 }
 
 /* Decodes the packet-in message starting at 'oh' into '*pin'.  Populates
- * 'pin->packet' and 'pin->len' with the part of the packet actually included
- * in the message, and '*total_len' with the original length of the packet
- * (which is larger than 'packet->len' if only part of the packet was
- * included).  Stores the packet's buffer ID in '*buffer_id' (UINT32_MAX if it
+ * 'pin->packet' and 'pin->packet_len' with the part of the packet actually
+ * included in the message.  If 'total_lenp' is nonnull, populates
+ * '*total_lenp' with the original length of the packet (which is larger than
+ * 'packet->len' if only part of the packet was included).  If 'buffer_idp' is
+ * nonnull, stores the packet's buffer ID in '*buffer_idp' (UINT32_MAX if it
  * was not buffered).
  *
+ * Populates 'continuation', if nonnull, with the continuation data from the
+ * packet-in (an empty buffer, if 'oh' did not contain continuation data).  The
+ * format of this data is supposed to be opaque to anything other than
+ * ovs-vswitchd, so that in any other process the only reasonable use of this
+ * data is to be copied into an NXT_RESUME message via ofputil_encode_resume().
+ *
+ * This function points 'pin->packet' into 'oh', so the caller should not free
+ * it separately from the original OpenFlow message.  This is also true for
+ * 'pin->userdata' (which could also end up NULL if there is no userdata).
+ *
  * Returns 0 if successful, otherwise an OpenFlow error code. */
 enum ofperr
-ofputil_decode_packet_in(const struct ofp_header *oh,
+ofputil_decode_packet_in(const struct ofp_header *oh, bool loose,
                          struct ofputil_packet_in *pin,
-                         size_t *total_len, uint32_t *buffer_id)
+                         size_t *total_lenp, uint32_t *buffer_idp,
+                         struct ofpbuf *continuation)
 {
+    uint32_t buffer_id;
+    size_t total_len;
+
     memset(pin, 0, sizeof *pin);
     pin->cookie = OVS_BE64_MAX;
+    if (continuation) {
+        ofpbuf_use_const(continuation, NULL, 0);
+    }
 
     struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
     enum ofpraw raw = ofpraw_pull_assert(&b);
@@ -3420,8 +3445,8 @@ ofputil_decode_packet_in(const struct ofp_header *oh,
 
         pin->reason = opi->reason;
         pin->table_id = opi->table_id;
-        *buffer_id = ntohl(opi->buffer_id);
-        *total_len = ntohs(opi->total_len);
+        buffer_id = ntohl(opi->buffer_id);
+        total_len = ntohs(opi->total_len);
         if (cookie) {
             pin->cookie = *cookie;
         }
@@ -3440,8 +3465,8 @@ ofputil_decode_packet_in(const struct ofp_header *oh,
         match_set_in_port(&pin->flow_metadata,
                           u16_to_ofp(ntohs(opi->in_port)));
         pin->reason = opi->reason;
-        *buffer_id = ntohl(opi->buffer_id);
-        *total_len = ntohs(opi->total_len);
+        buffer_id = ntohl(opi->buffer_id);
+        total_len = ntohs(opi->total_len);
     } else if (raw == OFPRAW_OFPT11_PACKET_IN) {
         const struct ofp11_packet_in *opi;
         ofp_port_t in_port;
@@ -3452,14 +3477,14 @@ ofputil_decode_packet_in(const struct ofp_header *oh,
         pin->packet = b.data;
         pin->packet_len = b.size;
 
-        *buffer_id = ntohl(opi->buffer_id);
+        buffer_id = ntohl(opi->buffer_id);
         error = ofputil_port_from_ofp11(opi->in_port, &in_port);
         if (error) {
             return error;
         }
         match_init_catchall(&pin->flow_metadata);
         match_set_in_port(&pin->flow_metadata, in_port);
-        *total_len = ntohs(opi->total_len);
+        total_len = ntohs(opi->total_len);
         pin->reason = opi->reason;
         pin->table_id = opi->table_id;
     } else if (raw == OFPRAW_NXT_PACKET_IN) {
@@ -3481,17 +3506,28 @@ ofputil_decode_packet_in(const struct ofp_header *oh,
         pin->table_id = npi->table_id;
         pin->cookie = npi->cookie;
 
-        *buffer_id = ntohl(npi->buffer_id);
-        *total_len = ntohs(npi->total_len);
+        buffer_id = ntohl(npi->buffer_id);
+        total_len = ntohs(npi->total_len);
 
         pin->packet = b.data;
         pin->packet_len = b.size;
-    } else if (raw == OFPRAW_NXT_PACKET_IN2) {
-        return decode_nx_packet_in2(oh, pin, total_len, buffer_id);
+    } else if (raw == OFPRAW_NXT_PACKET_IN2 || raw == OFPRAW_NXT_RESUME) {
+        enum ofperr error = decode_nx_packet_in2(oh, loose, pin, &total_len,
+                                                 &buffer_id, continuation);
+        if (error) {
+            return error;
+        }
     } else {
         OVS_NOT_REACHED();
     }
 
+    if (total_lenp) {
+        *total_lenp = total_len;
+    }
+    if (buffer_idp) {
+        *buffer_idp = buffer_id;
+    }
+
     return 0;
 }
 
@@ -3522,6 +3558,155 @@ encode_packet_in_reason(enum ofp_packet_in_reason reason,
     }
 }
 
+/* Only NXT_PACKET_IN2 (not NXT_RESUME) should include NXCPT_USERDATA, so this
+ * function omits it.  The caller can add it itself if desired. */
+static void
+ofputil_put_packet_in(const struct ofputil_packet_in *pin,
+                      enum ofp_version version, uint32_t buffer_id,
+                      size_t include_bytes, struct ofpbuf *msg)
+{
+    /* Add packet properties. */
+    ofpprop_put(msg, NXPINT_PACKET, pin->packet, include_bytes);
+    if (include_bytes != pin->packet_len) {
+        ofpprop_put_u32(msg, NXPINT_FULL_LEN, pin->packet_len);
+    }
+    if (buffer_id != UINT32_MAX) {
+        ofpprop_put_u32(msg, NXPINT_BUFFER_ID, buffer_id);
+    }
+
+    /* Add flow properties. */
+    ofpprop_put_u8(msg, NXPINT_TABLE_ID, pin->table_id);
+    if (pin->cookie != OVS_BE64_MAX) {
+        ofpprop_put_be64(msg, NXPINT_COOKIE, pin->cookie);
+    }
+
+    /* Add other properties. */
+    ofpprop_put_u8(msg, NXPINT_REASON,
+                   encode_packet_in_reason(pin->reason, version));
+
+    size_t start = ofpprop_start(msg, NXPINT_METADATA);
+    oxm_put_raw(msg, &pin->flow_metadata, version);
+    ofpprop_end(msg, start);
+}
+
+static void
+put_actions_property(struct ofpbuf *msg, uint64_t prop_type,
+                     enum ofp_version version,
+                     const struct ofpact *actions, size_t actions_len)
+{
+    if (actions_len) {
+        size_t start = ofpprop_start_nested(msg, prop_type);
+        ofpacts_put_openflow_actions(actions, actions_len, msg, version);
+        ofpprop_end(msg, start);
+    }
+}
+
+enum nx_continuation_prop_type {
+    NXCPT_BRIDGE = 0x8000,
+    NXCPT_STACK,
+    NXCPT_MIRRORS,
+    NXCPT_CONNTRACKED,
+    NXCPT_TABLE_ID,
+    NXCPT_COOKIE,
+    NXCPT_ACTIONS,
+    NXCPT_ACTION_SET,
+};
+
+/* Only NXT_PACKET_IN2 (not NXT_RESUME) should include NXCPT_USERDATA, so this
+ * function omits it.  The caller can add it itself if desired. */
+static void
+ofputil_put_packet_in_private(const struct ofputil_packet_in_private *pin,
+                              enum ofp_version version, uint32_t buffer_id,
+                              size_t include_bytes, struct ofpbuf *msg)
+{
+    ofputil_put_packet_in(&pin->public, version, buffer_id,
+                          include_bytes, msg);
+
+    size_t continuation_ofs = ofpprop_start_nested(msg, NXPINT_CONTINUATION);
+    size_t inner_ofs = msg->size;
+
+    if (!uuid_is_zero(&pin->bridge)) {
+        ofpprop_put_uuid(msg, NXCPT_BRIDGE, &pin->bridge);
+    }
+
+    for (size_t i = 0; i < pin->n_stack; i++) {
+        const union mf_subvalue *s = &pin->stack[i];
+        size_t ofs;
+        for (ofs = 0; ofs < sizeof *s; ofs++) {
+            if (s->u8[ofs]) {
+                break;
+            }
+        }
+
+        ofpprop_put(msg, NXCPT_STACK, &s->u8[ofs], sizeof *s - ofs);
+    }
+
+    if (pin->mirrors) {
+        ofpprop_put_u32(msg, NXCPT_MIRRORS, pin->mirrors);
+    }
+
+    if (pin->conntracked) {
+        ofpprop_put_flag(msg, NXCPT_CONNTRACKED);
+    }
+
+    if (pin->actions_len) {
+        /* Divide 'pin->actions' into groups that begins with an
+         * unroll_xlate action.  For each group, emit a NXCPT_TABLE_ID and
+         * NXCPT_COOKIE property (if either has changed; each is initially
+         * assumed 0), then a NXCPT_ACTIONS property with the grouped
+         * actions.
+         *
+         * The alternative is to make OFPACT_UNROLL_XLATE public.  We can
+         * always do that later, since this is a private property. */
+        const struct ofpact *const end = ofpact_end(pin->actions,
+                                                    pin->actions_len);
+        const struct ofpact_unroll_xlate *unroll = NULL;
+        uint8_t table_id = 0;
+        ovs_be64 cookie = 0;
+
+        const struct ofpact *a;
+        for (a = pin->actions; ; a = ofpact_next(a)) {
+            if (a == end || a->type == OFPACT_UNROLL_XLATE) {
+                if (unroll) {
+                    if (table_id != unroll->rule_table_id) {
+                        ofpprop_put_u8(msg, NXCPT_TABLE_ID,
+                                       unroll->rule_table_id);
+                        table_id = unroll->rule_table_id;
+                    }
+                    if (cookie != unroll->rule_cookie) {
+                        ofpprop_put_be64(msg, NXCPT_COOKIE,
+                                         unroll->rule_cookie);
+                        cookie = unroll->rule_cookie;
+                    }
+                }
+
+                const struct ofpact *start
+                    = unroll ? ofpact_next(&unroll->ofpact) : pin->actions;
+                put_actions_property(msg, NXCPT_ACTIONS, version,
+                                     start, (a - start) * sizeof *a);
+
+                if (a == end) {
+                    break;
+                }
+                unroll = ofpact_get_UNROLL_XLATE(a);
+            }
+        }
+    }
+
+    if (pin->action_set_len) {
+        size_t start = ofpprop_start_nested(msg, NXCPT_ACTION_SET);
+        ofpacts_put_openflow_actions(pin->action_set,
+                                     pin->action_set_len, msg, version);
+        ofpprop_end(msg, start);
+    }
+
+    if (msg->size > inner_ofs) {
+        ofpprop_end(msg, continuation_ofs);
+    } else {
+        msg->size = continuation_ofs;
+    }
+}
+
 static struct ofpbuf *
 ofputil_encode_ofp10_packet_in(const struct ofputil_packet_in *pin,
                                uint32_t buffer_id)
@@ -3567,40 +3752,24 @@ ofputil_encode_nx_packet_in(const struct ofputil_packet_in *pin,
 }
 
 static struct ofpbuf *
-ofputil_encode_nx_packet_in2(const struct ofputil_packet_in *pin,
+ofputil_encode_nx_packet_in2(const struct ofputil_packet_in_private *pin,
                              enum ofp_version version, uint32_t buffer_id,
                              size_t include_bytes)
 {
     /* 'extra' is just an estimate of the space required. */
-    size_t extra = include_bytes + NXM_TYPICAL_LEN + 256;
+    size_t extra = (pin->public.packet_len
+                    + NXM_TYPICAL_LEN   /* flow_metadata */
+                    + pin->n_stack * 16
+                    + pin->actions_len
+                    + pin->action_set_len
+                    + 256);     /* fudge factor */
     struct ofpbuf *msg = ofpraw_alloc_xid(OFPRAW_NXT_PACKET_IN2, version,
                                           htonl(0), extra);
 
-    /* Add packet properties. */
-    ofpprop_put(msg, NXPINT_PACKET, pin->packet, include_bytes);
-    if (include_bytes != pin->packet_len) {
-        ofpprop_put_u32(msg, NXPINT_FULL_LEN, pin->packet_len);
-    }
-    if (buffer_id != UINT32_MAX) {
-        ofpprop_put_u32(msg, NXPINT_BUFFER_ID, buffer_id);
-    }
-
-    /* Add flow properties. */
-    ofpprop_put_u8(msg, NXPINT_TABLE_ID, pin->table_id);
-    if (pin->cookie != OVS_BE64_MAX) {
-        ofpprop_put_be64(msg, NXPINT_COOKIE, pin->cookie);
-    }
-
-    /* Add other properties. */
-    ofpprop_put_u8(msg, NXPINT_REASON,
-                   encode_packet_in_reason(pin->reason, version));
-
-    size_t start = ofpprop_start(msg, NXPINT_METADATA);
-    oxm_put_raw(msg, &pin->flow_metadata, version);
-    ofpprop_end(msg, start);
-
-    if (pin->userdata_len) {
-        ofpprop_put(msg, NXPINT_USERDATA, pin->userdata, pin->userdata_len);
+    ofputil_put_packet_in_private(pin, version, buffer_id, include_bytes, msg);
+    if (pin->public.userdata_len) {
+        ofpprop_put(msg, NXPINT_USERDATA, pin->public.userdata,
+                    pin->public.userdata_len);
     }
 
     ofpmsg_update_length(msg);
@@ -3659,26 +3828,35 @@ ofputil_encode_ofp12_packet_in(const struct ofputil_packet_in *pin,
     return msg;
 }
 
-/* Converts abstract ofputil_packet_in 'pin' into a PACKET_IN message for
- * 'protocol', using the packet-in format specified by 'packet_in_format'.
+/* Converts abstract ofputil_packet_in_private 'pin' into a PACKET_IN message
+ * for 'protocol', using the packet-in format specified by 'packet_in_format'.
  *
  * If 'pkt_buf' is nonnull and 'max_len' allows the packet to be buffered, this
  * function will attempt to obtain a buffer ID from 'pktbuf' and truncate the
  * packet to 'max_len' bytes.  Otherwise, or if 'pktbuf' doesn't have a free
- * buffer, it will send the whole packet without buffering. */
+ * buffer, it will send the whole packet without buffering.
+ *
+ * This function is really meant only for use by ovs-vswitchd.  To any other
+ * code, the "continuation" data, i.e. the data that is in struct
+ * ofputil_packet_in_private but not in struct ofputil_packet_in, is supposed
+ * to be opaque (and it might change from one OVS version to another).  Thus,
+ * if any other code wants to encode a packet-in, it should use a non-"private"
+ * version of this function.  (Such a version doesn't currently exist because
+ * only ovs-vswitchd currently wants to encode packet-ins.  If you need one,
+ * write it...) */
 struct ofpbuf *
-ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
-                         enum ofputil_protocol protocol,
-                         enum nx_packet_in_format packet_in_format,
-                         uint16_t max_len, struct pktbuf *pktbuf)
+ofputil_encode_packet_in_private(const struct ofputil_packet_in_private *pin,
+                                 enum ofputil_protocol protocol,
+                                 enum nx_packet_in_format packet_in_format,
+                                 uint16_t max_len, struct pktbuf *pktbuf)
 {
     enum ofp_version version = ofputil_protocol_to_ofp_version(protocol);
 
     /* Get buffer ID. */
-    ofp_port_t in_port = pin->flow_metadata.flow.in_port.ofp_port;
+    ofp_port_t in_port = pin->public.flow_metadata.flow.in_port.ofp_port;
     uint32_t buffer_id = (max_len != OFPCML12_NO_BUFFER && pktbuf
-                          ? pktbuf_save(pktbuf, pin->packet,
-                                        pin->packet_len, in_port)
+                          ? pktbuf_save(pktbuf, pin->public.packet,
+                                        pin->public.packet_len, in_port)
                           : UINT32_MAX);
 
     /* Calculate the number of bytes of the packet to include in the
@@ -3688,8 +3866,8 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
      *
      *    - Otherwise, no more than 'max_len' bytes. */
     size_t include_bytes = (buffer_id == UINT32_MAX
-                            ? pin->packet_len
-                            : MIN(max_len, pin->packet_len));
+                            ? pin->public.packet_len
+                            : MIN(max_len, pin->public.packet_len));
 
     struct ofpbuf *msg;
     switch (packet_in_format) {
@@ -3699,18 +3877,18 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
         case OFPUTIL_P_OF10_STD_TID:
         case OFPUTIL_P_OF10_NXM:
         case OFPUTIL_P_OF10_NXM_TID:
-            msg = ofputil_encode_ofp10_packet_in(pin, buffer_id);
+            msg = ofputil_encode_ofp10_packet_in(&pin->public, buffer_id);
             break;
 
         case OFPUTIL_P_OF11_STD:
-            msg = ofputil_encode_ofp11_packet_in(pin, buffer_id);
+            msg = ofputil_encode_ofp11_packet_in(&pin->public, buffer_id);
             break;
 
         case OFPUTIL_P_OF12_OXM:
         case OFPUTIL_P_OF13_OXM:
         case OFPUTIL_P_OF14_OXM:
         case OFPUTIL_P_OF15_OXM:
-            msg = ofputil_encode_ofp12_packet_in(pin, version, buffer_id);
+            msg = ofputil_encode_ofp12_packet_in(&pin->public, version, buffer_id);
             break;
 
         default:
@@ -3719,7 +3897,7 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
         break;
 
     case NXPIF_NXT_PACKET_IN:
-        msg = ofputil_encode_nx_packet_in(pin, version, buffer_id);
+        msg = ofputil_encode_nx_packet_in(&pin->public, version, buffer_id);
         break;
 
     case NXPIF_NXT_PACKET_IN2:
@@ -3730,7 +3908,7 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
         OVS_NOT_REACHED();
     }
 
-    ofpbuf_put(msg, pin->packet, include_bytes);
+    ofpbuf_put(msg, pin->public.packet, include_bytes);
     ofpmsg_update_length(msg);
     return msg;
 }
@@ -3786,6 +3964,171 @@ ofputil_packet_in_reason_from_string(const char *s,
     return false;
 }
 
+/* Returns a newly allocated NXT_RESUME message for 'pin', with the given
+ * 'continuation', for 'protocol'.  This message is suitable for resuming the
+ * pipeline traveral of the packet represented by 'pin', if sent to the switch
+ * from which 'pin' was received. */
+struct ofpbuf *
+ofputil_encode_resume(const struct ofputil_packet_in *pin,
+                      const struct ofpbuf *continuation,
+                      enum ofputil_protocol protocol)
+{
+    enum ofp_version version = ofputil_protocol_to_ofp_version(protocol);
+    size_t extra = pin->packet_len + NXM_TYPICAL_LEN + continuation->size;
+    struct ofpbuf *msg = ofpraw_alloc_xid(OFPRAW_NXT_RESUME, version,
+                                          0, extra);
+    ofputil_put_packet_in(pin, version, UINT32_MAX, pin->packet_len, msg);
+    ofpprop_put_nested(msg, NXPINT_CONTINUATION, continuation);
+    ofpmsg_update_length(msg);
+    return msg;
+}
+
+static enum ofperr
+parse_subvalue_prop(const struct ofpbuf *property, union mf_subvalue *sv)
+{
+    unsigned int len = ofpbuf_msgsize(property);
+    if (len > sizeof *sv) {
+        VLOG_WARN_RL(&bad_ofmsg_rl, "NXCPT_STACK property has bad length %u",
+                     len);
+        return OFPERR_OFPBPC_BAD_LEN;
+    }
+    memset(sv, 0, sizeof *sv);
+    memcpy(&sv->u8[sizeof *sv - len], property->msg, len);
+    return 0;
+}
+
+static enum ofperr
+parse_actions_property(struct ofpbuf *property, enum ofp_version version,
+                       struct ofpbuf *ofpacts)
+{
+    if (!ofpbuf_try_pull(property, ROUND_UP(ofpbuf_headersize(property), 8))) {
+        VLOG_WARN_RL(&bad_ofmsg_rl, "actions property has bad length %"PRIu32,
+                     property->size);
+        return OFPERR_OFPBPC_BAD_LEN;
+    }
+
+    return ofpacts_pull_openflow_actions(property, property->size,
+                                         version, ofpacts);
+}
+
+/* This is like ofputil_decode_packet_in(), except that it decodes the
+ * continuation data into 'pin'.  The format of this data is supposed to be
+ * opaque to any process other than ovs-vswitchd, so this function should not
+ * be used outside ovs-vswitchd.
+ *
+ * When successful, 'pin' contains some dynamically allocated data.  Call
+ * ofputil_packet_in_private_destroy() to free this data. */
+enum ofperr
+ofputil_decode_packet_in_private(const struct ofp_header *oh, bool loose,
+                                 struct ofputil_packet_in_private *pin,
+                                 size_t *total_len, uint32_t *buffer_id)
+{
+    memset(pin, 0, sizeof *pin);
+
+    struct ofpbuf continuation;
+    enum ofperr error;
+    error = ofputil_decode_packet_in(oh, loose, &pin->public, total_len,
+                                     buffer_id, &continuation);
+    if (error) {
+        return error;
+    }
+
+    struct ofpbuf actions, action_set;
+    ofpbuf_init(&actions, 0);
+    ofpbuf_init(&action_set, 0);
+
+    uint8_t table_id = 0;
+    ovs_be64 cookie = 0;
+
+    size_t allocated_stack = 0;
+
+    while (continuation.size > 0) {
+        struct ofpbuf payload;
+        uint64_t type;
+
+        error = ofpprop_pull(&continuation, &payload, &type);
+        ovs_assert(!error);
+
+        switch (type) {
+        case NXCPT_BRIDGE:
+            error = ofpprop_parse_uuid(&payload, &pin->bridge);
+            break;
+
+        case NXCPT_STACK:
+            if (pin->n_stack >= allocated_stack) {
+                pin->stack = x2nrealloc(pin->stack, &allocated_stack,
+                                           sizeof *pin->stack);
+            }
+            error = parse_subvalue_prop(&payload,
+                                        &pin->stack[pin->n_stack++]);
+            break;
+
+        case NXCPT_MIRRORS:
+            error = ofpprop_parse_u32(&payload, &pin->mirrors);
+            break;
+
+        case NXCPT_CONNTRACKED:
+            pin->conntracked = true;
+            break;
+
+        case NXCPT_TABLE_ID:
+            error = ofpprop_parse_u8(&payload, &table_id);
+            break;
+
+        case NXCPT_COOKIE:
+            error = ofpprop_parse_be64(&payload, &cookie);
+            break;
+
+        case NXCPT_ACTIONS: {
+            struct ofpact_unroll_xlate *unroll
+                = ofpact_put_UNROLL_XLATE(&actions);
+            unroll->rule_table_id = table_id;
+            unroll->rule_cookie = cookie;
+            error = parse_actions_property(&payload, oh->version, &actions);
+            break;
+        }
+
+        case NXCPT_ACTION_SET:
+            error = parse_actions_property(&payload, oh->version, &action_set);
+            break;
+
+        default:
+            error = OFPPROP_UNKNOWN(loose, "continuation", type);
+            break;
+        }
+        if (error) {
+            break;
+        }
+    }
+
+    pin->actions_len = actions.size;
+    pin->actions = ofpbuf_steal_data(&actions);
+    pin->action_set_len = action_set.size;
+    pin->action_set = ofpbuf_steal_data(&action_set);
+
+    if (error) {
+        ofputil_packet_in_private_destroy(pin);
+    }
+
+    return 0;
+}
+
+/* Frees data in 'pin' that is dynamically allocated by
+ * ofputil_decode_packet_in_private().
+ *
+ * 'pin->public' contains some pointer members that
+ * ofputil_decode_packet_in_private() doesn't initialize to newly allocated
+ * data, so this function doesn't free those. */
+void
+ofputil_packet_in_private_destroy(struct ofputil_packet_in_private *pin)
+{
+    if (pin) {
+        free(pin->stack);
+        free(pin->actions);
+        free(pin->action_set);
+    }
+}
+
 /* Converts an OFPT_PACKET_OUT in 'opo' into an abstract ofputil_packet_out in
  * 'po'.
  *
@@ -9302,6 +9645,7 @@ ofputil_is_bundlable(enum ofptype type)
     case OFPTYPE_REQUESTFORWARD:
     case OFPTYPE_NXT_TLV_TABLE_REQUEST:
     case OFPTYPE_NXT_TLV_TABLE_REPLY:
+    case OFPTYPE_NXT_RESUME:
         break;
     }
 
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 05b10ea..187be8c 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -31,6 +31,7 @@
 #include "openflow/nicira-ext.h"
 #include "openvswitch/types.h"
 #include "type-props.h"
+#include "uuid.h"
 
 struct ofpbuf;
 union ofp_action;
@@ -447,14 +448,16 @@ struct ofputil_packet_in {
     size_t userdata_len;
 };
 
-struct ofpbuf *ofputil_encode_packet_in(const struct ofputil_packet_in *,
-                                        enum ofputil_protocol protocol,
-                                        enum nx_packet_in_format,
-                                        uint16_t max_len, struct pktbuf *);
+void ofputil_packet_in_destroy(struct ofputil_packet_in *);
 
-enum ofperr ofputil_decode_packet_in(const struct ofp_header *,
+enum ofperr ofputil_decode_packet_in(const struct ofp_header *, bool loose,
                                      struct ofputil_packet_in *,
-                                     size_t *total_len, uint32_t *buffer_id);
+                                     size_t *total_len, uint32_t *buffer_id,
+                                     struct ofpbuf *continuation);
+
+struct ofpbuf *ofputil_encode_resume(const struct ofputil_packet_in *pin,
+                                     const struct ofpbuf *continuation,
+                                     enum ofputil_protocol);
 
 enum { OFPUTIL_PACKET_IN_REASON_BUFSIZE = INT_STRLEN(int) + 1 };
 const char *ofputil_packet_in_reason_to_string(enum ofp_packet_in_reason,
@@ -463,6 +466,48 @@ const char *ofputil_packet_in_reason_to_string(enum ofp_packet_in_reason,
 bool ofputil_packet_in_reason_from_string(const char *,
                                           enum ofp_packet_in_reason *);
 
+/* A packet-in message, including continuation data.  The format of
+ * continuation data is subject to change and thus it is supposed to be opaque
+ * to any process other than ovs-vswitchd.  Therefore, only ovs-vswitchd should
+ * use ofputil_packet_in_private and the functions that operate on it. */
+struct ofputil_packet_in_private {
+    struct ofputil_packet_in public;
+
+    /* NXCPT_BRIDGE. */
+    struct uuid bridge;
+
+    /* NXCPT_STACK. */
+    union mf_subvalue *stack;
+    size_t n_stack;
+
+    /* NXCPT_MIRRORS. */
+    uint32_t mirrors;
+
+    /* NXCPT_CONNTRACKED. */
+    bool conntracked;
+
+    /* NXCPT_ACTIONS. */
+    struct ofpact *actions;
+    size_t actions_len;
+
+    /* NXCPT_ACTION_SET. */
+    struct ofpact *action_set;
+    size_t action_set_len;
+};
+
+struct ofpbuf *ofputil_encode_packet_in_private(
+    const struct ofputil_packet_in_private *,
+    enum ofputil_protocol protocol,
+    enum nx_packet_in_format,
+    uint16_t max_len, struct pktbuf *);
+
+enum ofperr ofputil_decode_packet_in_private(
+    const struct ofp_header *, bool loose,
+    struct ofputil_packet_in_private *,
+    size_t *total_len, uint32_t *buffer_id);
+
+void ofputil_packet_in_private_destroy(struct ofputil_packet_in_private *);
+
 /* Abstract packet-out message.
  *
  * ofputil_decode_packet_out() will ensure that 'in_port' is a physical port
diff --git a/lib/rconn.c b/lib/rconn.c
index b601eed..8aed96d 100644
--- a/lib/rconn.c
+++ b/lib/rconn.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1420,6 +1420,7 @@ is_admitted_msg(const struct ofpbuf *b)
     case OFPTYPE_NXT_TLV_TABLE_MOD:
     case OFPTYPE_NXT_TLV_TABLE_REQUEST:
     case OFPTYPE_NXT_TLV_TABLE_REPLY:
+    case OFPTYPE_NXT_RESUME:
     default:
         return true;
     }
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index 923975a..cdd7a69 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -1649,9 +1649,7 @@ connmgr_send_flow_removed(struct connmgr *mgr,
 }
 
 /* Given 'pin', sends an OFPT_PACKET_IN message to each OpenFlow controller as
- * necessary according to their individual configurations.
- *
- * The caller doesn't need to fill in pin->buffer_id or pin->total_len. */
+ * necessary according to their individual configurations. */
 void
 connmgr_send_async_msg(struct connmgr *mgr,
                        const struct ofproto_async_msg *am)
@@ -1663,21 +1661,21 @@ connmgr_send_async_msg(struct connmgr *mgr,
         if (protocol == OFPUTIL_P_NONE || !rconn_is_connected(ofconn->rconn)
             || ofconn->controller_id != am->controller_id
             || !ofconn_receives_async_msg(ofconn, am->oam,
-                                          am->pin.up.reason)) {
+                                          am->pin.up.public.reason)) {
             continue;
         }
 
-        struct ofpbuf *msg = ofputil_encode_packet_in(
+        struct ofpbuf *msg = ofputil_encode_packet_in_private(
             &am->pin.up, protocol, ofconn->packet_in_format,
             am->pin.max_len >= 0 ? am->pin.max_len : ofconn->miss_send_len,
             ofconn->pktbuf);
 
         struct ovs_list txq;
-        bool is_miss = (am->pin.up.reason == OFPR_NO_MATCH ||
-                        am->pin.up.reason == OFPR_EXPLICIT_MISS ||
-                        am->pin.up.reason == OFPR_IMPLICIT_MISS);
+        bool is_miss = (am->pin.up.public.reason == OFPR_NO_MATCH ||
+                        am->pin.up.public.reason == OFPR_EXPLICIT_MISS ||
+                        am->pin.up.public.reason == OFPR_IMPLICIT_MISS);
         pinsched_send(ofconn->schedulers[is_miss],
-                      am->pin.up.flow_metadata.flow.in_port.ofp_port,
+                      am->pin.up.public.flow_metadata.flow.in_port.ofp_port,
                       msg, &txq);
         do_send_packet_ins(ofconn, &txq);
     }
@@ -2247,7 +2245,10 @@ ofmonitor_wait(struct connmgr *mgr)
 void
 ofproto_async_msg_free(struct ofproto_async_msg *am)
 {
-    free(am->pin.up.packet);
-    free(am->pin.up.userdata);
+    free(am->pin.up.public.packet);
+    free(am->pin.up.public.userdata);
+    free(am->pin.up.stack);
+    free(am->pin.up.actions);
+    free(am->pin.up.action_set);
     free(am);
 }
diff --git a/ofproto/connmgr.h b/ofproto/connmgr.h
index fb7573e..c599218 100644
--- a/ofproto/connmgr.h
+++ b/ofproto/connmgr.h
@@ -63,7 +63,7 @@ struct ofproto_async_msg {
     union {
         /* OAM_PACKET_IN. */
         struct {
-            struct ofputil_packet_in up;
+            struct ofputil_packet_in_private up;
             int max_len;                /* From action, or -1 if none. */
         } pin;
     };
diff --git a/ofproto/fail-open.c b/ofproto/fail-open.c
index 77b99ad..a288054 100644
--- a/ofproto/fail-open.c
+++ b/ofproto/fail-open.c
@@ -129,14 +129,16 @@ send_bogus_packet_ins(struct fail_open *fo)
         .oam = OAM_PACKET_IN,
         .pin = {
             .up = {
-                .packet = dp_packet_data(&b),
-                .packet_len = dp_packet_size(&b),
-                .flow_metadata = MATCH_CATCHALL_INITIALIZER,
-                .flow_metadata.flow.in_port.ofp_port = OFPP_LOCAL,
-                .flow_metadata.wc.masks.in_port.ofp_port
+                .public = {
+                    .packet = dp_packet_data(&b),
+                    .packet_len = dp_packet_size(&b),
+                    .flow_metadata = MATCH_CATCHALL_INITIALIZER,
+                    .flow_metadata.flow.in_port.ofp_port = OFPP_LOCAL,
+                    .flow_metadata.wc.masks.in_port.ofp_port
                     = u16_to_ofp(UINT16_MAX),
-                .reason = OFPR_NO_MATCH,
-                .cookie = OVS_BE64_MAX,
+                    .reason = OFPR_NO_MATCH,
+                    .cookie = OVS_BE64_MAX,
+                },
             },
             .max_len = UINT16_MAX,
         }
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index e5cce9a..15449b9 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -249,6 +249,13 @@ struct xlate_ctx {
     *       translation isn't needed, and so bonds don't follow the above
     *       process.)
     *
+    *     - "Continuation".  A continuation is a way for an OpenFlow controller
+    *       to interpose on a packet's traversal of the OpenFlow tables.  When
+    *       the translation process encounters a "controller" action with the
+    *       "pause" flag, it freezes translation, serializes the frozen data,
+    *       and sends it to an OpenFlow controller.  The controller then
+    *       examines and possibly modifies the frozen data and eventually sends
+    *       it back to the switch, which thaws it and continues translation.
     *
     * The main problem of freezing translation is preserving state, so that
     * when the translation is thawed later it resumes from where it left off,
@@ -311,6 +318,7 @@ struct xlate_ctx {
     */
     bool freezing;
     struct ofpbuf frozen_actions;
+    const struct ofpact_controller *pause;
 
     /* 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
@@ -387,7 +395,7 @@ ctx_cancel_freeze(struct xlate_ctx *ctx)
     }
 }
 
-static void compose_recirculate_action(struct xlate_ctx *ctx);
+static void finish_freezing(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
@@ -3008,7 +3016,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
                     xlate_action_set(ctx);
                 }
                 if (ctx->freezing) {
-                    compose_recirculate_action(ctx);
+                    finish_freezing(ctx);
                 }
             } else {
                 /* Forwarding is disabled by STP and RSTP.  Let OFPP_NORMAL and
@@ -3333,9 +3341,9 @@ xlate_group_bucket(struct xlate_ctx *ctx, struct ofputil_bucket *bucket)
 
     ofpbuf_uninit(&action_list);
 
-    /* Check if need to recirculate. */
+    /* Check if need to freeze. */
     if (ctx->freezing) {
-        compose_recirculate_action(ctx);
+        finish_freezing(ctx);
     }
 
     /* Roll back flow to previous state.
@@ -3619,39 +3627,70 @@ execute_controller_action(struct xlate_ctx *ctx, int len,
         .oam = OAM_PACKET_IN,
         .pin = {
             .up = {
-                .packet = dp_packet_steal_data(packet),
-                .packet_len = packet_len,
-                .reason = reason,
-                .table_id = ctx->table_id,
-                .cookie = ctx->rule_cookie,
-                .userdata = (userdata_len
-                             ? xmemdup(userdata, userdata_len)
-                             : NULL),
-                .userdata_len = userdata_len,
+                .public = {
+                    .packet = dp_packet_steal_data(packet),
+                    .packet_len = packet_len,
+                    .reason = reason,
+                    .table_id = ctx->table_id,
+                    .cookie = ctx->rule_cookie,
+                    .userdata = (userdata_len
+                                 ? xmemdup(userdata, userdata_len)
+                                 : NULL),
+                    .userdata_len = userdata_len,
+                }
             },
             .max_len = len,
         },
     };
-    flow_get_metadata(&ctx->xin->flow, &am->pin.up.flow_metadata);
+    flow_get_metadata(&ctx->xin->flow, &am->pin.up.public.flow_metadata);
 
     ofproto_dpif_send_async_msg(ctx->xbridge->ofproto, am);
     dp_packet_delete(packet);
 }
 
 static void
-compose_recirculate_action__(struct xlate_ctx *ctx, uint8_t table)
+emit_continuation(struct xlate_ctx *ctx, const struct frozen_state *state)
 {
-    struct frozen_metadata md;
-    uint32_t id;
-
-    frozen_metadata_from_flow(&md, &ctx->xin->flow);
+    struct ofproto_async_msg *am = xmalloc(sizeof *am);
+    *am = (struct ofproto_async_msg) {
+        .controller_id = ctx->pause->controller_id,
+        .oam = OAM_PACKET_IN,
+        .pin = {
+            .up = {
+                .public = {
+                    .userdata = xmemdup(ctx->pause->userdata,
+                                        ctx->pause->userdata_len),
+                    .userdata_len = ctx->pause->userdata_len,
+                    .packet = xmemdup(dp_packet_data(ctx->xin->packet),
+                                      dp_packet_size(ctx->xin->packet)),
+                    .packet_len = dp_packet_size(ctx->xin->packet),
+                },
+                .bridge = *ofproto_dpif_get_uuid(ctx->xbridge->ofproto),
+                .stack = xmemdup(state->stack,
+                                 state->n_stack * sizeof *state->stack),
+                .n_stack = state->n_stack,
+                .mirrors = state->mirrors,
+                .conntracked = state->conntracked,
+                .actions = xmemdup(state->ofpacts, state->ofpacts_len),
+                .actions_len = state->ofpacts_len,
+                .action_set = xmemdup(state->action_set,
+                                      state->action_set_len),
+                .action_set_len = state->action_set_len,
+            },
+        },
+    };
+    flow_get_metadata(&ctx->xin->flow, &am->pin.up.public.flow_metadata);
+    ofproto_dpif_send_async_msg(ctx->xbridge->ofproto, am);
+}
 
+static void
+finish_freezing__(struct xlate_ctx *ctx, uint8_t table)
+{
     ovs_assert(ctx->freezing);
 
     struct frozen_state state = {
         .table_id = table,
         .ofproto_uuid = *ofproto_dpif_get_uuid(ctx->xbridge->ofproto),
-        .metadata = md,
         .stack = ctx->stack.data,
         .n_stack = ctx->stack.size / sizeof(union mf_subvalue),
         .mirrors = ctx->mirrors,
@@ -3661,21 +3700,28 @@ compose_recirculate_action__(struct xlate_ctx *ctx, uint8_t table)
         .action_set = ctx->action_set.data,
         .action_set_len = ctx->action_set.size,
     };
+    frozen_metadata_from_flow(&state.metadata, &ctx->xin->flow);
 
-    /* Allocate a unique recirc id for the given metadata state in the
-     * flow.  An existing id, with a new reference to the corresponding
-     * recirculation context, will be returned if possible.
-     * 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(&state);
-    if (!id) {
-        XLATE_REPORT_ERROR(ctx, "Failed to allocate recirculation id");
-        ctx->error = XLATE_NO_RECIRCULATION_CONTEXT;
-        return;
-    }
-    recirc_refs_add(&ctx->xout->recircs, id);
+    if (ctx->pause) {
+        if (ctx->xin->packet) {
+            emit_continuation(ctx, &state);
+        }
+    } else {
+        /* Allocate a unique recirc id for the given metadata state in the
+         * flow.  An existing id, with a new reference to the corresponding
+         * recirculation context, will be returned if possible.
+         * The life-cycle of this recirc id is managed by associating it
+         * with the udpif key ('ukey') created for each new datapath flow. */
+        uint32_t id = recirc_alloc_id_ctx(&state);
+        if (!id) {
+            XLATE_REPORT_ERROR(ctx, "Failed to allocate recirculation id");
+            ctx->error = XLATE_NO_RECIRCULATION_CONTEXT;
+            return;
+        }
+        recirc_refs_add(&ctx->xout->recircs, id);
 
-    nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_RECIRC, id);
+        nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_RECIRC, id);
+    }
 
     /* Undo changes done by freezing. */
     ctx_cancel_freeze(ctx);
@@ -3683,10 +3729,10 @@ compose_recirculate_action__(struct xlate_ctx *ctx, uint8_t table)
 
 /* Called only when we're freezing. */
 static void
-compose_recirculate_action(struct xlate_ctx *ctx)
+finish_freezing(struct xlate_ctx *ctx)
 {
     xlate_commit_actions(ctx);
-    compose_recirculate_action__(ctx, 0);
+    finish_freezing__(ctx, 0);
 }
 
 /* Fork the pipeline here. The current packet will continue processing the
@@ -3697,7 +3743,7 @@ static void
 compose_recirculate_and_fork(struct xlate_ctx *ctx, uint8_t table)
 {
     ctx->freezing = true;
-    compose_recirculate_action__(ctx, table);
+    finish_freezing__(ctx, table);
 }
 
 static void
@@ -4462,11 +4508,18 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
 
         case OFPACT_CONTROLLER:
             controller = ofpact_get_CONTROLLER(a);
-            execute_controller_action(ctx, controller->max_len,
-                                      controller->reason,
-                                      controller->controller_id,
-                                      controller->userdata,
-                                      controller->userdata_len);
+            if (controller->pause) {
+                ctx->pause = controller;
+                ctx->xout->slow |= SLOW_CONTROLLER;
+                ctx_trigger_freeze(ctx);
+                a = ofpact_next(a);
+            } else {
+                execute_controller_action(ctx, controller->max_len,
+                                          controller->reason,
+                                          controller->controller_id,
+                                          controller->userdata,
+                                          controller->userdata_len);
+            }
             break;
 
         case OFPACT_ENQUEUE:
@@ -5128,6 +5181,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
 
         .freezing = false,
         .frozen_actions = OFPBUF_STUB_INITIALIZER(frozen_actions_stub),
+        .pause = NULL,
 
         .was_mpls = false,
         .conntracked = false,
@@ -5354,7 +5408,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
                 xlate_action_set(&ctx);
             }
             if (ctx.freezing) {
-                compose_recirculate_action(&ctx);
+                finish_freezing(&ctx);
             }
         }
 
@@ -5424,6 +5478,67 @@ exit:
     return ctx.error;
 }
 
+enum ofperr
+xlate_resume(struct ofproto_dpif *ofproto,
+             const struct ofputil_packet_in_private *pin,
+             struct ofpbuf *odp_actions,
+             enum slow_path_reason *slow)
+{
+    struct dp_packet packet;
+    dp_packet_use_const(&packet, pin->public.packet,
+                        pin->public.packet_len);
+
+    struct flow flow;
+    flow_extract(&packet, &flow);
+
+    struct xlate_in xin;
+    xlate_in_init(&xin, ofproto, &flow, 0, NULL, ntohs(flow.tcp_flags),
+                  &packet, NULL, odp_actions);
+
+    struct ofpact_note noop;
+    ofpact_init_NOTE(&noop);
+    noop.length = 0;
+
+    bool any_actions = pin->actions_len > 0;
+    struct frozen_state state = {
+        .table_id = 0,     /* Not the table where NXAST_PAUSE was executed. */
+        .ofproto_uuid = pin->bridge,
+        .stack = pin->stack,
+        .n_stack = pin->n_stack,
+        .mirrors = pin->mirrors,
+        .conntracked = pin->conntracked,
+
+        /* When there are no actions, xlate_actions() will search the flow
+         * table.  We don't want it to do that (we want it to resume), so
+         * supply a no-op action if there aren't any.
+         *
+         * (We can't necessarily avoid translating actions entirely if there
+         * aren't any actions, because there might be some finishing-up to do
+         * at the end of the pipeline, and we don't check for those
+         * conditions.) */
+        .ofpacts = any_actions ? pin->actions : &noop.ofpact,
+        .ofpacts_len = any_actions ? pin->actions_len : sizeof noop,
+
+        .action_set = pin->action_set,
+        .action_set_len = pin->action_set_len,
+    };
+    frozen_metadata_from_flow(&state.metadata,
+                              &pin->public.flow_metadata.flow);
+    xin.frozen_state = &state;
+
+    struct xlate_out xout;
+    enum xlate_error error = xlate_actions(&xin, &xout);
+    *slow = xout.slow;
+    xlate_out_uninit(&xout);
+
+    /* xlate_actions() can generate a number of errors, but only
+     * XLATE_BRIDGE_NOT_FOUND really stands out to me as one that we should be
+     * sure to report over OpenFlow.  The others could come up in packet-outs
+     * or regular flow translation and I don't think that it's going to be too
+     * useful to report them to the controller. */
+    return error == XLATE_BRIDGE_NOT_FOUND ? OFPERR_NXR_STALE : 0;
+}
+
 /* Sends 'packet' out 'ofport'.
  * May modify 'packet'.
  * Returns 0 if successful, otherwise a positive errno value. */
diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h
index 227a161..640ed4b 100644
--- a/ofproto/ofproto-dpif-xlate.h
+++ b/ofproto/ofproto-dpif-xlate.h
@@ -201,6 +201,10 @@ void xlate_in_init(struct xlate_in *, struct ofproto_dpif *,
 void xlate_out_uninit(struct xlate_out *);
 void xlate_actions_for_side_effects(struct xlate_in *);
 
+enum ofperr xlate_resume(struct ofproto_dpif *,
+                         const struct ofputil_packet_in_private *,
+                         struct ofpbuf *odp_actions, enum slow_path_reason *);
+
 int xlate_send_packet(const struct ofport_dpif *, struct dp_packet *);
 
 struct xlate_cache *xlate_cache_new(void);
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 826e6e6..b963ff2 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -4425,6 +4425,39 @@ packet_out(struct ofproto *ofproto_, struct dp_packet *packet,
                                  ofpacts_len, packet);
     return 0;
 }
+
+static enum ofperr
+nxt_resume(struct ofproto *ofproto_,
+           const struct ofputil_packet_in_private *pin)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+
+    /* Translate pin into datapath actions. */
+    uint64_t odp_actions_stub[1024 / 8];
+    struct ofpbuf odp_actions = OFPBUF_STUB_INITIALIZER(odp_actions_stub);
+    enum slow_path_reason slow;
+    enum ofperr error = xlate_resume(ofproto, pin, &odp_actions, &slow);
+
+    /* Steal 'pin->packet' and put it into a dp_packet. */
+    struct dp_packet packet;
+    dp_packet_init(&packet, pin->public.packet_len);
+    dp_packet_put(&packet, pin->public.packet, pin->public.packet_len);
+
+    /* Execute the datapath actions on the packet. */
+    struct dpif_execute execute = {
+        .actions = odp_actions.data,
+        .actions_len = odp_actions.size,
+        .needs_help = (slow & SLOW_ACTION) != 0,
+        .packet = &packet,
+    };
+    dpif_execute(ofproto->backer->dpif, &execute);
+
+    /* Clean up. */
+    ofpbuf_uninit(&odp_actions);
+    dp_packet_uninit(&packet);
+
+    return error;
+}
 
 /* NetFlow. */
 
@@ -5772,6 +5805,7 @@ const struct ofproto_class ofproto_dpif_class = {
     rule_execute,
     set_frag_handling,
     packet_out,
+    nxt_resume,
     set_netflow,
     get_netflow_ids,
     set_sflow,
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 5fa03b5..180ed48 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1326,6 +1326,9 @@ struct ofproto_class {
                               const struct ofpact *ofpacts,
                               size_t ofpacts_len);
 
+    enum ofperr (*nxt_resume)(struct ofproto *ofproto,
+                              const struct ofputil_packet_in_private *);
+
 /* ## ------------------------- ## */
 /* ## OFPP_NORMAL configuration ## */
 /* ## ------------------------- ## */
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 0135355..5fa3493 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3411,6 +3411,27 @@ exit:
     return error;
 }
 
+static enum ofperr
+handle_nxt_resume(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+    struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+    struct ofputil_packet_in_private pin;
+    enum ofperr error;
+
+    error = ofputil_decode_packet_in_private(oh, false, &pin, NULL, NULL);
+    if (error) {
+        return error;
+    }
+
+    error = (ofproto->ofproto_class->nxt_resume
+             ? ofproto->ofproto_class->nxt_resume(ofproto, &pin)
+             : OFPERR_NXR_NOT_SUPPORTED);
+
+    ofputil_packet_in_private_destroy(&pin);
+
+    return error;
+}
+
 static void
 update_port_config(struct ofconn *ofconn, struct ofport *port,
                    enum ofputil_port_config config,
@@ -7211,6 +7232,9 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
     case OFPTYPE_GET_ASYNC_REQUEST:
         return handle_nxt_get_async_request(ofconn, oh);
 
+    case OFPTYPE_NXT_RESUME:
+        return handle_nxt_resume(ofconn, oh);
+
         /* Statistics requests. */
     case OFPTYPE_DESC_STATS_REQUEST:
         return handle_desc_stats_request(ofconn, oh);
diff --git a/ovn/TODO b/ovn/TODO
index a827421..b08a4f4 100644
--- a/ovn/TODO
+++ b/ovn/TODO
@@ -50,63 +50,6 @@ ovn-sb.xml includes a tentative specification for this action.
 IPv6 will probably need an action or actions for ND that is similar to
 the "arp" action, and an action for generating
 
-*** ovn-controller translation to OpenFlow
-
-The following two translation strategies come to mind.  Some of the
-new actions we might want to implement one way, some of them the
-other, depending on the details.
-
-*** Implementation strategies
-
-One way to do this is to define new actions as Open vSwitch extensions
-to OpenFlow, emit those actions in ovn-controller, and implement them
-in ovs-vswitchd (possibly pushing the implementations into the Linux
-and DPDK datapaths as well).  This is the only acceptable way for
-actions that need high performance.  None of these actions obviously
-need high performance, but it might be necessary to have fairness in
-handling e.g. a flood of incoming packets that require these actions.
-The main disadvantage of this approach is that it ties ovs-vswitchd
-(and the Linux kernel module) to supporting these actions essentially
-forever, which means that we'd want to make sure that they are
-general-purpose, well designed, maintainable, and supportable.
-
-The other way to do this is to send the packets across an OpenFlow
-channel to ovn-controller and have ovn-controller process them.  This
-is acceptable for actions that don't need high performance, and it
-means that we don't add anything permanently to ovs-vswitchd or the
-kernel (so we can be more casual about the design).  The big
-disadvantage is that it becomes necessary to add a way to resume the
-OpenFlow pipeline when it is interrupted in the middle by sending a
-packet to the controller.  This is not as simple as doing a new flow
-table lookup and resuming from that point.  Instead, it is equivalent
-to the (very complicated) recirculation logic in ofproto-dpif-xlate.c.
-Much of this logic can be translated into OpenFlow actions (e.g. the
-call stack and data stack), but some of it is entirely outside
-OpenFlow (e.g. the state of mirrors).  To implement it properly, it
-seems that we'll have to introduce a new Open vSwitch extension to
-OpenFlow, a "send-to-controller" action that causes extra data to be
-sent to the controller, where the extra data packages up the state
-necessary to resume the pipeline.  Maybe the bits of the state that
-can be represented in OpenFlow can be embedded in this extra data in a
-controller-readable form, but other bits we might want to be opaque.
-It's also likely that we'll want to change and extend the form of this
-opaque data over time, so this should be allowed for, e.g. by
-including a nonce in the extra data that is newly generated every time
-ovs-vswitchd starts.
-
-*** OpenFlow action definitions
-
-Define OpenFlow wire structures for each new OpenFlow action and
-implement them in lib/ofp-actions.[ch].
-
-*** OVS implementation
-
-Add code for action translation.  Possibly add datapath code for
-action implementation.  However, none of these new actions should
-require high-bandwidth processing so we could at least start with them
-implemented in userspace only.  (ARP field modification is already
-userspace-only and no one has complained yet.)
-
 ** IPv6
 
 *** ND versus ARP
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index 7b7532d..6b3b442 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -84,10 +84,8 @@ process_packet_in(struct controller_ctx *ctx OVS_UNUSED,
                   const struct ofp_header *msg)
 {
     struct ofputil_packet_in pin;
-    uint32_t buffer_id;
-    size_t total_len;
 
-    if (ofputil_decode_packet_in(msg, &pin, &total_len, &buffer_id) != 0) {
+    if (ofputil_decode_packet_in(msg, true, &pin, NULL, NULL, NULL) != 0) {
         return;
     }
     if (pin.reason != OFPR_ACTION) {
diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at
index e16de32..83a2301 100644
--- a/tests/ofp-actions.at
+++ b/tests/ofp-actions.at
@@ -113,12 +113,13 @@ ffff 0010 00002320 0013 000a 0014 0000
 # actions=controller(reason=invalid_ttl,max_len=1234,id=5678)
 ffff 0010 00002320 0014 04d2 162e 02 00
 
-# actions=controller(reason=invalid_ttl,max_len=1234,id=5678,userdata=01.02.03.04.05)
-ffff 0038 00002320 0025   000000000000 dnl
+# actions=controller(reason=invalid_ttl,max_len=1234,id=5678,userdata=01.02.03.04.05,pause)
+ffff 0040 00002320 0025   000000000000 dnl
 0000 0008 04d2   0000 dnl
 0001 0008 162e   0000 dnl
 0002 0005 02   000000 dnl
-0003 0009 0102030405   00000000000000
+0003 0009 0102030405   00000000000000 dnl
+0004 0004   00000000
 
 # actions=dec_ttl(32768,12345,90,765,1024)
 ffff 0020 00002320 0015 000500000000 80003039005A02fd 0400000000000000
@@ -237,6 +238,12 @@ fe800000 00000000 020c 29ff fe88 0001 dnl
 fe800000 00000000 020c 29ff fe88 a18b dnl
 00ff1000 00000000
 
+# bad OpenFlow10 actions: NXBRC_MUST_BE_ZERO
+ffff 0018 00002320 0025 0000 0005 0000 1122334455 000005
+
+# bad OpenFlow10 actions: NXBRC_MUST_BE_ZERO
+ffff 0018 00002320 0025 0000 0005 5000 1122334455 000000
+
 # bad OpenFlow10 actions: OFPBAC_BAD_ARGUMENT
 ffff 0048 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
 ffff 0030 00002320 0024 00 00 0011 000c fe800000 00000000 020c 29ff fe88 a18b fe800000 00000000 020c 29ff fe88 0001
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index 10ec04b..8e97434 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -3114,6 +3114,18 @@ NXT_FLOW_MOD_TABLE_ID (xid=0x1020304): enable
 ])
 AT_CLEANUP
 
+AT_SETUP([NXT_RESUME])
+AT_KEYWORDS([ofp-print])
+AT_CHECK([ovs-ofctl ofp-print "\
+01 04 0038 01020304 00002320 0000001c \
+0000 0012 ffffffffffff 102030405060 1234 000000000000 \
+0006 000a 00000002 fffd 000000000000
+"], [0], [dnl
+NXT_RESUME (xid=0x1020304): total_len=14 in_port=CONTROLLER (via no_match) data_len=14 (unbuffered)
+vlan_tci=0x0000,dl_src=10:20:30:40:50:60,dl_dst=ff:ff:ff:ff:ff:ff,dl_type=0x1234
+])
+AT_CLEANUP
+
 AT_SETUP([NXST_FLOW request])
 AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
 AT_CHECK([ovs-ofctl ofp-print "\
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 735f6fd..a597f4d 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -4225,6 +4225,178 @@ AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: 2
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+dnl CHECK_CONTINUATION(TITLE, N_PORTS0, N_PORTS1, ACTIONS0, ACTIONS1, [EXTRA_SETUP])
+dnl
+dnl Checks the implementation of the continuation mechanism that allows the
+dnl packet processing pipeline to be paused and resumed.  Starts by creating
+dnl bridge br0 with N_PORTS0 ports numbered 1 through N_PORTS0, and adds the
+dnl flows listed in ACTIONS0 to that bridge.  Then, injects a packet at port 1
+dnl in the bridge, resuming each time the pipeline pauses, and expects a single
+dnl packet to be output at each port 2 through N_PORTS0.  Then, as long as
+dnl ACTIONS0 still contains at least one "pause" action, removes one of them
+dnl and repeats the process.
+dnl
+dnl If N_PORTS1 is nonzero, also creates a bridge br1 and adds ports numbered
+dnl N_PORTS0 + 1 to N_PORTS0 + N_PORTS1 to it, as well as flows ACTIONS1.
+dnl ACTIONS1 may also contain "pause" actions.  Packets are only ever injected
+dnl into port 1 on br0, so br1 only comes into action if a patch port (added
+dnl by EXTRA_SETUP) jumps from one bridge to another.
+dnl
+dnl EXTRA_SETUP is an optional list of extra commands to run after setting up
+dnl both bridges, e.g. to configure mirrors or patch ports.
+m4_define([CHECK_CONTINUATION], [dnl
+    AT_SETUP([ofproto-dpif - continuation - $1])
+    AT_KEYWORDS([continuations pause resume])
+    OVS_VSWITCHD_START
+
+    # count_matches STRING
+    #
+    # Prints on stdout the number of occurrences of STRING in stdin.
+    count_matches () {
+        sed -n ":start
+    s/$[1]//p
+    t start" | wc -l
+    }
+
+    add_of_ports --pcap br0 `seq 1 $2`
+    m4_if([$3], [0], [],
+      [add_of_br 1
+       add_of_ports --pcap br1 `seq m4_eval([$2 + 1]) m4_eval([$2 + $3])`])
+
+    AT_CAPTURE_FILE([ofctl_monitor0.log])
+    AT_CHECK([ovs-ofctl monitor br0 resume --detach --no-chdir --pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log])
+    m4_if([$3], [0], [],
+      [AT_CAPTURE_FILE([ofctl_monitor1.log])
+       AT_CHECK([ovs-ofctl monitor br1 resume --detach --no-chdir --pidfile=ovs-ofctl1.pid 2> ofctl_monitor1.log])])
+
+    actions0='$4'
+    actions1='$5'
+    $6
+    flow="in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)"
+    n_packets=0
+    n_resumes=0
+    while true; do
+        printf "\n\nactions for br0:\n%s\n" "$actions0"
+        m4_if([$3], [0], [], [printf "actions for br1:\n%s\n" "$actions1"])
+
+        # Add flows.
+        AT_CHECK([echo "$actions0" | sed 's/pause/controller(pause)/g' | ovs-ofctl -O OpenFlow13 add-flows br0 -])
+        m4_if([$3], [0], [],
+            [AT_CHECK([echo "$actions1" | sed 's/pause/controller(pause)/g' | ovs-ofctl -O OpenFlow13 add-flows br1 -])])
+
+        # Run a packet through the switch.
+        AT_CHECK([ovs-appctl netdev-dummy/receive p1 "$flow"], [0], [stdout])
+
+        # Wait for the expected number of packets to show up.
+        n_packets=`expr $n_packets + $2 - 1 + $3`
+        echo "waiting for $n_packets packets..."
+        OVS_WAIT_UNTIL([test $n_packets = `ovs-ofctl parse-pcap p*-tx.pcap | wc -l`])
+
+        # Wait for the expected number of NXT_RESUMEs to be logged.
+        n_resumes=$(expr $n_resumes + $(echo "$actions0 $actions1" | count_matches pause) )
+        echo "waiting for $n_resumes NXT_RESUMEs..."
+        OVS_WAIT_UNTIL([test $n_resumes = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+        # Eliminate one "pause" from the actions.
+        #
+        # If there were none left, then we're done.
+        next_actions0=`echo "$actions0" | sed '1,/pause/s/pause//'`
+        if test X"$actions0" = X"$next_actions0"; then
+            next_actions1=`echo "$actions1" | sed '1,/pause/s/pause//'`
+            if test X"$actions1" = X"$next_actions1"; then
+                break
+            else
+                actions1=$next_actions1
+            fi
+        else
+            actions0=$next_actions0
+        fi
+
+        # Delete all the flows and verify that there are none, so that we
+        # can be sure that our updated flow tables is actually in use
+        # later.
+        AT_CHECK([ovs-ofctl del-flows br0 && ovs-ofctl dump-flows br0 | strip_xids], [0],
+          [NXST_FLOW reply:
+])
+        m4_if([$3], [0], [],
+          [AT_CHECK([ovs-ofctl del-flows br1 && ovs-ofctl dump-flows br1 | strip_xids], [0],
+             [NXST_FLOW reply:
+])])
+    done
+    OVS_VSWITCHD_STOP
+    AT_CLEANUP
+])
+
+# Check that pause at the end of the pipeline works OK.
+#
+# (xlate_continuation() has a special case for no-op actions; this
+# fails without that special case.)
+CHECK_CONTINUATION([pause at end of pipeline], [2], [0], [actions=2 pause])
+
+# Check that remaining actions are preserved following resume.
+CHECK_CONTINUATION([actions], [7], [0],
+  [in_port=1 actions=pause 2 pause 3 pause 4 pause 5 pause 6 pause 7])
+
+# Check that multiple levels of resubmit continue following resume.
+#
+# The "resubmit:55", which is relative to the current table, is
+# particularly interesting because it checks that the notion of the
+# current table is correctly preserved.
+CHECK_CONTINUATION([resubmit], [10], [0],
+  [table=0 in_port=1  actions=pause 2 pause resubmit(,1) pause 10 pause
+   table=1 in_port=1  actions=pause 3 pause resubmit(,2) pause 9 pause
+   table=2 in_port=1  actions=pause 4 pause resubmit(,3) pause 8 pause
+   table=3 in_port=1  actions=pause 5 pause resubmit:55  pause 7 pause
+   table=3 in_port=55 actions=pause 6 pause])
+
+# Check that the action set is preserved across pause/resume.
+CHECK_CONTINUATION([action set], [3], [0],
+  [in_port=1 actions=1 pause resubmit(,1) pause 2
+   table=1 actions=write_actions(3)])
+
+# Check that metadata and the stack used by push and pop is preserved
+# across pause/resume.
+CHECK_CONTINUATION([data stack], [3], [0],
+  [in_port=1 actions=pause dnl
+                     set_field:1->reg0 dnl
+                     pause dnl
+                     set_field:2->reg1 dnl
+                     pause dnl
+                     output:NXM_NX_REG0[[]] dnl
+                     pause dnl
+                     push:NXM_NX_REG1[[]] dnl
+                     dnl
+                     pop:NXM_NX_REG2[[]] dnl
+                     pause dnl
+                     output:NXM_NX_REG2[[]] dnl
+                     pause dnl
+                     3])
+
+# Check that mirror output occurs once and once only, even if
+# separated by pause/resume.
+CHECK_CONTINUATION([mirroring], [5], [0],
+  [in_port=1 actions=pause 2 pause 3 pause 4 pause], [],
+  [ovs-vsctl \
+       set Bridge br0 mirrors=@m --\
+       --id=@p2 get Port p2 --\
+       --id=@p3 get Port p3 --\
+       --id=@p4 get Port p4 --\
+       --id=@p5 get Port p5 --\
+       --id=@m create Mirror name=mymirror select_dst_port=@p2, at p3, at p4 output_port=@p5])
+
+# Check that pause works in the presence of patch ports.
+CHECK_CONTINUATION([patch ports], [4], [1],
+  [table=0 in_port=1  actions=pause 2 resubmit(,1) pause 4
+   table=1 in_port=1  actions=pause 3 pause 10 pause],
+  [table=0 in_port=11 actions=pause 5 pause],
+  [ovs-vsctl \
+       -- add-port br0 patch10 \
+       -- set interface patch10 type=patch options:peer=patch11 \
+                                ofport_request=10 \
+       -- add-port br1 patch11 \
+       -- set interface patch11 type=patch options:peer=patch10 \
+                                ofport_request=11])
+
 # Two testcases below are for the ofproto/trace command
 # The first one tests all correct syntax:
 # ofproto/trace [dp_name] odp_flow [-generate|packet]
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index c6c3321..a88bc8f 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -300,9 +300,7 @@ m4_define([_OVS_VSWITCHD_START],
 # system's real Ethernet devices).
 m4_define([OVS_VSWITCHD_START],
   [_OVS_VSWITCHD_START([--enable-dummy$3 --disable-system])
-
-   dnl Add bridges, ports, etc.
-   AT_CHECK([ovs-vsctl -- add-br br0 -- set bridge br0 datapath-type=dummy other-config:datapath-id=fedcba9876543210 other-config:hwaddr=aa:55:aa:55:00:00 protocols=[[OpenFlow10,OpenFlow11,OpenFlow12,OpenFlow13,OpenFlow14,OpenFlow15]] fail-mode=secure -- $1 m4_if([$2], [], [], [| ${PERL} $srcdir/uuidfilt.pl])], [0], [$2])
+   AT_CHECK([add_of_br 0 $1 m4_if([$2], [], [], [| ${PERL} $srcdir/uuidfilt.pl])], [0], [$2])
 ])
 
 # check_logs scans through all *.log files (except '*.log' and testsuite.log)
@@ -329,18 +327,47 @@ check_logs () {
 /|EMER|/p" ${logs}
 }
 
-# add_of_ports BRIDGE PNUM...
+# add_of_br BRNUM [ARG...]
+add_of_br () {
+    local brnum=$1; shift
+    local br=br$brnum
+    local dpid=fedcba987654321$brnum
+    local mac=aa:55:aa:55:00:0$brnum
+    ovs-vsctl \
+        -- add-br $br \
+	-- set bridge $br datapath-type=dummy \
+			  fail-mode=secure \
+			  other-config:datapath-id=$dpid \
+			  other-config:hwaddr=$mac \
+			  protocols="[[OpenFlow10,OpenFlow11,OpenFlow12,\
+                                       OpenFlow13,OpenFlow14,OpenFlow15]]" \
+        -- "$@"
+}
+
+# add_of_ports [--pcap] BRIDGE PNUM...
 #
 # Creates dummy interfaces in BRIDGE named pPNUM, OpenFlow port number
 # PNUM, and datapath port number PNUM (the latter is a consequence of
 # the dummy implementation, which tries to assign datapath port
 # numbers based on port names).
+#
+# If --pcap is supplied then packets received from the interface will
+# be written to $port-rx.pcap and those sent to it to $port-tx.pcap.
 add_of_ports () {
     local args
+    local pcap=false
+    if test "$1" = --pcap; then
+        pcap=:
+	shift
+    fi
     local br=$1; shift
     for pnum; do
         AS_VAR_APPEND([args], [" -- add-port $br p$pnum -- set Interface p$pnum type=dummy ofport_request=$pnum"])
+	if $pcap; then
+	    AS_VAR_APPEND([args], [" -- set Interface p$pnum options:rxq_pcap=p$pnum-rx.pcap options:tx_pcap=p$pnum-tx.pcap"])
+	fi
     done
+    echo ovs-vsctl $args
     ovs-vsctl $args
 }
 m4_divert_pop([PREPARE_TESTS])
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 49e95a7..981e60a 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1505,7 +1505,7 @@ network device that has the same name as the bridge.
 Outputs the packet on the port from which it was received.
 .
 .IP \fBcontroller(\fIkey\fB=\fIvalue\fR...\fB)
-Sends the packet to the OpenFlow controller as a ``packet in''
+Sends the packet and its metadata to the OpenFlow controller as a ``packet in''
 message.  The supported key-value pairs are:
 .RS
 .IP "\fBmax_len=\fInbytes\fR"
@@ -1527,6 +1527,15 @@ OpenFlow.
 Supplies the bytes represented as hex digits \fIhh\fR as additional
 data to the controller in the packet-in message.  Pairs of hex digits
 may be separated by periods for readability.
+.IP "\fBpause\fR"
+Causes the switch to freeze the packet's trip through Open vSwitch
+flow tables and serializes that state into the packet-in message as a
+``continuation,'' an additional property in the \fBNXT_PACKET_IN2\fR
+message.  The controller can later send the continuation back to the
+switch in an \fBNXT_RESUME\fR message, which will restart the packet's
+traversal from the point where it was interrupted.  This permits an
+OpenFlow controller to interpose on a packet midway through processing
+in Open vSwitch.
 .
 .RE
 .IP
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 0d990aa..c1876fd 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -1633,9 +1633,13 @@ ofctl_unblock(struct unixctl_conn *conn, int argc OVS_UNUSED,
 /* Prints to stdout all of the messages received on 'vconn'.
  *
  * Iff 'reply_to_echo_requests' is true, sends a reply to any echo request
- * received on 'vconn'. */
+ * received on 'vconn'.
+ *
+ * If 'resume_continuations' is true, sends an NXT_RESUME in reply to any
+ * NXT_PACKET_IN2 that includes a continuation. */
 static void
-monitor_vconn(struct vconn *vconn, bool reply_to_echo_requests)
+monitor_vconn(struct vconn *vconn, bool reply_to_echo_requests,
+              bool resume_continuations)
 {
     struct barrier_aux barrier_aux = { vconn, NULL };
     struct unixctl_server *server;
@@ -1663,6 +1667,10 @@ monitor_vconn(struct vconn *vconn, bool reply_to_echo_requests)
 
     daemonize_complete();
 
+    enum ofp_version version = vconn_get_version(vconn);
+    enum ofputil_protocol protocol
+        = ofputil_protocol_from_ofp_version(version);
+
     for (;;) {
         struct ofpbuf *b;
         int retval;
@@ -1708,6 +1716,36 @@ monitor_vconn(struct vconn *vconn, bool reply_to_echo_requests)
                     }
                 }
                 break;
+
+            case OFPTYPE_PACKET_IN:
+                if (resume_continuations) {
+                    struct ofputil_packet_in pin;
+                    struct ofpbuf continuation;
+
+                    error = ofputil_decode_packet_in(b->data, true, &pin,
+                                                     NULL, NULL,
+                                                     &continuation);
+                    if (error) {
+                        fprintf(stderr, "decoding packet-in failed: %s",
+                                ofperr_to_string(error));
+                    } else if (continuation.size) {
+                        struct ofpbuf *reply;
+
+                        reply = ofputil_encode_resume(&pin, &continuation,
+                                                      protocol);
+
+                        fprintf(stderr, "send: ");
+                        ofp_print(stderr, reply->data, reply->size,
+                                  verbosity + 2);
+                        fflush(stderr);
+
+                        retval = vconn_send_block(vconn, reply);
+                        if (retval) {
+                            ovs_fatal(retval, "failed to send NXT_RESUME");
+                        }
+                    }
+                }
+                break;
             }
             ofpbuf_delete(b);
         }
@@ -1758,6 +1796,7 @@ ofctl_monitor(struct ovs_cmdl_context *ctx)
     }
 
     open_vconn(ctx->argv[1], &vconn);
+    bool resume_continuations = false;
     for (i = 2; i < ctx->argc; i++) {
         const char *arg = ctx->argv[i];
 
@@ -1784,6 +1823,16 @@ ofctl_monitor(struct ovs_cmdl_context *ctx)
             ofputil_append_flow_monitor_request(&fmr, msg);
             dump_transaction(vconn, msg);
             fflush(stdout);
+        } else if (!strcmp(arg, "resume")) {
+            /* This option is intentionally undocumented because it is meant
+             * only for testing. */
+            resume_continuations = true;
+
+            /* Set miss_send_len to ensure that we get packet-ins. */
+            struct ofputil_switch_config config;
+            fetch_switch_config(vconn, &config);
+            config.miss_send_len = UINT16_MAX;
+            set_switch_config(vconn, &config);
         } else {
             ovs_fatal(0, "%s: unsupported \"monitor\" argument", arg);
         }
@@ -1805,7 +1854,7 @@ ofctl_monitor(struct ovs_cmdl_context *ctx)
         }
     }
 
-    monitor_vconn(vconn, true);
+    monitor_vconn(vconn, true, resume_continuations);
 }
 
 static void
@@ -1814,7 +1863,7 @@ ofctl_snoop(struct ovs_cmdl_context *ctx)
     struct vconn *vconn;
 
     open_vconn__(ctx->argv[1], SNOOP, &vconn);
-    monitor_vconn(vconn, false);
+    monitor_vconn(vconn, false, false);
 }
 
 static void
@@ -3610,35 +3659,43 @@ ofctl_parse_ofp11_match(struct ovs_cmdl_context *ctx OVS_UNUSED)
     ds_destroy(&in);
 }
 
-/* "parse-pcap PCAP": read packets from PCAP and print their flows. */
+/* "parse-pcap PCAP...": read packets from each PCAP file and print their
+ * flows. */
 static void
 ofctl_parse_pcap(struct ovs_cmdl_context *ctx)
 {
-    FILE *pcap;
+    int error = 0;
+    for (int i = 1; i < ctx->argc; i++) {
+        const char *filename = ctx->argv[i];
+        FILE *pcap = ovs_pcap_open(filename, "rb");
+        if (!pcap) {
+            error = errno;
+            ovs_error(error, "%s: open failed", filename);
+            continue;
+        }
 
-    pcap = ovs_pcap_open(ctx->argv[1], "rb");
-    if (!pcap) {
-        ovs_fatal(errno, "%s: open failed", ctx->argv[1]);
-    }
+        for (;;) {
+            struct dp_packet *packet;
+            struct flow flow;
+            int retval;
 
-    for (;;) {
-        struct dp_packet *packet;
-        struct flow flow;
-        int error;
+            retval = ovs_pcap_read(pcap, &packet, NULL);
+            if (retval == EOF) {
+                break;
+            } else if (retval) {
+                error = retval;
+                ovs_error(error, "%s: read failed", filename);
+            }
 
-        error = ovs_pcap_read(pcap, &packet, NULL);
-        if (error == EOF) {
-            break;
-        } else if (error) {
-            ovs_fatal(error, "%s: read failed", ctx->argv[1]);
+            pkt_metadata_init(&packet->md, ODPP_NONE);
+            flow_extract(packet, &flow);
+            flow_print(stdout, &flow);
+            putchar('\n');
+            dp_packet_delete(packet);
         }
-
-        pkt_metadata_init(&packet->md, ODPP_NONE);
-        flow_extract(packet, &flow);
-        flow_print(stdout, &flow);
-        putchar('\n');
-        dp_packet_delete(packet);
+        fclose(pcap);
     }
+    exit(error);
 }
 
 /* "check-vlan VLAN_TCI VLAN_TCI_MASK": converts the specified vlan_tci and
@@ -3976,7 +4033,7 @@ static const struct ovs_cmdl_command all_commands[] = {
     { "parse-instructions", NULL, 1, 1, ofctl_parse_instructions },
     { "parse-ofp10-match", NULL, 0, 0, ofctl_parse_ofp10_match },
     { "parse-ofp11-match", NULL, 0, 0, ofctl_parse_ofp11_match },
-    { "parse-pcap", NULL, 1, 1, ofctl_parse_pcap },
+    { "parse-pcap", NULL, 1, INT_MAX, ofctl_parse_pcap },
     { "check-vlan", NULL, 2, 2, ofctl_check_vlan },
     { "print-error", NULL, 1, 1, ofctl_print_error },
     { "encode-error-reply", NULL, 2, 2, ofctl_encode_error_reply },
-- 
2.1.3




More information about the dev mailing list