[ovs-dev] [of1.1 v3 04/13] Introduce ofpacts, an abstraction of OpenFlow actions.

Ben Pfaff blp at nicira.com
Wed Jun 27 06:53:47 UTC 2012


OpenFlow actions have always been somewhat awkward to handle.
Moreover, over time we've started creating actions that require more
complicated parsing.  When we maintain those actions internally in
their wire format, we end up parsing them multiple times, whenever
we have to look at the set of actions.

When we add support for OpenFlow 1.1 or later protocols, the situation
will get worse, because these newer protocols support many of the same
actions but with different representations.  It becomes unrealistic to
handle each protocol in its wire format.

This commit adopts a new strategy, by converting OpenFlow actions into
an internal form from the wire format when they are read, and converting
them back to the wire format when flows are dumped.  I believe that this
will be more maintainable over time.

Signed-off-by: Ben Pfaff <blp at nicira.com>

---

v2:

- Fix confusion between NXAST_RESUBMIT and NXAST_RESUBMIT_TABLE.

- Rename functions for NXAST_ actions to mention nxast in their
  names instead of openflow10, since they are not OpenFlow 1.0
  specific.

- Move the code for NXAST_ encoding into a separate function so
  that later on it can be reused for encoding these actions into
  OpenFlow 1.1 also.
---
 DESIGN                     |   20 +
 lib/automake.mk            |    2 +
 lib/autopath.c             |   70 ++--
 lib/autopath.h             |   15 +-
 lib/bundle.c               |  277 ++++++-----
 lib/bundle.h               |   26 +-
 lib/compiler.h             |   20 +-
 lib/learn.c                |  672 ++++++++++++-------------
 lib/learn.h                |   17 +-
 lib/learning-switch.c      |   99 ++--
 lib/multipath.c            |  160 ++++---
 lib/multipath.h            |   18 +-
 lib/nx-match.c             |  200 ++++----
 lib/nx-match.h             |   31 +-
 lib/ofp-actions.c          | 1213 ++++++++++++++++++++++++++++++++++++++++++++
 lib/ofp-actions.h          |  479 +++++++++++++++++
 lib/ofp-parse.c            |  252 +++++-----
 lib/ofp-parse.h            |    2 +-
 lib/ofp-print.c            |  291 +----------
 lib/ofp-print.h            |    2 -
 lib/ofp-util.c             |  474 ++++--------------
 lib/ofp-util.h             |   61 +--
 ofproto/connmgr.c          |   15 +-
 ofproto/fail-open.c        |   14 +-
 ofproto/ofproto-dpif.c     |  398 +++++++--------
 ofproto/ofproto-provider.h |   33 +-
 ofproto/ofproto.c          |  121 +++---
 tests/learn.at             |    4 +-
 tests/test-bundle.c        |   55 +--
 tests/test-multipath.c     |   11 +-
 utilities/ovs-ofctl.c      |   54 +-
 31 files changed, 3119 insertions(+), 1987 deletions(-)
 create mode 100644 lib/ofp-actions.c
 create mode 100644 lib/ofp-actions.h

diff --git a/DESIGN b/DESIGN
index a3a62b2..8dfc1ff 100644
--- a/DESIGN
+++ b/DESIGN
@@ -612,6 +612,26 @@ The following are explicitly *not* supported by in-band control:
      gateway.
 
 
+Action Reproduction
+===================
+
+It seems likely that many controllers, at least at startup, use the
+OpenFlow "flow statistics" request to obtain existing flows, then
+compare the flows' actions against the actions that they expect to
+find.  Before version 1.8.0, Open vSwitch always returned exact,
+byte-for-byte copies of the actions that had been added to the flow
+table.  The current version of Open vSwitch does not always do this in
+some exceptional cases.  This section lists the exceptions that
+controller authors must keep in mind if they compare actual actions
+against desired actions in a bytewise fashion:
+
+	- Open vSwitch zeros padding bytes in action structures,
+          regardless of their values when the flows were added.
+
+Please report other discrepancies, if you notice any, so that we can
+fix or document them.
+
+
 Suggestions
 ===========
 
diff --git a/lib/automake.mk b/lib/automake.mk
index 9d8b426..641a2f7 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -96,6 +96,8 @@ lib_libopenvswitch_a_SOURCES = \
 	lib/nx-match.h \
 	lib/odp-util.c \
 	lib/odp-util.h \
+	lib/ofp-actions.c \
+	lib/ofp-actions.h \
 	lib/ofp-errors.c \
 	lib/ofp-errors.h \
 	lib/ofp-parse.c \
diff --git a/lib/autopath.c b/lib/autopath.c
index 9511a6d..3d9b350 100644
--- a/lib/autopath.c
+++ b/lib/autopath.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Nicira, Inc.
+ * Copyright (c) 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
 #include "flow.h"
 #include "meta-flow.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "openflow/nicira-ext.h"
@@ -31,32 +32,21 @@
 
 VLOG_DEFINE_THIS_MODULE(autopath);
 
-/* Loads 'ofp_port' into the appropriate register in accordance with the
- * autopath action. */
 void
-autopath_execute(const struct nx_action_autopath *ap, struct flow *flow,
-                 uint16_t ofp_port)
-{
-    struct mf_subfield dst;
-
-    nxm_decode(&dst, ap->dst, ap->ofs_nbits);
-    mf_set_subfield_value(&dst, ofp_port, flow);
-}
-
-void
-autopath_parse(struct nx_action_autopath *ap, const char *s_)
+autopath_parse(struct ofpact_autopath *ap, const char *s_)
 {
     char *s;
-    char *id_str, *dst_s, *save_ptr;
-    struct mf_subfield dst;
     int id_int;
+    char *id_str, *dst, *save_ptr;
+
+    ofpact_init_AUTOPATH(ap);
 
     s = xstrdup(s_);
     save_ptr = NULL;
     id_str = strtok_r(s, ", ", &save_ptr);
-    dst_s = strtok_r(NULL, ", ", &save_ptr);
+    dst = strtok_r(NULL, ", ", &save_ptr);
 
-    if (!dst_s) {
+    if (!dst) {
         ovs_fatal(0, "%s: not enough arguments to autopath action", s_);
     }
 
@@ -65,33 +55,51 @@ autopath_parse(struct nx_action_autopath *ap, const char *s_)
         ovs_fatal(0, "%s: autopath id %d is not in valid range "
                   "1 to %"PRIu32, s_, id_int, UINT32_MAX);
     }
+    ap->port = id_int;
 
-    mf_parse_subfield(&dst, dst_s);
-    if (dst.n_bits < 16) {
+    mf_parse_subfield(&ap->dst, dst);
+    if (ap->dst.n_bits < 16) {
         ovs_fatal(0, "%s: %d-bit destination field has %u possible values, "
                   "less than required 65536",
-                  s_, dst.n_bits, 1u << dst.n_bits);
+                  s_, ap->dst.n_bits, 1u << ap->dst.n_bits);
     }
 
-    ofputil_init_NXAST_AUTOPATH(ap);
-    ap->id = htonl(id_int);
-    ap->ofs_nbits = nxm_encode_ofs_nbits(dst.ofs, dst.n_bits);
-    ap->dst = htonl(dst.field->nxm_header);
-
     free(s);
 }
 
 enum ofperr
-autopath_check(const struct nx_action_autopath *ap, const struct flow *flow)
+autopath_from_openflow(const struct nx_action_autopath *nap,
+                       struct ofpact_autopath *autopath)
 {
-    struct mf_subfield dst;
+    ofpact_init_AUTOPATH(autopath);
+    autopath->dst.field = mf_from_nxm_header(ntohl(nap->dst));
+    autopath->dst.ofs = nxm_decode_ofs(nap->ofs_nbits);
+    autopath->dst.n_bits = nxm_decode_n_bits(nap->ofs_nbits);
+    autopath->port = ntohl(nap->id);
 
-    nxm_decode(&dst, ap->dst, ap->ofs_nbits);
-    if (dst.n_bits < 16) {
+    if (autopath->dst.n_bits < 16) {
         VLOG_WARN("at least 16 bit destination is required for autopath "
                   "action.");
         return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
 
-    return mf_check_dst(&dst, flow);
+    return autopath_check(autopath, NULL);
+}
+
+enum ofperr
+autopath_check(const struct ofpact_autopath *autopath, const struct flow *flow)
+{
+    return mf_check_dst(&autopath->dst, flow);
+}
+
+void
+autopath_to_openflow(const struct ofpact_autopath *autopath,
+                     struct ofpbuf *openflow)
+{
+    struct nx_action_autopath *ap = ofputil_put_NXAST_AUTOPATH(openflow);
+
+    ap->ofs_nbits = nxm_encode_ofs_nbits(autopath->dst.ofs,
+                                         autopath->dst.n_bits);
+    ap->dst = htonl(autopath->dst.field->nxm_header);
+    ap->id = htonl(autopath->port);
 }
diff --git a/lib/autopath.h b/lib/autopath.h
index 480d40a..b23c13f 100644
--- a/lib/autopath.h
+++ b/lib/autopath.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Nicira, Inc.
+ * Copyright (c) 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,15 +22,20 @@
 
 struct flow;
 struct nx_action_autopath;
+struct ofpact_autopath;
+struct ofpbuf;
 
 /* NXAST_AUTOPATH  helper functions.
  *
  * See include/openflow/nicira-ext.h for NXAST_AUTOPATH specification. */
 
-void autopath_execute(const struct nx_action_autopath *, struct flow *,
-                      uint16_t ofp_port);
-void autopath_parse(struct nx_action_autopath *, const char *);
-enum ofperr autopath_check(const struct nx_action_autopath *,
+void autopath_parse(struct ofpact_autopath *, const char *);
+
+enum ofperr autopath_from_openflow(const struct nx_action_autopath *,
+                                   struct ofpact_autopath *);
+enum ofperr autopath_check(const struct ofpact_autopath *,
                            const struct flow *);
+void autopath_to_openflow(const struct ofpact_autopath *,
+                          struct ofpbuf *openflow);
 
 #endif /* autopath.h */
diff --git a/lib/bundle.c b/lib/bundle.c
index a205974..830c002 100644
--- a/lib/bundle.c
+++ b/lib/bundle.c
@@ -25,6 +25,7 @@
 #include "meta-flow.h"
 #include "nx-match.h"
 #include "ofpbuf.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "openflow/nicira-ext.h"
@@ -35,14 +36,13 @@
 VLOG_DEFINE_THIS_MODULE(bundle);
 
 static uint16_t
-execute_ab(const struct nx_action_bundle *nab,
+execute_ab(const struct ofpact_bundle *bundle,
            bool (*slave_enabled)(uint16_t ofp_port, void *aux), void *aux)
 {
     size_t i;
 
-    for (i = 0; i < ntohs(nab->n_slaves); i++) {
-        uint16_t slave = bundle_get_slave(nab, i);
-
+    for (i = 0; i < bundle->n_slaves; i++) {
+        uint16_t slave = bundle->slaves[i];
         if (slave_enabled(slave, aux)) {
             return slave;
         }
@@ -52,18 +52,18 @@ execute_ab(const struct nx_action_bundle *nab,
 }
 
 static uint16_t
-execute_hrw(const struct nx_action_bundle *nab, const struct flow *flow,
+execute_hrw(const struct ofpact_bundle *bundle, const struct flow *flow,
             bool (*slave_enabled)(uint16_t ofp_port, void *aux), void *aux)
 {
     uint32_t flow_hash, best_hash;
     int best, i;
 
-    flow_hash = flow_hash_fields(flow, ntohs(nab->fields), ntohs(nab->basis));
+    flow_hash = flow_hash_fields(flow, bundle->fields, bundle->basis);
     best = -1;
     best_hash = 0;
 
-    for (i = 0; i < ntohs(nab->n_slaves); i++) {
-        if (slave_enabled(bundle_get_slave(nab, i), aux)) {
+    for (i = 0; i < bundle->n_slaves; i++) {
+        if (slave_enabled(bundle->slaves[i], aux)) {
             uint32_t hash = hash_2words(i, flow_hash);
 
             if (best < 0 || hash > best_hash) {
@@ -73,33 +73,26 @@ execute_hrw(const struct nx_action_bundle *nab, const struct flow *flow,
         }
     }
 
-    return best >= 0 ? bundle_get_slave(nab, best) : OFPP_NONE;
+    return best >= 0 ? bundle->slaves[best] : OFPP_NONE;
 }
 
-/* Executes 'nab' on 'flow'.  Uses 'slave_enabled' to determine if the slave
+/* Executes 'bundle' on 'flow'.  Uses 'slave_enabled' to determine if the slave
  * designated by 'ofp_port' is up.  Returns the chosen slave, or OFPP_NONE if
  * none of the slaves are acceptable. */
 uint16_t
-bundle_execute(const struct nx_action_bundle *nab, const struct flow *flow,
+bundle_execute(const struct ofpact_bundle *bundle, const struct flow *flow,
                bool (*slave_enabled)(uint16_t ofp_port, void *aux), void *aux)
 {
-    switch (ntohs(nab->algorithm)) {
-    case NX_BD_ALG_HRW: return execute_hrw(nab, flow, slave_enabled, aux);
-    case NX_BD_ALG_ACTIVE_BACKUP: return execute_ab(nab, slave_enabled, aux);
-    default: NOT_REACHED();
-    }
-}
+    switch (bundle->algorithm) {
+    case NX_BD_ALG_HRW:
+        return execute_hrw(bundle, flow, slave_enabled, aux);
 
-void
-bundle_execute_load(const struct nx_action_bundle *nab, struct flow *flow,
-                    bool (*slave_enabled)(uint16_t ofp_port, void *aux),
-                    void *aux)
-{
-    struct mf_subfield dst;
+    case NX_BD_ALG_ACTIVE_BACKUP:
+        return execute_ab(bundle, slave_enabled, aux);
 
-    nxm_decode(&dst, nab->dst, nab->ofs_nbits);
-    mf_set_subfield_value(&dst, bundle_execute(nab, flow, slave_enabled, aux),
-                          flow);
+    default:
+        NOT_REACHED();
+    }
 }
 
 /* Checks that 'nab' specifies a bundle action which is supported by this
@@ -107,41 +100,43 @@ bundle_execute_load(const struct nx_action_bundle *nab, struct flow *flow,
  * ofputil_check_output_port().  Returns 0 if 'nab' is supported, otherwise an
  * OFPERR_* error code. */
 enum ofperr
-bundle_check(const struct nx_action_bundle *nab, int max_ports,
-             const struct flow *flow)
+bundle_from_openflow(const struct nx_action_bundle *nab,
+                     struct ofpbuf *ofpacts)
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-    uint16_t n_slaves, fields, algorithm, subtype;
+    struct ofpact_bundle *bundle;
+    uint16_t subtype;
     uint32_t slave_type;
     size_t slaves_size, i;
     enum ofperr error;
 
+    bundle = ofpact_put_BUNDLE(ofpacts);
+
     subtype = ntohs(nab->subtype);
-    n_slaves = ntohs(nab->n_slaves);
-    fields = ntohs(nab->fields);
-    algorithm = ntohs(nab->algorithm);
+    bundle->n_slaves = ntohs(nab->n_slaves);
+    bundle->basis = ntohs(nab->basis);
+    bundle->fields = ntohs(nab->fields);
+    bundle->algorithm = ntohs(nab->algorithm);
     slave_type = ntohl(nab->slave_type);
     slaves_size = ntohs(nab->len) - sizeof *nab;
 
     error = OFPERR_OFPBAC_BAD_ARGUMENT;
-    if (!flow_hash_fields_valid(fields)) {
-        VLOG_WARN_RL(&rl, "unsupported fields %"PRIu16, fields);
-    } else if (n_slaves > BUNDLE_MAX_SLAVES) {
+    if (!flow_hash_fields_valid(bundle->fields)) {
+        VLOG_WARN_RL(&rl, "unsupported fields %d", (int) bundle->fields);
+    } else if (bundle->n_slaves > BUNDLE_MAX_SLAVES) {
         VLOG_WARN_RL(&rl, "too may slaves");
-    } else if (algorithm != NX_BD_ALG_HRW
-               && algorithm != NX_BD_ALG_ACTIVE_BACKUP) {
-        VLOG_WARN_RL(&rl, "unsupported algorithm %"PRIu16, algorithm);
+    } else if (bundle->algorithm != NX_BD_ALG_HRW
+               && bundle->algorithm != NX_BD_ALG_ACTIVE_BACKUP) {
+        VLOG_WARN_RL(&rl, "unsupported algorithm %d", (int) bundle->algorithm);
     } else if (slave_type != NXM_OF_IN_PORT) {
         VLOG_WARN_RL(&rl, "unsupported slave type %"PRIu16, slave_type);
     } else {
         error = 0;
     }
 
-    for (i = 0; i < sizeof(nab->zero); i++) {
-        if (nab->zero[i]) {
-            VLOG_WARN_RL(&rl, "reserved field is nonzero");
-            error = OFPERR_OFPBAC_BAD_ARGUMENT;
-        }
+    if (!is_all_zeros(nab->zero, sizeof nab->zero)) {
+        VLOG_WARN_RL(&rl, "reserved field is nonzero");
+        error = OFPERR_OFPBAC_BAD_ARGUMENT;
     }
 
     if (subtype == NXAST_BUNDLE && (nab->ofs_nbits || nab->dst)) {
@@ -150,34 +145,61 @@ bundle_check(const struct nx_action_bundle *nab, int max_ports,
     }
 
     if (subtype == NXAST_BUNDLE_LOAD) {
-        struct mf_subfield dst;
+        bundle->dst.field = mf_from_nxm_header(ntohl(nab->dst));
+        bundle->dst.ofs = nxm_decode_ofs(nab->ofs_nbits);
+        bundle->dst.n_bits = nxm_decode_n_bits(nab->ofs_nbits);
 
-        nxm_decode(&dst, nab->dst, nab->ofs_nbits);
-        if (dst.n_bits < 16) {
+        if (bundle->dst.n_bits < 16) {
             VLOG_WARN_RL(&rl, "bundle_load action requires at least 16 bit "
                          "destination.");
             error = OFPERR_OFPBAC_BAD_ARGUMENT;
-        } else if (!error) {
-            error = mf_check_dst(&dst, flow);
         }
     }
 
-    if (slaves_size < n_slaves * sizeof(ovs_be16)) {
+    if (slaves_size < bundle->n_slaves * sizeof(ovs_be16)) {
         VLOG_WARN_RL(&rl, "Nicira action %"PRIu16" only has %zu bytes "
                      "allocated for slaves.  %zu bytes are required for "
                      "%"PRIu16" slaves.", subtype, slaves_size,
-                     n_slaves * sizeof(ovs_be16), n_slaves);
+                     bundle->n_slaves * sizeof(ovs_be16), bundle->n_slaves);
         error = OFPERR_OFPBAC_BAD_LEN;
     }
 
-    for (i = 0; i < n_slaves; i++) {
-        uint16_t ofp_port = bundle_get_slave(nab, i);
-        enum ofperr ofputil_error;
+    for (i = 0; i < bundle->n_slaves; i++) {
+        uint16_t ofp_port = ntohs(((ovs_be16 *)(nab + 1))[i]);
+        ofpbuf_put(ofpacts, &ofp_port, sizeof ofp_port);
+    }
+
+    bundle = ofpacts->l2;
+    ofpact_update_len(ofpacts, &bundle->ofpact);
+
+    if (!error) {
+        error = bundle_check(bundle, OFPP_MAX, NULL);
+    }
+    return error;
+}
+
+enum ofperr
+bundle_check(const struct ofpact_bundle *bundle, int max_ports,
+             const struct flow *flow)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+    size_t i;
+
+    if (bundle->dst.field) {
+        enum ofperr error = mf_check_dst(&bundle->dst, flow);
+        if (error) {
+            return error;
+        }
+    }
+
+    for (i = 0; i < bundle->n_slaves; i++) {
+        uint16_t ofp_port = bundle->slaves[i];
+        enum ofperr error;
 
-        ofputil_error = ofputil_check_output_port(ofp_port, max_ports);
-        if (ofputil_error) {
+        error = ofputil_check_output_port(ofp_port, max_ports);
+        if (error) {
             VLOG_WARN_RL(&rl, "invalid slave %"PRIu16, ofp_port);
-            error = ofputil_error;
+            return error;
         }
 
         /* Controller slaves are unsupported due to the lack of a max_len
@@ -185,23 +207,50 @@ bundle_check(const struct nx_action_bundle *nab, int max_ports,
          * seem to be a real-world use-case for supporting it. */
         if (ofp_port == OFPP_CONTROLLER) {
             VLOG_WARN_RL(&rl, "unsupported controller slave");
-            error = OFPERR_OFPBAC_BAD_OUT_PORT;
+            return OFPERR_OFPBAC_BAD_OUT_PORT;
         }
     }
 
-    return error;
+    return 0;
+}
+
+void
+bundle_to_openflow(const struct ofpact_bundle *bundle, struct ofpbuf *openflow)
+{
+    int slaves_len = ROUND_UP(bundle->n_slaves, OFP_ACTION_ALIGN);
+    struct nx_action_bundle *nab;
+    ovs_be16 *slaves;
+    size_t i;
+
+    nab = (bundle->dst.field
+           ? ofputil_put_NXAST_BUNDLE_LOAD(openflow)
+           : ofputil_put_NXAST_BUNDLE(openflow));
+    nab->len = htons(ntohs(nab->len) + slaves_len);
+    nab->algorithm = htons(bundle->algorithm);
+    nab->fields = htons(bundle->fields);
+    nab->basis = htons(bundle->basis);
+    nab->slave_type = htonl(NXM_OF_IN_PORT);
+    nab->n_slaves = htons(bundle->n_slaves);
+    if (bundle->dst.field) {
+        nab->ofs_nbits = nxm_encode_ofs_nbits(bundle->dst.ofs,
+                                              bundle->dst.n_bits);
+        nab->dst = htonl(bundle->dst.field->nxm_header);
+    }
+
+    slaves = ofpbuf_put_zeros(openflow, slaves_len);
+    for (i = 0; i < bundle->n_slaves; i++) {
+        slaves[i] = htons(bundle->slaves[i]);
+    }
 }
 
 /* Helper for bundle_parse and bundle_parse_load. */
 static void
-bundle_parse__(struct ofpbuf *b, const char *s, char **save_ptr,
+bundle_parse__(const char *s, char **save_ptr,
                const char *fields, const char *basis, const char *algorithm,
-               const char *slave_type, const char *dst_s,
-               const char *slave_delim)
+               const char *slave_type, const char *dst,
+               const char *slave_delim, struct ofpbuf *ofpacts)
 {
-    enum ofputil_action_code code;
-    struct nx_action_bundle *nab;
-    uint16_t n_slaves;
+    struct ofpact_bundle *bundle;
 
     if (!slave_delim) {
         ovs_fatal(0, "%s: not enough arguments to bundle action", s);
@@ -212,72 +261,56 @@ bundle_parse__(struct ofpbuf *b, const char *s, char **save_ptr,
                    s, slave_delim);
     }
 
-    code = dst_s ? OFPUTIL_NXAST_BUNDLE_LOAD : OFPUTIL_NXAST_BUNDLE;
-    b->l2 = ofputil_put_action(code, b);
+    bundle = ofpact_put_BUNDLE(ofpacts);
 
-    n_slaves = 0;
     for (;;) {
-        ovs_be16 slave_be;
+        uint16_t slave_port;
         char *slave;
 
         slave = strtok_r(NULL, ", [", save_ptr);
-        if (!slave || n_slaves >= BUNDLE_MAX_SLAVES) {
+        if (!slave || bundle->n_slaves >= BUNDLE_MAX_SLAVES) {
             break;
         }
 
-        slave_be = htons(atoi(slave));
-        ofpbuf_put(b, &slave_be, sizeof slave_be);
+        slave_port = atoi(slave);
+        ofpbuf_put(ofpacts, &slave_port, sizeof slave_port);
 
-        n_slaves++;
+        bundle = ofpacts->l2;
+        bundle->n_slaves++;
     }
+    ofpact_update_len(ofpacts, &bundle->ofpact);
 
-    /* Slaves array must be multiple of 8 bytes long. */
-    if (b->size % 8) {
-        ofpbuf_put_zeros(b, 8 - (b->size % 8));
-    }
-
-    nab = b->l2;
-    nab->len = htons(b->size - ((char *) b->l2 - (char *) b->data));
-    nab->n_slaves = htons(n_slaves);
-    nab->basis = htons(atoi(basis));
+    bundle->basis = atoi(basis);
 
     if (!strcasecmp(fields, "eth_src")) {
-        nab->fields = htons(NX_HASH_FIELDS_ETH_SRC);
+        bundle->fields = NX_HASH_FIELDS_ETH_SRC;
     } else if (!strcasecmp(fields, "symmetric_l4")) {
-        nab->fields = htons(NX_HASH_FIELDS_SYMMETRIC_L4);
+        bundle->fields = NX_HASH_FIELDS_SYMMETRIC_L4;
     } else {
         ovs_fatal(0, "%s: unknown fields `%s'", s, fields);
     }
 
     if (!strcasecmp(algorithm, "active_backup")) {
-        nab->algorithm = htons(NX_BD_ALG_ACTIVE_BACKUP);
+        bundle->algorithm = NX_BD_ALG_ACTIVE_BACKUP;
     } else if (!strcasecmp(algorithm, "hrw")) {
-        nab->algorithm = htons(NX_BD_ALG_HRW);
+        bundle->algorithm = NX_BD_ALG_HRW;
     } else {
         ovs_fatal(0, "%s: unknown algorithm `%s'", s, algorithm);
     }
 
-    if (!strcasecmp(slave_type, "ofport")) {
-        nab->slave_type = htonl(NXM_OF_IN_PORT);
-    } else {
+    if (strcasecmp(slave_type, "ofport")) {
         ovs_fatal(0, "%s: unknown slave_type `%s'", s, slave_type);
     }
 
-    if (dst_s) {
-        struct mf_subfield dst;
-
-        mf_parse_subfield(&dst, dst_s);
-        nab->dst = htonl(dst.field->nxm_header);
-        nab->ofs_nbits = nxm_encode_ofs_nbits(dst.ofs, dst.n_bits);
+    if (dst) {
+        mf_parse_subfield(&bundle->dst, dst);
     }
-
-    b->l2 = NULL;
 }
 
 /* Converts a bundle action string contained in 's' to an nx_action_bundle and
  * stores it in 'b'.  Sets 'b''s l2 pointer to NULL. */
 void
-bundle_parse(struct ofpbuf *b, const char *s)
+bundle_parse(const char *s, struct ofpbuf *ofpacts)
 {
     char *fields, *basis, *algorithm, *slave_type, *slave_delim;
     char *tokstr, *save_ptr;
@@ -290,15 +323,15 @@ bundle_parse(struct ofpbuf *b, const char *s)
     slave_type = strtok_r(NULL, ", ", &save_ptr);
     slave_delim = strtok_r(NULL, ": ", &save_ptr);
 
-    bundle_parse__(b, s, &save_ptr, fields, basis, algorithm, slave_type, NULL,
-                   slave_delim);
+    bundle_parse__(s, &save_ptr, fields, basis, algorithm, slave_type, NULL,
+                   slave_delim, ofpacts);
     free(tokstr);
 }
 
 /* Converts a bundle_load action string contained in 's' to an nx_action_bundle
  * and stores it in 'b'.  Sets 'b''s l2 pointer to NULL. */
 void
-bundle_parse_load(struct ofpbuf *b, const char *s)
+bundle_parse_load(const char *s, struct ofpbuf *ofpacts)
 {
     char *fields, *basis, *algorithm, *slave_type, *dst, *slave_delim;
     char *tokstr, *save_ptr;
@@ -312,22 +345,22 @@ bundle_parse_load(struct ofpbuf *b, const char *s)
     dst = strtok_r(NULL, ", ", &save_ptr);
     slave_delim = strtok_r(NULL, ": ", &save_ptr);
 
-    bundle_parse__(b, s, &save_ptr, fields, basis, algorithm, slave_type, dst,
-                   slave_delim);
+    bundle_parse__(s, &save_ptr, fields, basis, algorithm, slave_type, dst,
+                   slave_delim, ofpacts);
 
     free(tokstr);
 }
 
 /* Appends a human-readable representation of 'nab' to 's'. */
 void
-bundle_format(const struct nx_action_bundle *nab, struct ds *s)
+bundle_format(const struct ofpact_bundle *bundle, struct ds *s)
 {
-    const char *action, *fields, *algorithm, *slave_type;
+    const char *action, *fields, *algorithm;
     size_t i;
 
-    fields = flow_hash_fields_to_str(ntohs(nab->fields));
+    fields = flow_hash_fields_to_str(bundle->fields);
 
-    switch (ntohs(nab->algorithm)) {
+    switch (bundle->algorithm) {
     case NX_BD_ALG_HRW:
         algorithm = "hrw";
         break;
@@ -338,43 +371,23 @@ bundle_format(const struct nx_action_bundle *nab, struct ds *s)
         algorithm = "<unknown>";
     }
 
-    switch (ntohl(nab->slave_type)) {
-    case NXM_OF_IN_PORT:
-        slave_type = "ofport";
-        break;
-    default:
-        slave_type = "<unknown>";
-    }
-
-    switch (ntohs(nab->subtype)) {
-    case NXAST_BUNDLE:
-        action = "bundle";
-        break;
-    case NXAST_BUNDLE_LOAD:
-        action = "bundle_load";
-        break;
-    default:
-        NOT_REACHED();
-    }
+    action = bundle->dst.field ? "bundle_load" : "bundle";
 
     ds_put_format(s, "%s(%s,%"PRIu16",%s,%s,", action, fields,
-                  ntohs(nab->basis), algorithm, slave_type);
-
-    if (nab->subtype == htons(NXAST_BUNDLE_LOAD)) {
-        struct mf_subfield dst;
+                  bundle->basis, algorithm, "ofport");
 
-        nxm_decode(&dst, nab->dst, nab->ofs_nbits);
-        mf_format_subfield(&dst, s);
+    if (bundle->dst.field) {
+        mf_format_subfield(&bundle->dst, s);
         ds_put_cstr(s, ",");
     }
 
     ds_put_cstr(s, "slaves:");
-    for (i = 0; i < ntohs(nab->n_slaves); i++) {
+    for (i = 0; i < bundle->n_slaves; i++) {
         if (i) {
             ds_put_cstr(s, ",");
         }
 
-        ds_put_format(s, "%"PRIu16, bundle_get_slave(nab, i));
+        ds_put_format(s, "%"PRIu16, bundle->slaves[i]);
     }
 
     ds_put_cstr(s, ")");
diff --git a/lib/bundle.h b/lib/bundle.h
index 4c0dff5..6d07b33 100644
--- a/lib/bundle.h
+++ b/lib/bundle.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011 Nicira, Inc.
+/* Copyright (c) 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,29 +27,23 @@
 
 struct ds;
 struct flow;
+struct ofpact_bundle;
 struct ofpbuf;
 
 /* NXAST_BUNDLE helper functions.
  *
  * See include/openflow/nicira-ext.h for NXAST_BUNDLE specification. */
 
-uint16_t bundle_execute(const struct nx_action_bundle *, const struct flow *,
+uint16_t bundle_execute(const struct ofpact_bundle *, const struct flow *,
                         bool (*slave_enabled)(uint16_t ofp_port, void *aux),
                         void *aux);
-void bundle_execute_load(const struct nx_action_bundle *, struct flow *,
-                         bool (*slave_enabled)(uint16_t ofp_port, void *aux),
-                         void *aux);
-enum ofperr bundle_check(const struct nx_action_bundle *, int max_ports,
+enum ofperr bundle_from_openflow(const struct nx_action_bundle *,
+                                 struct ofpbuf *ofpact);
+enum ofperr bundle_check(const struct ofpact_bundle *, int max_ports,
                          const struct flow *);
-void bundle_parse(struct ofpbuf *, const char *);
-void bundle_parse_load(struct ofpbuf *b, const char *);
-void bundle_format(const struct nx_action_bundle *, struct ds *);
-
-/* Returns the 'i'th slave in 'nab'. */
-static inline uint16_t
-bundle_get_slave(const struct nx_action_bundle *nab, size_t i)
-{
-    return ntohs(((ovs_be16 *)(nab + 1))[i]);
-}
+void bundle_to_openflow(const struct ofpact_bundle *, struct ofpbuf *of10);
+void bundle_parse(const char *, struct ofpbuf *ofpacts);
+void bundle_parse_load(const char *, struct ofpbuf *ofpacts);
+void bundle_format(const struct ofpact_bundle *, struct ds *);
 
 #endif /* bundle.h */
diff --git a/lib/compiler.h b/lib/compiler.h
index 75e8610..27612a7 100644
--- a/lib/compiler.h
+++ b/lib/compiler.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -37,4 +37,22 @@
 #define SENTINEL(N)
 #endif
 
+/* ISO C says that a C implementation may choose any integer type for an enum
+ * that is sufficient to hold all of its values.  Common ABIs (such as the
+ * System V ABI used on i386 GNU/Linux) always use a full-sized "int", even
+ * when a smaller type would suffice.
+ *
+ * In GNU C, "enum __attribute__((packed)) name { ... }" defines 'name' as an
+ * enum compatible with a type that is no bigger than necessary.  This is the
+ * intended use of OVS_PACKED_ENUM.
+ *
+ * OVS_PACKED_ENUM is intended for use only as a space optimization, since it
+ * only works with GCC.  That means that it must not be used in wire protocols
+ * or otherwise exposed outside of a single process. */
+#if __GNUC__ && !__CHECKER__
+#define OVS_PACKED_ENUM __attribute__((__packed__))
+#else
+#define OVS_PACKED_ENUM
+#endif
+
 #endif /* compiler.h */
diff --git a/lib/learn.c b/lib/learn.c
index 5478b74..7dd02d0 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -22,6 +22,7 @@
 #include "dynamic-string.h"
 #include "meta-flow.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -46,19 +47,6 @@ get_be32(const void **pp)
     return value;
 }
 
-static uint64_t
-get_bits(int n_bits, const void **p)
-{
-    int n_segs = DIV_ROUND_UP(n_bits, 16);
-    uint64_t value;
-
-    value = 0;
-    while (n_segs-- > 0) {
-        value = (value << 16) | ntohs(get_be16(p));
-    }
-    return value;
-}
-
 static void
 get_subfield(int n_bits, const void **p, struct mf_subfield *sf)
 {
@@ -90,105 +78,85 @@ learn_min_len(uint16_t header)
     return min_len;
 }
 
-static enum ofperr
-learn_check_header(uint16_t header, size_t len)
+/* Converts 'nal' into a "struct ofpact_learn" and appends that struct to
+ * 'ofpacts'.  Returns 0 if successful, otherwise an OFPERR_*. */
+enum ofperr
+learn_from_openflow(const struct nx_action_learn *nal, struct ofpbuf *ofpacts)
 {
-    int src_type = header & NX_LEARN_SRC_MASK;
-    int dst_type = header & NX_LEARN_DST_MASK;
+    struct ofpact_learn *learn;
+    const void *p, *end;
 
-    /* Check for valid src and dst type combination. */
-    if (dst_type == NX_LEARN_DST_MATCH ||
-        dst_type == NX_LEARN_DST_LOAD ||
-        (dst_type == NX_LEARN_DST_OUTPUT &&
-         src_type == NX_LEARN_SRC_FIELD)) {
-        /* OK. */
-    } else {
+    if (nal->pad) {
         return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
 
-    /* Check that the arguments don't overrun the end of the action. */
-    if (len < learn_min_len(header)) {
-        return OFPERR_OFPBAC_BAD_LEN;
-    }
+    learn = ofpact_put_LEARN(ofpacts);
 
-    return 0;
-}
-
-/* Checks that 'learn' (which must be at least 'sizeof *learn' bytes long) is a
- * valid action on 'flow'. */
-enum ofperr
-learn_check(const struct nx_action_learn *learn, const struct flow *flow)
-{
-    struct cls_rule rule;
-    const void *p, *end;
-
-    cls_rule_init_catchall(&rule, 0);
+    learn->idle_timeout = ntohs(nal->idle_timeout);
+    learn->hard_timeout = ntohs(nal->hard_timeout);
+    learn->priority = ntohs(nal->priority);
+    learn->cookie = ntohll(nal->cookie);
+    learn->flags = ntohs(nal->flags);
+    learn->table_id = nal->table_id;
+    learn->fin_idle_timeout = ntohs(nal->fin_idle_timeout);
+    learn->fin_hard_timeout = ntohs(nal->fin_hard_timeout);
 
-    if (learn->flags & ~htons(OFPFF_SEND_FLOW_REM)
-        || learn->pad
-        || learn->table_id == 0xff) {
+    if (learn->flags & ~OFPFF_SEND_FLOW_REM || learn->table_id == 0xff) {
         return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
 
-    end = (char *) learn + ntohs(learn->len);
-    for (p = learn + 1; p != end; ) {
+    end = (char *) nal + ntohs(nal->len);
+    for (p = nal + 1; p != end; ) {
+        struct ofpact_learn_spec *spec;
         uint16_t header = ntohs(get_be16(&p));
-        int n_bits = header & NX_LEARN_N_BITS_MASK;
-        int src_type = header & NX_LEARN_SRC_MASK;
-        int dst_type = header & NX_LEARN_DST_MASK;
-
-        enum ofperr error;
-        uint64_t value;
 
         if (!header) {
             break;
         }
 
-        error = learn_check_header(header, (char *) end - (char *) p);
-        if (error) {
-            return error;
-        }
+        spec = ofpbuf_put_zeros(ofpacts, sizeof *spec);
+        learn = ofpacts->l2;
+        learn->n_specs++;
 
-        /* Check the source. */
-        if (src_type == NX_LEARN_SRC_FIELD) {
-            struct mf_subfield src;
+        spec->src_type = header & NX_LEARN_SRC_MASK;
+        spec->dst_type = header & NX_LEARN_DST_MASK;
+        spec->n_bits = header & NX_LEARN_N_BITS_MASK;
 
-            get_subfield(n_bits, &p, &src);
-            error = mf_check_src(&src, flow);
-            if (error) {
-                return error;
-            }
-            value = 0;
+        /* Check for valid src and dst type combination. */
+        if (spec->dst_type == NX_LEARN_DST_MATCH ||
+            spec->dst_type == NX_LEARN_DST_LOAD ||
+            (spec->dst_type == NX_LEARN_DST_OUTPUT &&
+             spec->src_type == NX_LEARN_SRC_FIELD)) {
+            /* OK. */
         } else {
-            value = get_bits(n_bits, &p);
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
         }
 
-        /* Check the destination. */
-        if (dst_type == NX_LEARN_DST_MATCH || dst_type == NX_LEARN_DST_LOAD) {
-            struct mf_subfield dst;
+        /* Check that the arguments don't overrun the end of the action. */
+        if ((char *) end - (char *) p < learn_min_len(header)) {
+            return OFPERR_OFPBAC_BAD_LEN;
+        }
 
-            get_subfield(n_bits, &p, &dst);
-            error = (dst_type == NX_LEARN_DST_LOAD
-                     ? mf_check_dst(&dst, &rule.flow)
-                     : mf_check_src(&dst, &rule.flow));
-            if (error) {
-                return error;
-            }
+        /* Get the source. */
+        if (spec->src_type == NX_LEARN_SRC_FIELD) {
+            get_subfield(spec->n_bits, &p, &spec->src);
+        } else {
+            int p_bytes = 2 * DIV_ROUND_UP(spec->n_bits, 16);
 
-            if (dst_type == NX_LEARN_DST_MATCH
-                && src_type == NX_LEARN_SRC_IMMEDIATE) {
-                if (n_bits <= 64) {
-                    mf_set_subfield(&dst, value, &rule);
-                } else {
-                    /* We're only setting subfields to allow us to check
-                     * prerequisites.  No prerequisite depends on the value of
-                     * a field that is wider than 64 bits.  So just skip
-                     * setting it entirely. */
-                    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 12);
-                }
-            }
+            bitwise_copy(p, p_bytes, 0,
+                         &spec->src_imm, sizeof spec->src_imm, 0,
+                         spec->n_bits);
+            p = (const uint8_t *) p + p_bytes;
+        }
+
+        /* Get the destination. */
+        if (spec->dst_type == NX_LEARN_DST_MATCH ||
+            spec->dst_type == NX_LEARN_DST_LOAD) {
+            get_subfield(spec->n_bits, &p, &spec->dst);
         }
     }
+    ofpact_update_len(ofpacts, &learn->ofpact);
+
     if (!is_all_zeros(p, (char *) end - (char *) p)) {
         return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
@@ -196,98 +164,50 @@ learn_check(const struct nx_action_learn *learn, const struct flow *flow)
     return 0;
 }
 
-void
-learn_execute(const struct nx_action_learn *learn, const struct flow *flow,
-              struct ofputil_flow_mod *fm)
+/* Checks that 'learn' is a valid action on 'flow'.  Returns 0 if it is valid,
+ * otherwise an OFPERR_*. */
+enum ofperr
+learn_check(const struct ofpact_learn *learn, const struct flow *flow)
 {
-    const void *p, *end;
-    struct ofpbuf actions;
-
-    cls_rule_init_catchall(&fm->cr, ntohs(learn->priority));
-    fm->cookie = htonll(0);
-    fm->cookie_mask = htonll(0);
-    fm->new_cookie = learn->cookie;
-    fm->table_id = learn->table_id;
-    fm->command = OFPFC_MODIFY_STRICT;
-    fm->idle_timeout = ntohs(learn->idle_timeout);
-    fm->hard_timeout = ntohs(learn->hard_timeout);
-    fm->buffer_id = UINT32_MAX;
-    fm->out_port = OFPP_NONE;
-    fm->flags = ntohs(learn->flags) & OFPFF_SEND_FLOW_REM;
-    fm->actions = NULL;
-    fm->n_actions = 0;
-
-    ofpbuf_init(&actions, 64);
-
-    if (learn->fin_idle_timeout || learn->fin_hard_timeout) {
-        struct nx_action_fin_timeout *naft;
-
-        naft = ofputil_put_NXAST_FIN_TIMEOUT(&actions);
-        naft->fin_idle_timeout = learn->fin_idle_timeout;
-        naft->fin_hard_timeout = learn->fin_hard_timeout;
-    }
-
-    for (p = learn + 1, end = (char *) learn + ntohs(learn->len); p != end; ) {
-        uint16_t header = ntohs(get_be16(&p));
-        int n_bits = header & NX_LEARN_N_BITS_MASK;
-        int src_type = header & NX_LEARN_SRC_MASK;
-        int dst_type = header & NX_LEARN_DST_MASK;
-        union mf_subvalue value;
-
-        struct mf_subfield dst;
-        int chunk, ofs;
-
-        if (!header) {
-            break;
-        }
-
-        if (src_type == NX_LEARN_SRC_FIELD) {
-            struct mf_subfield src;
+    const struct ofpact_learn_spec *spec;
+    struct cls_rule rule;
 
-            get_subfield(n_bits, &p, &src);
-            mf_read_subfield(&src, flow, &value);
-        } else {
-            int p_bytes = 2 * DIV_ROUND_UP(n_bits, 16);
+    cls_rule_init_catchall(&rule, 0);
+    for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
+        enum ofperr error;
 
-            memset(&value, 0, sizeof value);
-            bitwise_copy(p, p_bytes, 0,
-                         &value, sizeof value, 0,
-                         n_bits);
-            p = (const uint8_t *) p + p_bytes;
+        /* Check the source. */
+        if (spec->src_type == NX_LEARN_SRC_FIELD) {
+            error = mf_check_src(&spec->src, flow);
+            if (error) {
+                return error;
+            }
         }
 
-        switch (dst_type) {
+        /* Check the destination. */
+        switch (spec->dst_type) {
         case NX_LEARN_DST_MATCH:
-            get_subfield(n_bits, &p, &dst);
-            mf_write_subfield(&dst, &value, &fm->cr);
+            error = mf_check_src(&spec->dst, &rule.flow);
+            if (error) {
+                return error;
+            }
+
+            mf_write_subfield(&spec->dst, &spec->src_imm, &rule);
             break;
 
         case NX_LEARN_DST_LOAD:
-            get_subfield(n_bits, &p, &dst);
-            for (ofs = 0; ofs < n_bits; ofs += chunk) {
-                struct nx_action_reg_load *load;
-
-                chunk = MIN(n_bits - ofs, 64);
-
-                load = ofputil_put_NXAST_REG_LOAD(&actions);
-                load->ofs_nbits = nxm_encode_ofs_nbits(dst.ofs + ofs, chunk);
-                load->dst = htonl(dst.field->nxm_header);
-                bitwise_copy(&value, sizeof value, ofs,
-                             &load->value, sizeof load->value, 0,
-                             chunk);
+            error = mf_check_dst(&spec->dst, &rule.flow);
+            if (error) {
+                return error;
             }
             break;
 
         case NX_LEARN_DST_OUTPUT:
-            if (n_bits <= 16 || is_all_zeros(value.u8, sizeof value - 2)) {
-                ofputil_put_OFPAT10_OUTPUT(&actions)->port = value.be16[7];
-            }
+            /* Nothing to do. */
             break;
         }
     }
-
-    fm->actions = ofpbuf_steal_data(&actions);
-    fm->n_actions = actions.size / sizeof(struct ofp_action_header);
+    return 0;
 }
 
 static void
@@ -314,19 +234,150 @@ put_u32(struct ofpbuf *b, uint32_t x)
     put_be32(b, htonl(x));
 }
 
-struct learn_spec {
-    int n_bits;
+/* Converts 'learn' into a "struct nx_action_learn" and appends that action to
+ * 'ofpacts'. */
+void
+learn_to_openflow(const struct ofpact_learn *learn, struct ofpbuf *openflow)
+{
+    const struct ofpact_learn_spec *spec;
+    struct nx_action_learn *nal;
+    size_t start_ofs;
+
+    start_ofs = openflow->size;
+    nal = ofputil_put_NXAST_LEARN(openflow);
+    nal->idle_timeout = htons(learn->idle_timeout);
+    nal->hard_timeout = htons(learn->hard_timeout);
+    nal->fin_idle_timeout = htons(learn->fin_idle_timeout);
+    nal->fin_hard_timeout = htons(learn->fin_hard_timeout);
+    nal->priority = htons(learn->priority);
+    nal->cookie = htonll(learn->cookie);
+    nal->flags = htons(learn->flags);
+    nal->table_id = learn->table_id;
+
+    for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
+        put_u16(openflow, spec->n_bits | spec->dst_type | spec->src_type);
+
+        if (spec->src_type == NX_LEARN_SRC_FIELD) {
+            put_u32(openflow, spec->src.field->nxm_header);
+            put_u16(openflow, spec->src.ofs);
+        } else {
+            size_t n_dst_bytes = 2 * DIV_ROUND_UP(spec->n_bits, 16);
+            uint8_t *bits = ofpbuf_put_zeros(openflow, n_dst_bytes);
+            bitwise_copy(&spec->src_imm, sizeof spec->src_imm, 0,
+                         bits, n_dst_bytes, 0,
+                         spec->n_bits);
+        }
+
+        if (spec->dst_type == NX_LEARN_DST_MATCH ||
+            spec->dst_type == NX_LEARN_DST_LOAD) {
+            put_u32(openflow, spec->dst.field->nxm_header);
+            put_u16(openflow, spec->dst.ofs);
+        }
+    }
+
+    if ((openflow->size - start_ofs) % 8) {
+        ofpbuf_put_zeros(openflow, 8 - (openflow->size - start_ofs) % 8);
+    }
+
+    nal = ofpbuf_at_assert(openflow, start_ofs, sizeof *nal);
+    nal->len = htons(openflow->size - start_ofs);
+}
+
+/* Composes 'fm' so that executing it will implement 'learn' given that the
+ * packet being processed has 'flow' as its flow.
+ *
+ * Uses 'ofpacts' to store the flow mod's actions.  The caller must initialize
+ * 'ofpacts' and retains ownership of it.  'fm->ofpacts' will point into the
+ * 'ofpacts' buffer.
+ *
+ * The caller has to actually execute 'fm'. */
+void
+learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
+              struct ofputil_flow_mod *fm, struct ofpbuf *ofpacts)
+{
+    const struct ofpact_learn_spec *spec;
 
-    int src_type;
-    struct mf_subfield src;
-    union mf_subvalue src_imm;
+    cls_rule_init_catchall(&fm->cr, learn->priority);
+    fm->cookie = htonll(0);
+    fm->cookie_mask = htonll(0);
+    fm->new_cookie = htonll(learn->cookie);
+    fm->table_id = learn->table_id;
+    fm->command = OFPFC_MODIFY_STRICT;
+    fm->idle_timeout = learn->idle_timeout;
+    fm->hard_timeout = learn->hard_timeout;
+    fm->buffer_id = UINT32_MAX;
+    fm->out_port = OFPP_NONE;
+    fm->flags = learn->flags;
+    fm->ofpacts = NULL;
+    fm->ofpacts_len = 0;
 
-    int dst_type;
-    struct mf_subfield dst;
-};
+    if (learn->fin_idle_timeout || learn->fin_hard_timeout) {
+        struct ofpact_fin_timeout *oft;
+
+        oft = ofpact_put_FIN_TIMEOUT(ofpacts);
+        oft->fin_idle_timeout = learn->fin_idle_timeout;
+        oft->fin_hard_timeout = learn->fin_hard_timeout;
+    }
+
+    for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
+        union mf_subvalue value;
+        int chunk, ofs;
+
+        if (spec->src_type == NX_LEARN_SRC_FIELD) {
+            mf_read_subfield(&spec->src, flow, &value);
+        } else {
+            value = spec->src_imm;
+        }
+
+        switch (spec->dst_type) {
+        case NX_LEARN_DST_MATCH:
+            mf_write_subfield(&spec->dst, &value, &fm->cr);
+            break;
+
+        case NX_LEARN_DST_LOAD:
+            for (ofs = 0; ofs < spec->n_bits; ofs += chunk) {
+                struct ofpact_reg_load *load;
+                ovs_be64 value_be;
+
+                chunk = MIN(spec->n_bits - ofs, 64);
+
+                load = ofpact_put_REG_LOAD(ofpacts);
+                load->dst.field = spec->dst.field;
+                load->dst.ofs = spec->dst.ofs + ofs;
+                load->dst.n_bits = chunk;
+
+                memset(&value_be, 0, sizeof value_be);
+                bitwise_copy(&value, sizeof value, ofs,
+                             &value_be, sizeof value_be, 0,
+                             chunk);
+                load->value = ntohll(value_be);
+            }
+            break;
+
+        case NX_LEARN_DST_OUTPUT:
+            if (spec->n_bits <= 16
+                || is_all_zeros(value.u8, sizeof value - 2)) {
+                uint16_t port = ntohs(value.be16[7]);
+
+                if (port < OFPP_MAX
+                    || port == OFPP_IN_PORT
+                    || port == OFPP_FLOOD
+                    || port == OFPP_LOCAL
+                    || port == OFPP_ALL) {
+                    ofpact_put_OUTPUT(ofpacts)->port = port;
+                }
+            }
+            break;
+        }
+    }
+    ofpact_put_END(ofpacts);
+
+    fm->ofpacts = ofpacts->data;
+    fm->ofpacts_len = ofpacts->size;
+}
 
 static void
-learn_parse_load_immediate(const char *s, struct learn_spec *spec)
+learn_parse_load_immediate(const char *s, struct ofpact_learn_spec *spec)
 {
     const char *full_s = s;
     const char *arrow = strstr(s, "->");
@@ -377,9 +428,8 @@ learn_parse_load_immediate(const char *s, struct learn_spec *spec)
 
 static void
 learn_parse_spec(const char *orig, char *name, char *value,
-                 struct learn_spec *spec)
+                 struct ofpact_learn_spec *spec)
 {
-    memset(spec, 0, sizeof *spec);
     if (mf_from_name(name)) {
         const struct mf_field *dst = mf_from_name(name);
         union mf_value imm;
@@ -428,17 +478,15 @@ learn_parse_spec(const char *orig, char *name, char *value,
         if (value[strcspn(value, "[-")] == '-') {
             learn_parse_load_immediate(value, spec);
         } else {
-            struct nx_action_reg_move move;
+            struct ofpact_reg_move move;
 
             nxm_parse_reg_move(&move, value);
 
-            spec->n_bits = ntohs(move.n_bits);
+            spec->n_bits = move.src.n_bits;
             spec->src_type = NX_LEARN_SRC_FIELD;
-            nxm_decode_discrete(&spec->src,
-                                move.src, move.src_ofs, move.n_bits);
+            spec->src = move.src;
             spec->dst_type = NX_LEARN_DST_LOAD;
-            nxm_decode_discrete(&spec->dst,
-                                move.dst, move.dst_ofs, move.n_bits);
+            spec->dst = move.dst;
         }
     } else if (!strcmp(name, "output")) {
         if (mf_parse_subfield(&spec->src, value)[0] != '\0') {
@@ -455,8 +503,8 @@ learn_parse_spec(const char *orig, char *name, char *value,
 }
 
 /* Parses 'arg' as a set of arguments to the "learn" action and appends a
- * matching NXAST_LEARN action to 'b'.  The format parsed is described in
- * ovs-ofctl(8).
+ * matching OFPACT_LEARN action to 'ofpacts'.  ovs-ofctl(8) describes the
+ * format parsed.
  *
  * Prints an error on stderr and aborts the program if 'arg' syntax is invalid.
  *
@@ -466,29 +514,23 @@ learn_parse_spec(const char *orig, char *name, char *value,
  *
  * Modifies 'arg'. */
 void
-learn_parse(struct ofpbuf *b, char *arg, const struct flow *flow)
+learn_parse(char *arg, const struct flow *flow, struct ofpbuf *ofpacts)
 {
     char *orig = xstrdup(arg);
     char *name, *value;
-    enum ofperr error;
-    size_t learn_ofs;
-    size_t len;
 
-    struct nx_action_learn *learn;
+    struct ofpact_learn *learn;
     struct cls_rule rule;
+    enum ofperr error;
 
-    learn_ofs = b->size;
-    learn = ofputil_put_NXAST_LEARN(b);
-    learn->idle_timeout = htons(OFP_FLOW_PERMANENT);
-    learn->hard_timeout = htons(OFP_FLOW_PERMANENT);
-    learn->priority = htons(OFP_DEFAULT_PRIORITY);
-    learn->cookie = htonll(0);
-    learn->flags = htons(0);
+    learn = ofpact_put_LEARN(ofpacts);
+    learn->idle_timeout = OFP_FLOW_PERMANENT;
+    learn->hard_timeout = OFP_FLOW_PERMANENT;
+    learn->priority = OFP_DEFAULT_PRIORITY;
     learn->table_id = 1;
 
     cls_rule_init_catchall(&rule, 0);
     while (ofputil_parse_key_value(&arg, &name, &value)) {
-        learn = ofpbuf_at_assert(b, learn_ofs, sizeof *learn);
         if (!strcmp(name, "table")) {
             learn->table_id = atoi(value);
             if (learn->table_id == 255) {
@@ -496,73 +538,50 @@ learn_parse(struct ofpbuf *b, char *arg, const struct flow *flow)
                           orig);
             }
         } else if (!strcmp(name, "priority")) {
-            learn->priority = htons(atoi(value));
+            learn->priority = atoi(value);
         } else if (!strcmp(name, "idle_timeout")) {
-            learn->idle_timeout = htons(atoi(value));
+            learn->idle_timeout = atoi(value);
         } else if (!strcmp(name, "hard_timeout")) {
-            learn->hard_timeout = htons(atoi(value));
+            learn->hard_timeout = atoi(value);
         } else if (!strcmp(name, "fin_idle_timeout")) {
-            learn->fin_idle_timeout = htons(atoi(value));
+            learn->fin_idle_timeout = atoi(value);
         } else if (!strcmp(name, "fin_hard_timeout")) {
-            learn->fin_hard_timeout = htons(atoi(value));
+            learn->fin_hard_timeout = atoi(value);
         } else if (!strcmp(name, "cookie")) {
-            learn->cookie = htonll(strtoull(value, NULL, 0));
+            learn->cookie = strtoull(value, NULL, 0);
         } else {
-            struct learn_spec spec;
+            struct ofpact_learn_spec *spec;
+
+            spec = ofpbuf_put_zeros(ofpacts, sizeof *spec);
+            learn = ofpacts->l2;
+            learn->n_specs++;
 
-            learn_parse_spec(orig, name, value, &spec);
+            learn_parse_spec(orig, name, value, spec);
 
             /* Check prerequisites. */
-            if (spec.src_type == NX_LEARN_SRC_FIELD
-                && flow && !mf_are_prereqs_ok(spec.src.field, flow)) {
+            if (spec->src_type == NX_LEARN_SRC_FIELD
+                && flow && !mf_are_prereqs_ok(spec->src.field, flow)) {
                 ovs_fatal(0, "%s: cannot specify source field %s because "
                           "prerequisites are not satisfied",
-                          orig, spec.src.field->name);
+                          orig, spec->src.field->name);
             }
-            if ((spec.dst_type == NX_LEARN_DST_MATCH
-                 || spec.dst_type == NX_LEARN_DST_LOAD)
-                && !mf_are_prereqs_ok(spec.dst.field, &rule.flow)) {
+            if ((spec->dst_type == NX_LEARN_DST_MATCH
+                 || spec->dst_type == NX_LEARN_DST_LOAD)
+                && !mf_are_prereqs_ok(spec->dst.field, &rule.flow)) {
                 ovs_fatal(0, "%s: cannot specify destination field %s because "
                           "prerequisites are not satisfied",
-                          orig, spec.dst.field->name);
+                          orig, spec->dst.field->name);
             }
 
             /* Update 'rule' to allow for satisfying destination
              * prerequisites. */
-            if (spec.src_type == NX_LEARN_SRC_IMMEDIATE
-                && spec.dst_type == NX_LEARN_DST_MATCH) {
-                mf_write_subfield(&spec.dst, &spec.src_imm, &rule);
-            }
-
-            /* Output the flow_mod_spec. */
-            put_u16(b, spec.n_bits | spec.src_type | spec.dst_type);
-            if (spec.src_type == NX_LEARN_SRC_IMMEDIATE) {
-                int n_bytes = DIV_ROUND_UP(spec.n_bits, 16) * 2;
-                int ofs = sizeof spec.src_imm - n_bytes;
-                ofpbuf_put(b, &spec.src_imm.u8[ofs], n_bytes);
-            } else {
-                put_u32(b, spec.src.field->nxm_header);
-                put_u16(b, spec.src.ofs);
-            }
-            if (spec.dst_type == NX_LEARN_DST_MATCH ||
-                spec.dst_type == NX_LEARN_DST_LOAD) {
-                put_u32(b, spec.dst.field->nxm_header);
-                put_u16(b, spec.dst.ofs);
-            } else {
-                assert(spec.dst_type == NX_LEARN_DST_OUTPUT);
+            if (spec->src_type == NX_LEARN_SRC_IMMEDIATE
+                && spec->dst_type == NX_LEARN_DST_MATCH) {
+                mf_write_subfield(&spec->dst, &spec->src_imm, &rule);
             }
         }
     }
-
-    put_u16(b, 0);
-
-    len = b->size - learn_ofs;
-    if (len % 8) {
-        ofpbuf_put_zeros(b, 8 - len % 8);
-    }
-
-    learn = ofpbuf_at_assert(b, learn_ofs, sizeof *learn);
-    learn->len = htons(b->size - learn_ofs);
+    ofpact_update_len(ofpacts, &learn->ofpact);
 
     /* In theory the above should have caught any errors, but... */
     if (flow) {
@@ -574,169 +593,106 @@ learn_parse(struct ofpbuf *b, char *arg, const struct flow *flow)
     free(orig);
 }
 
+static void
+format_subvalue(const union mf_subvalue *subvalue, struct ds *s)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(subvalue->u8); i++) {
+        if (subvalue->u8[i]) {
+            ds_put_format(s, "0x%"PRIx8, subvalue->u8[i]);
+            for (i++; i < ARRAY_SIZE(subvalue->u8); i++) {
+                ds_put_format(s, "%02"PRIx8, subvalue->u8[i]);
+            }
+            return;
+        }
+    }
+    ds_put_char(s, '0');
+}
+
+/* Appends a description of 'learn' to 's', in the format that ovs-ofctl(8)
+ * describes. */
 void
-learn_format(const struct nx_action_learn *learn, struct ds *s)
+learn_format(const struct ofpact_learn *learn, struct ds *s)
 {
+    const struct ofpact_learn_spec *spec;
     struct cls_rule rule;
-    const void *p, *end;
 
     cls_rule_init_catchall(&rule, 0);
 
     ds_put_format(s, "learn(table=%"PRIu8, learn->table_id);
-    if (learn->idle_timeout != htons(OFP_FLOW_PERMANENT)) {
-        ds_put_format(s, ",idle_timeout=%"PRIu16, ntohs(learn->idle_timeout));
+    if (learn->idle_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(s, ",idle_timeout=%"PRIu16, learn->idle_timeout);
     }
-    if (learn->hard_timeout != htons(OFP_FLOW_PERMANENT)) {
-        ds_put_format(s, ",hard_timeout=%"PRIu16, ntohs(learn->hard_timeout));
+    if (learn->hard_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(s, ",hard_timeout=%"PRIu16, learn->hard_timeout);
     }
     if (learn->fin_idle_timeout) {
-        ds_put_format(s, ",fin_idle_timeout=%"PRIu16,
-                      ntohs(learn->fin_idle_timeout));
+        ds_put_format(s, ",fin_idle_timeout=%"PRIu16, learn->fin_idle_timeout);
     }
     if (learn->fin_hard_timeout) {
-        ds_put_format(s, ",fin_hard_timeout=%"PRIu16,
-                      ntohs(learn->fin_hard_timeout));
+        ds_put_format(s, ",fin_hard_timeout=%"PRIu16, learn->fin_hard_timeout);
     }
-    if (learn->priority != htons(OFP_DEFAULT_PRIORITY)) {
-        ds_put_format(s, ",priority=%"PRIu16, ntohs(learn->priority));
+    if (learn->priority != OFP_DEFAULT_PRIORITY) {
+        ds_put_format(s, ",priority=%"PRIu16, learn->priority);
     }
-    if (learn->flags & htons(OFPFF_SEND_FLOW_REM)) {
+    if (learn->flags & OFPFF_SEND_FLOW_REM) {
         ds_put_cstr(s, ",OFPFF_SEND_FLOW_REM");
     }
-    if (learn->flags & htons(~OFPFF_SEND_FLOW_REM)) {
-        ds_put_format(s, ",***flags=%"PRIu16"***",
-                      ntohs(learn->flags) & ~OFPFF_SEND_FLOW_REM);
+    if (learn->cookie != 0) {
+        ds_put_format(s, ",cookie=%#"PRIx64, learn->cookie);
     }
-    if (learn->cookie != htonll(0)) {
-        ds_put_format(s, ",cookie=0x%"PRIx64, ntohll(learn->cookie));
-    }
-    if (learn->pad != 0) {
-        ds_put_cstr(s, ",***nonzero pad***");
-    }
-
-    end = (char *) learn + ntohs(learn->len);
-    for (p = learn + 1; p != end; ) {
-        uint16_t header = ntohs(get_be16(&p));
-        int n_bits = header & NX_LEARN_N_BITS_MASK;
-
-        int src_type = header & NX_LEARN_SRC_MASK;
-        struct mf_subfield src;
-        const uint8_t *src_value;
-        int src_value_bytes;
-
-        int dst_type = header & NX_LEARN_DST_MASK;
-        struct mf_subfield dst;
-
-        enum ofperr error;
-        int i;
-
-        if (!header) {
-            break;
-        }
-
-        error = learn_check_header(header, (char *) end - (char *) p);
-        if (error == OFPERR_OFPBAC_BAD_ARGUMENT) {
-            ds_put_format(s, ",***bad flow_mod_spec header %"PRIx16"***)",
-                          header);
-            return;
-        } else if (error == OFPERR_OFPBAC_BAD_LEN) {
-            ds_put_format(s, ",***flow_mod_spec at offset %td is %u bytes "
-                          "long but only %td bytes are left***)",
-                          (char *) p - (char *) (learn + 1) - 2,
-                          learn_min_len(header) + 2,
-                          (char *) end - (char *) p + 2);
-            return;
-        }
-        assert(!error);
-
-        /* Get the source. */
-        if (src_type == NX_LEARN_SRC_FIELD) {
-            get_subfield(n_bits, &p, &src);
-            src_value_bytes = 0;
-            src_value = NULL;
-        } else {
-            src.field = NULL;
-            src.ofs = 0;
-            src.n_bits = 0;
-            src_value_bytes = 2 * DIV_ROUND_UP(n_bits, 16);
-            src_value = p;
-            p = (const void *) ((const uint8_t *) p + src_value_bytes);
-        }
-
-        /* Get the destination. */
-        if (dst_type == NX_LEARN_DST_MATCH || dst_type == NX_LEARN_DST_LOAD) {
-            get_subfield(n_bits, &p, &dst);
-        } else {
-            dst.field = NULL;
-            dst.ofs = 0;
-            dst.n_bits = 0;
-        }
 
+    for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
         ds_put_char(s, ',');
 
-        switch (src_type | dst_type) {
+        switch (spec->src_type | spec->dst_type) {
         case NX_LEARN_SRC_IMMEDIATE | NX_LEARN_DST_MATCH:
-            if (dst.field && dst.ofs == 0 && n_bits == dst.field->n_bits) {
+            if (spec->dst.ofs == 0
+                && spec->dst.n_bits == spec->dst.field->n_bits) {
                 union mf_value value;
-                uint8_t *bytes = (uint8_t *) &value;
-
-                if (src_value_bytes > dst.field->n_bytes) {
-                    /* The destination field is an odd number of bytes, which
-                     * got rounded up to a multiple of 2 to be put into the
-                     * learning action.  Skip over the leading byte, which
-                     * should be zero anyway.  Otherwise the memcpy() below
-                     * will overrun the start of 'value'. */
-                    int diff = src_value_bytes - dst.field->n_bytes;
-                    src_value += diff;
-                    src_value_bytes -= diff;
-                }
 
                 memset(&value, 0, sizeof value);
-                memcpy(&bytes[dst.field->n_bytes - src_value_bytes],
-                       src_value, src_value_bytes);
-                ds_put_format(s, "%s=", dst.field->name);
-                mf_format(dst.field, &value, NULL, s);
+                bitwise_copy(&spec->src_imm, sizeof spec->src_imm, 0,
+                             &value, spec->dst.field->n_bytes, 0,
+                             spec->dst.field->n_bits);
+                ds_put_format(s, "%s=", spec->dst.field->name);
+                mf_format(spec->dst.field, &value, NULL, s);
             } else {
-                mf_format_subfield(&dst, s);
-                ds_put_cstr(s, "=0x");
-                for (i = 0; i < src_value_bytes; i++) {
-                    ds_put_format(s, "%02"PRIx8, src_value[i]);
-                }
+                mf_format_subfield(&spec->dst, s);
+                ds_put_char(s, '=');
+                format_subvalue(&spec->src_imm, s);
             }
             break;
 
         case NX_LEARN_SRC_FIELD | NX_LEARN_DST_MATCH:
-            mf_format_subfield(&dst, s);
-            if (src.field != dst.field || src.ofs != dst.ofs) {
+            mf_format_subfield(&spec->dst, s);
+            if (spec->src.field != spec->dst.field ||
+                spec->src.ofs != spec->dst.ofs) {
                 ds_put_char(s, '=');
-                mf_format_subfield(&src, s);
+                mf_format_subfield(&spec->src, s);
             }
             break;
 
         case NX_LEARN_SRC_IMMEDIATE | NX_LEARN_DST_LOAD:
-            ds_put_cstr(s, "load:0x");
-            for (i = 0; i < src_value_bytes; i++) {
-                ds_put_format(s, "%02"PRIx8, src_value[i]);
-            }
+            ds_put_format(s, "load:");
+            format_subvalue(&spec->src_imm, s);
             ds_put_cstr(s, "->");
-            mf_format_subfield(&dst, s);
+            mf_format_subfield(&spec->dst, s);
             break;
 
         case NX_LEARN_SRC_FIELD | NX_LEARN_DST_LOAD:
             ds_put_cstr(s, "load:");
-            mf_format_subfield(&src, s);
+            mf_format_subfield(&spec->src, s);
             ds_put_cstr(s, "->");
-            mf_format_subfield(&dst, s);
+            mf_format_subfield(&spec->dst, s);
             break;
 
         case NX_LEARN_SRC_FIELD | NX_LEARN_DST_OUTPUT:
             ds_put_cstr(s, "output:");
-            mf_format_subfield(&src, s);
+            mf_format_subfield(&spec->src, s);
             break;
         }
     }
-    if (!is_all_zeros(p, (char *) end - (char *) p)) {
-        ds_put_cstr(s, ",***nonzero trailer***");
-    }
     ds_put_char(s, ')');
 }
diff --git a/lib/learn.h b/lib/learn.h
index 2859172..e7fe390 100644
--- a/lib/learn.h
+++ b/lib/learn.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Nicira, Inc.
+ * Copyright (c) 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
 struct ds;
 struct flow;
 struct ofpbuf;
+struct ofpact_learn;
 struct ofputil_flow_mod;
 struct nx_action_learn;
 
@@ -30,11 +31,15 @@ struct nx_action_learn;
  * See include/openflow/nicira-ext.h for NXAST_LEARN specification.
  */
 
-enum ofperr learn_check(const struct nx_action_learn *, const struct flow *);
-void learn_execute(const struct nx_action_learn *, const struct flow *,
-                   struct ofputil_flow_mod *);
+enum ofperr learn_from_openflow(const struct nx_action_learn *,
+                                struct ofpbuf *ofpacts);
+enum ofperr learn_check(const struct ofpact_learn *, const struct flow *);
+void learn_to_openflow(const struct ofpact_learn *, struct ofpbuf *openflow);
 
-void learn_parse(struct ofpbuf *, char *, const struct flow *);
-void learn_format(const struct nx_action_learn *, struct ds *);
+void learn_execute(const struct ofpact_learn *, const struct flow *,
+                   struct ofputil_flow_mod *, struct ofpbuf *ofpacts);
+
+void learn_parse(char *, const struct flow *, struct ofpbuf *ofpacts);
+void learn_format(const struct ofpact_learn *, struct ds *);
 
 #endif /* learn.h */
diff --git a/lib/learning-switch.c b/lib/learning-switch.c
index 6b74f82..916abf3 100644
--- a/lib/learning-switch.c
+++ b/lib/learning-switch.c
@@ -29,6 +29,7 @@
 #include "hmap.h"
 #include "mac-learning.h"
 #include "ofpbuf.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-parse.h"
 #include "ofp-print.h"
@@ -56,6 +57,7 @@ struct lswitch {
      * Otherwise, the switch processes every packet. */
     int max_idle;
 
+    enum ofputil_protocol protocol;
     unsigned long long int datapath_id;
     time_t last_features_request;
     struct mac_learning *ml;    /* NULL to act as hub instead of switch. */
@@ -81,7 +83,7 @@ static void send_features_request(struct lswitch *, struct rconn *);
 static enum ofperr process_switch_features(struct lswitch *,
                                            struct ofp_switch_features *);
 static void process_packet_in(struct lswitch *, struct rconn *,
-                              const struct ofp_packet_in *);
+                              const struct ofp_header *);
 static void process_echo_request(struct lswitch *, struct rconn *,
                                  const struct ofp_header *);
 
@@ -92,6 +94,7 @@ static void process_echo_request(struct lswitch *, struct rconn *,
 struct lswitch *
 lswitch_create(struct rconn *rconn, const struct lswitch_config *cfg)
 {
+    enum ofputil_protocol protocol;
     struct lswitch *sw;
 
     sw = xzalloc(sizeof *sw);
@@ -139,18 +142,13 @@ lswitch_create(struct rconn *rconn, const struct lswitch_config *cfg)
     sw->queued = rconn_packet_counter_create();
     send_features_request(sw, rconn);
 
+    protocol = ofputil_protocol_from_ofp_version(rconn_get_version(rconn));
     if (cfg->default_flows) {
         enum ofputil_protocol usable_protocols;
-        enum ofputil_protocol protocol;
         struct ofpbuf *msg = NULL;
-        int ofp_version;
         int error = 0;
         size_t i;
 
-        /* Figure out the initial protocol on the connection. */
-        ofp_version = rconn_get_version(rconn);
-        protocol = ofputil_protocol_from_ofp_version(ofp_version);
-
         /* If the initial protocol isn't good enough for default_flows, then
          * pick one that will work and encode messages to set up that
          * protocol.
@@ -181,6 +179,7 @@ lswitch_create(struct rconn *rconn, const struct lswitch_config *cfg)
                          rconn_get_name(rconn), strerror(error));
         }
     }
+    sw->protocol = protocol;
 
     return sw;
 }
@@ -250,6 +249,7 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn,
         break;
 
     case OFPUTIL_OFPT_PACKET_IN:
+    case OFPUTIL_NXT_PACKET_IN:
         process_packet_in(sw, rconn, msg->data);
         break;
 
@@ -292,7 +292,6 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn,
     case OFPUTIL_NXT_FLOW_MOD_TABLE_ID:
     case OFPUTIL_NXT_SET_FLOW_FORMAT:
     case OFPUTIL_NXT_SET_PACKET_IN_FORMAT:
-    case OFPUTIL_NXT_PACKET_IN:
     case OFPUTIL_NXT_FLOW_MOD:
     case OFPUTIL_NXT_FLOW_REMOVED:
     case OFPUTIL_NXT_FLOW_AGE:
@@ -439,67 +438,58 @@ get_queue_id(const struct lswitch *sw, uint16_t in_port)
 
 static void
 process_packet_in(struct lswitch *sw, struct rconn *rconn,
-                  const struct ofp_packet_in *opi)
+                  const struct ofp_header *oh)
 {
-    uint16_t in_port = ntohs(opi->in_port);
+    struct ofputil_packet_in pi;
     uint32_t queue_id;
     uint16_t out_port;
 
-    struct ofp_action_header actions[2];
-    size_t actions_len;
+    uint64_t ofpacts_stub[64 / 8];
+    struct ofpbuf ofpacts;
 
     struct ofputil_packet_out po;
+    enum ofperr error;
 
-    size_t pkt_ofs, pkt_len;
     struct ofpbuf pkt;
     struct flow flow;
 
+    error = ofputil_decode_packet_in(&pi, oh);
+    if (error) {
+        VLOG_WARN_RL(&rl, "failed to decode packet-in: %s",
+                     ofperr_to_string(error));
+        return;
+    }
+
     /* Ignore packets sent via output to OFPP_CONTROLLER.  This library never
      * uses such an action.  You never know what experiments might be going on,
      * though, and it seems best not to interfere with them. */
-    if (opi->reason != OFPR_NO_MATCH) {
+    if (pi.reason != OFPR_NO_MATCH) {
         return;
     }
 
     /* Extract flow data from 'opi' into 'flow'. */
-    pkt_ofs = offsetof(struct ofp_packet_in, data);
-    pkt_len = ntohs(opi->header.length) - pkt_ofs;
-    ofpbuf_use_const(&pkt, opi->data, pkt_len);
-    flow_extract(&pkt, 0, 0, in_port, &flow);
+    ofpbuf_use_const(&pkt, pi.packet, pi.packet_len);
+    flow_extract(&pkt, 0, pi.fmd.tun_id, pi.fmd.in_port, &flow);
 
     /* Choose output port. */
     out_port = lswitch_choose_destination(sw, &flow);
 
     /* Make actions. */
-    queue_id = get_queue_id(sw, in_port);
+    queue_id = get_queue_id(sw, pi.fmd.in_port);
+    ofpbuf_use_stack(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
     if (out_port == OFPP_NONE) {
-        actions_len = 0;
+        /* No actions. */
     } else if (queue_id == UINT32_MAX || out_port >= OFPP_MAX) {
-        struct ofp_action_output oao;
-
-        memset(&oao, 0, sizeof oao);
-        oao.type = htons(OFPAT10_OUTPUT);
-        oao.len = htons(sizeof oao);
-        oao.port = htons(out_port);
-
-        memcpy(actions, &oao, sizeof oao);
-        actions_len = sizeof oao;
+        ofpact_put_OUTPUT(&ofpacts)->port = out_port;
     } else {
-        struct ofp_action_enqueue oae;
-
-        memset(&oae, 0, sizeof oae);
-        oae.type = htons(OFPAT10_ENQUEUE);
-        oae.len = htons(sizeof oae);
-        oae.port = htons(out_port);
-        oae.queue_id = htonl(queue_id);
-
-        memcpy(actions, &oae, sizeof oae);
-        actions_len = sizeof oae;
+        struct ofpact_enqueue *enqueue = ofpact_put_ENQUEUE(&ofpacts);
+        enqueue->port = out_port;
+        enqueue->queue = queue_id;
     }
-    assert(actions_len <= sizeof actions);
+    ofpact_put_END(&ofpacts);
 
     /* Prepare packet_out in case we need one. */
-    po.buffer_id = ntohl(opi->buffer_id);
+    po.buffer_id = pi.buffer_id;
     if (po.buffer_id == UINT32_MAX) {
         po.packet = pkt.data;
         po.packet_len = pkt.size;
@@ -507,31 +497,38 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn,
         po.packet = NULL;
         po.packet_len = 0;
     }
-    po.in_port = in_port;
-    po.actions = (union ofp_action *) actions;
-    po.n_actions = actions_len / sizeof *actions;
+    po.in_port = pi.fmd.in_port;
+    po.ofpacts = ofpacts.data;
+    po.ofpacts_len = ofpacts.size;
 
     /* Send the packet, and possibly the whole flow, to the output port. */
     if (sw->max_idle >= 0 && (!sw->ml || out_port != OFPP_FLOOD)) {
+        struct ofputil_flow_mod fm;
         struct ofpbuf *buffer;
-        struct cls_rule rule;
 
         /* The output port is known, or we always flood everything, so add a
          * new flow. */
-        cls_rule_init(&flow, &sw->wc, 0, &rule);
-        buffer = make_add_flow(&rule, ntohl(opi->buffer_id),
-                               sw->max_idle, actions_len);
-        ofpbuf_put(buffer, actions, actions_len);
+        memset(&fm, 0, sizeof fm);
+        cls_rule_init(&flow, &sw->wc, 0, &fm.cr);
+        fm.table_id = 0xff;
+        fm.command = OFPFC_ADD;
+        fm.idle_timeout = sw->max_idle;
+        fm.buffer_id = pi.buffer_id;
+        fm.out_port = OFPP_NONE;
+        fm.ofpacts = ofpacts.data;
+        fm.ofpacts_len = ofpacts.size;
+        buffer = ofputil_encode_flow_mod(&fm, sw->protocol);
+
         queue_tx(sw, rconn, buffer);
 
         /* If the switch didn't buffer the packet, we need to send a copy. */
-        if (ntohl(opi->buffer_id) == UINT32_MAX && actions_len > 0) {
+        if (pi.buffer_id == UINT32_MAX && out_port != OFPP_NONE) {
             queue_tx(sw, rconn, ofputil_encode_packet_out(&po));
         }
     } else {
         /* We don't know that MAC, or we don't set up flows.  Send along the
          * packet without setting up a flow. */
-        if (ntohl(opi->buffer_id) != UINT32_MAX || actions_len > 0) {
+        if (pi.buffer_id != UINT32_MAX || out_port != OFPP_NONE) {
             queue_tx(sw, rconn, ofputil_encode_packet_out(&po));
         }
     }
diff --git a/lib/multipath.c b/lib/multipath.c
index 8d93211..c0a5723 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -22,8 +22,8 @@
 #include <sys/types.h>
 #include <netinet/in.h>
 #include "dynamic-string.h"
-#include "meta-flow.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "openflow/nicira-ext.h"
@@ -34,37 +34,67 @@ VLOG_DEFINE_THIS_MODULE(multipath);
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
-/* multipath_check(). */
+/* Converts 'nam' into 'mp'.  Returns 0 if successful, otherwise an
+ * OFPERR_*. */
 enum ofperr
-multipath_check(const struct nx_action_multipath *mp, const struct flow *flow)
+multipath_from_openflow(const struct nx_action_multipath *nam,
+                        struct ofpact_multipath *mp)
 {
-    uint32_t n_links = ntohs(mp->max_link) + 1;
+    uint32_t n_links = ntohs(nam->max_link) + 1;
     size_t min_n_bits = log_2_ceil(n_links);
-    struct mf_subfield dst;
-    enum ofperr error;
 
-    nxm_decode(&dst, mp->dst, mp->ofs_nbits);
-    error = mf_check_dst(&dst, flow);
-    if (error) {
-        return error;
-    }
-
-    if (!flow_hash_fields_valid(ntohs(mp->fields))) {
-        VLOG_WARN_RL(&rl, "unsupported fields %"PRIu16, ntohs(mp->fields));
-    } else if (mp->algorithm != htons(NX_MP_ALG_MODULO_N)
-               && mp->algorithm != htons(NX_MP_ALG_HASH_THRESHOLD)
-               && mp->algorithm != htons(NX_MP_ALG_HRW)
-               && mp->algorithm != htons(NX_MP_ALG_ITER_HASH)) {
-        VLOG_WARN_RL(&rl, "unsupported algorithm %"PRIu16,
-                     ntohs(mp->algorithm));
-    } else if (dst.n_bits < min_n_bits) {
+    ofpact_init_MULTIPATH(mp);
+    mp->fields = ntohs(nam->fields);
+    mp->basis = ntohs(nam->basis);
+    mp->algorithm = ntohs(nam->algorithm);
+    mp->max_link = ntohs(nam->max_link);
+    mp->arg = ntohl(nam->arg);
+    mp->dst.field = mf_from_nxm_header(ntohl(nam->dst));
+    mp->dst.ofs = nxm_decode_ofs(nam->ofs_nbits);
+    mp->dst.n_bits = nxm_decode_n_bits(nam->ofs_nbits);
+
+    if (!flow_hash_fields_valid(mp->fields)) {
+        VLOG_WARN_RL(&rl, "unsupported fields %d", (int) mp->fields);
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    } else if (mp->algorithm != NX_MP_ALG_MODULO_N
+               && mp->algorithm != NX_MP_ALG_HASH_THRESHOLD
+               && mp->algorithm != NX_MP_ALG_HRW
+               && mp->algorithm != NX_MP_ALG_ITER_HASH) {
+        VLOG_WARN_RL(&rl, "unsupported algorithm %d", (int) mp->algorithm);
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    } else if (mp->dst.n_bits < min_n_bits) {
         VLOG_WARN_RL(&rl, "multipath action requires at least %zu bits for "
                      "%"PRIu32" links", min_n_bits, n_links);
-    } else {
-        return 0;
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
 
-    return OFPERR_OFPBAC_BAD_ARGUMENT;
+    return multipath_check(mp, NULL);
+}
+
+/* Checks that 'mp' is valid on flow.  Returns 0 if it is valid, otherwise an
+ * OFPERR_*. */
+enum ofperr
+multipath_check(const struct ofpact_multipath *mp,
+                const struct flow *flow)
+{
+    return mf_check_dst(&mp->dst, flow);
+}
+
+/* Converts 'mp' into an OpenFlow NXAST_MULTIPATH action, which it appends to
+ * 'openflow'. */
+void
+multipath_to_openflow(const struct ofpact_multipath *mp,
+                      struct ofpbuf *openflow)
+{
+    struct nx_action_multipath *nam = ofputil_put_NXAST_MULTIPATH(openflow);
+
+    nam->fields = htons(mp->fields);
+    nam->basis = htons(mp->basis);
+    nam->algorithm = htons(mp->algorithm);
+    nam->max_link = htons(mp->max_link);
+    nam->arg = htonl(mp->arg);
+    nam->ofs_nbits = nxm_encode_ofs_nbits(mp->dst.ofs, mp->dst.n_bits);
+    nam->dst = htonl(mp->dst.field->nxm_header);
 }
 
 /* multipath_execute(). */
@@ -72,19 +102,17 @@ multipath_check(const struct nx_action_multipath *mp, const struct flow *flow)
 static uint16_t multipath_algorithm(uint32_t hash, enum nx_mp_algorithm,
                                     unsigned int n_links, unsigned int arg);
 
+/* Executes 'mp' based on the current contents of 'flow', writing the results
+ * back into 'flow'. */
 void
-multipath_execute(const struct nx_action_multipath *mp, struct flow *flow)
+multipath_execute(const struct ofpact_multipath *mp, struct flow *flow)
 {
     /* Calculate value to store. */
-    uint32_t hash = flow_hash_fields(flow, ntohs(mp->fields),
-                                     ntohs(mp->basis));
-    uint16_t link = multipath_algorithm(hash, ntohs(mp->algorithm),
-                                        ntohs(mp->max_link) + 1,
-                                        ntohl(mp->arg));
-    struct mf_subfield dst;
-
-    nxm_decode(&dst, mp->dst, mp->ofs_nbits);
-    mf_set_subfield_value(&dst, link, flow);
+    uint32_t hash = flow_hash_fields(flow, mp->fields, mp->basis);
+    uint16_t link = multipath_algorithm(hash, mp->algorithm,
+                                        mp->max_link + 1, mp->arg);
+
+    nxm_reg_load(&mp->dst, link, flow);
 }
 
 static uint16_t
@@ -162,15 +190,17 @@ multipath_algorithm(uint32_t hash, enum nx_mp_algorithm algorithm,
     NOT_REACHED();
 }
 
-/* multipath_parse(). */
-
+/* Parses 's_' as a set of arguments to the "multipath" action and initializes
+ * 'mp' accordingly.  ovs-ofctl(8) describes the format parsed.
+ *
+ * Prints an error on stderr and aborts the program if 's_' syntax is
+ * invalid. */
 void
-multipath_parse(struct nx_action_multipath *mp, const char *s_)
+multipath_parse(struct ofpact_multipath *mp, const char *s_)
 {
     char *s = xstrdup(s_);
     char *save_ptr = NULL;
-    char *fields, *basis, *algorithm, *n_links_str, *arg, *dst_s;
-    struct mf_subfield dst;
+    char *fields, *basis, *algorithm, *n_links_str, *arg, *dst;
     int n_links;
 
     fields = strtok_r(s, ", ", &save_ptr);
@@ -178,28 +208,28 @@ multipath_parse(struct nx_action_multipath *mp, const char *s_)
     algorithm = strtok_r(NULL, ", ", &save_ptr);
     n_links_str = strtok_r(NULL, ", ", &save_ptr);
     arg = strtok_r(NULL, ", ", &save_ptr);
-    dst_s = strtok_r(NULL, ", ", &save_ptr);
-    if (!dst_s) {
+    dst = strtok_r(NULL, ", ", &save_ptr);
+    if (!dst) {
         ovs_fatal(0, "%s: not enough arguments to multipath action", s_);
     }
 
-    ofputil_init_NXAST_MULTIPATH(mp);
+    ofpact_init_MULTIPATH(mp);
     if (!strcasecmp(fields, "eth_src")) {
-        mp->fields = htons(NX_HASH_FIELDS_ETH_SRC);
+        mp->fields = NX_HASH_FIELDS_ETH_SRC;
     } else if (!strcasecmp(fields, "symmetric_l4")) {
-        mp->fields = htons(NX_HASH_FIELDS_SYMMETRIC_L4);
+        mp->fields = NX_HASH_FIELDS_SYMMETRIC_L4;
     } else {
         ovs_fatal(0, "%s: unknown fields `%s'", s_, fields);
     }
-    mp->basis = htons(atoi(basis));
+    mp->basis = atoi(basis);
     if (!strcasecmp(algorithm, "modulo_n")) {
-        mp->algorithm = htons(NX_MP_ALG_MODULO_N);
+        mp->algorithm = NX_MP_ALG_MODULO_N;
     } else if (!strcasecmp(algorithm, "hash_threshold")) {
-        mp->algorithm = htons(NX_MP_ALG_HASH_THRESHOLD);
+        mp->algorithm = NX_MP_ALG_HASH_THRESHOLD;
     } else if (!strcasecmp(algorithm, "hrw")) {
-        mp->algorithm = htons(NX_MP_ALG_HRW);
+        mp->algorithm = NX_MP_ALG_HRW;
     } else if (!strcasecmp(algorithm, "iter_hash")) {
-        mp->algorithm = htons(NX_MP_ALG_ITER_HASH);
+        mp->algorithm = NX_MP_ALG_ITER_HASH;
     } else {
         ovs_fatal(0, "%s: unknown algorithm `%s'", s_, algorithm);
     }
@@ -208,34 +238,29 @@ multipath_parse(struct nx_action_multipath *mp, const char *s_)
         ovs_fatal(0, "%s: n_links %d is not in valid range 1 to 65536",
                   s_, n_links);
     }
-    mp->max_link = htons(n_links - 1);
-    mp->arg = htonl(atoi(arg));
+    mp->max_link = n_links - 1;
+    mp->arg = atoi(arg);
 
-    mf_parse_subfield(&dst, dst_s);
-    if (dst.n_bits < 16 && n_links > (1u << dst.n_bits)) {
+    mf_parse_subfield(&mp->dst, dst);
+    if (mp->dst.n_bits < 16 && n_links > (1u << mp->dst.n_bits)) {
         ovs_fatal(0, "%s: %d-bit destination field has %u possible values, "
                   "less than specified n_links %d",
-                  s_, dst.n_bits, 1u << dst.n_bits, n_links);
+                  s_, mp->dst.n_bits, 1u << mp->dst.n_bits, n_links);
     }
-    mp->ofs_nbits = nxm_encode_ofs_nbits(dst.ofs, dst.n_bits);
-    mp->dst = htonl(dst.field->nxm_header);
 
     free(s);
 }
 
+/* Appends a description of 'mp' to 's', in the format that ovs-ofctl(8)
+ * describes. */
 void
-multipath_format(const struct nx_action_multipath *mp, struct ds *s)
+multipath_format(const struct ofpact_multipath *mp, struct ds *s)
 {
     const char *fields, *algorithm;
 
-    uint16_t mp_fields    = ntohs(mp->fields);
-    uint16_t mp_algorithm = ntohs(mp->algorithm);
-
-    struct mf_subfield dst;
-
-    fields = flow_hash_fields_to_str(mp_fields);
+    fields = flow_hash_fields_to_str(mp->fields);
 
-    switch ((enum nx_mp_algorithm) mp_algorithm) {
+    switch (mp->algorithm) {
     case NX_MP_ALG_MODULO_N:
         algorithm = "modulo_n";
         break;
@@ -253,9 +278,8 @@ multipath_format(const struct nx_action_multipath *mp, struct ds *s)
     }
 
     ds_put_format(s, "multipath(%s,%"PRIu16",%s,%d,%"PRIu16",",
-                  fields, ntohs(mp->basis), algorithm, ntohs(mp->max_link) + 1,
-                  ntohl(mp->arg));
-    nxm_decode(&dst, mp->dst, mp->ofs_nbits);
-    mf_format_subfield(&dst, s);
+                  fields, mp->basis, algorithm, mp->max_link + 1,
+                  mp->arg);
+    mf_format_subfield(&mp->dst, s);
     ds_put_char(s, ')');
 }
diff --git a/lib/multipath.h b/lib/multipath.h
index 2cd646c..4e45d6c 100644
--- a/lib/multipath.h
+++ b/lib/multipath.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2011 Nicira, Inc.
+ * Copyright (c) 2010, 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,18 +23,24 @@
 struct ds;
 struct flow;
 struct nx_action_multipath;
-struct nx_action_reg_move;
+struct ofpact_multipath;
+struct ofpbuf;
 
 /* NXAST_MULTIPATH helper functions.
  *
  * See include/openflow/nicira-ext.h for NXAST_MULTIPATH specification.
  */
 
-enum ofperr multipath_check(const struct nx_action_multipath *,
+enum ofperr multipath_from_openflow(const struct nx_action_multipath *,
+                                    struct ofpact_multipath *);
+enum ofperr multipath_check(const struct ofpact_multipath *,
                             const struct flow *);
-void multipath_execute(const struct nx_action_multipath *, struct flow *);
+void multipath_to_openflow(const struct ofpact_multipath *,
+                           struct ofpbuf *openflow);
 
-void multipath_parse(struct nx_action_multipath *, const char *);
-void multipath_format(const struct nx_action_multipath *, struct ds *);
+void multipath_execute(const struct ofpact_multipath *, struct flow *);
+
+void multipath_parse(struct ofpact_multipath *, const char *);
+void multipath_format(const struct ofpact_multipath *, struct ds *);
 
 #endif /* multipath.h */
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 92e94f8..061b6b0 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -23,6 +23,7 @@
 #include "classifier.h"
 #include "dynamic-string.h"
 #include "meta-flow.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -789,180 +790,185 @@ nx_match_from_string(const char *s, struct ofpbuf *b)
 }
 
 void
-nxm_parse_reg_move(struct nx_action_reg_move *move, const char *s)
+nxm_parse_reg_move(struct ofpact_reg_move *move, const char *s)
 {
     const char *full_s = s;
-    struct mf_subfield src, dst;
 
-    s = mf_parse_subfield(&src, s);
+    s = mf_parse_subfield(&move->src, s);
     if (strncmp(s, "->", 2)) {
         ovs_fatal(0, "%s: missing `->' following source", full_s);
     }
     s += 2;
-    s = mf_parse_subfield(&dst, s);
+    s = mf_parse_subfield(&move->dst, s);
     if (*s != '\0') {
         ovs_fatal(0, "%s: trailing garbage following destination", full_s);
     }
 
-    if (src.n_bits != dst.n_bits) {
+    if (move->src.n_bits != move->dst.n_bits) {
         ovs_fatal(0, "%s: source field is %d bits wide but destination is "
-                  "%d bits wide", full_s, src.n_bits, dst.n_bits);
+                  "%d bits wide", full_s,
+                  move->src.n_bits, move->dst.n_bits);
     }
-
-    ofputil_init_NXAST_REG_MOVE(move);
-    move->n_bits = htons(src.n_bits);
-    move->src_ofs = htons(src.ofs);
-    move->dst_ofs = htons(dst.ofs);
-    move->src = htonl(src.field->nxm_header);
-    move->dst = htonl(dst.field->nxm_header);
 }
 
 void
-nxm_parse_reg_load(struct nx_action_reg_load *load, const char *s)
+nxm_parse_reg_load(struct ofpact_reg_load *load, const char *s)
 {
     const char *full_s = s;
-    struct mf_subfield dst;
-    uint64_t value;
 
-    value = strtoull(s, (char **) &s, 0);
+    load->value = strtoull(s, (char **) &s, 0);
     if (strncmp(s, "->", 2)) {
         ovs_fatal(0, "%s: missing `->' following value", full_s);
     }
     s += 2;
-    s = mf_parse_subfield(&dst, s);
+    s = mf_parse_subfield(&load->dst, s);
     if (*s != '\0') {
         ovs_fatal(0, "%s: trailing garbage following destination", full_s);
     }
 
-    if (dst.n_bits < 64 && (value >> dst.n_bits) != 0) {
-        ovs_fatal(0, "%s: value %"PRIu64" does not fit into %u bits",
-                  full_s, value, dst.n_bits);
+    if (load->dst.n_bits < 64 && (load->value >> load->dst.n_bits) != 0) {
+        ovs_fatal(0, "%s: value %"PRIu64" does not fit into %d bits",
+                  full_s, load->value, load->dst.n_bits);
     }
-
-    ofputil_init_NXAST_REG_LOAD(load);
-    load->ofs_nbits = nxm_encode_ofs_nbits(dst.ofs, dst.n_bits);
-    load->dst = htonl(dst.field->nxm_header);
-    load->value = htonll(value);
 }
 
 /* nxm_format_reg_move(), nxm_format_reg_load(). */
 
 void
-nxm_format_reg_move(const struct nx_action_reg_move *move, struct ds *s)
+nxm_format_reg_move(const struct ofpact_reg_move *move, struct ds *s)
 {
-    struct mf_subfield src, dst;
-
-    nxm_decode_discrete(&src, move->src, move->src_ofs, move->n_bits);
-    nxm_decode_discrete(&dst, move->dst, move->dst_ofs, move->n_bits);
-
     ds_put_format(s, "move:");
-    mf_format_subfield(&src, s);
+    mf_format_subfield(&move->src, s);
     ds_put_cstr(s, "->");
-    mf_format_subfield(&dst, s);
+    mf_format_subfield(&move->dst, s);
 }
 
 void
-nxm_format_reg_load(const struct nx_action_reg_load *load, struct ds *s)
+nxm_format_reg_load(const struct ofpact_reg_load *load, struct ds *s)
+{
+    ds_put_format(s, "load:%#"PRIx64"->", load->value);
+    mf_format_subfield(&load->dst, s);
+}
+
+enum ofperr
+nxm_reg_move_from_openflow(const struct nx_action_reg_move *narm,
+                           struct ofpbuf *ofpacts)
 {
-    struct mf_subfield dst;
+    struct ofpact_reg_move *move;
 
-    ds_put_format(s, "load:%#"PRIx64"->", ntohll(load->value));
+    move = ofpact_put_REG_MOVE(ofpacts);
+    move->src.field = mf_from_nxm_header(ntohl(narm->src));
+    move->src.ofs = ntohs(narm->src_ofs);
+    move->src.n_bits = ntohs(narm->n_bits);
+    move->dst.field = mf_from_nxm_header(ntohl(narm->dst));
+    move->dst.ofs = ntohs(narm->dst_ofs);
+    move->dst.n_bits = ntohs(narm->n_bits);
 
-    nxm_decode(&dst, load->dst, load->ofs_nbits);
-    mf_format_subfield(&dst, s);
+    return nxm_reg_move_check(move, NULL);
 }
-
-/* nxm_check_reg_move(), nxm_check_reg_load(). */
 
 enum ofperr
-nxm_check_reg_move(const struct nx_action_reg_move *action,
-                   const struct flow *flow)
+nxm_reg_load_from_openflow(const struct nx_action_reg_load *narl,
+                           struct ofpbuf *ofpacts)
 {
-    struct mf_subfield src;
-    struct mf_subfield dst;
-    int error;
+    struct ofpact_reg_load *load;
 
-    nxm_decode_discrete(&src, action->src, action->src_ofs, action->n_bits);
-    error = mf_check_src(&src, flow);
-    if (error) {
-        return error;
+    load = ofpact_put_REG_LOAD(ofpacts);
+    load->dst.field = mf_from_nxm_header(ntohl(narl->dst));
+    load->dst.ofs = nxm_decode_ofs(narl->ofs_nbits);
+    load->dst.n_bits = nxm_decode_n_bits(narl->ofs_nbits);
+    load->value = ntohll(narl->value);
+
+    /* Reject 'narl' if a bit numbered 'n_bits' or higher is set to 1 in
+     * narl->value. */
+    if (load->dst.n_bits < 64 && load->value >> load->dst.n_bits) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
 
-    nxm_decode_discrete(&dst, action->dst, action->dst_ofs, action->n_bits);
-    return mf_check_dst(&dst, flow);
+    return nxm_reg_load_check(load, NULL);
 }
-
+
 enum ofperr
-nxm_check_reg_load(const struct nx_action_reg_load *action,
-                   const struct flow *flow)
+nxm_reg_move_check(const struct ofpact_reg_move *move, const struct flow *flow)
 {
-    struct mf_subfield dst;
     enum ofperr error;
 
-    nxm_decode(&dst, action->dst, action->ofs_nbits);
-    error = mf_check_dst(&dst, flow);
+    error = mf_check_src(&move->src, flow);
     if (error) {
         return error;
     }
 
-    /* Reject 'action' if a bit numbered 'n_bits' or higher is set to 1 in
-     * action->value. */
-    if (dst.n_bits < 64 && ntohll(action->value) >> dst.n_bits) {
-        return OFPERR_OFPBAC_BAD_ARGUMENT;
-    }
+    return mf_check_dst(&move->dst, NULL);
+}
 
-    return 0;
+enum ofperr
+nxm_reg_load_check(const struct ofpact_reg_load *load, const struct flow *flow)
+{
+    return mf_check_dst(&load->dst, flow);
+}
+
+void
+nxm_reg_move_to_openflow(const struct ofpact_reg_move *move,
+                           struct ofpbuf *openflow)
+{
+    struct nx_action_reg_move *narm;
+
+    narm = ofputil_put_NXAST_REG_MOVE(openflow);
+    narm->n_bits = htons(move->dst.n_bits);
+    narm->src_ofs = htons(move->src.ofs);
+    narm->dst_ofs = htons(move->dst.ofs);
+    narm->src = htonl(move->src.field->nxm_header);
+    narm->dst = htonl(move->dst.field->nxm_header);
+}
+
+void
+nxm_reg_load_to_openflow(const struct ofpact_reg_load *load,
+                         struct ofpbuf *openflow)
+{
+    struct nx_action_reg_load *narl;
+
+    narl = ofputil_put_NXAST_REG_LOAD(openflow);
+    narl->ofs_nbits = nxm_encode_ofs_nbits(load->dst.ofs, load->dst.n_bits);
+    narl->dst = htonl(load->dst.field->nxm_header);
+    narl->value = htonll(load->value);
 }
 
 /* nxm_execute_reg_move(), nxm_execute_reg_load(). */
 
 void
-nxm_execute_reg_move(const struct nx_action_reg_move *action,
+nxm_execute_reg_move(const struct ofpact_reg_move *move,
                      struct flow *flow)
 {
-    struct mf_subfield src, dst;
     union mf_value src_value;
     union mf_value dst_value;
 
-    nxm_decode_discrete(&src, action->src, action->src_ofs, action->n_bits);
-    nxm_decode_discrete(&dst, action->dst, action->dst_ofs, action->n_bits);
-
-    mf_get_value(dst.field, flow, &dst_value);
-    mf_get_value(src.field, flow, &src_value);
-    bitwise_copy(&src_value, src.field->n_bytes, src.ofs,
-                 &dst_value, dst.field->n_bytes, dst.ofs,
-                 src.n_bits);
-    mf_set_flow_value(dst.field, &dst_value, flow);
+    mf_get_value(move->dst.field, flow, &dst_value);
+    mf_get_value(move->src.field, flow, &src_value);
+    bitwise_copy(&src_value, move->src.field->n_bytes, move->src.ofs,
+                 &dst_value, move->dst.field->n_bytes, move->dst.ofs,
+                 move->src.n_bits);
+    mf_set_flow_value(move->dst.field, &dst_value, flow);
 }
 
 void
-nxm_execute_reg_load(const struct nx_action_reg_load *action,
-                     struct flow *flow)
+nxm_execute_reg_load(const struct ofpact_reg_load *load, struct flow *flow)
 {
-    struct mf_subfield dst;
-
-    nxm_decode(&dst, action->dst, action->ofs_nbits);
-    mf_set_subfield_value(&dst, ntohll(action->value), flow);
+    nxm_reg_load(&load->dst, load->value, flow);
 }
 
-/* Initializes 'sf->field' with the field corresponding to the given NXM
- * 'header' and 'sf->ofs' and 'sf->n_bits' decoded from 'ofs_nbits' with
- * nxm_decode_ofs() and nxm_decode_n_bits(), respectively.
- *
- * Afterward, 'sf' might be invalid in a few different ways:
- *
- *   - 'sf->field' will be NULL if 'header' is unknown.
- *
- *   - 'sf->ofs' and 'sf->n_bits' might exceed the width of sf->field.
- *
- * The caller should call mf_check_src() or mf_check_dst() to check for these
- * problems. */
 void
-nxm_decode(struct mf_subfield *sf, ovs_be32 header, ovs_be16 ofs_nbits)
+nxm_reg_load(const struct mf_subfield *dst, uint64_t src_data,
+             struct flow *flow)
 {
-    sf->field = mf_from_nxm_header(ntohl(header));
-    sf->ofs = nxm_decode_ofs(ofs_nbits);
-    sf->n_bits = nxm_decode_n_bits(ofs_nbits);
+    union mf_value dst_value;
+    union mf_value src_value;
+
+    mf_get_value(dst->field, flow, &dst_value);
+    src_value.be64 = htonll(src_data);
+    bitwise_copy(&src_value, sizeof src_value.be64, 0,
+                 &dst_value, dst->field->n_bytes, dst->ofs,
+                 dst->n_bits);
+    mf_set_flow_value(dst->field, &dst_value, flow);
 }
 
 /* Initializes 'sf->field' with the field corresponding to the given NXM
diff --git a/lib/nx-match.h b/lib/nx-match.h
index c814275..c57e240 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 #include <netinet/in.h>
 #include "flow.h"
+#include "ofp-errors.h"
 #include "openvswitch/types.h"
 #include "ofp-errors.h"
 
@@ -28,6 +29,8 @@ struct cls_rule;
 struct ds;
 struct flow;
 struct mf_subfield;
+struct ofpact_reg_move;
+struct ofpact_reg_load;
 struct ofpbuf;
 struct nx_action_reg_load;
 struct nx_action_reg_move;
@@ -49,19 +52,31 @@ int nx_put_match(struct ofpbuf *, bool oxm, const struct cls_rule *,
 char *nx_match_to_string(const uint8_t *, unsigned int match_len);
 int nx_match_from_string(const char *, struct ofpbuf *);
 
-void nxm_parse_reg_move(struct nx_action_reg_move *, const char *);
-void nxm_parse_reg_load(struct nx_action_reg_load *, const char *);
+void nxm_parse_reg_move(struct ofpact_reg_move *, const char *);
+void nxm_parse_reg_load(struct ofpact_reg_load *, const char *);
+
+void nxm_format_reg_move(const struct ofpact_reg_move *, struct ds *);
+void nxm_format_reg_load(const struct ofpact_reg_load *, struct ds *);
 
-void nxm_format_reg_move(const struct nx_action_reg_move *, struct ds *);
-void nxm_format_reg_load(const struct nx_action_reg_load *, struct ds *);
+enum ofperr nxm_reg_move_from_openflow(const struct nx_action_reg_move *,
+                                       struct ofpbuf *ofpacts);
+enum ofperr nxm_reg_load_from_openflow(const struct nx_action_reg_load *,
+                                       struct ofpbuf *ofpacts);
 
-enum ofperr nxm_check_reg_move(const struct nx_action_reg_move *,
+enum ofperr nxm_reg_move_check(const struct ofpact_reg_move *,
                                const struct flow *);
-enum ofperr nxm_check_reg_load(const struct nx_action_reg_load *,
+enum ofperr nxm_reg_load_check(const struct ofpact_reg_load *,
                                const struct flow *);
 
-void nxm_execute_reg_move(const struct nx_action_reg_move *, struct flow *);
-void nxm_execute_reg_load(const struct nx_action_reg_load *, struct flow *);
+void nxm_reg_move_to_openflow(const struct ofpact_reg_move *,
+                              struct ofpbuf *openflow);
+void nxm_reg_load_to_openflow(const struct ofpact_reg_load *,
+                              struct ofpbuf *openflow);
+
+void nxm_execute_reg_move(const struct ofpact_reg_move *, struct flow *);
+void nxm_execute_reg_load(const struct ofpact_reg_load *, struct flow *);
+void nxm_reg_load(const struct mf_subfield *, uint64_t src_data,
+                  struct flow *);
 
 int nxm_field_bytes(uint32_t header);
 int nxm_field_bits(uint32_t header);
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
new file mode 100644
index 0000000..cf8ea95
--- /dev/null
+++ b/lib/ofp-actions.c
@@ -0,0 +1,1213 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "ofp-actions.h"
+#include "autopath.h"
+#include "bundle.h"
+#include "byte-order.h"
+#include "compiler.h"
+#include "dynamic-string.h"
+#include "learn.h"
+#include "meta-flow.h"
+#include "multipath.h"
+#include "nx-match.h"
+#include "ofp-util.h"
+#include "ofpbuf.h"
+#include "vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(ofp_actions);
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+/* Converting OpenFlow 1.0 to ofpacts. */
+
+static enum ofperr
+output_from_openflow10(const struct ofp_action_output *oao,
+                       struct ofpbuf *out)
+{
+    struct ofpact_output *output;
+
+    output = ofpact_put_OUTPUT(out);
+    output->port = ntohs(oao->port);
+    output->max_len = ntohs(oao->max_len);
+
+    return ofputil_check_output_port(output->port, OFPP_MAX);
+}
+
+static enum ofperr
+enqueue_from_openflow10(const struct ofp_action_enqueue *oae,
+                        struct ofpbuf *out)
+{
+    struct ofpact_enqueue *enqueue;
+
+    enqueue = ofpact_put_ENQUEUE(out);
+    enqueue->port = ntohs(oae->port);
+    enqueue->queue = ntohl(oae->queue_id);
+    if (enqueue->port >= OFPP_MAX && enqueue->port != OFPP_IN_PORT
+        && enqueue->port != OFPP_LOCAL) {
+        return OFPERR_OFPBAC_BAD_OUT_PORT;
+    }
+    return 0;
+}
+
+static void
+resubmit_from_openflow(const struct nx_action_resubmit *nar,
+                       struct ofpbuf *out)
+{
+    struct ofpact_resubmit *resubmit;
+
+    resubmit = ofpact_put_RESUBMIT(out);
+    resubmit->ofpact.compat = OFPUTIL_NXAST_RESUBMIT;
+    resubmit->in_port = ntohs(nar->in_port);
+    resubmit->table_id = 0xff;
+}
+
+static enum ofperr
+resubmit_table_from_openflow(const struct nx_action_resubmit *nar,
+                             struct ofpbuf *out)
+{
+    struct ofpact_resubmit *resubmit;
+
+    if (nar->pad[0] || nar->pad[1] || nar->pad[2]) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
+
+    resubmit = ofpact_put_RESUBMIT(out);
+    resubmit->ofpact.compat = OFPUTIL_NXAST_RESUBMIT_TABLE;
+    resubmit->in_port = ntohs(nar->in_port);
+    resubmit->table_id = nar->table;
+    return 0;
+}
+
+static enum ofperr
+output_reg_from_openflow(const struct nx_action_output_reg *naor,
+                         struct ofpbuf *out)
+{
+    struct ofpact_output_reg *output_reg;
+
+    if (!is_all_zeros(naor->zero, sizeof naor->zero)) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
+
+    output_reg = ofpact_put_OUTPUT_REG(out);
+    output_reg->src.field = mf_from_nxm_header(ntohl(naor->src));
+    output_reg->src.ofs = nxm_decode_ofs(naor->ofs_nbits);
+    output_reg->src.n_bits = nxm_decode_n_bits(naor->ofs_nbits);
+    output_reg->max_len = ntohs(naor->max_len);
+
+    return mf_check_src(&output_reg->src, NULL);
+}
+
+static void
+fin_timeout_from_openflow(const struct nx_action_fin_timeout *naft,
+                          struct ofpbuf *out)
+{
+    struct ofpact_fin_timeout *oft;
+
+    oft = ofpact_put_FIN_TIMEOUT(out);
+    oft->fin_idle_timeout = ntohs(naft->fin_idle_timeout);
+    oft->fin_hard_timeout = ntohs(naft->fin_hard_timeout);
+}
+
+static void
+controller_from_openflow(const struct nx_action_controller *nac,
+                         struct ofpbuf *out)
+{
+    struct ofpact_controller *oc;
+
+    oc = ofpact_put_CONTROLLER(out);
+    oc->max_len = ntohs(nac->max_len);
+    oc->controller_id = ntohs(nac->controller_id);
+    oc->reason = nac->reason;
+}
+
+static void
+note_from_openflow(const struct nx_action_note *nan, struct ofpbuf *out)
+{
+    struct ofpact_note *note;
+    unsigned int length;
+
+    length = ntohs(nan->len) - offsetof(struct nx_action_note, note);
+    note = ofpact_put(out, OFPACT_NOTE,
+                      offsetof(struct ofpact_note, data) + length);
+    note->length = length;
+    memcpy(note->data, nan->note, length);
+}
+
+static enum ofperr
+decode_nxast_action(const union ofp_action *a, enum ofputil_action_code *code)
+{
+    const struct nx_action_header *nah = (const struct nx_action_header *) a;
+    uint16_t len = ntohs(a->header.len);
+
+    if (len < sizeof(struct nx_action_header)) {
+        return OFPERR_OFPBAC_BAD_LEN;
+    } else if (a->vendor.vendor != CONSTANT_HTONL(NX_VENDOR_ID)) {
+        return OFPERR_OFPBAC_BAD_VENDOR;
+    }
+
+    switch (nah->subtype) {
+#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME)    \
+        case CONSTANT_HTONS(ENUM):                      \
+            if (EXTENSIBLE                              \
+                ? len >= sizeof(struct STRUCT)          \
+                : len == sizeof(struct STRUCT)) {       \
+                *code = OFPUTIL_##ENUM;                 \
+                return 0;                               \
+            } else {                                    \
+                return OFPERR_OFPBAC_BAD_LEN;           \
+            }                                           \
+            NOT_REACHED();
+#include "ofp-util.def"
+
+    case CONSTANT_HTONS(NXAST_SNAT__OBSOLETE):
+    case CONSTANT_HTONS(NXAST_DROP_SPOOFED_ARP__OBSOLETE):
+    default:
+        return OFPERR_OFPBAC_BAD_TYPE;
+    }
+}
+
+/* Parses 'a' to determine its type.  On success stores the correct type into
+ * '*code' and returns 0.  On failure returns an OFPERR_* error code and
+ * '*code' is indeterminate.
+ *
+ * The caller must have already verified that 'a''s length is potentially
+ * correct (that is, a->header.len is nonzero and a multiple of sizeof(union
+ * ofp_action) and no longer than the amount of space allocated to 'a').
+ *
+ * This function verifies that 'a''s length is correct for the type of action
+ * that it represents. */
+static enum ofperr
+decode_openflow10_action(const union ofp_action *a,
+                         enum ofputil_action_code *code)
+{
+    switch (a->type) {
+    case CONSTANT_HTONS(OFPAT10_VENDOR):
+        return decode_nxast_action(a, code);
+
+#define OFPAT10_ACTION(ENUM, STRUCT, NAME)                          \
+        case CONSTANT_HTONS(ENUM):                                  \
+            if (a->header.len == htons(sizeof(struct STRUCT))) {    \
+                *code = OFPUTIL_##ENUM;                             \
+                return 0;                                           \
+            } else {                                                \
+                return OFPERR_OFPBAC_BAD_LEN;                       \
+            }                                                       \
+            break;
+#include "ofp-util.def"
+
+    default:
+        return OFPERR_OFPBAC_BAD_TYPE;
+    }
+}
+
+static enum ofperr
+ofpact_from_openflow10__(const union ofp_action *a, struct ofpbuf *out)
+{
+    const struct nx_action_resubmit *nar;
+    const struct nx_action_set_tunnel *nast;
+    const struct nx_action_set_queue *nasq;
+    const struct nx_action_note *nan;
+    const struct nx_action_set_tunnel64 *nast64;
+    struct ofpact_tunnel *tunnel;
+    enum ofputil_action_code code;
+    enum ofperr error;
+
+    error = decode_openflow10_action(a, &code);
+    if (error) {
+        return error;
+    }
+
+    switch (code) {
+    case OFPUTIL_ACTION_INVALID:
+        NOT_REACHED();
+
+    case OFPUTIL_OFPAT10_OUTPUT:
+        return output_from_openflow10((const struct ofp_action_output *) a,
+                                      out);
+
+    case OFPUTIL_OFPAT10_SET_VLAN_VID:
+        if (a->vlan_vid.vlan_vid & ~htons(0xfff)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        ofpact_put_SET_VLAN_VID(out)->vlan_vid = ntohs(a->vlan_vid.vlan_vid);
+        break;
+
+    case OFPUTIL_OFPAT10_SET_VLAN_PCP:
+        if (a->vlan_pcp.vlan_pcp & ~7) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        ofpact_put_SET_VLAN_PCP(out)->vlan_pcp = a->vlan_pcp.vlan_pcp;
+        break;
+
+    case OFPUTIL_OFPAT10_STRIP_VLAN:
+        ofpact_put_STRIP_VLAN(out);
+        break;
+
+    case OFPUTIL_OFPAT10_SET_DL_SRC:
+        memcpy(ofpact_put_SET_ETH_SRC(out)->mac,
+               ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN);
+        break;
+
+    case OFPUTIL_OFPAT10_SET_DL_DST:
+        memcpy(ofpact_put_SET_ETH_DST(out)->mac,
+               ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN);
+        break;
+
+    case OFPUTIL_OFPAT10_SET_NW_SRC:
+        ofpact_put_SET_IPV4_SRC(out)->ipv4 = a->nw_addr.nw_addr;
+        break;
+
+    case OFPUTIL_OFPAT10_SET_NW_DST:
+        ofpact_put_SET_IPV4_DST(out)->ipv4 = a->nw_addr.nw_addr;
+        break;
+
+    case OFPUTIL_OFPAT10_SET_NW_TOS:
+        if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        ofpact_put_SET_IPV4_DSCP(out)->dscp = a->nw_tos.nw_tos;
+        break;
+
+    case OFPUTIL_OFPAT10_SET_TP_SRC:
+        ofpact_put_SET_L4_SRC_PORT(out)->port = ntohs(a->tp_port.tp_port);
+        break;
+
+    case OFPUTIL_OFPAT10_SET_TP_DST:
+        ofpact_put_SET_L4_DST_PORT(out)->port = ntohs(a->tp_port.tp_port);
+
+        break;
+
+    case OFPUTIL_OFPAT10_ENQUEUE:
+        error = enqueue_from_openflow10((const struct ofp_action_enqueue *) a,
+                                        out);
+        break;
+
+    case OFPUTIL_NXAST_RESUBMIT:
+        resubmit_from_openflow((const struct nx_action_resubmit *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_SET_TUNNEL:
+        nast = (const struct nx_action_set_tunnel *) a;
+        tunnel = ofpact_put_SET_TUNNEL(out);
+        tunnel->ofpact.compat = code;
+        tunnel->tun_id = ntohl(nast->tun_id);
+        break;
+
+    case OFPUTIL_NXAST_SET_QUEUE:
+        nasq = (const struct nx_action_set_queue *) a;
+        ofpact_put_SET_QUEUE(out)->queue_id = ntohl(nasq->queue_id);
+        break;
+
+    case OFPUTIL_NXAST_POP_QUEUE:
+        ofpact_put_POP_QUEUE(out);
+        break;
+
+    case OFPUTIL_NXAST_REG_MOVE:
+        error = nxm_reg_move_from_openflow(
+            (const struct nx_action_reg_move *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_REG_LOAD:
+        error = nxm_reg_load_from_openflow(
+            (const struct nx_action_reg_load *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_NOTE:
+        nan = (const struct nx_action_note *) a;
+        note_from_openflow(nan, out);
+        break;
+
+    case OFPUTIL_NXAST_SET_TUNNEL64:
+        nast64 = (const struct nx_action_set_tunnel64 *) a;
+        tunnel = ofpact_put_SET_TUNNEL(out);
+        tunnel->ofpact.compat = code;
+        tunnel->tun_id = ntohll(nast64->tun_id);
+        break;
+
+    case OFPUTIL_NXAST_MULTIPATH:
+        error = multipath_from_openflow((const struct nx_action_multipath *) a,
+                                        ofpact_put_MULTIPATH(out));
+        break;
+
+    case OFPUTIL_NXAST_AUTOPATH:
+        error = autopath_from_openflow((const struct nx_action_autopath *) a,
+                                       ofpact_put_AUTOPATH(out));
+        break;
+
+    case OFPUTIL_NXAST_BUNDLE:
+    case OFPUTIL_NXAST_BUNDLE_LOAD:
+        error = bundle_from_openflow((const struct nx_action_bundle *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_OUTPUT_REG:
+        error = output_reg_from_openflow(
+            (const struct nx_action_output_reg *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_RESUBMIT_TABLE:
+        nar = (const struct nx_action_resubmit *) a;
+        error = resubmit_table_from_openflow(nar, out);
+        break;
+
+    case OFPUTIL_NXAST_LEARN:
+        error = learn_from_openflow((const struct nx_action_learn *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_EXIT:
+        ofpact_put_EXIT(out);
+        break;
+
+    case OFPUTIL_NXAST_DEC_TTL:
+        ofpact_put_DEC_TTL(out);
+        break;
+
+    case OFPUTIL_NXAST_FIN_TIMEOUT:
+        fin_timeout_from_openflow(
+            (const struct nx_action_fin_timeout *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_CONTROLLER:
+        controller_from_openflow((const struct nx_action_controller *) a, out);
+        break;
+    }
+
+    return error;
+}
+
+static inline union ofp_action *
+action_next(const union ofp_action *a)
+{
+    return ((union ofp_action *) (void *)
+            ((uint8_t *) a + ntohs(a->header.len)));
+}
+
+static inline bool
+action_is_valid(const union ofp_action *a, size_t n_actions)
+{
+    uint16_t len = ntohs(a->header.len);
+    return (!(len % OFP_ACTION_ALIGN)
+            && len >= sizeof *a
+            && len / sizeof *a <= n_actions);
+}
+
+/* This macro is careful to check for actions with bad lengths. */
+#define ACTION_FOR_EACH(ITER, LEFT, ACTIONS, N_ACTIONS)                 \
+    for ((ITER) = (ACTIONS), (LEFT) = (N_ACTIONS);                      \
+         (LEFT) > 0 && action_is_valid(ITER, LEFT);                     \
+         ((LEFT) -= ntohs((ITER)->header.len) / sizeof(union ofp_action), \
+          (ITER) = action_next(ITER)))
+
+static enum ofperr
+ofpact_from_openflow10(const union ofp_action *in, size_t n_in,
+                       struct ofpbuf *out)
+{
+    const union ofp_action *a;
+    size_t left;
+
+    ACTION_FOR_EACH (a, left, in, n_in) {
+        enum ofperr error = ofpact_from_openflow10__(a, out);
+        if (error) {
+            VLOG_WARN_RL(&rl, "bad action at offset %td (%s)",
+                         (a - in) * sizeof *a, ofperr_get_name(error));
+            return error;
+        }
+    }
+    if (left) {
+        VLOG_WARN_RL(&rl, "bad action format at offset %zu",
+                     (n_in - left) * sizeof *a);
+        return OFPERR_OFPBAC_BAD_LEN;
+    }
+
+    ofpact_put_END(out);
+
+    return 0;
+}
+
+/* Attempts to convert 'actions_len' bytes of OpenFlow actions from the front
+ * of 'openflow' into ofpacts.  On success, replaces any existing content in
+ * 'ofpacts' by the converted ofpacts; on failure, clears 'ofpacts'.  Returns 0
+ * if successful, otherwise an OpenFlow error.
+ *
+ * This function does not check that the actions are valid in a given context.
+ * The caller should do so, with ofpacts_check(). */
+enum ofperr
+ofpacts_pull_openflow(struct ofpbuf *openflow, unsigned int actions_len,
+                      struct ofpbuf *ofpacts)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+    const union ofp_action *actions;
+    enum ofperr error;
+
+    ofpbuf_clear(ofpacts);
+
+    if (actions_len % OFP_ACTION_ALIGN != 0) {
+        VLOG_WARN_RL(&rl, "OpenFlow message actions length %u is not a "
+                     "multiple of %d", actions_len, OFP_ACTION_ALIGN);
+        return OFPERR_OFPBRC_BAD_LEN;
+    }
+
+    actions = ofpbuf_try_pull(openflow, actions_len);
+    if (actions == NULL) {
+        VLOG_WARN_RL(&rl, "OpenFlow message actions length %u exceeds "
+                     "remaining message length (%zu)",
+                     actions_len, openflow->size);
+        return OFPERR_OFPBRC_BAD_LEN;
+    }
+
+    error = ofpact_from_openflow10(actions, actions_len / OFP_ACTION_ALIGN,
+                                   ofpacts);
+    if (error) {
+        ofpbuf_clear(ofpacts);
+    }
+    return 0;
+}
+
+static enum ofperr
+ofpact_check__(const struct ofpact *a, const struct flow *flow, int max_ports)
+{
+    const struct ofpact_enqueue *enqueue;
+
+    switch (a->type) {
+    case OFPACT_END:
+        return 0;
+
+    case OFPACT_OUTPUT:
+        return ofputil_check_output_port(ofpact_get_OUTPUT(a)->port,
+                                         max_ports);
+
+    case OFPACT_CONTROLLER:
+        return 0;
+
+    case OFPACT_ENQUEUE:
+        enqueue = ofpact_get_ENQUEUE(a);
+        if (enqueue->port >= max_ports && enqueue->port != OFPP_IN_PORT
+            && enqueue->port != OFPP_LOCAL) {
+            return OFPERR_OFPBAC_BAD_OUT_PORT;
+        }
+        return 0;
+
+    case OFPACT_OUTPUT_REG:
+        return mf_check_src(&ofpact_get_OUTPUT_REG(a)->src, flow);
+
+    case OFPACT_BUNDLE:
+        return bundle_check(ofpact_get_BUNDLE(a), max_ports, flow);
+
+    case OFPACT_SET_VLAN_VID:
+    case OFPACT_SET_VLAN_PCP:
+    case OFPACT_STRIP_VLAN:
+    case OFPACT_SET_ETH_SRC:
+    case OFPACT_SET_ETH_DST:
+    case OFPACT_SET_IPV4_SRC:
+    case OFPACT_SET_IPV4_DST:
+    case OFPACT_SET_IPV4_DSCP:
+    case OFPACT_SET_L4_SRC_PORT:
+    case OFPACT_SET_L4_DST_PORT:
+        return 0;
+
+    case OFPACT_REG_MOVE:
+        return nxm_reg_move_check(ofpact_get_REG_MOVE(a), flow);
+
+    case OFPACT_REG_LOAD:
+        return nxm_reg_load_check(ofpact_get_REG_LOAD(a), flow);
+
+    case OFPACT_DEC_TTL:
+    case OFPACT_SET_TUNNEL:
+    case OFPACT_SET_QUEUE:
+    case OFPACT_POP_QUEUE:
+    case OFPACT_FIN_TIMEOUT:
+    case OFPACT_RESUBMIT:
+        return 0;
+
+    case OFPACT_LEARN:
+        return learn_check(ofpact_get_LEARN(a), flow);
+
+    case OFPACT_MULTIPATH:
+        return multipath_check(ofpact_get_MULTIPATH(a), flow);
+
+    case OFPACT_AUTOPATH:
+        return autopath_check(ofpact_get_AUTOPATH(a), flow);
+
+    case OFPACT_NOTE:
+    case OFPACT_EXIT:
+        return 0;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+/* Checks that the actions in 'ofpacts' (terminated by OFPACT_END) are
+ * appropriate for a packet with the prerequisites satisfied by 'flow' in a
+ * switch with no more than 'max_ports' ports. */
+enum ofperr
+ofpacts_check(const struct ofpact ofpacts[],
+              const struct flow *flow, int max_ports)
+{
+    const struct ofpact *a;
+
+    OFPACT_FOR_EACH (a, ofpacts) {
+        enum ofperr error = ofpact_check__(a, flow, max_ports);
+        if (error) {
+            return error;
+        }
+    }
+
+    return 0;
+}
+
+/* Converting ofpacts to Nicira OpenFlow extensions. */
+
+static void
+ofpact_output_reg_to_nxast(const struct ofpact_output_reg *output_reg,
+                                struct ofpbuf *out)
+{
+    struct nx_action_output_reg *naor = ofputil_put_NXAST_OUTPUT_REG(out);
+
+    naor->ofs_nbits = nxm_encode_ofs_nbits(output_reg->src.ofs,
+                                           output_reg->src.n_bits);
+    naor->src = htonl(output_reg->src.field->nxm_header);
+    naor->max_len = htons(output_reg->max_len);
+}
+
+static void
+ofpact_resubmit_to_nxast(const struct ofpact_resubmit *resubmit,
+                         struct ofpbuf *out)
+{
+    struct nx_action_resubmit *nar;
+
+    if (resubmit->table_id == 0xff
+        && resubmit->ofpact.compat != OFPUTIL_NXAST_RESUBMIT_TABLE) {
+        nar = ofputil_put_NXAST_RESUBMIT(out);
+    } else {
+        nar = ofputil_put_NXAST_RESUBMIT_TABLE(out);
+        nar->table = resubmit->table_id;
+    }
+    nar->in_port = htons(resubmit->in_port);
+}
+
+static void
+ofpact_set_tunnel_to_nxast(const struct ofpact_tunnel *tunnel,
+                           struct ofpbuf *out)
+{
+    uint64_t tun_id = tunnel->tun_id;
+
+    if (tun_id <= UINT32_MAX
+        && tunnel->ofpact.compat != OFPUTIL_NXAST_SET_TUNNEL64) {
+        ofputil_put_NXAST_SET_TUNNEL(out)->tun_id = htonl(tun_id);
+    } else {
+        ofputil_put_NXAST_SET_TUNNEL64(out)->tun_id = htonll(tun_id);
+    }
+}
+
+static void
+ofpact_note_to_nxast(const struct ofpact_note *note, struct ofpbuf *out)
+{
+    size_t start_ofs = out->size;
+    struct nx_action_note *nan;
+    unsigned int remainder;
+    unsigned int len;
+
+    nan = ofputil_put_NXAST_NOTE(out);
+    out->size -= sizeof nan->note;
+
+    ofpbuf_put(out, note->data, note->length);
+
+    len = out->size - start_ofs;
+    remainder = len % OFP_ACTION_ALIGN;
+    if (remainder) {
+        ofpbuf_put_zeros(out, OFP_ACTION_ALIGN - remainder);
+    }
+    nan = (struct nx_action_note *)((char *)out->data + start_ofs);
+    nan->len = htons(out->size - start_ofs);
+}
+
+static void
+ofpact_controller_to_nxast(const struct ofpact_controller *oc,
+                           struct ofpbuf *out)
+{
+    struct nx_action_controller *nac;
+
+    nac = ofputil_put_NXAST_CONTROLLER(out);
+    nac->max_len = htons(oc->max_len);
+    nac->controller_id = htons(oc->controller_id);
+    nac->reason = oc->reason;
+}
+
+static void
+ofpact_fin_timeout_to_nxast(const struct ofpact_fin_timeout *fin_timeout,
+                            struct ofpbuf *out)
+{
+    struct nx_action_fin_timeout *naft = ofputil_put_NXAST_FIN_TIMEOUT(out);
+    naft->fin_idle_timeout = htons(fin_timeout->fin_idle_timeout);
+    naft->fin_hard_timeout = htons(fin_timeout->fin_hard_timeout);
+}
+
+static void
+ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out)
+{
+    switch (a->type) {
+    case OFPACT_CONTROLLER:
+        ofpact_controller_to_nxast(ofpact_get_CONTROLLER(a), out);
+        break;
+
+    case OFPACT_OUTPUT_REG:
+        ofpact_output_reg_to_nxast(ofpact_get_OUTPUT_REG(a), out);
+        break;
+
+    case OFPACT_BUNDLE:
+        bundle_to_openflow(ofpact_get_BUNDLE(a), out);
+        break;
+
+    case OFPACT_REG_MOVE:
+        nxm_reg_move_to_openflow(ofpact_get_REG_MOVE(a), out);
+        break;
+
+    case OFPACT_REG_LOAD:
+        nxm_reg_load_to_openflow(ofpact_get_REG_LOAD(a), out);
+        break;
+
+    case OFPACT_DEC_TTL:
+        ofputil_put_NXAST_DEC_TTL(out);
+        break;
+
+    case OFPACT_SET_TUNNEL:
+        ofpact_set_tunnel_to_nxast(ofpact_get_SET_TUNNEL(a), out);
+        break;
+
+    case OFPACT_SET_QUEUE:
+        ofputil_put_NXAST_SET_QUEUE(out)->queue_id
+            = htonl(ofpact_get_SET_QUEUE(a)->queue_id);
+        break;
+
+    case OFPACT_POP_QUEUE:
+        ofputil_put_NXAST_POP_QUEUE(out);
+        break;
+
+    case OFPACT_FIN_TIMEOUT:
+        ofpact_fin_timeout_to_nxast(ofpact_get_FIN_TIMEOUT(a), out);
+        break;
+
+    case OFPACT_RESUBMIT:
+        ofpact_resubmit_to_nxast(ofpact_get_RESUBMIT(a), out);
+        break;
+
+    case OFPACT_LEARN:
+        learn_to_openflow(ofpact_get_LEARN(a), out);
+        break;
+
+    case OFPACT_MULTIPATH:
+        multipath_to_openflow(ofpact_get_MULTIPATH(a), out);
+        break;
+
+    case OFPACT_AUTOPATH:
+        autopath_to_openflow(ofpact_get_AUTOPATH(a), out);
+        break;
+
+    case OFPACT_NOTE:
+        ofpact_note_to_nxast(ofpact_get_NOTE(a), out);
+        break;
+
+    case OFPACT_EXIT:
+        ofputil_put_NXAST_EXIT(out);
+        break;
+
+    case OFPACT_END:
+    case OFPACT_OUTPUT:
+    case OFPACT_ENQUEUE:
+    case OFPACT_SET_VLAN_VID:
+    case OFPACT_SET_VLAN_PCP:
+    case OFPACT_STRIP_VLAN:
+    case OFPACT_SET_ETH_SRC:
+    case OFPACT_SET_ETH_DST:
+    case OFPACT_SET_IPV4_SRC:
+    case OFPACT_SET_IPV4_DST:
+    case OFPACT_SET_IPV4_DSCP:
+    case OFPACT_SET_L4_SRC_PORT:
+    case OFPACT_SET_L4_DST_PORT:
+        NOT_REACHED();
+    }
+}
+
+/* Converting ofpacts to OpenFlow 1.0. */
+
+static void
+ofpact_output_to_openflow10(const struct ofpact_output *output,
+                            struct ofpbuf *out)
+{
+    struct ofp_action_output *oao;
+
+    oao = ofputil_put_OFPAT10_OUTPUT(out);
+    oao->port = htons(output->port);
+    oao->max_len = htons(output->max_len);
+}
+
+static void
+ofpact_enqueue_to_openflow10(const struct ofpact_enqueue *enqueue,
+                             struct ofpbuf *out)
+{
+    struct ofp_action_enqueue *oae;
+
+    oae = ofputil_put_OFPAT10_ENQUEUE(out);
+    oae->port = htons(enqueue->port);
+    oae->queue_id = htonl(enqueue->queue);
+}
+
+static void
+ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out)
+{
+    switch (a->type) {
+    case OFPACT_END:
+        NOT_REACHED();
+
+    case OFPACT_OUTPUT:
+        ofpact_output_to_openflow10(ofpact_get_OUTPUT(a), out);
+        break;
+
+    case OFPACT_ENQUEUE:
+        ofpact_enqueue_to_openflow10(ofpact_get_ENQUEUE(a), out);
+        break;
+
+    case OFPACT_SET_VLAN_VID:
+        ofputil_put_OFPAT10_SET_VLAN_VID(out)->vlan_vid
+            = htons(ofpact_get_SET_VLAN_VID(a)->vlan_vid);
+        break;
+
+    case OFPACT_SET_VLAN_PCP:
+        ofputil_put_OFPAT10_SET_VLAN_PCP(out)->vlan_pcp
+            = ofpact_get_SET_VLAN_PCP(a)->vlan_pcp;
+        break;
+
+    case OFPACT_STRIP_VLAN:
+        ofputil_put_OFPAT10_STRIP_VLAN(out);
+        break;
+
+    case OFPACT_SET_ETH_SRC:
+        memcpy(ofputil_put_OFPAT10_SET_DL_SRC(out)->dl_addr,
+               ofpact_get_SET_ETH_SRC(a)->mac, ETH_ADDR_LEN);
+        break;
+
+    case OFPACT_SET_ETH_DST:
+        memcpy(ofputil_put_OFPAT10_SET_DL_DST(out)->dl_addr,
+               ofpact_get_SET_ETH_DST(a)->mac, ETH_ADDR_LEN);
+        break;
+
+    case OFPACT_SET_IPV4_SRC:
+        ofputil_put_OFPAT10_SET_NW_SRC(out)->nw_addr
+            = ofpact_get_SET_IPV4_SRC(a)->ipv4;
+        break;
+
+    case OFPACT_SET_IPV4_DST:
+        ofputil_put_OFPAT10_SET_NW_DST(out)->nw_addr
+            = ofpact_get_SET_IPV4_DST(a)->ipv4;
+        break;
+
+    case OFPACT_SET_IPV4_DSCP:
+        ofputil_put_OFPAT10_SET_NW_TOS(out)->nw_tos
+            = ofpact_get_SET_IPV4_DSCP(a)->dscp;
+        break;
+
+    case OFPACT_SET_L4_SRC_PORT:
+        ofputil_put_OFPAT10_SET_TP_SRC(out)->tp_port
+            = htons(ofpact_get_SET_L4_SRC_PORT(a)->port);
+        break;
+
+    case OFPACT_SET_L4_DST_PORT:
+        ofputil_put_OFPAT10_SET_TP_DST(out)->tp_port
+            = htons(ofpact_get_SET_L4_DST_PORT(a)->port);
+        break;
+
+    case OFPACT_CONTROLLER:
+    case OFPACT_OUTPUT_REG:
+    case OFPACT_BUNDLE:
+    case OFPACT_REG_MOVE:
+    case OFPACT_REG_LOAD:
+    case OFPACT_DEC_TTL:
+    case OFPACT_SET_TUNNEL:
+    case OFPACT_SET_QUEUE:
+    case OFPACT_POP_QUEUE:
+    case OFPACT_FIN_TIMEOUT:
+    case OFPACT_RESUBMIT:
+    case OFPACT_LEARN:
+    case OFPACT_MULTIPATH:
+    case OFPACT_AUTOPATH:
+    case OFPACT_NOTE:
+    case OFPACT_EXIT:
+        ofpact_to_nxast(a, out);
+        break;
+    }
+}
+
+/* Converts the ofpacts in 'ofpacts' (terminated by OFPACT_END) into OpenFlow
+ * actions in 'openflow', appending the actions to any existing data in
+ * 'openflow'. */
+void
+ofpacts_to_openflow(const struct ofpact ofpacts[], struct ofpbuf *openflow)
+{
+    const struct ofpact *a;
+
+    OFPACT_FOR_EACH (a, ofpacts) {
+        ofpact_to_openflow10(a, openflow);
+    }
+}
+
+/* Returns true if 'action' outputs to 'port', false otherwise. */
+static bool
+ofpact_outputs_to_port(const struct ofpact *ofpact, uint16_t port)
+{
+    switch (ofpact->type) {
+    case OFPACT_OUTPUT:
+        return ofpact_get_OUTPUT(ofpact)->port == port;
+    case OFPACT_ENQUEUE:
+        return ofpact_get_ENQUEUE(ofpact)->port == port;
+    case OFPACT_CONTROLLER:
+        return port == OFPP_CONTROLLER;
+
+    case OFPACT_END:
+    case OFPACT_OUTPUT_REG:
+    case OFPACT_BUNDLE:
+    case OFPACT_SET_VLAN_VID:
+    case OFPACT_SET_VLAN_PCP:
+    case OFPACT_STRIP_VLAN:
+    case OFPACT_SET_ETH_SRC:
+    case OFPACT_SET_ETH_DST:
+    case OFPACT_SET_IPV4_SRC:
+    case OFPACT_SET_IPV4_DST:
+    case OFPACT_SET_IPV4_DSCP:
+    case OFPACT_SET_L4_SRC_PORT:
+    case OFPACT_SET_L4_DST_PORT:
+    case OFPACT_REG_MOVE:
+    case OFPACT_REG_LOAD:
+    case OFPACT_DEC_TTL:
+    case OFPACT_SET_TUNNEL:
+    case OFPACT_SET_QUEUE:
+    case OFPACT_POP_QUEUE:
+    case OFPACT_FIN_TIMEOUT:
+    case OFPACT_RESUBMIT:
+    case OFPACT_LEARN:
+    case OFPACT_MULTIPATH:
+    case OFPACT_AUTOPATH:
+    case OFPACT_NOTE:
+    case OFPACT_EXIT:
+    default:
+        return false;
+    }
+}
+
+/* Returns true if any action in 'ofpacts' outputs to 'port', false
+ * otherwise. */
+bool
+ofpacts_output_to_port(const struct ofpact *ofpacts, uint16_t port)
+{
+    const struct ofpact *a;
+
+    OFPACT_FOR_EACH (a, ofpacts) {
+        if (ofpact_outputs_to_port(a, port)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+ofpacts_equal(const struct ofpact *a, size_t a_len,
+              const struct ofpact *b, size_t b_len)
+{
+    return a_len == b_len && !memcmp(a, b, a_len);
+}
+
+/* Formatting ofpacts. */
+
+static void
+print_note(const struct ofpact_note *note, struct ds *string)
+{
+    size_t i;
+
+    ds_put_cstr(string, "note:");
+    for (i = 0; i < note->length; i++) {
+        if (i) {
+            ds_put_char(string, '.');
+        }
+        ds_put_format(string, "%02"PRIx8, note->data[i]);
+    }
+}
+
+static void
+print_fin_timeout(const struct ofpact_fin_timeout *fin_timeout,
+                  struct ds *s)
+{
+    ds_put_cstr(s, "fin_timeout(");
+    if (fin_timeout->fin_idle_timeout) {
+        ds_put_format(s, "idle_timeout=%"PRIu16",",
+                      fin_timeout->fin_idle_timeout);
+    }
+    if (fin_timeout->fin_hard_timeout) {
+        ds_put_format(s, "hard_timeout=%"PRIu16",",
+                      fin_timeout->fin_hard_timeout);
+    }
+    ds_chomp(s, ',');
+    ds_put_char(s, ')');
+}
+
+static void
+ofpact_format(const struct ofpact *a, struct ds *s)
+{
+    const struct ofpact_enqueue *enqueue;
+    const struct ofpact_resubmit *resubmit;
+    const struct ofpact_autopath *autopath;
+    const struct ofpact_controller *controller;
+    const struct ofpact_tunnel *tunnel;
+    uint16_t port;
+
+    switch (a->type) {
+    case OFPACT_END:
+        NOT_REACHED();
+
+    case OFPACT_OUTPUT:
+        port = ofpact_get_OUTPUT(a)->port;
+        if (port < OFPP_MAX) {
+            ds_put_format(s, "output:%"PRIu16, port);
+        } else {
+            ofputil_format_port(port, s);
+            if (port == OFPP_CONTROLLER) {
+                ds_put_format(s, ":%"PRIu16, ofpact_get_OUTPUT(a)->max_len);
+            }
+        }
+        break;
+
+    case OFPACT_CONTROLLER:
+        controller = ofpact_get_CONTROLLER(a);
+        if (controller->reason == OFPR_ACTION &&
+            controller->controller_id == 0) {
+            ds_put_format(s, "CONTROLLER:%"PRIu16,
+                          ofpact_get_CONTROLLER(a)->max_len);
+        } else {
+            enum ofp_packet_in_reason reason = controller->reason;
+
+            ds_put_cstr(s, "controller(");
+            if (reason != OFPR_ACTION) {
+                ds_put_format(s, "reason=%s,",
+                              ofputil_packet_in_reason_to_string(reason));
+            }
+            if (controller->max_len != UINT16_MAX) {
+                ds_put_format(s, "max_len=%"PRIu16",", controller->max_len);
+            }
+            if (controller->controller_id != 0) {
+                ds_put_format(s, "id=%"PRIu16",", controller->controller_id);
+            }
+            ds_chomp(s, ',');
+            ds_put_char(s, ')');
+        }
+        break;
+
+    case OFPACT_ENQUEUE:
+        enqueue = ofpact_get_ENQUEUE(a);
+        ds_put_format(s, "enqueue:");
+        ofputil_format_port(enqueue->port, s);
+        ds_put_format(s, "q%"PRIu32, enqueue->queue);
+        break;
+
+    case OFPACT_OUTPUT_REG:
+        ds_put_cstr(s, "output:");
+        mf_format_subfield(&ofpact_get_OUTPUT_REG(a)->src, s);
+        break;
+
+    case OFPACT_BUNDLE:
+        bundle_format(ofpact_get_BUNDLE(a), s);
+        break;
+
+    case OFPACT_SET_VLAN_VID:
+        ds_put_format(s, "mod_vlan_vid:%"PRIu16,
+                      ofpact_get_SET_VLAN_VID(a)->vlan_vid);
+        break;
+
+    case OFPACT_SET_VLAN_PCP:
+        ds_put_format(s, "mod_vlan_pcp:%"PRIu8,
+                      ofpact_get_SET_VLAN_PCP(a)->vlan_pcp);
+        break;
+
+    case OFPACT_STRIP_VLAN:
+        ds_put_cstr(s, "strip_vlan");
+        break;
+
+    case OFPACT_SET_ETH_SRC:
+        ds_put_format(s, "mod_dl_src:"ETH_ADDR_FMT,
+                      ETH_ADDR_ARGS(ofpact_get_SET_ETH_SRC(a)->mac));
+        break;
+
+    case OFPACT_SET_ETH_DST:
+        ds_put_format(s, "mod_dl_dst:"ETH_ADDR_FMT,
+                      ETH_ADDR_ARGS(ofpact_get_SET_ETH_DST(a)->mac));
+        break;
+
+    case OFPACT_SET_IPV4_SRC:
+        ds_put_format(s, "mod_nw_src:"IP_FMT,
+                      IP_ARGS(&ofpact_get_SET_IPV4_SRC(a)->ipv4));
+        break;
+
+    case OFPACT_SET_IPV4_DST:
+        ds_put_format(s, "mod_nw_dst:"IP_FMT,
+                      IP_ARGS(&ofpact_get_SET_IPV4_DST(a)->ipv4));
+        break;
+
+    case OFPACT_SET_IPV4_DSCP:
+        ds_put_format(s, "mod_nw_tos:%d", ofpact_get_SET_IPV4_DSCP(a)->dscp);
+        break;
+
+    case OFPACT_SET_L4_SRC_PORT:
+        ds_put_format(s, "mod_tp_src:%d", ofpact_get_SET_L4_SRC_PORT(a)->port);
+        break;
+
+    case OFPACT_SET_L4_DST_PORT:
+        ds_put_format(s, "mod_tp_dst:%d", ofpact_get_SET_L4_DST_PORT(a)->port);
+        break;
+
+    case OFPACT_REG_MOVE:
+        nxm_format_reg_move(ofpact_get_REG_MOVE(a), s);
+        break;
+
+    case OFPACT_REG_LOAD:
+        nxm_format_reg_load(ofpact_get_REG_LOAD(a), s);
+        break;
+
+    case OFPACT_DEC_TTL:
+        ds_put_cstr(s, "dec_ttl");
+        break;
+
+    case OFPACT_SET_TUNNEL:
+        tunnel = ofpact_get_SET_TUNNEL(a);
+        ds_put_format(s, "set_tunnel%s:%#"PRIx64,
+                      (tunnel->tun_id > UINT32_MAX
+                       || a->compat == OFPUTIL_NXAST_SET_TUNNEL64 ? "64" : ""),
+                      tunnel->tun_id);
+        break;
+
+    case OFPACT_SET_QUEUE:
+        ds_put_format(s, "set_queue:%"PRIu32,
+                      ofpact_get_SET_QUEUE(a)->queue_id);
+        break;
+
+    case OFPACT_POP_QUEUE:
+        ds_put_cstr(s, "pop_queue");
+        break;
+
+    case OFPACT_FIN_TIMEOUT:
+        print_fin_timeout(ofpact_get_FIN_TIMEOUT(a), s);
+        break;
+
+    case OFPACT_RESUBMIT:
+        resubmit = ofpact_get_RESUBMIT(a);
+        if (resubmit->in_port != OFPP_IN_PORT && resubmit->table_id == 255) {
+            ds_put_format(s, "resubmit:%"PRIu16, resubmit->in_port);
+        } else {
+            ds_put_format(s, "resubmit(");
+            if (resubmit->in_port != OFPP_IN_PORT) {
+                ofputil_format_port(resubmit->in_port, s);
+            }
+            ds_put_char(s, ',');
+            if (resubmit->table_id != 255) {
+                ds_put_format(s, "%"PRIu8, resubmit->table_id);
+            }
+            ds_put_char(s, ')');
+        }
+        break;
+
+    case OFPACT_LEARN:
+        learn_format(ofpact_get_LEARN(a), s);
+        break;
+
+    case OFPACT_MULTIPATH:
+        multipath_format(ofpact_get_MULTIPATH(a), s);
+        break;
+
+    case OFPACT_AUTOPATH:
+        autopath = ofpact_get_AUTOPATH(a);
+        ds_put_format(s, "autopath(%u,", autopath->port);
+        mf_format_subfield(&autopath->dst, s);
+        ds_put_char(s, ')');
+        break;
+
+    case OFPACT_NOTE:
+        print_note(ofpact_get_NOTE(a), s);
+        break;
+
+    case OFPACT_EXIT:
+        ds_put_cstr(s, "exit");
+        break;
+    }
+}
+
+/* Appends a string representing the actions in 'ofpacts' (terminated by
+ * OFPACT_END) to 'string'. */
+void
+ofpacts_format(const struct ofpact *ofpacts, struct ds *string)
+{
+    ds_put_cstr(string, "actions=");
+    if (ofpacts->type == OFPACT_END) {
+        ds_put_cstr(string, "drop");
+    } else {
+        const struct ofpact *a;
+
+        OFPACT_FOR_EACH (a, ofpacts) {
+            if (a != ofpacts) {
+                ds_put_cstr(string, ",");
+            }
+            ofpact_format(a, string);
+        }
+    }
+}
+
+/* Internal use by helpers. */
+
+void *
+ofpact_put(struct ofpbuf *ofpacts, enum ofpact_type type, size_t len)
+{
+    struct ofpact *ofpact;
+    unsigned int rem;
+
+    rem = ofpacts->size % OFPACT_ALIGNTO;
+    if (rem) {
+        ofpbuf_put_zeros(ofpacts, OFPACT_ALIGNTO - rem);
+    }
+
+    ofpact = ofpacts->l2 = ofpbuf_put_uninit(ofpacts, len);
+    ofpact_init(ofpact, type, len);
+    return ofpact;
+}
+
+void
+ofpact_init(struct ofpact *ofpact, enum ofpact_type type, size_t len)
+{
+    memset(ofpact, 0, len);
+    ofpact->type = type;
+    ofpact->compat = OFPUTIL_ACTION_INVALID;
+    ofpact->len = len;
+}
+
+/* Updates 'ofpact->len' to the number of bytes in the tail of 'ofpacts'
+ * starting at 'ofpact'.
+ *
+ * This is the correct way to update a variable-length ofpact's length after
+ * adding the variable-length part of the payload.  (See the large comment
+ * near the end of ofp-actions.h for more information.) */
+void
+ofpact_update_len(struct ofpbuf *ofpacts, struct ofpact *ofpact)
+{
+    assert(ofpact == ofpacts->l2);
+    ofpact->len = (char *) ofpbuf_tail(ofpacts) - (char *) ofpact;
+}
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
new file mode 100644
index 0000000..7db01a5
--- /dev/null
+++ b/lib/ofp-actions.h
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) 2012 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OFP_ACTIONS_H
+#define OFP_ACTIONS_H 1
+
+#include <stdint.h>
+#include "meta-flow.h"
+#include "ofp-errors.h"
+#include "ofp-util.h"
+#include "openflow/openflow.h"
+#include "openflow/nicira-ext.h"
+#include "openvswitch/types.h"
+
+/* List of OVS abstracted actions.
+ *
+ * This macro is used directly only internally by this header, but the list is
+ * still of interest to developers.
+ *
+ * Each DEFINE_OFPACT invocation has the following parameters:
+ *
+ * 1. <ENUM>, used below in the enum definition of OFPACT_<ENUM>, and
+ *    elsewhere.
+ *
+ * 2. <STRUCT> corresponding to a structure "struct <STRUCT>", that must be
+ *    defined below.  This structure must be an abstract definition of the
+ *    action.  Its first member must have type "struct ofpact" and name
+ *    "ofpact".  It may be fixed length or end with a flexible array member
+ *    (e.g. "int member[];").
+ *
+ * 3. <MEMBER>, which has one of two possible values:
+ *
+ *        - If "struct <STRUCT>" is fixed-length, it must be "ofpact".
+ *
+ *        - If "struct <STRUCT>" is variable-length, it must be the name of the
+ *          flexible array member.
+ */
+#define OFPACTS                                                     \
+    /* Sentinel. */                                                 \
+    DEFINE_OFPACT(END,             ofpact_null,          ofpact)    \
+                                                                    \
+    /* Output. */                                                   \
+    DEFINE_OFPACT(OUTPUT,          ofpact_output,        ofpact)    \
+    DEFINE_OFPACT(CONTROLLER,      ofpact_controller,    ofpact)    \
+    DEFINE_OFPACT(ENQUEUE,         ofpact_enqueue,       ofpact)    \
+    DEFINE_OFPACT(OUTPUT_REG,      ofpact_output_reg,    ofpact)    \
+    DEFINE_OFPACT(BUNDLE,          ofpact_bundle,        slaves)    \
+                                                                    \
+    /* Header changes. */                                           \
+    DEFINE_OFPACT(SET_VLAN_VID,    ofpact_vlan_vid,      ofpact)    \
+    DEFINE_OFPACT(SET_VLAN_PCP,    ofpact_vlan_pcp,      ofpact)    \
+    DEFINE_OFPACT(STRIP_VLAN,      ofpact_null,          ofpact)    \
+    DEFINE_OFPACT(SET_ETH_SRC,     ofpact_mac,           ofpact)    \
+    DEFINE_OFPACT(SET_ETH_DST,     ofpact_mac,           ofpact)    \
+    DEFINE_OFPACT(SET_IPV4_SRC,    ofpact_ipv4,          ofpact)    \
+    DEFINE_OFPACT(SET_IPV4_DST,    ofpact_ipv4,          ofpact)    \
+    DEFINE_OFPACT(SET_IPV4_DSCP,   ofpact_dscp,          ofpact)    \
+    DEFINE_OFPACT(SET_L4_SRC_PORT, ofpact_l4_port,       ofpact)    \
+    DEFINE_OFPACT(SET_L4_DST_PORT, ofpact_l4_port,       ofpact)    \
+    DEFINE_OFPACT(REG_MOVE,        ofpact_reg_move,      ofpact)    \
+    DEFINE_OFPACT(REG_LOAD,        ofpact_reg_load,      ofpact)    \
+    DEFINE_OFPACT(DEC_TTL,         ofpact_null,          ofpact)    \
+                                                                    \
+    /* Metadata. */                                                 \
+    DEFINE_OFPACT(SET_TUNNEL,      ofpact_tunnel,        ofpact)    \
+    DEFINE_OFPACT(SET_QUEUE,       ofpact_queue,         ofpact)    \
+    DEFINE_OFPACT(POP_QUEUE,       ofpact_null,          ofpact)    \
+    DEFINE_OFPACT(FIN_TIMEOUT,     ofpact_fin_timeout,   ofpact)    \
+                                                                    \
+    /* Flow table interaction. */                                   \
+    DEFINE_OFPACT(RESUBMIT,        ofpact_resubmit,      ofpact)    \
+    DEFINE_OFPACT(LEARN,           ofpact_learn,         specs)     \
+                                                                    \
+    /* Arithmetic. */                                               \
+    DEFINE_OFPACT(MULTIPATH,       ofpact_multipath,     ofpact)    \
+    DEFINE_OFPACT(AUTOPATH,        ofpact_autopath,      ofpact)    \
+                                                                    \
+    /* Other. */                                                    \
+    DEFINE_OFPACT(NOTE,            ofpact_note,          data)      \
+    DEFINE_OFPACT(EXIT,            ofpact_null,          ofpact)
+
+/* enum ofpact_type, with a member OFPACT_<ENUM> for each action. */
+enum OVS_PACKED_ENUM ofpact_type {
+#define DEFINE_OFPACT(ENUM, STRUCT, MEMBER) OFPACT_##ENUM,
+    OFPACTS
+#undef DEFINE_OFPACT
+};
+
+/* N_OFPACTS, the number of values of "enum ofpact_type". */
+enum {
+    N_OFPACTS =
+#define DEFINE_OFPACT(ENUM, STRUCT, MEMBER) + 1
+    OFPACTS
+#undef DEFINE_OFPACT
+};
+
+/* Header for an action.
+ *
+ * Each action is a structure "struct ofpact_*" that begins with "struct
+ * ofpact", usually followed by other data that describes the action.  Actions
+ * are padded out to a multiple of OFPACT_ALIGNTO bytes in length. */
+struct ofpact {
+    enum ofpact_type type;      /* OFPACT_*. */
+    enum ofputil_action_code compat; /* Original type when added, if any. */
+    uint16_t len;               /* Length of the action, in bytes, including
+                                 * struct ofpact, excluding padding. */
+};
+
+#ifdef __GNUC__
+/* Make sure that OVS_PACKED_ENUM really worked. */
+BUILD_ASSERT_DECL(sizeof(struct ofpact) == 4);
+#endif
+
+/* Alignment. */
+#define OFPACT_ALIGNTO 8
+#define OFPACT_ALIGN(SIZE) ROUND_UP(SIZE, OFPACT_ALIGNTO)
+
+static inline struct ofpact *
+ofpact_next(const struct ofpact *ofpact)
+{
+    return (void *) ((uint8_t *) ofpact + OFPACT_ALIGN(ofpact->len));
+}
+
+/* Assigns POS to each action starting at OFPACTS in turn, assuming that the
+ * set of actions is terminated by OFPACT_END. */
+#define OFPACT_FOR_EACH(POS, OFPACTS)                           \
+    for ((POS) = (OFPACTS); (POS)->type != OFPACT_END;          \
+         (POS) = ofpact_next(POS))
+
+/* Action structure for each OFPACT_*. */
+
+/* OFPACT_END, OFPACT_STRIP_VLAN, OFPACT_DEC_TTL, OFPACT_POP_QUEUE,
+ * OFPACT_EXIT.
+ *
+ * Used for OFPAT10_STRIP_VLAN, NXAST_DEC_TTL, NXAST_POP_QUEUE, NXAST_EXIT.
+ *
+ * Action structure for actions that do not have any extra data beyond the
+ * action type. */
+struct ofpact_null {
+    struct ofpact ofpact;
+};
+
+/* OFPACT_OUTPUT.
+ *
+ * Used for OFPAT10_OUTPUT. */
+struct ofpact_output {
+    struct ofpact ofpact;
+    uint16_t port;              /* Output port. */
+    uint16_t max_len;           /* Max send len, for port OFPP_CONTROLLER. */
+};
+
+/* OFPACT_CONTROLLER.
+ *
+ * Used for NXAST_CONTROLLER. */
+struct ofpact_controller {
+    struct ofpact ofpact;
+    uint16_t max_len;           /* Maximum length to send to controller. */
+    uint16_t controller_id;     /* Controller ID to send packet-in. */
+    enum ofp_packet_in_reason reason; /* Reason to put in packet-in. */
+};
+
+/* OFPACT_ENQUEUE.
+ *
+ * Used for OFPAT10_ENQUEUE. */
+struct ofpact_enqueue {
+    struct ofpact ofpact;
+    uint16_t port;
+    uint32_t queue;
+};
+
+/* OFPACT_OUTPUT_REG.
+ *
+ * Used for NXAST_OUTPUT_REG. */
+struct ofpact_output_reg {
+    struct ofpact ofpact;
+    struct mf_subfield src;
+    uint16_t max_len;
+};
+
+/* OFPACT_BUNDLE.
+ *
+ * Used for NXAST_BUNDLE. */
+struct ofpact_bundle {
+    struct ofpact ofpact;
+
+    /* Slave choice algorithm to apply to hash value. */
+    enum nx_bd_algorithm algorithm;
+
+    /* What fields to hash and how. */
+    enum nx_hash_fields fields;
+    uint16_t basis;             /* Universal hash parameter. */
+
+    struct mf_subfield dst;
+
+    /* Slaves for output. */
+    unsigned int n_slaves;
+    uint16_t slaves[];
+};
+
+/* OFPACT_SET_VLAN_VID.
+ *
+ * Used for OFPAT10_SET_VLAN_VID. */
+struct ofpact_vlan_vid {
+    struct ofpact ofpact;
+    uint16_t vlan_vid;          /* VLAN VID in low 12 bits, 0 in other bits. */
+};
+
+/* OFPACT_SET_VLAN_PCP.
+ *
+ * Used for OFPAT10_SET_VLAN_PCP. */
+struct ofpact_vlan_pcp {
+    struct ofpact ofpact;
+    uint8_t vlan_pcp;           /* VLAN PCP in low 3 bits, 0 in other bits. */
+};
+
+/* OFPACT_SET_ETH_SRC, OFPACT_SET_ETH_DST.
+ *
+ * Used for OFPAT10_SET_DL_SRC, OFPAT10_SET_DL_DST. */
+struct ofpact_mac {
+    struct ofpact ofpact;
+    uint8_t mac[ETH_ADDR_LEN];
+};
+
+/* OFPACT_SET_IPV4_SRC, OFPACT_SET_IPV4_DST.
+ *
+ * Used for OFPAT10_SET_NW_SRC, OFPAT10_SET_NW_DST. */
+struct ofpact_ipv4 {
+    struct ofpact ofpact;
+    ovs_be32 ipv4;
+};
+
+/* OFPACT_SET_IPV4_DSCP.
+ *
+ * Used for OFPAT10_SET_NW_TOS. */
+struct ofpact_dscp {
+    struct ofpact ofpact;
+    uint8_t dscp;               /* DSCP in high 6 bits, rest ignored. */
+};
+
+/* OFPACT_SET_L4_SRC_PORT, OFPACT_SET_L4_DST_PORT.
+ *
+ * Used for OFPAT10_SET_TP_SRC, OFPAT10_SET_TP_DST. */
+struct ofpact_l4_port {
+    struct ofpact ofpact;
+    uint16_t port;              /* TCP or UDP port number. */
+};
+
+/* OFPACT_REG_MOVE.
+ *
+ * Used for NXAST_REG_MOVE. */
+struct ofpact_reg_move {
+    struct ofpact ofpact;
+    struct mf_subfield src;
+    struct mf_subfield dst;
+};
+
+/* OFPACT_REG_LOAD.
+ *
+ * Used for NXAST_REG_LOAD. */
+struct ofpact_reg_load {
+    struct ofpact ofpact;
+    struct mf_subfield dst;
+    uint64_t value;
+};
+
+/* OFPACT_SET_TUNNEL.
+ *
+ * Used for NXAST_SET_TUNNEL, NXAST_SET_TUNNEL64. */
+struct ofpact_tunnel {
+    struct ofpact ofpact;
+    uint64_t tun_id;
+};
+
+/* OFPACT_SET_QUEUE.
+ *
+ * Used for NXAST_SET_QUEUE. */
+struct ofpact_queue {
+    struct ofpact ofpact;
+    uint32_t queue_id;
+};
+
+/* OFPACT_FIN_TIMEOUT.
+ *
+ * Used for NXAST_FIN_TIMEOUT. */
+struct ofpact_fin_timeout {
+    struct ofpact ofpact;
+    uint16_t fin_idle_timeout;
+    uint16_t fin_hard_timeout;
+};
+
+/* OFPACT_RESUBMIT.
+ *
+ * Used for NXAST_RESUBMIT, NXAST_RESUBMIT_TABLE. */
+struct ofpact_resubmit {
+    struct ofpact ofpact;
+    uint16_t in_port;
+    uint8_t table_id;
+};
+
+/* Part of struct ofpact_learn, below. */
+struct ofpact_learn_spec {
+    int n_bits;
+
+    int src_type;
+    struct mf_subfield src;
+    union mf_subvalue src_imm;
+
+    int dst_type;
+    struct mf_subfield dst;
+};
+
+/* OFPACT_LEARN.
+ *
+ * Used for NXAST_LEARN. */
+struct ofpact_learn {
+    struct ofpact ofpact;
+
+    uint16_t idle_timeout;      /* Idle time before discarding (seconds). */
+    uint16_t hard_timeout;      /* Max time before discarding (seconds). */
+    uint16_t priority;          /* Priority level of flow entry. */
+    uint64_t cookie;            /* Cookie for new flow. */
+    uint16_t flags;             /* Either 0 or OFPFF_SEND_FLOW_REM. */
+    uint8_t table_id;           /* Table to insert flow entry. */
+    uint16_t fin_idle_timeout;  /* Idle timeout after FIN, if nonzero. */
+    uint16_t fin_hard_timeout;  /* Hard timeout after FIN, if nonzero. */
+
+    unsigned int n_specs;
+    struct ofpact_learn_spec specs[];
+};
+
+/* OFPACT_MULTIPATH.
+ *
+ * Used for NXAST_MULTIPATH. */
+struct ofpact_multipath {
+    struct ofpact ofpact;
+
+    /* What fields to hash and how. */
+    enum nx_hash_fields fields;
+    uint16_t basis;             /* Universal hash parameter. */
+
+    /* Multipath link choice algorithm to apply to hash value. */
+    enum nx_mp_algorithm algorithm;
+    uint16_t max_link;          /* Number of output links, minus 1. */
+    uint32_t arg;               /* Algorithm-specific argument. */
+
+    /* Where to store the result. */
+    struct mf_subfield dst;
+};
+
+/* OFPACT_AUTOPATH.
+ *
+ * Used for NXAST_AUTOPATH. */
+struct ofpact_autopath {
+    struct ofpact ofpact;
+    struct mf_subfield dst;
+    uint32_t port;
+};
+
+/* OFPACT_NOTE.
+ *
+ * Used for NXAST_NOTE. */
+struct ofpact_note {
+    struct ofpact ofpact;
+    size_t length;
+    uint8_t data[];
+};
+
+/* Converting OpenFlow to ofpacts. */
+enum ofperr ofpacts_pull_openflow(struct ofpbuf *openflow,
+                                  unsigned int actions_len,
+                                  struct ofpbuf *ofpacts);
+enum ofperr ofpacts_check(const struct ofpact[],
+                          const struct flow *, int max_ports);
+
+/* Converting ofpacts to OpenFlow. */
+void ofpacts_to_openflow(const struct ofpact[], struct ofpbuf *openflow);
+
+/* Working with ofpacts. */
+bool ofpacts_output_to_port(const struct ofpact[], uint16_t port);
+bool ofpacts_equal(const struct ofpact a[], size_t a_len,
+                   const struct ofpact b[], size_t b_len);
+
+/* Formatting ofpacts.
+ *
+ * (For parsing ofpacts, see ofp-parse.h.) */
+void ofpacts_format(const struct ofpact[], struct ds *);
+
+/* Internal use by the helpers below. */
+void ofpact_init(struct ofpact *, enum ofpact_type, size_t len);
+void *ofpact_put(struct ofpbuf *, enum ofpact_type, size_t len);
+
+/* For each OFPACT_<ENUM> with a corresponding struct <STRUCT>, this defines
+ * the following commonly useful functions:
+ *
+ *   struct <STRUCT> *ofpact_put_<ENUM>(struct ofpbuf *ofpacts);
+ *
+ *     Appends a new 'ofpact', of length OFPACT_<ENUM>_RAW_SIZE, to 'ofpacts',
+ *     initializes it with ofpact_init_<ENUM>(), and returns it.  Also sets
+ *     'ofpacts->l2' to the returned action.
+ *
+ *     After using this function to add a variable-length action, add the
+ *     elements of the flexible array (e.g. with ofpbuf_put()), then use
+ *     ofpact_update_len() to update the length embedded into the action.
+ *     (Keep in mind the need to refresh the structure from 'ofpacts->l2' after
+ *     adding data to 'ofpacts'.)
+ *
+ *   struct <STRUCT> *ofpact_get_<ENUM>(const struct ofpact *ofpact);
+ *
+ *     Returns 'ofpact' cast to "struct <STRUCT> *".  'ofpact->type' must be
+ *     OFPACT_<ENUM>.
+ *
+ * as well as the following more rarely useful definitions:
+ *
+ *   void ofpact_init_<ENUM>(struct <STRUCT> *ofpact);
+ *
+ *     Initializes the parts of 'ofpact' that identify it as having type
+ *     OFPACT_<ENUM> and length OFPACT_<ENUM>_RAW_SIZE and zeros the rest.
+ *
+ *   <ENUM>_RAW_SIZE
+ *
+ *     The size of the action structure.  For a fixed-length action, this is
+ *     sizeof(struct <STRUCT>).  For a variable-length action, this is the
+ *     offset to the variable-length part.
+ *
+ *   <ENUM>_SIZE
+ *
+ *     An integer constant, the value of OFPACT_<ENUM>_RAW_SIZE rounded up to a
+ *     multiple of OFPACT_ALIGNTO.
+ */
+#define DEFINE_OFPACT(ENUM, STRUCT, MEMBER)                             \
+    BUILD_ASSERT_DECL(offsetof(struct STRUCT, ofpact) == 0);            \
+                                                                        \
+    enum { OFPACT_##ENUM##_RAW_SIZE                                     \
+           = (offsetof(struct STRUCT, MEMBER)                           \
+              ? offsetof(struct STRUCT, MEMBER)                         \
+              : sizeof(struct STRUCT)) };                               \
+                                                                        \
+    enum { OFPACT_##ENUM##_SIZE                                         \
+           = ROUND_UP(OFPACT_##ENUM##_RAW_SIZE, OFPACT_ALIGNTO) };      \
+                                                                        \
+    static inline struct STRUCT *                                       \
+    ofpact_get_##ENUM(const struct ofpact *ofpact)                      \
+    {                                                                   \
+        assert(ofpact->type == OFPACT_##ENUM);                          \
+        return (struct STRUCT *) ofpact;                                \
+    }                                                                   \
+                                                                        \
+    static inline struct STRUCT *                                       \
+    ofpact_put_##ENUM(struct ofpbuf *ofpacts)                           \
+    {                                                                   \
+        return ofpact_put(ofpacts, OFPACT_##ENUM,                       \
+                          OFPACT_##ENUM##_RAW_SIZE);                    \
+    }                                                                   \
+                                                                        \
+    static inline void                                                  \
+    ofpact_init_##ENUM(struct STRUCT *ofpact)                           \
+    {                                                                   \
+        ofpact_init(&ofpact->ofpact, OFPACT_##ENUM,                     \
+                    OFPACT_##ENUM##_RAW_SIZE);                          \
+    }
+OFPACTS
+#undef DEFINE_OFPACT
+
+void ofpact_update_len(struct ofpbuf *, struct ofpact *);
+
+#endif /* ofp-actions.h */
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 1d331bb..ae70fb5 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -28,9 +28,10 @@
 #include "dynamic-string.h"
 #include "learn.h"
 #include "meta-flow.h"
-#include "netdev.h"
 #include "multipath.h"
+#include "netdev.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
@@ -122,107 +123,73 @@ str_to_ip(const char *str, ovs_be32 *ip)
     *ip = in_addr.s_addr;
 }
 
-static struct ofp_action_output *
-put_output_action(struct ofpbuf *b, uint16_t port)
-{
-    struct ofp_action_output *oao;
-
-    oao = ofputil_put_OFPAT10_OUTPUT(b);
-    oao->port = htons(port);
-    return oao;
-}
-
 static void
-parse_enqueue(struct ofpbuf *b, char *arg)
+parse_enqueue(char *arg, struct ofpbuf *ofpacts)
 {
     char *sp = NULL;
     char *port = strtok_r(arg, ":q", &sp);
     char *queue = strtok_r(NULL, "", &sp);
-    struct ofp_action_enqueue *oae;
+    struct ofpact_enqueue *enqueue;
 
     if (port == NULL || queue == NULL) {
         ovs_fatal(0, "\"enqueue\" syntax is \"enqueue:PORT:QUEUE\"");
     }
 
-    oae = ofputil_put_OFPAT10_ENQUEUE(b);
-    oae->port = htons(str_to_u32(port));
-    oae->queue_id = htonl(str_to_u32(queue));
+    enqueue = ofpact_put_ENQUEUE(ofpacts);
+    enqueue->port = str_to_u32(port);
+    enqueue->queue = str_to_u32(queue);
 }
 
 static void
-parse_output(struct ofpbuf *b, char *arg)
+parse_output(char *arg, struct ofpbuf *ofpacts)
 {
     if (strchr(arg, '[')) {
-        struct nx_action_output_reg *naor;
-        struct mf_subfield src;
-
-        mf_parse_subfield(&src, arg);
+        struct ofpact_output_reg *output_reg;
 
-        naor = ofputil_put_NXAST_OUTPUT_REG(b);
-        naor->ofs_nbits = nxm_encode_ofs_nbits(src.ofs, src.n_bits);
-        naor->src = htonl(src.field->nxm_header);
-        naor->max_len = htons(UINT16_MAX);
+        output_reg = ofpact_put_OUTPUT_REG(ofpacts);
+        mf_parse_subfield(&output_reg->src, arg);
+        output_reg->max_len = UINT16_MAX;
     } else {
-        put_output_action(b, str_to_u32(arg));
+        struct ofpact_output *output;
+
+        output = ofpact_put_OUTPUT(ofpacts);
+        output->port = str_to_u32(arg);
+        output->max_len = output->port == OFPP_CONTROLLER ? UINT16_MAX : 0;
     }
 }
 
 static void
-parse_resubmit(struct ofpbuf *b, char *arg)
+parse_resubmit(char *arg, struct ofpbuf *ofpacts)
 {
-    struct nx_action_resubmit *nar;
+    struct ofpact_resubmit *resubmit;
     char *in_port_s, *table_s;
-    uint16_t in_port;
-    uint8_t table;
+
+    resubmit = ofpact_put_RESUBMIT(ofpacts);
 
     in_port_s = strsep(&arg, ",");
     if (in_port_s && in_port_s[0]) {
-        if (!ofputil_port_from_string(in_port_s, &in_port)) {
-            in_port = str_to_u32(in_port_s);
+        if (!ofputil_port_from_string(in_port_s, &resubmit->in_port)) {
+            resubmit->in_port = str_to_u32(in_port_s);
         }
     } else {
-        in_port = OFPP_IN_PORT;
+        resubmit->in_port = OFPP_IN_PORT;
     }
 
     table_s = strsep(&arg, ",");
-    table = table_s && table_s[0] ? str_to_u32(table_s) : 255;
+    resubmit->table_id = table_s && table_s[0] ? str_to_u32(table_s) : 255;
 
-    if (in_port == OFPP_IN_PORT && table == 255) {
+    if (resubmit->in_port == OFPP_IN_PORT && resubmit->table_id == 255) {
         ovs_fatal(0, "at least one \"in_port\" or \"table\" must be specified "
                   " on resubmit");
     }
-
-    if (in_port != OFPP_IN_PORT && table == 255) {
-        nar = ofputil_put_NXAST_RESUBMIT(b);
-    } else {
-        nar = ofputil_put_NXAST_RESUBMIT_TABLE(b);
-        nar->table = table;
-    }
-    nar->in_port = htons(in_port);
-}
-
-static void
-parse_set_tunnel(struct ofpbuf *b, const char *arg)
-{
-    uint64_t tun_id = str_to_u64(arg);
-    if (tun_id > UINT32_MAX) {
-        ofputil_put_NXAST_SET_TUNNEL64(b)->tun_id = htonll(tun_id);
-    } else {
-        ofputil_put_NXAST_SET_TUNNEL(b)->tun_id = htonl(tun_id);
-    }
 }
 
 static void
-parse_note(struct ofpbuf *b, const char *arg)
+parse_note(const char *arg, struct ofpbuf *ofpacts)
 {
-    size_t start_ofs = b->size;
-    struct nx_action_note *nan;
-    int remainder;
-    size_t len;
+    struct ofpact_note *note;
 
-    nan = ofputil_put_NXAST_NOTE(b);
-
-    b->size -= sizeof nan->note;
+    note = ofpact_put_NOTE(ofpacts);
     while (*arg != '\0') {
         uint8_t byte;
         bool ok;
@@ -238,32 +205,27 @@ parse_note(struct ofpbuf *b, const char *arg)
         if (!ok) {
             ovs_fatal(0, "bad hex digit in `note' argument");
         }
-        ofpbuf_put(b, &byte, 1);
+        ofpbuf_put(ofpacts, &byte, 1);
 
-        arg += 2;
-    }
+        note = ofpacts->l2;
+        note->length++;
 
-    len = b->size - start_ofs;
-    remainder = len % OFP_ACTION_ALIGN;
-    if (remainder) {
-        ofpbuf_put_zeros(b, OFP_ACTION_ALIGN - remainder);
+        arg += 2;
     }
-    nan = (struct nx_action_note *)((char *)b->data + start_ofs);
-    nan->len = htons(b->size - start_ofs);
+    ofpact_update_len(ofpacts, &note->ofpact);
 }
 
 static void
 parse_fin_timeout(struct ofpbuf *b, char *arg)
 {
-    struct nx_action_fin_timeout *naft;
+    struct ofpact_fin_timeout *oft = ofpact_put_FIN_TIMEOUT(b);
     char *key, *value;
 
-    naft = ofputil_put_NXAST_FIN_TIMEOUT(b);
     while (ofputil_parse_key_value(&arg, &key, &value)) {
         if (!strcmp(key, "idle_timeout")) {
-            naft->fin_idle_timeout = htons(str_to_u16(value, key));
+            oft->fin_idle_timeout = str_to_u16(value, key);
         } else if (!strcmp(key, "hard_timeout")) {
-            naft->fin_hard_timeout = htons(str_to_u16(value, key));
+            oft->fin_hard_timeout = str_to_u16(value, key);
         } else {
             ovs_fatal(0, "invalid key '%s' in 'fin_timeout' argument", key);
         }
@@ -301,121 +263,142 @@ parse_controller(struct ofpbuf *b, char *arg)
     }
 
     if (reason == OFPR_ACTION && controller_id == 0) {
-        put_output_action(b, OFPP_CONTROLLER)->max_len = htons(max_len);
+        struct ofpact_output *output;
+
+        output = ofpact_put_OUTPUT(b);
+        output->port = OFPP_CONTROLLER;
+        output->max_len = max_len;
     } else {
-        struct nx_action_controller *nac;
+        struct ofpact_controller *controller;
 
-        nac = ofputil_put_NXAST_CONTROLLER(b);
-        nac->max_len = htons(max_len);
-        nac->reason = reason;
-        nac->controller_id = htons(controller_id);
+        controller = ofpact_put_CONTROLLER(b);
+        controller->max_len = max_len;
+        controller->reason = reason;
+        controller->controller_id = controller_id;
     }
 }
 
 static void
 parse_named_action(enum ofputil_action_code code, const struct flow *flow,
-                   struct ofpbuf *b, char *arg)
+                   char *arg, struct ofpbuf *ofpacts)
 {
-    struct ofp_action_dl_addr *oada;
-    struct ofp_action_vlan_pcp *oavp;
-    struct ofp_action_vlan_vid *oavv;
-    struct ofp_action_nw_addr *oana;
-    struct ofp_action_tp_port *oata;
+    struct ofpact_tunnel *tunnel;
+    uint16_t vid;
+    ovs_be32 ip;
+    uint8_t pcp;
+    uint8_t tos;
 
     switch (code) {
     case OFPUTIL_ACTION_INVALID:
         NOT_REACHED();
 
     case OFPUTIL_OFPAT10_OUTPUT:
-        parse_output(b, arg);
+        parse_output(arg, ofpacts);
         break;
 
     case OFPUTIL_OFPAT10_SET_VLAN_VID:
-        oavv = ofputil_put_OFPAT10_SET_VLAN_VID(b);
-        oavv->vlan_vid = htons(str_to_u32(arg));
+        vid = str_to_u32(arg);
+        if (vid & ~VLAN_VID_MASK) {
+            ovs_fatal(0, "%s: not a valid VLAN VID", arg);
+        }
+        ofpact_put_SET_VLAN_VID(ofpacts)->vlan_vid = vid;
         break;
 
     case OFPUTIL_OFPAT10_SET_VLAN_PCP:
-        oavp = ofputil_put_OFPAT10_SET_VLAN_PCP(b);
-        oavp->vlan_pcp = str_to_u32(arg);
+        pcp = str_to_u32(arg);
+        if (pcp & ~7) {
+            ovs_fatal(0, "%s: not a valid VLAN PCP", arg);
+        }
+        ofpact_put_SET_VLAN_PCP(ofpacts)->vlan_pcp = pcp;
         break;
 
     case OFPUTIL_OFPAT10_STRIP_VLAN:
-        ofputil_put_OFPAT10_STRIP_VLAN(b);
+        ofpact_put_STRIP_VLAN(ofpacts);
         break;
 
     case OFPUTIL_OFPAT10_SET_DL_SRC:
+        str_to_mac(arg, ofpact_put_SET_ETH_SRC(ofpacts)->mac);
+        break;
+
     case OFPUTIL_OFPAT10_SET_DL_DST:
-        oada = ofputil_put_action(code, b);
-        str_to_mac(arg, oada->dl_addr);
+        str_to_mac(arg, ofpact_put_SET_ETH_DST(ofpacts)->mac);
         break;
 
     case OFPUTIL_OFPAT10_SET_NW_SRC:
+        str_to_ip(arg, &ip);
+        ofpact_put_SET_IPV4_SRC(ofpacts)->ipv4 = ip;
+        break;
+
     case OFPUTIL_OFPAT10_SET_NW_DST:
-        oana = ofputil_put_action(code, b);
-        str_to_ip(arg, &oana->nw_addr);
+        str_to_ip(arg, &ip);
+        ofpact_put_SET_IPV4_DST(ofpacts)->ipv4 = ip;
         break;
 
     case OFPUTIL_OFPAT10_SET_NW_TOS:
-        ofputil_put_OFPAT10_SET_NW_TOS(b)->nw_tos = str_to_u32(arg);
+        tos = str_to_u32(arg);
+        if (tos & ~IP_DSCP_MASK) {
+            ovs_fatal(0, "%s: not a valid TOS", arg);
+        }
+        ofpact_put_SET_IPV4_DSCP(ofpacts)->dscp = tos;
         break;
 
     case OFPUTIL_OFPAT10_SET_TP_SRC:
+        ofpact_put_SET_L4_SRC_PORT(ofpacts)->port = str_to_u32(arg);
+        break;
+
     case OFPUTIL_OFPAT10_SET_TP_DST:
-        oata = ofputil_put_action(code, b);
-        oata->tp_port = htons(str_to_u32(arg));
+        ofpact_put_SET_L4_DST_PORT(ofpacts)->port = str_to_u32(arg);
         break;
 
     case OFPUTIL_OFPAT10_ENQUEUE:
-        parse_enqueue(b, arg);
+        parse_enqueue(arg, ofpacts);
         break;
 
     case OFPUTIL_NXAST_RESUBMIT:
-        parse_resubmit(b, arg);
+        parse_resubmit(arg, ofpacts);
         break;
 
     case OFPUTIL_NXAST_SET_TUNNEL:
-        parse_set_tunnel(b, arg);
+    case OFPUTIL_NXAST_SET_TUNNEL64:
+        tunnel = ofpact_put_SET_TUNNEL(ofpacts);
+        tunnel->ofpact.compat = code;
+        tunnel->tun_id = str_to_u64(arg);
         break;
 
     case OFPUTIL_NXAST_SET_QUEUE:
-        ofputil_put_NXAST_SET_QUEUE(b)->queue_id = htonl(str_to_u32(arg));
+        ofpact_put_SET_QUEUE(ofpacts)->queue_id = str_to_u32(arg);
         break;
 
     case OFPUTIL_NXAST_POP_QUEUE:
-        ofputil_put_NXAST_POP_QUEUE(b);
+        ofpact_put_POP_QUEUE(ofpacts);
         break;
 
     case OFPUTIL_NXAST_REG_MOVE:
-        nxm_parse_reg_move(ofputil_put_NXAST_REG_MOVE(b), arg);
+        nxm_parse_reg_move(ofpact_put_REG_MOVE(ofpacts), arg);
         break;
 
     case OFPUTIL_NXAST_REG_LOAD:
-        nxm_parse_reg_load(ofputil_put_NXAST_REG_LOAD(b), arg);
+        nxm_parse_reg_load(ofpact_put_REG_LOAD(ofpacts), arg);
         break;
 
     case OFPUTIL_NXAST_NOTE:
-        parse_note(b, arg);
-        break;
-
-    case OFPUTIL_NXAST_SET_TUNNEL64:
-        ofputil_put_NXAST_SET_TUNNEL64(b)->tun_id = htonll(str_to_u64(arg));
+        parse_note(arg, ofpacts);
         break;
 
     case OFPUTIL_NXAST_MULTIPATH:
-        multipath_parse(ofputil_put_NXAST_MULTIPATH(b), arg);
+        multipath_parse(ofpact_put_MULTIPATH(ofpacts), arg);
         break;
 
     case OFPUTIL_NXAST_AUTOPATH:
-        autopath_parse(ofputil_put_NXAST_AUTOPATH(b), arg);
+        autopath_parse(ofpact_put_AUTOPATH(ofpacts), arg);
         break;
 
     case OFPUTIL_NXAST_BUNDLE:
-        bundle_parse(b, arg);
+        bundle_parse(arg, ofpacts);
         break;
 
     case OFPUTIL_NXAST_BUNDLE_LOAD:
-        bundle_parse_load(b, arg);
+        bundle_parse_load(arg, ofpacts);
         break;
 
     case OFPUTIL_NXAST_RESUBMIT_TABLE:
@@ -423,29 +406,29 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
         NOT_REACHED();
 
     case OFPUTIL_NXAST_LEARN:
-        learn_parse(b, arg, flow);
+        learn_parse(arg, flow, ofpacts);
         break;
 
     case OFPUTIL_NXAST_EXIT:
-        ofputil_put_NXAST_EXIT(b);
+        ofpact_put_EXIT(ofpacts);
         break;
 
     case OFPUTIL_NXAST_DEC_TTL:
-        ofputil_put_NXAST_DEC_TTL(b);
+        ofpact_put_DEC_TTL(ofpacts);
         break;
 
     case OFPUTIL_NXAST_FIN_TIMEOUT:
-        parse_fin_timeout(b, arg);
+        parse_fin_timeout(ofpacts, arg);
         break;
 
     case OFPUTIL_NXAST_CONTROLLER:
-        parse_controller(b, arg);
+        parse_controller(ofpacts, arg);
         break;
     }
 }
 
 static void
-str_to_action(const struct flow *flow, char *str, struct ofpbuf *b)
+str_to_ofpacts(const struct flow *flow, char *str, struct ofpbuf *ofpacts)
 {
     char *pos, *act, *arg;
     int n_actions;
@@ -458,10 +441,8 @@ str_to_action(const struct flow *flow, char *str, struct ofpbuf *b)
 
         code = ofputil_action_code_from_name(act);
         if (code >= 0) {
-            parse_named_action(code, flow, b, arg);
+            parse_named_action(code, flow, arg, ofpacts);
         } else if (!strcasecmp(act, "drop")) {
-            /* A drop action in OpenFlow occurs by just not setting
-             * an action. */
             if (n_actions) {
                 ovs_fatal(0, "Drop actions must not be preceded by other "
                           "actions");
@@ -471,12 +452,13 @@ str_to_action(const struct flow *flow, char *str, struct ofpbuf *b)
             }
             break;
         } else if (ofputil_port_from_string(act, &port)) {
-            put_output_action(b, port);
+            ofpact_put_OUTPUT(ofpacts)->port = port;
         } else {
             ovs_fatal(0, "Unknown action: %s", act);
         }
         n_actions++;
     }
+    ofpact_put_END(ofpacts);
 }
 
 struct protocol {
@@ -694,15 +676,15 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
         fm->new_cookie = htonll(0);
     }
     if (fields & F_ACTIONS) {
-        struct ofpbuf actions;
+        struct ofpbuf ofpacts;
 
-        ofpbuf_init(&actions, sizeof(union ofp_action));
-        str_to_action(&fm->cr.flow, act_str, &actions);
-        fm->actions = ofpbuf_steal_data(&actions);
-        fm->n_actions = actions.size / sizeof(union ofp_action);
+        ofpbuf_init(&ofpacts, 32);
+        str_to_ofpacts(&fm->cr.flow, act_str, &ofpacts);
+        fm->ofpacts_len = ofpacts.size;
+        fm->ofpacts = ofpbuf_steal_data(&ofpacts);
     } else {
-        fm->actions = NULL;
-        fm->n_actions = 0;
+        fm->ofpacts_len = 0;
+        fm->ofpacts = NULL;
     }
 
     free(string);
@@ -714,10 +696,10 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
  * Prints an error on stderr and aborts the program if 's' syntax is
  * invalid. */
 void
-parse_ofp_actions(const char *s_, struct ofpbuf *actions)
+parse_ofpacts(const char *s_, struct ofpbuf *ofpacts)
 {
     char *s = xstrdup(s_);
-    str_to_action(NULL, s, actions);
+    str_to_ofpacts(NULL, s, ofpacts);
     free(s);
 }
 
diff --git a/lib/ofp-parse.h b/lib/ofp-parse.h
index 3e5e62a..e930388 100644
--- a/lib/ofp-parse.h
+++ b/lib/ofp-parse.h
@@ -40,7 +40,7 @@ void parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *,
                                       bool aggregate, const char *string);
 
 
-void parse_ofp_actions(const char *, struct ofpbuf *actions);
+void parse_ofpacts(const char *, struct ofpbuf *ofpacts);
 
 char *parse_ofp_exact_flow(struct flow *, const char *);
 
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 347b69c..6983af7 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -36,6 +36,7 @@
 #include "meta-flow.h"
 #include "netdev.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -154,278 +155,17 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
 }
 
 static void
-print_note(struct ds *string, const struct nx_action_note *nan)
-{
-    size_t len;
-    size_t i;
-
-    ds_put_cstr(string, "note:");
-    len = ntohs(nan->len) - offsetof(struct nx_action_note, note);
-    for (i = 0; i < len; i++) {
-        if (i) {
-            ds_put_char(string, '.');
-        }
-        ds_put_format(string, "%02"PRIx8, nan->note[i]);
-    }
-}
-
-static void
-ofp_print_action(struct ds *s, const union ofp_action *a,
-                 enum ofputil_action_code code)
-{
-    const struct ofp_action_enqueue *oae;
-    const struct ofp_action_dl_addr *oada;
-    const struct nx_action_set_tunnel64 *nast64;
-    const struct nx_action_set_tunnel *nast;
-    const struct nx_action_set_queue *nasq;
-    const struct nx_action_resubmit *nar;
-    const struct nx_action_reg_move *move;
-    const struct nx_action_reg_load *load;
-    const struct nx_action_multipath *nam;
-    const struct nx_action_autopath *naa;
-    const struct nx_action_output_reg *naor;
-    const struct nx_action_fin_timeout *naft;
-    const struct nx_action_controller *nac;
-    struct mf_subfield subfield;
-    uint16_t port;
-
-    switch (code) {
-    case OFPUTIL_ACTION_INVALID:
-        NOT_REACHED();
-
-    case OFPUTIL_OFPAT10_OUTPUT:
-        port = ntohs(a->output.port);
-        if (port < OFPP_MAX) {
-            ds_put_format(s, "output:%"PRIu16, port);
-        } else {
-            ofputil_format_port(port, s);
-            if (port == OFPP_CONTROLLER) {
-                if (a->output.max_len != htons(0)) {
-                    ds_put_format(s, ":%"PRIu16, ntohs(a->output.max_len));
-                } else {
-                    ds_put_cstr(s, ":all");
-                }
-            }
-        }
-        break;
-
-    case OFPUTIL_OFPAT10_ENQUEUE:
-        oae = (const struct ofp_action_enqueue *) a;
-        ds_put_format(s, "enqueue:");
-        ofputil_format_port(ntohs(oae->port), s);
-        ds_put_format(s, "q%"PRIu32, ntohl(oae->queue_id));
-        break;
-
-    case OFPUTIL_OFPAT10_SET_VLAN_VID:
-        ds_put_format(s, "mod_vlan_vid:%"PRIu16,
-                      ntohs(a->vlan_vid.vlan_vid));
-        break;
-
-    case OFPUTIL_OFPAT10_SET_VLAN_PCP:
-        ds_put_format(s, "mod_vlan_pcp:%"PRIu8, a->vlan_pcp.vlan_pcp);
-        break;
-
-    case OFPUTIL_OFPAT10_STRIP_VLAN:
-        ds_put_cstr(s, "strip_vlan");
-        break;
-
-    case OFPUTIL_OFPAT10_SET_DL_SRC:
-        oada = (const struct ofp_action_dl_addr *) a;
-        ds_put_format(s, "mod_dl_src:"ETH_ADDR_FMT,
-                      ETH_ADDR_ARGS(oada->dl_addr));
-        break;
-
-    case OFPUTIL_OFPAT10_SET_DL_DST:
-        oada = (const struct ofp_action_dl_addr *) a;
-        ds_put_format(s, "mod_dl_dst:"ETH_ADDR_FMT,
-                      ETH_ADDR_ARGS(oada->dl_addr));
-        break;
-
-    case OFPUTIL_OFPAT10_SET_NW_SRC:
-        ds_put_format(s, "mod_nw_src:"IP_FMT, IP_ARGS(&a->nw_addr.nw_addr));
-        break;
-
-    case OFPUTIL_OFPAT10_SET_NW_DST:
-        ds_put_format(s, "mod_nw_dst:"IP_FMT, IP_ARGS(&a->nw_addr.nw_addr));
-        break;
-
-    case OFPUTIL_OFPAT10_SET_NW_TOS:
-        ds_put_format(s, "mod_nw_tos:%d", a->nw_tos.nw_tos);
-        break;
-
-    case OFPUTIL_OFPAT10_SET_TP_SRC:
-        ds_put_format(s, "mod_tp_src:%d", ntohs(a->tp_port.tp_port));
-        break;
-
-    case OFPUTIL_OFPAT10_SET_TP_DST:
-        ds_put_format(s, "mod_tp_dst:%d", ntohs(a->tp_port.tp_port));
-        break;
-
-    case OFPUTIL_NXAST_RESUBMIT:
-        nar = (struct nx_action_resubmit *)a;
-        ds_put_format(s, "resubmit:");
-        ofputil_format_port(ntohs(nar->in_port), s);
-        break;
-
-    case OFPUTIL_NXAST_RESUBMIT_TABLE:
-        nar = (struct nx_action_resubmit *)a;
-        ds_put_format(s, "resubmit(");
-        if (nar->in_port != htons(OFPP_IN_PORT)) {
-            ofputil_format_port(ntohs(nar->in_port), s);
-        }
-        ds_put_char(s, ',');
-        if (nar->table != 255) {
-            ds_put_format(s, "%"PRIu8, nar->table);
-        }
-        ds_put_char(s, ')');
-        break;
-
-    case OFPUTIL_NXAST_SET_TUNNEL:
-        nast = (struct nx_action_set_tunnel *)a;
-        ds_put_format(s, "set_tunnel:%#"PRIx32, ntohl(nast->tun_id));
-        break;
-
-    case OFPUTIL_NXAST_SET_QUEUE:
-        nasq = (struct nx_action_set_queue *)a;
-        ds_put_format(s, "set_queue:%u", ntohl(nasq->queue_id));
-        break;
-
-    case OFPUTIL_NXAST_POP_QUEUE:
-        ds_put_cstr(s, "pop_queue");
-        break;
-
-    case OFPUTIL_NXAST_NOTE:
-        print_note(s, (const struct nx_action_note *) a);
-        break;
-
-    case OFPUTIL_NXAST_REG_MOVE:
-        move = (const struct nx_action_reg_move *) a;
-        nxm_format_reg_move(move, s);
-        break;
-
-    case OFPUTIL_NXAST_REG_LOAD:
-        load = (const struct nx_action_reg_load *) a;
-        nxm_format_reg_load(load, s);
-        break;
-
-    case OFPUTIL_NXAST_SET_TUNNEL64:
-        nast64 = (const struct nx_action_set_tunnel64 *) a;
-        ds_put_format(s, "set_tunnel64:%#"PRIx64,
-                      ntohll(nast64->tun_id));
-        break;
-
-    case OFPUTIL_NXAST_MULTIPATH:
-        nam = (const struct nx_action_multipath *) a;
-        multipath_format(nam, s);
-        break;
-
-    case OFPUTIL_NXAST_AUTOPATH:
-        naa = (const struct nx_action_autopath *)a;
-        ds_put_format(s, "autopath(%u,", ntohl(naa->id));
-        nxm_decode(&subfield, naa->dst, naa->ofs_nbits);
-        mf_format_subfield(&subfield, s);
-        ds_put_char(s, ')');
-        break;
-
-    case OFPUTIL_NXAST_BUNDLE:
-    case OFPUTIL_NXAST_BUNDLE_LOAD:
-        bundle_format((const struct nx_action_bundle *) a, s);
-        break;
-
-    case OFPUTIL_NXAST_OUTPUT_REG:
-        naor = (const struct nx_action_output_reg *) a;
-        ds_put_cstr(s, "output:");
-        nxm_decode(&subfield, naor->src, naor->ofs_nbits);
-        mf_format_subfield(&subfield, s);
-        break;
-
-    case OFPUTIL_NXAST_LEARN:
-        learn_format((const struct nx_action_learn *) a, s);
-        break;
-
-    case OFPUTIL_NXAST_DEC_TTL:
-        ds_put_cstr(s, "dec_ttl");
-        break;
-
-    case OFPUTIL_NXAST_EXIT:
-        ds_put_cstr(s, "exit");
-        break;
-
-    case OFPUTIL_NXAST_FIN_TIMEOUT:
-        naft = (const struct nx_action_fin_timeout *) a;
-        ds_put_cstr(s, "fin_timeout(");
-        if (naft->fin_idle_timeout) {
-            ds_put_format(s, "idle_timeout=%"PRIu16",",
-                          ntohs(naft->fin_idle_timeout));
-        }
-        if (naft->fin_hard_timeout) {
-            ds_put_format(s, "hard_timeout=%"PRIu16",",
-                          ntohs(naft->fin_hard_timeout));
-        }
-        ds_chomp(s, ',');
-        ds_put_char(s, ')');
-        break;
-
-    case OFPUTIL_NXAST_CONTROLLER:
-        nac = (const struct nx_action_controller *) a;
-        ds_put_cstr(s, "controller(");
-        if (nac->reason != OFPR_ACTION) {
-            ds_put_format(s, "reason=%s,",
-                          ofputil_packet_in_reason_to_string(nac->reason));
-        }
-        if (nac->max_len != htons(UINT16_MAX)) {
-            ds_put_format(s, "max_len=%"PRIu16",", ntohs(nac->max_len));
-        }
-        if (nac->controller_id != htons(0)) {
-            ds_put_format(s, "id=%"PRIu16",", ntohs(nac->controller_id));
-        }
-        ds_chomp(s, ',');
-        ds_put_char(s, ')');
-        break;
-
-    default:
-        break;
-    }
-}
-
-void
-ofp_print_actions(struct ds *string, const union ofp_action *actions,
-                  size_t n_actions)
-{
-    const union ofp_action *a;
-    size_t left;
-
-    ds_put_cstr(string, "actions=");
-    if (!n_actions) {
-        ds_put_cstr(string, "drop");
-    }
-
-    OFPUTIL_ACTION_FOR_EACH (a, left, actions, n_actions) {
-        int code = ofputil_decode_action(a);
-        if (code >= 0) {
-            if (a != actions) {
-                ds_put_cstr(string, ",");
-            }
-            ofp_print_action(string, a, code);
-        } else {
-            ofp_print_error(string, -code);
-        }
-    }
-    if (left > 0) {
-        ds_put_format(string, " ***%zu leftover bytes following actions",
-                      left * sizeof *a);
-    }
-}
-
-static void
 ofp_print_packet_out(struct ds *string, const struct ofp_packet_out *opo,
                      int verbosity)
 {
     struct ofputil_packet_out po;
+    struct ofpbuf ofpacts;
     enum ofperr error;
 
-    error = ofputil_decode_packet_out(&po, opo);
+    ofpbuf_init(&ofpacts, 64);
+    error = ofputil_decode_packet_out(&po, opo, &ofpacts);
     if (error) {
+        ofpbuf_uninit(&ofpacts);
         ofp_print_error(string, error);
         return;
     }
@@ -434,7 +174,7 @@ ofp_print_packet_out(struct ds *string, const struct ofp_packet_out *opo,
     ofputil_format_port(po.in_port, string);
 
     ds_put_char(string, ' ');
-    ofp_print_actions(string, po.actions, po.n_actions);
+    ofpacts_format(po.ofpacts, string);
 
     if (po.buffer_id == UINT32_MAX) {
         ds_put_format(string, " data_len=%zu", po.packet_len);
@@ -448,6 +188,8 @@ ofp_print_packet_out(struct ds *string, const struct ofp_packet_out *opo,
         ds_put_format(string, " buffer=0x%08"PRIx32, po.buffer_id);
     }
     ds_put_char(string, '\n');
+
+    ofpbuf_uninit(&ofpacts);
 }
 
 /* qsort comparison function. */
@@ -928,11 +670,14 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
                    enum ofputil_msg_code code, int verbosity)
 {
     struct ofputil_flow_mod fm;
+    struct ofpbuf ofpacts;
     bool need_priority;
     enum ofperr error;
 
-    error = ofputil_decode_flow_mod(&fm, oh, OFPUTIL_P_OF10_TID);
+    ofpbuf_init(&ofpacts, 64);
+    error = ofputil_decode_flow_mod(&fm, oh, OFPUTIL_P_OF10_TID, &ofpacts);
     if (error) {
+        ofpbuf_uninit(&ofpacts);
         ofp_print_error(s, error);
         return;
     }
@@ -1027,7 +772,8 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
         }
     }
 
-    ofp_print_actions(s, fm.actions, fm.n_actions);
+    ofpacts_format(fm.ofpacts, s);
+    ofpbuf_uninit(&ofpacts);
 }
 
 static void
@@ -1229,14 +975,16 @@ ofp_print_flow_stats_request(struct ds *string,
 static void
 ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh)
 {
+    struct ofpbuf ofpacts;
     struct ofpbuf b;
 
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
+    ofpbuf_init(&ofpacts, 64);
     for (;;) {
         struct ofputil_flow_stats fs;
         int retval;
 
-        retval = ofputil_decode_flow_stats_reply(&fs, &b, true);
+        retval = ofputil_decode_flow_stats_reply(&fs, &b, true, &ofpacts);
         if (retval) {
             if (retval != EOF) {
                 ds_put_cstr(string, " ***parse error***");
@@ -1269,8 +1017,9 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh)
         if (string->string[string->length - 1] != ' ') {
             ds_put_char(string, ' ');
         }
-        ofp_print_actions(string, fs.actions, fs.n_actions);
-     }
+        ofpacts_format(fs.ofpacts, string);
+    }
+    ofpbuf_uninit(&ofpacts);
 }
 
 static void
diff --git a/lib/ofp-print.h b/lib/ofp-print.h
index ae868a4..49bcfcd 100644
--- a/lib/ofp-print.h
+++ b/lib/ofp-print.h
@@ -25,7 +25,6 @@
 struct ofp_flow_mod;
 struct ofp10_match;
 struct ds;
-union ofp_action;
 
 #ifdef  __cplusplus
 extern "C" {
@@ -34,7 +33,6 @@ extern "C" {
 void ofp_print(FILE *, const void *, size_t, int verbosity);
 void ofp_print_packet(FILE *stream, const void *data, size_t len);
 
-void ofp_print_actions(struct ds *, const union ofp_action *, size_t);
 void ofp10_match_print(struct ds *, const struct ofp10_match *, int verbosity);
 
 char *ofp_to_string(const void *, size_t, int verbosity);
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index cebc523..e31a304 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -32,6 +32,7 @@
 #include "multipath.h"
 #include "netdev.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -1644,11 +1645,17 @@ ofputil_make_flow_mod_table_id(bool flow_mod_table_id)
  * flow_mod in 'fm'.  Returns 0 if successful, otherwise an OpenFlow error
  * code.
  *
- * Does not validate the flow_mod actions. */
+ * Uses 'ofpacts' to store the abstract OFPACT_* version of 'oh''s actions.
+ * The caller must initialize 'ofpacts' and retains ownership of it.
+ * 'fm->ofpacts' will point into the 'ofpacts' buffer.
+ *
+ * Does not validate the flow_mod actions.  The caller should do that, with
+ * ofpacts_check(). */
 enum ofperr
 ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
                         const struct ofp_header *oh,
-                        enum ofputil_protocol protocol)
+                        enum ofputil_protocol protocol,
+                        struct ofpbuf *ofpacts)
 {
     const struct ofputil_msg_type *type;
     uint16_t command;
@@ -1663,12 +1670,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
         uint16_t priority;
         enum ofperr error;
 
-        /* Dissect the message. */
+        /* Get the ofp_flow_mod. */
         ofm = ofpbuf_pull(&b, sizeof *ofm);
-        error = ofputil_pull_actions(&b, b.size, &fm->actions, &fm->n_actions);
-        if (error) {
-            return error;
-        }
 
         /* Set priority based on original wildcards.  Normally we'd allow
          * ofputil_cls_rule_from_match() to do this for us, but
@@ -1683,6 +1686,12 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
         ofputil_cls_rule_from_ofp10_match(&ofm->match, priority, &fm->cr);
         ofputil_normalize_rule(&fm->cr);
 
+        /* Now get the actions. */
+        error = ofpacts_pull_openflow(&b, b.size, ofpacts);
+        if (error) {
+            return error;
+        }
+
         /* Translate the message. */
         command = ntohs(ofm->command);
         fm->cookie = htonll(0);
@@ -1705,7 +1714,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
         if (error) {
             return error;
         }
-        error = ofputil_pull_actions(&b, b.size, &fm->actions, &fm->n_actions);
+        error = ofpacts_pull_openflow(&b, b.size, ofpacts);
         if (error) {
             return error;
         }
@@ -1727,6 +1736,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
         NOT_REACHED();
     }
 
+    fm->ofpacts = ofpacts->data;
+    fm->ofpacts_len = ofpacts->size;
     if (protocol & OFPUTIL_P_TID) {
         fm->command = command & 0xff;
         fm->table_id = command >> 8;
@@ -1744,7 +1755,6 @@ struct ofpbuf *
 ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
                         enum ofputil_protocol protocol)
 {
-    size_t actions_len = fm->n_actions * sizeof *fm->actions;
     struct ofp_flow_mod *ofm;
     struct nx_flow_mod *nfm;
     struct ofpbuf *msg;
@@ -1758,7 +1768,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
     switch (protocol) {
     case OFPUTIL_P_OF10:
     case OFPUTIL_P_OF10_TID:
-        msg = ofpbuf_new(sizeof *ofm + actions_len);
+        msg = ofpbuf_new(sizeof *ofm + fm->ofpacts_len);
         ofm = put_openflow(sizeof *ofm, OFPT10_FLOW_MOD, msg);
         ofputil_cls_rule_to_ofp10_match(&fm->cr, &ofm->match);
         ofm->cookie = fm->new_cookie;
@@ -1773,7 +1783,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
 
     case OFPUTIL_P_NXM:
     case OFPUTIL_P_NXM_TID:
-        msg = ofpbuf_new(sizeof *nfm + NXM_TYPICAL_LEN + actions_len);
+        msg = ofpbuf_new(sizeof *nfm + NXM_TYPICAL_LEN + fm->ofpacts_len);
         put_nxmsg(sizeof *nfm, NXT_FLOW_MOD, msg);
         nfm = msg->data;
         nfm->command = htons(command);
@@ -1794,7 +1804,9 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
         NOT_REACHED();
     }
 
-    ofpbuf_put(msg, fm->actions, actions_len);
+    if (fm->ofpacts) {
+        ofpacts_to_openflow(fm->ofpacts, msg);
+    }
     update_openflow_length(msg);
     return msg;
 }
@@ -1988,12 +2000,17 @@ ofputil_flow_stats_request_usable_protocols(
  * 'flow_age_extension' as true so that the contents of 'msg' determine the
  * 'idle_age' and 'hard_age' members in 'fs'.
  *
+ * Uses 'ofpacts' to store the abstract OFPACT_* version of the flow stats
+ * reply's actions.  The caller must initialize 'ofpacts' and retains ownership
+ * of it.  'fs->ofpacts' will point into the 'ofpacts' buffer.
+ *
  * Returns 0 if successful, EOF if no replies were left in this 'msg',
  * otherwise a positive errno value. */
 int
 ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
                                 struct ofpbuf *msg,
-                                bool flow_age_extension)
+                                bool flow_age_extension,
+                                struct ofpbuf *ofpacts)
 {
     const struct ofputil_msg_type *type;
     int code;
@@ -2031,8 +2048,7 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
             return EINVAL;
         }
 
-        if (ofputil_pull_actions(msg, length - sizeof *ofs,
-                                 &fs->actions, &fs->n_actions)) {
+        if (ofpacts_pull_openflow(msg, length - sizeof *ofs, ofpacts)) {
             return EINVAL;
         }
 
@@ -2050,7 +2066,7 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
         fs->byte_count = ntohll(get_32aligned_be64(&ofs->byte_count));
     } else if (code == OFPUTIL_NXST_FLOW_REPLY) {
         const struct nx_flow_stats *nfs;
-        size_t match_len, length;
+        size_t match_len, actions_len, length;
 
         nfs = ofpbuf_try_pull(msg, sizeof *nfs);
         if (!nfs) {
@@ -2071,9 +2087,8 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
             return EINVAL;
         }
 
-        if (ofputil_pull_actions(msg,
-                                 length - sizeof *nfs - ROUND_UP(match_len, 8),
-                                 &fs->actions, &fs->n_actions)) {
+        actions_len = length - sizeof *nfs - ROUND_UP(match_len, 8);
+        if (ofpacts_pull_openflow(msg, actions_len, ofpacts)) {
             return EINVAL;
         }
 
@@ -2099,6 +2114,9 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
         NOT_REACHED();
     }
 
+    fs->ofpacts = ofpacts->data;
+    fs->ofpacts_len = ofpacts->size;
+
     return 0;
 }
 
@@ -2119,16 +2137,14 @@ void
 ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
                                 struct list *replies)
 {
-    size_t act_len = fs->n_actions * sizeof *fs->actions;
-    const struct ofp_stats_msg *osm;
+    struct ofpbuf *reply = ofpbuf_from_list(list_back(replies));
+    const struct ofp_stats_msg *osm = reply->data;
+    size_t start_ofs = reply->size;
 
-    osm = ofpbuf_from_list(list_back(replies))->data;
     if (osm->type == htons(OFPST_FLOW)) {
-        size_t len = offsetof(struct ofp_flow_stats, actions) + act_len;
         struct ofp_flow_stats *ofs;
 
-        ofs = ofputil_append_stats_reply(len, replies);
-        ofs->length = htons(len);
+        ofs = ofpbuf_put_uninit(reply, sizeof *ofs);
         ofs->table_id = fs->table_id;
         ofs->pad = 0;
         ofputil_cls_rule_to_ofp10_match(&fs->rule, &ofs->match);
@@ -2143,17 +2159,14 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
                            htonll(unknown_to_zero(fs->packet_count)));
         put_32aligned_be64(&ofs->byte_count,
                            htonll(unknown_to_zero(fs->byte_count)));
-        memcpy(ofs->actions, fs->actions, act_len);
+        ofpacts_to_openflow(fs->ofpacts, reply);
+
+        ofs = ofpbuf_at_assert(reply, start_ofs, sizeof *ofs);
+        ofs->length = htons(reply->size - start_ofs);
     } else if (osm->type == htons(OFPST_VENDOR)) {
         struct nx_flow_stats *nfs;
-        struct ofpbuf *msg;
-        size_t start_len;
 
-        msg = ofputil_reserve_stats_reply(
-            sizeof *nfs + NXM_MAX_LEN + act_len, replies);
-        start_len = msg->size;
-
-        nfs = ofpbuf_put_uninit(msg, sizeof *nfs);
+        nfs = ofpbuf_put_uninit(reply, sizeof *nfs);
         nfs->table_id = fs->table_id;
         nfs->pad = 0;
         nfs->duration_sec = htonl(fs->duration_sec);
@@ -2167,15 +2180,19 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
         nfs->hard_age = htons(fs->hard_age < 0 ? 0
                               : fs->hard_age < UINT16_MAX ? fs->hard_age + 1
                               : UINT16_MAX);
-        nfs->match_len = htons(nx_put_match(msg, false, &fs->rule, 0, 0));
+        nfs->match_len = htons(nx_put_match(reply, false, &fs->rule, 0, 0));
         nfs->cookie = fs->cookie;
         nfs->packet_count = htonll(fs->packet_count);
         nfs->byte_count = htonll(fs->byte_count);
-        ofpbuf_put(msg, fs->actions, act_len);
-        nfs->length = htons(msg->size - start_len);
+        ofpacts_to_openflow(fs->ofpacts, reply);
+
+        nfs = ofpbuf_at_assert(reply, start_ofs, sizeof *nfs);
+        nfs->length = htons(reply->size - start_ofs);
     } else {
         NOT_REACHED();
     }
+
+    ofputil_postappend_stats_reply(start_ofs, replies);
 }
 
 /* Converts abstract ofputil_aggregate_stats 'stats' into an OFPST_AGGREGATE or
@@ -2501,9 +2518,18 @@ ofputil_packet_in_reason_from_string(const char *s,
     return false;
 }
 
+/* Converts an OFPT_PACKET_OUT in 'opo' into an abstract ofputil_packet_out in
+ * 'po'.
+ *
+ * Uses 'ofpacts' to store the abstract OFPACT_* version of the packet out
+ * message's actions.  The caller must initialize 'ofpacts' and retains
+ * ownership of it.  'po->ofpacts' will point into the 'ofpacts' buffer.
+ *
+ * Returns 0 if successful, otherwise an OFPERR_* value. */
 enum ofperr
 ofputil_decode_packet_out(struct ofputil_packet_out *po,
-                          const struct ofp_packet_out *opo)
+                          const struct ofp_packet_out *opo,
+                          struct ofpbuf *ofpacts)
 {
     enum ofperr error;
     struct ofpbuf b;
@@ -2520,11 +2546,12 @@ ofputil_decode_packet_out(struct ofputil_packet_out *po,
     ofpbuf_use_const(&b, opo, ntohs(opo->header.length));
     ofpbuf_pull(&b, sizeof *opo);
 
-    error = ofputil_pull_actions(&b, ntohs(opo->actions_len),
-                                 &po->actions, &po->n_actions);
+    error = ofpacts_pull_openflow(&b, ntohs(opo->actions_len), ofpacts);
     if (error) {
         return error;
     }
+    po->ofpacts = ofpacts->data;
+    po->ofpacts_len = ofpacts->size;
 
     if (po->buffer_id == UINT32_MAX) {
         po->packet = b.data;
@@ -3070,25 +3097,27 @@ struct ofpbuf *
 ofputil_encode_packet_out(const struct ofputil_packet_out *po)
 {
     struct ofp_packet_out *opo;
-    size_t actions_len;
     struct ofpbuf *msg;
     size_t size;
 
-    actions_len = po->n_actions * sizeof *po->actions;
-    size = sizeof *opo + actions_len;
+    size = sizeof *opo + po->ofpacts_len;
     if (po->buffer_id == UINT32_MAX) {
         size += po->packet_len;
     }
 
     msg = ofpbuf_new(size);
-    opo = put_openflow(sizeof *opo, OFPT10_PACKET_OUT, msg);
+    put_openflow(sizeof *opo, OFPT10_PACKET_OUT, msg);
+    ofpacts_to_openflow(po->ofpacts, msg);
+
+    opo = msg->data;
     opo->buffer_id = htonl(po->buffer_id);
     opo->in_port = htons(po->in_port);
-    opo->actions_len = htons(actions_len);
-    ofpbuf_put(msg, po->actions, actions_len);
+    opo->actions_len = htons(msg->size - sizeof *opo);
+
     if (po->buffer_id == UINT32_MAX) {
         ofpbuf_put(msg, po->packet, po->packet_len);
     }
+
     update_openflow_length(msg);
 
     return msg;
@@ -3362,6 +3391,20 @@ ofputil_append_stats_reply(size_t len, struct list *replies)
     return ofpbuf_put_uninit(ofputil_reserve_stats_reply(len, replies), len);
 }
 
+void
+ofputil_postappend_stats_reply(size_t start_ofs, struct list *replies)
+{
+    struct ofpbuf *msg = ofpbuf_from_list(list_back(replies));
+
+    assert(start_ofs <= UINT16_MAX);
+    if (msg->size > UINT16_MAX) {
+        size_t len = msg->size - start_ofs;
+        memcpy(ofputil_append_stats_reply(len, replies),
+               (const uint8_t *) msg->data + start_ofs, len);
+        msg->size = start_ofs;
+    }
+}
+
 /* Returns the first byte past the ofp_stats_msg header in 'oh'. */
 const void *
 ofputil_stats_body(const struct ofp_header *oh)
@@ -3661,280 +3704,6 @@ size_t ofputil_count_phy_ports(uint8_t ofp_version, struct ofpbuf *b)
     return b->size / ofputil_get_phy_port_size(ofp_version);
 }
 
-static enum ofperr
-check_resubmit_table(const struct nx_action_resubmit *nar)
-{
-    if (nar->pad[0] || nar->pad[1] || nar->pad[2]) {
-        return OFPERR_OFPBAC_BAD_ARGUMENT;
-    }
-    return 0;
-}
-
-static enum ofperr
-check_output_reg(const struct nx_action_output_reg *naor,
-                 const struct flow *flow)
-{
-    struct mf_subfield src;
-    size_t i;
-
-    for (i = 0; i < sizeof naor->zero; i++) {
-        if (naor->zero[i]) {
-            return OFPERR_OFPBAC_BAD_ARGUMENT;
-        }
-    }
-
-    nxm_decode(&src, naor->src, naor->ofs_nbits);
-    return mf_check_src(&src, flow);
-}
-
-enum ofperr
-validate_actions(const union ofp_action *actions, size_t n_actions,
-                 const struct flow *flow, int max_ports)
-{
-    const union ofp_action *a;
-    size_t left;
-
-    OFPUTIL_ACTION_FOR_EACH (a, left, actions, n_actions) {
-        enum ofperr error;
-        uint16_t port;
-        int code;
-
-        code = ofputil_decode_action(a);
-        if (code < 0) {
-            error = -code;
-            VLOG_WARN_RL(&bad_ofmsg_rl,
-                         "action decoding error at offset %td (%s)",
-                         (a - actions) * sizeof *a, ofperr_get_name(error));
-
-            return error;
-        }
-
-        error = 0;
-        switch ((enum ofputil_action_code) code) {
-        case OFPUTIL_ACTION_INVALID:
-            NOT_REACHED();
-
-        case OFPUTIL_OFPAT10_OUTPUT:
-            error = ofputil_check_output_port(ntohs(a->output.port),
-                                              max_ports);
-            break;
-
-        case OFPUTIL_OFPAT10_SET_VLAN_VID:
-            if (a->vlan_vid.vlan_vid & ~htons(0xfff)) {
-                error = OFPERR_OFPBAC_BAD_ARGUMENT;
-            }
-            break;
-
-        case OFPUTIL_OFPAT10_SET_VLAN_PCP:
-            if (a->vlan_pcp.vlan_pcp & ~7) {
-                error = OFPERR_OFPBAC_BAD_ARGUMENT;
-            }
-            break;
-
-        case OFPUTIL_OFPAT10_ENQUEUE:
-            port = ntohs(((const struct ofp_action_enqueue *) a)->port);
-            if (port >= max_ports && port != OFPP_IN_PORT
-                && port != OFPP_LOCAL) {
-                error = OFPERR_OFPBAC_BAD_OUT_PORT;
-            }
-            break;
-
-        case OFPUTIL_NXAST_REG_MOVE:
-            error = nxm_check_reg_move((const struct nx_action_reg_move *) a,
-                                       flow);
-            break;
-
-        case OFPUTIL_NXAST_REG_LOAD:
-            error = nxm_check_reg_load((const struct nx_action_reg_load *) a,
-                                       flow);
-            break;
-
-        case OFPUTIL_NXAST_MULTIPATH:
-            error = multipath_check((const struct nx_action_multipath *) a,
-                                    flow);
-            break;
-
-        case OFPUTIL_NXAST_AUTOPATH:
-            error = autopath_check((const struct nx_action_autopath *) a,
-                                   flow);
-            break;
-
-        case OFPUTIL_NXAST_BUNDLE:
-        case OFPUTIL_NXAST_BUNDLE_LOAD:
-            error = bundle_check((const struct nx_action_bundle *) a,
-                                 max_ports, flow);
-            break;
-
-        case OFPUTIL_NXAST_OUTPUT_REG:
-            error = check_output_reg((const struct nx_action_output_reg *) a,
-                                     flow);
-            break;
-
-        case OFPUTIL_NXAST_RESUBMIT_TABLE:
-            error = check_resubmit_table(
-                (const struct nx_action_resubmit *) a);
-            break;
-
-        case OFPUTIL_NXAST_LEARN:
-            error = learn_check((const struct nx_action_learn *) a, flow);
-            break;
-
-        case OFPUTIL_NXAST_CONTROLLER:
-            if (((const struct nx_action_controller *) a)->zero) {
-                error = OFPERR_NXBAC_MUST_BE_ZERO;
-            }
-            break;
-
-        case OFPUTIL_OFPAT10_STRIP_VLAN:
-        case OFPUTIL_OFPAT10_SET_NW_SRC:
-        case OFPUTIL_OFPAT10_SET_NW_DST:
-        case OFPUTIL_OFPAT10_SET_NW_TOS:
-        case OFPUTIL_OFPAT10_SET_TP_SRC:
-        case OFPUTIL_OFPAT10_SET_TP_DST:
-        case OFPUTIL_OFPAT10_SET_DL_SRC:
-        case OFPUTIL_OFPAT10_SET_DL_DST:
-        case OFPUTIL_NXAST_RESUBMIT:
-        case OFPUTIL_NXAST_SET_TUNNEL:
-        case OFPUTIL_NXAST_SET_QUEUE:
-        case OFPUTIL_NXAST_POP_QUEUE:
-        case OFPUTIL_NXAST_NOTE:
-        case OFPUTIL_NXAST_SET_TUNNEL64:
-        case OFPUTIL_NXAST_EXIT:
-        case OFPUTIL_NXAST_DEC_TTL:
-        case OFPUTIL_NXAST_FIN_TIMEOUT:
-            break;
-        }
-
-        if (error) {
-            VLOG_WARN_RL(&bad_ofmsg_rl, "bad action at offset %td (%s)",
-                         (a - actions) * sizeof *a, ofperr_get_name(error));
-            return error;
-        }
-    }
-    if (left) {
-        VLOG_WARN_RL(&bad_ofmsg_rl, "bad action format at offset %zu",
-                     (n_actions - left) * sizeof *a);
-        return OFPERR_OFPBAC_BAD_LEN;
-    }
-    return 0;
-}
-
-struct ofputil_action {
-    int code;
-    unsigned int min_len;
-    unsigned int max_len;
-};
-
-static const struct ofputil_action action_bad_type
-    = { -OFPERR_OFPBAC_BAD_TYPE,   0, UINT_MAX };
-static const struct ofputil_action action_bad_len
-    = { -OFPERR_OFPBAC_BAD_LEN,    0, UINT_MAX };
-static const struct ofputil_action action_bad_vendor
-    = { -OFPERR_OFPBAC_BAD_VENDOR, 0, UINT_MAX };
-
-static const struct ofputil_action *
-ofputil_decode_ofpat_action(const union ofp_action *a)
-{
-    enum ofp10_action_type type = ntohs(a->type);
-
-    switch (type) {
-#define OFPAT10_ACTION(ENUM, STRUCT, NAME)                    \
-        case ENUM: {                                        \
-            static const struct ofputil_action action = {   \
-                OFPUTIL_##ENUM,                             \
-                sizeof(struct STRUCT),                      \
-                sizeof(struct STRUCT)                       \
-            };                                              \
-            return &action;                                 \
-        }
-#include "ofp-util.def"
-
-    case OFPAT10_VENDOR:
-    default:
-        return &action_bad_type;
-    }
-}
-
-static const struct ofputil_action *
-ofputil_decode_nxast_action(const union ofp_action *a)
-{
-    const struct nx_action_header *nah = (const struct nx_action_header *) a;
-    enum nx_action_subtype subtype = ntohs(nah->subtype);
-
-    switch (subtype) {
-#define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME)            \
-        case ENUM: {                                            \
-            static const struct ofputil_action action = {       \
-                OFPUTIL_##ENUM,                                 \
-                sizeof(struct STRUCT),                          \
-                EXTENSIBLE ? UINT_MAX : sizeof(struct STRUCT)   \
-            };                                                  \
-            return &action;                                     \
-        }
-#include "ofp-util.def"
-
-    case NXAST_SNAT__OBSOLETE:
-    case NXAST_DROP_SPOOFED_ARP__OBSOLETE:
-    default:
-        return &action_bad_type;
-    }
-}
-
-/* Parses 'a' to determine its type.  Returns a nonnegative OFPUTIL_OFPAT10_* or
- * OFPUTIL_NXAST_* constant if successful, otherwise a negative OFPERR_* error
- * code.
- *
- * The caller must have already verified that 'a''s length is correct (that is,
- * a->header.len is nonzero and a multiple of sizeof(union ofp_action) and no
- * longer than the amount of space allocated to 'a').
- *
- * This function verifies that 'a''s length is correct for the type of action
- * that it represents. */
-int
-ofputil_decode_action(const union ofp_action *a)
-{
-    const struct ofputil_action *action;
-    uint16_t len = ntohs(a->header.len);
-
-    if (a->type != htons(OFPAT10_VENDOR)) {
-        action = ofputil_decode_ofpat_action(a);
-    } else {
-        switch (ntohl(a->vendor.vendor)) {
-        case NX_VENDOR_ID:
-            if (len < sizeof(struct nx_action_header)) {
-                return -OFPERR_OFPBAC_BAD_LEN;
-            }
-            action = ofputil_decode_nxast_action(a);
-            break;
-        default:
-            action = &action_bad_vendor;
-            break;
-        }
-    }
-
-    return (len >= action->min_len && len <= action->max_len
-            ? action->code
-            : -OFPERR_OFPBAC_BAD_LEN);
-}
-
-/* Parses 'a' and returns its type as an OFPUTIL_OFPAT10_* or OFPUTIL_NXAST_*
- * constant.  The caller must have already validated that 'a' is a valid action
- * understood by Open vSwitch (e.g. by a previous successful call to
- * ofputil_decode_action()). */
-enum ofputil_action_code
-ofputil_decode_action_unsafe(const union ofp_action *a)
-{
-    const struct ofputil_action *action;
-
-    if (a->type != htons(OFPAT10_VENDOR)) {
-        action = ofputil_decode_ofpat_action(a);
-    } else {
-        action = ofputil_decode_nxast_action(a);
-    }
-
-    return action->code;
-}
-
 /* Returns the 'enum ofputil_action_code' corresponding to 'name' (e.g. if
  * 'name' is "output" then the return value is OFPUTIL_OFPAT10_OUTPUT), or -1 if
  * 'name' is not the name of any action.
@@ -4017,22 +3786,6 @@ ofputil_put_action(enum ofputil_action_code code, struct ofpbuf *buf)
     }
 #include "ofp-util.def"
 
-/* Returns true if 'action' outputs to 'port', false otherwise. */
-bool
-action_outputs_to_port(const union ofp_action *action, ovs_be16 port)
-{
-    switch (ofputil_decode_action(action)) {
-    case OFPUTIL_OFPAT10_OUTPUT:
-        return action->output.port == port;
-    case OFPUTIL_OFPAT10_ENQUEUE:
-        return ((const struct ofp_action_enqueue *) action)->port == port;
-    case OFPUTIL_NXAST_CONTROLLER:
-        return port == htons(OFPP_CONTROLLER);
-    default:
-        return false;
-    }
-}
-
 /* "Normalizes" the wildcards in 'rule'.  That means:
  *
  *    1. If the type of level N is known, then only the valid fields for that
@@ -4140,57 +3893,6 @@ ofputil_normalize_rule(struct cls_rule *rule)
     }
 }
 
-/* Attempts to pull 'actions_len' bytes from the front of 'b'.  Returns 0 if
- * successful, otherwise an OpenFlow error.
- *
- * If successful, the first action is stored in '*actionsp' and the number of
- * "union ofp_action" size elements into '*n_actionsp'.  Otherwise NULL and 0
- * are stored, respectively.
- *
- * This function does not check that the actions are valid (the caller should
- * do so, with validate_actions()).  The caller is also responsible for making
- * sure that 'b->data' is initially aligned appropriately for "union
- * ofp_action". */
-enum ofperr
-ofputil_pull_actions(struct ofpbuf *b, unsigned int actions_len,
-                     union ofp_action **actionsp, size_t *n_actionsp)
-{
-    if (actions_len % OFP_ACTION_ALIGN != 0) {
-        VLOG_WARN_RL(&bad_ofmsg_rl, "OpenFlow message actions length %u "
-                     "is not a multiple of %d", actions_len, OFP_ACTION_ALIGN);
-        goto error;
-    }
-
-    *actionsp = ofpbuf_try_pull(b, actions_len);
-    if (*actionsp == NULL) {
-        VLOG_WARN_RL(&bad_ofmsg_rl, "OpenFlow message actions length %u "
-                     "exceeds remaining message length (%zu)",
-                     actions_len, b->size);
-        goto error;
-    }
-
-    *n_actionsp = actions_len / OFP_ACTION_ALIGN;
-    return 0;
-
-error:
-    *actionsp = NULL;
-    *n_actionsp = 0;
-    return OFPERR_OFPBRC_BAD_LEN;
-}
-
-bool
-ofputil_actions_equal(const union ofp_action *a, size_t n_a,
-                      const union ofp_action *b, size_t n_b)
-{
-    return n_a == n_b && (!n_a || !memcmp(a, b, n_a * sizeof *a));
-}
-
-union ofp_action *
-ofputil_actions_clone(const union ofp_action *actions, size_t n)
-{
-    return n ? xmemdup(actions, n * sizeof *actions) : NULL;
-}
-
 /* Parses a key or a key-value pair from '*stringp'.
  *
  * On success: Stores the key into '*keyp'.  Stores the value, if present, into
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 3f9e440..f703c8a 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -22,6 +22,7 @@
 #include <stddef.h>
 #include <stdint.h>
 #include "classifier.h"
+#include "compiler.h"
 #include "flow.h"
 #include "netdev.h"
 #include "openflow/nicira-ext.h"
@@ -236,13 +237,14 @@ struct ofputil_flow_mod {
     uint32_t buffer_id;
     uint16_t out_port;
     uint16_t flags;
-    union ofp_action *actions;
-    size_t n_actions;
+    struct ofpact *ofpacts;     /* Series of "struct ofpact"s. */
+    size_t ofpacts_len;         /* Length of ofpacts, in bytes. */
 };
 
 enum ofperr ofputil_decode_flow_mod(struct ofputil_flow_mod *,
                                     const struct ofp_header *,
-                                    enum ofputil_protocol);
+                                    enum ofputil_protocol,
+                                    struct ofpbuf *ofpacts);
 struct ofpbuf *ofputil_encode_flow_mod(const struct ofputil_flow_mod *,
                                        enum ofputil_protocol);
 
@@ -279,13 +281,14 @@ struct ofputil_flow_stats {
     int hard_age;               /* Seconds since last change, -1 if unknown. */
     uint64_t packet_count;      /* Packet count, UINT64_MAX if unknown. */
     uint64_t byte_count;        /* Byte count, UINT64_MAX if unknown. */
-    union ofp_action *actions;
-    size_t n_actions;
+    struct ofpact *ofpacts;
+    size_t ofpacts_len;
 };
 
 int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *,
                                     struct ofpbuf *msg,
-                                    bool flow_age_extension);
+                                    bool flow_age_extension,
+                                    struct ofpbuf *ofpacts);
 void ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *,
                                      struct list *replies);
 
@@ -352,12 +355,13 @@ struct ofputil_packet_out {
     size_t packet_len;          /* Length of packet data in bytes. */
     uint32_t buffer_id;         /* Buffer id or UINT32_MAX if no buffer. */
     uint16_t in_port;           /* Packet's input port. */
-    union ofp_action *actions;  /* Actions. */
-    size_t n_actions;           /* Number of elements in 'actions' array. */
+    struct ofpact *ofpacts;     /* Actions. */
+    size_t ofpacts_len;         /* Size of ofpacts in bytes. */
 };
 
 enum ofperr ofputil_decode_packet_out(struct ofputil_packet_out *,
-                                      const struct ofp_packet_out *);
+                                      const struct ofp_packet_out *,
+                                      struct ofpbuf *ofpacts);
 struct ofpbuf *ofputil_encode_packet_out(const struct ofputil_packet_out *);
 
 enum ofputil_port_config {
@@ -531,6 +535,7 @@ void ofputil_start_stats_reply(const struct ofp_stats_msg *request,
                                struct list *);
 struct ofpbuf *ofputil_reserve_stats_reply(size_t len, struct list *);
 void *ofputil_append_stats_reply(size_t len, struct list *);
+void ofputil_postappend_stats_reply(size_t start_ofs, struct list *);
 
 void ofputil_append_port_desc_stats_reply(uint8_t ofp_version,
                                           const struct ofputil_phy_port *pp,
@@ -597,7 +602,7 @@ bool ofputil_frag_handling_from_string(const char *, enum ofp_config_flags *);
  *
  * (The above list helps developers who want to "grep" for these definitions.)
  */
-enum ofputil_action_code {
+enum OVS_PACKED_ENUM ofputil_action_code {
     OFPUTIL_ACTION_INVALID,
 #define OFPAT10_ACTION(ENUM, STRUCT, NAME)             OFPUTIL_##ENUM,
 #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) OFPUTIL_##ENUM,
@@ -612,10 +617,6 @@ enum {
 #include "ofp-util.def"
 };
 
-int ofputil_decode_action(const union ofp_action *);
-enum ofputil_action_code ofputil_decode_action_unsafe(
-    const union ofp_action *);
-
 int ofputil_action_code_from_name(const char *);
 
 void *ofputil_put_action(enum ofputil_action_code, struct ofpbuf *buf);
@@ -644,38 +645,6 @@ void *ofputil_put_action(enum ofputil_action_code, struct ofpbuf *buf);
 
 #define OFP_ACTION_ALIGN 8      /* Alignment of ofp_actions. */
 
-static inline union ofp_action *
-ofputil_action_next(const union ofp_action *a)
-{
-    return ((union ofp_action *) (void *)
-            ((uint8_t *) a + ntohs(a->header.len)));
-}
-
-static inline bool
-ofputil_action_is_valid(const union ofp_action *a, size_t n_actions)
-{
-    uint16_t len = ntohs(a->header.len);
-    return (!(len % OFP_ACTION_ALIGN)
-            && len >= sizeof *a
-            && len / sizeof *a <= n_actions);
-}
-
-/* This macro is careful to check for actions with bad lengths. */
-#define OFPUTIL_ACTION_FOR_EACH(ITER, LEFT, ACTIONS, N_ACTIONS)         \
-    for ((ITER) = (ACTIONS), (LEFT) = (N_ACTIONS);                      \
-         (LEFT) > 0 && ofputil_action_is_valid(ITER, LEFT);             \
-         ((LEFT) -= ntohs((ITER)->header.len) / sizeof(union ofp_action), \
-          (ITER) = ofputil_action_next(ITER)))
-
-/* This macro does not check for actions with bad lengths.  It should only be
- * used with actions from trusted sources or with actions that have already
- * been validated (e.g. with OFPUTIL_ACTION_FOR_EACH).  */
-#define OFPUTIL_ACTION_FOR_EACH_UNSAFE(ITER, LEFT, ACTIONS, N_ACTIONS)  \
-    for ((ITER) = (ACTIONS), (LEFT) = (N_ACTIONS);                      \
-         (LEFT) > 0;                                                    \
-         ((LEFT) -= ntohs((ITER)->header.len) / sizeof(union ofp_action), \
-          (ITER) = ofputil_action_next(ITER)))
-
 enum ofperr validate_actions(const union ofp_action *, size_t n_actions,
                              const struct flow *, int max_ports);
 bool action_outputs_to_port(const union ofp_action *, ovs_be16 port);
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index 8cdaa1f..93e5a71 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -25,6 +25,7 @@
 #include "fail-open.h"
 #include "in-band.h"
 #include "odp-util.h"
+#include "ofp-actions.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
 #include "ofproto-provider.h"
@@ -1573,15 +1574,17 @@ connmgr_flushed(struct connmgr *mgr)
      * traffic until a controller has been defined and it tells us to do so. */
     if (!connmgr_has_controllers(mgr)
         && mgr->fail_mode == OFPROTO_FAIL_STANDALONE) {
-        union ofp_action action;
+        struct ofpbuf ofpacts;
         struct cls_rule rule;
 
-        memset(&action, 0, sizeof action);
-        action.type = htons(OFPAT10_OUTPUT);
-        action.output.len = htons(sizeof action);
-        action.output.port = htons(OFPP_NORMAL);
+        ofpbuf_init(&ofpacts, OFPACT_OUTPUT_SIZE + OFPACT_END_SIZE);
+        ofpact_put_OUTPUT(&ofpacts)->port = OFPP_NORMAL;
+        ofpact_put_END(&ofpacts);
+
         cls_rule_init_catchall(&rule, 0);
-        ofproto_add_flow(mgr->ofproto, &rule, &action, 1);
+        ofproto_add_flow(mgr->ofproto, &rule, ofpacts.data, ofpacts.size);
+
+        ofpbuf_uninit(&ofpacts);
     }
 }
 
diff --git a/ofproto/fail-open.c b/ofproto/fail-open.c
index 912dc4e..3baa7b4 100644
--- a/ofproto/fail-open.c
+++ b/ofproto/fail-open.c
@@ -23,6 +23,7 @@
 #include "flow.h"
 #include "mac-learning.h"
 #include "odp-util.h"
+#include "ofp-actions.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
 #include "ofproto.h"
@@ -214,18 +215,19 @@ fail_open_flushed(struct fail_open *fo)
     int disconn_secs = connmgr_failure_duration(fo->connmgr);
     bool open = disconn_secs >= trigger_duration(fo);
     if (open) {
-        union ofp_action action;
+        struct ofpbuf ofpacts;
         struct cls_rule rule;
 
         /* Set up a flow that matches every packet and directs them to
          * OFPP_NORMAL. */
-        memset(&action, 0, sizeof action);
-        action.type = htons(OFPAT10_OUTPUT);
-        action.output.len = htons(sizeof action);
-        action.output.port = htons(OFPP_NORMAL);
+        ofpbuf_init(&ofpacts, OFPACT_OUTPUT_SIZE + OFPACT_END_SIZE);
+        ofpact_put_OUTPUT(&ofpacts)->port = OFPP_NORMAL;
+        ofpact_put_END(&ofpacts);
 
         cls_rule_init_catchall(&rule, FAIL_OPEN_PRIORITY);
-        ofproto_add_flow(fo->ofproto, &rule, &action, 1);
+        ofproto_add_flow(fo->ofproto, &rule, ofpacts.data, ofpacts.size);
+
+        ofpbuf_uninit(&ofpacts);
     }
 }
 
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index f51182a..8013598 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -42,6 +42,7 @@
 #include "odp-util.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
+#include "ofp-actions.h"
 #include "ofp-parse.h"
 #include "ofp-print.h"
 #include "ofproto-dpif-governor.h"
@@ -280,12 +281,10 @@ static void action_xlate_ctx_init(struct action_xlate_ctx *,
                                   struct ofproto_dpif *, const struct flow *,
                                   ovs_be16 initial_tci, struct rule_dpif *,
                                   uint8_t tcp_flags, const struct ofpbuf *);
-static void xlate_actions(struct action_xlate_ctx *,
-                          const union ofp_action *in, size_t n_in,
+static void xlate_actions(struct action_xlate_ctx *, const struct ofpact *in,
                           struct ofpbuf *odp_actions);
 static void xlate_actions_for_side_effects(struct action_xlate_ctx *,
-                                           const union ofp_action *in,
-                                           size_t n_in);
+                                           const struct ofpact *in);
 
 static size_t put_userspace_action(const struct ofproto_dpif *,
                                    struct ofpbuf *odp_actions,
@@ -804,7 +803,7 @@ construct(struct ofproto *ofproto_)
 
 static int
 add_internal_flow(struct ofproto_dpif *ofproto, int id,
-                  const struct ofpbuf *actions, struct rule_dpif **rulep)
+                  const struct ofpbuf *ofpacts, struct rule_dpif **rulep)
 {
     struct ofputil_flow_mod fm;
     int error;
@@ -821,8 +820,8 @@ add_internal_flow(struct ofproto_dpif *ofproto, int id,
     fm.buffer_id = 0;
     fm.out_port = 0;
     fm.flags = 0;
-    fm.actions = actions->data;
-    fm.n_actions = actions->size / sizeof(union ofp_action);
+    fm.ofpacts = ofpacts->data;
+    fm.ofpacts_len = ofpacts->size;
 
     error = ofproto_flow_mod(&ofproto->up, &fm);
     if (error) {
@@ -840,26 +839,29 @@ add_internal_flow(struct ofproto_dpif *ofproto, int id,
 static int
 add_internal_flows(struct ofproto_dpif *ofproto)
 {
-    struct nx_action_controller *nac;
-    uint64_t actions_stub[128 / 8];
-    struct ofpbuf actions;
+    struct ofpact_controller *controller;
+    uint64_t ofpacts_stub[128 / 8];
+    struct ofpbuf ofpacts;
     int error;
     int id;
 
-    ofpbuf_use_stack(&actions, actions_stub, sizeof actions_stub);
+    ofpbuf_use_stack(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
     id = 1;
 
-    nac = ofputil_put_NXAST_CONTROLLER(&actions);
-    nac->max_len = htons(UINT16_MAX);
-    nac->controller_id = htons(0);
-    nac->reason = OFPR_NO_MATCH;
-    error = add_internal_flow(ofproto, id++, &actions, &ofproto->miss_rule);
+    controller = ofpact_put_CONTROLLER(&ofpacts);
+    controller->max_len = UINT16_MAX;
+    controller->controller_id = 0;
+    controller->reason = OFPR_NO_MATCH;
+    ofpact_put_END(&ofpacts);
+
+    error = add_internal_flow(ofproto, id++, &ofpacts, &ofproto->miss_rule);
     if (error) {
         return error;
     }
 
-    ofpbuf_clear(&actions);
-    error = add_internal_flow(ofproto, id++, &actions,
+    ofpbuf_clear(&ofpacts);
+    ofpact_put_END(&ofpacts);
+    error = add_internal_flow(ofproto, id++, &ofpacts,
                               &ofproto->no_packet_in_rule);
     return error;
 }
@@ -2864,8 +2866,7 @@ handle_flow_miss_without_facet(struct flow_miss *miss,
         action_xlate_ctx_init(&ctx, ofproto, &miss->flow, miss->initial_tci,
                               rule, 0, packet);
         ctx.resubmit_stats = &stats;
-        xlate_actions(&ctx, rule->up.actions, rule->up.n_actions,
-                      &odp_actions);
+        xlate_actions(&ctx, rule->up.ofpacts, &odp_actions);
 
         if (odp_actions.size) {
             struct dpif_execute *execute = &op->dpif_op.u.execute;
@@ -3699,8 +3700,7 @@ facet_learn(struct facet *facet)
                           facet->flow.vlan_tci,
                           facet->rule, facet->tcp_flags, NULL);
     ctx.may_learn = true;
-    xlate_actions_for_side_effects(&ctx, facet->rule->up.actions,
-                                   facet->rule->up.n_actions);
+    xlate_actions_for_side_effects(&ctx, facet->rule->up.ofpacts);
 }
 
 static void
@@ -3761,10 +3761,15 @@ facet_account(struct facet *facet)
 static bool
 facet_is_controller_flow(struct facet *facet)
 {
-    return (facet
-            && facet->rule->up.n_actions == 1
-            && action_outputs_to_port(&facet->rule->up.actions[0],
-                                      htons(OFPP_CONTROLLER)));
+    if (facet) {
+        const struct ofpact *ofpact = facet->rule->up.ofpacts;
+
+        if (ofpact->type == OFPACT_CONTROLLER &&
+            ofpact_next(ofpact)->type == OFPACT_END) {
+            return true;
+        }
+    }
+    return false;
 }
 
 /* Folds all of 'facet''s statistics into its rule.  Also updates the
@@ -3939,8 +3944,7 @@ facet_check_consistency(struct facet *facet)
 
         action_xlate_ctx_init(&ctx, ofproto, &facet->flow,
                               subfacet->initial_tci, rule, 0, NULL);
-        xlate_actions(&ctx, rule->up.actions, rule->up.n_actions,
-                      &odp_actions);
+        xlate_actions(&ctx, rule->up.ofpacts, &odp_actions);
 
         if (subfacet->path == SF_NOT_INSTALLED) {
             /* This only happens if the datapath reported an error when we
@@ -4049,8 +4053,7 @@ facet_revalidate(struct facet *facet)
 
         action_xlate_ctx_init(&ctx, ofproto, &facet->flow,
                               subfacet->initial_tci, new_rule, 0, NULL);
-        xlate_actions(&ctx, new_rule->up.actions, new_rule->up.n_actions,
-                      &odp_actions);
+        xlate_actions(&ctx, new_rule->up.ofpacts, &odp_actions);
 
         slow = (subfacet->slow & SLOW_MATCH) | ctx.slow;
         if (subfacet_should_install(subfacet, slow, &odp_actions)) {
@@ -4179,7 +4182,7 @@ flow_push_stats(struct rule_dpif *rule,
     action_xlate_ctx_init(&ctx, ofproto, flow, flow->vlan_tci, rule,
                           0, NULL);
     ctx.resubmit_stats = stats;
-    xlate_actions_for_side_effects(&ctx, rule->up.actions, rule->up.n_actions);
+    xlate_actions_for_side_effects(&ctx, rule->up.ofpacts);
 }
 
 /* Subfacets. */
@@ -4339,7 +4342,7 @@ subfacet_make_actions(struct subfacet *subfacet, const struct ofpbuf *packet,
 
     action_xlate_ctx_init(&ctx, ofproto, &facet->flow, subfacet->initial_tci,
                           rule, 0, packet);
-    xlate_actions(&ctx, rule->up.actions, rule->up.n_actions, odp_actions);
+    xlate_actions(&ctx, rule->up.ofpacts, odp_actions);
     facet->tags = ctx.tags;
     facet->has_learn = ctx.has_learn;
     facet->has_normal = ctx.has_normal;
@@ -4576,8 +4579,8 @@ rule_construct(struct rule *rule_)
     uint8_t table_id;
     enum ofperr error;
 
-    error = validate_actions(rule->up.actions, rule->up.n_actions,
-                             &rule->up.cr.flow, ofproto->max_ports);
+    error = ofpacts_check(rule->up.ofpacts,
+                          &rule->up.cr.flow, ofproto->max_ports);
     if (error) {
         return error;
     }
@@ -4669,7 +4672,7 @@ rule_execute(struct rule *rule_, const struct flow *flow,
     action_xlate_ctx_init(&ctx, ofproto, flow, flow->vlan_tci,
                           rule, stats.tcp_flags, packet);
     ctx.resubmit_stats = &stats;
-    xlate_actions(&ctx, rule->up.actions, rule->up.n_actions, &odp_actions);
+    xlate_actions(&ctx, rule->up.ofpacts, &odp_actions);
 
     execute_odp_actions(ofproto, flow, odp_actions.data,
                         odp_actions.size, packet);
@@ -4686,8 +4689,8 @@ rule_modify_actions(struct rule *rule_)
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
     enum ofperr error;
 
-    error = validate_actions(rule->up.actions, rule->up.n_actions,
-                             &rule->up.cr.flow, ofproto->max_ports);
+    error = ofpacts_check(rule->up.ofpacts, &rule->up.cr.flow,
+                          ofproto->max_ports);
     if (error) {
         ofoperation_complete(rule->up.pending, error);
         return;
@@ -4740,8 +4743,7 @@ send_packet(const struct ofport_dpif *ofport, struct ofpbuf *packet)
 
 /* OpenFlow to datapath action translation. */
 
-static void do_xlate_actions(const union ofp_action *in, size_t n_in,
-                             struct action_xlate_ctx *ctx);
+static void do_xlate_actions(const struct ofpact *, struct action_xlate_ctx *);
 static void xlate_normal(struct action_xlate_ctx *);
 
 /* Composes an ODP action for a "slow path" action for 'flow' within 'ofproto'.
@@ -4987,7 +4989,7 @@ xlate_table_action(struct action_xlate_ctx *ctx,
 
             ctx->recurse++;
             ctx->rule = rule;
-            do_xlate_actions(rule->up.actions, rule->up.n_actions, ctx);
+            do_xlate_actions(rule->up.ofpacts, ctx);
             ctx->rule = old_rule;
             ctx->recurse--;
         }
@@ -5003,16 +5005,21 @@ xlate_table_action(struct action_xlate_ctx *ctx,
 }
 
 static void
-xlate_resubmit_table(struct action_xlate_ctx *ctx,
-                     const struct nx_action_resubmit *nar)
+xlate_ofpact_resubmit(struct action_xlate_ctx *ctx,
+                      const struct ofpact_resubmit *resubmit)
 {
     uint16_t in_port;
     uint8_t table_id;
 
-    in_port = (nar->in_port == htons(OFPP_IN_PORT)
-               ? ctx->flow.in_port
-               : ntohs(nar->in_port));
-    table_id = nar->table == 255 ? ctx->table_id : nar->table;
+    in_port = resubmit->in_port;
+    if (in_port == OFPP_IN_PORT) {
+        in_port = ctx->flow.in_port;
+    }
+
+    table_id = resubmit->table_id;
+    if (table_id == 255) {
+        table_id = ctx->table_id;
+    }
 
     xlate_table_action(ctx, in_port, table_id);
 }
@@ -5125,8 +5132,8 @@ compose_dec_ttl(struct action_xlate_ctx *ctx)
 }
 
 static void
-xlate_output_action__(struct action_xlate_ctx *ctx,
-                      uint16_t port, uint16_t max_len)
+xlate_output_action(struct action_xlate_ctx *ctx,
+                    uint16_t port, uint16_t max_len)
 {
     uint16_t prev_nf_output_iface = ctx->nf_output_iface;
 
@@ -5173,44 +5180,32 @@ xlate_output_action__(struct action_xlate_ctx *ctx,
 
 static void
 xlate_output_reg_action(struct action_xlate_ctx *ctx,
-                        const struct nx_action_output_reg *naor)
+                        const struct ofpact_output_reg *or)
 {
-    struct mf_subfield src;
-    uint64_t ofp_port;
-
-    nxm_decode(&src, naor->src, naor->ofs_nbits);
-    ofp_port = mf_get_subfield(&src, &ctx->flow);
-
-    if (ofp_port <= UINT16_MAX) {
-        xlate_output_action__(ctx, ofp_port, ntohs(naor->max_len));
+    uint64_t port = mf_get_subfield(&or->src, &ctx->flow);
+    if (port <= UINT16_MAX) {
+        xlate_output_action(ctx, port, or->max_len);
     }
 }
 
 static void
-xlate_output_action(struct action_xlate_ctx *ctx,
-                    const struct ofp_action_output *oao)
-{
-    xlate_output_action__(ctx, ntohs(oao->port), ntohs(oao->max_len));
-}
-
-static void
 xlate_enqueue_action(struct action_xlate_ctx *ctx,
-                     const struct ofp_action_enqueue *oae)
+                     const struct ofpact_enqueue *enqueue)
 {
-    uint16_t ofp_port;
+    uint16_t ofp_port = enqueue->port;
+    uint32_t queue_id = enqueue->queue;
     uint32_t flow_priority, priority;
     int error;
 
-    error = dpif_queue_to_priority(ctx->ofproto->dpif, ntohl(oae->queue_id),
-                                   &priority);
+    /* Translate queue to priority. */
+    error = dpif_queue_to_priority(ctx->ofproto->dpif, queue_id, &priority);
     if (error) {
         /* Fall back to ordinary output action. */
-        xlate_output_action__(ctx, ntohs(oae->port), 0);
+        xlate_output_action(ctx, enqueue->port, 0);
         return;
     }
 
-    /* Figure out datapath output port. */
-    ofp_port = ntohs(oae->port);
+    /* Check output port. */
     if (ofp_port == OFPP_IN_PORT) {
         ofp_port = ctx->flow.in_port;
     } else if (ofp_port == ctx->flow.in_port) {
@@ -5232,21 +5227,16 @@ xlate_enqueue_action(struct action_xlate_ctx *ctx,
 }
 
 static void
-xlate_set_queue_action(struct action_xlate_ctx *ctx,
-                       const struct nx_action_set_queue *nasq)
+xlate_set_queue_action(struct action_xlate_ctx *ctx, uint32_t queue_id)
 {
-    uint32_t priority;
-    int error;
+    uint32_t skb_priority;
 
-    error = dpif_queue_to_priority(ctx->ofproto->dpif, ntohl(nasq->queue_id),
-                                   &priority);
-    if (error) {
-        /* Couldn't translate queue to a priority, so ignore.  A warning
+    if (!dpif_queue_to_priority(ctx->ofproto->dpif, queue_id, &skb_priority)) {
+        ctx->flow.skb_priority = skb_priority;
+    } else {
+        /* Couldn't translate queue to a priority.  Nothing to do.  A warning
          * has already been logged. */
-        return;
     }
-
-    ctx->flow.skb_priority = priority;
 }
 
 struct xlate_reg_state {
@@ -5256,9 +5246,9 @@ struct xlate_reg_state {
 
 static void
 xlate_autopath(struct action_xlate_ctx *ctx,
-               const struct nx_action_autopath *naa)
+               const struct ofpact_autopath *ap)
 {
-    uint16_t ofp_port = ntohl(naa->id);
+    uint16_t ofp_port = ap->port;
     struct ofport_dpif *port = get_ofp_port(ctx->ofproto, ofp_port);
 
     if (!port || !port->bundle) {
@@ -5271,7 +5261,7 @@ xlate_autopath(struct action_xlate_ctx *ctx,
             ofp_port = slave->up.ofp_port;
         }
     }
-    autopath_execute(naa, &ctx->flow, ofp_port);
+    nxm_reg_load(&ap->dst, ofp_port, &ctx->flow);
 }
 
 static bool
@@ -5297,14 +5287,31 @@ slave_enabled_cb(uint16_t ofp_port, void *ofproto_)
 }
 
 static void
+xlate_bundle_action(struct action_xlate_ctx *ctx,
+                    const struct ofpact_bundle *bundle)
+{
+    uint16_t port;
+
+    port = bundle_execute(bundle, &ctx->flow, slave_enabled_cb, ctx->ofproto);
+    if (bundle->dst.field) {
+        nxm_reg_load(&bundle->dst, port, &ctx->flow);
+    } else {
+        xlate_output_action(ctx, port, 0);
+    }
+}
+
+static void
 xlate_learn_action(struct action_xlate_ctx *ctx,
-                   const struct nx_action_learn *learn)
+                   const struct ofpact_learn *learn)
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
     struct ofputil_flow_mod fm;
+    uint64_t ofpacts_stub[1024 / 8];
+    struct ofpbuf ofpacts;
     int error;
 
-    learn_execute(learn, &ctx->flow, &fm);
+    ofpbuf_use_stack(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
+    learn_execute(learn, &ctx->flow, &fm, &ofpacts);
 
     error = ofproto_flow_mod(&ctx->ofproto->up, &fm);
     if (error && !VLOG_DROP_WARN(&rl)) {
@@ -5312,7 +5319,7 @@ xlate_learn_action(struct action_xlate_ctx *ctx,
                   ofperr_get_name(error));
     }
 
-    free(fm.actions);
+    ofpbuf_uninit(&ofpacts);
 }
 
 /* Reduces '*timeout' to no more than 'max'.  A value of zero in either case
@@ -5327,13 +5334,13 @@ reduce_timeout(uint16_t max, uint16_t *timeout)
 
 static void
 xlate_fin_timeout(struct action_xlate_ctx *ctx,
-                  const struct nx_action_fin_timeout *naft)
+                  const struct ofpact_fin_timeout *oft)
 {
     if (ctx->tcp_flags & (TCP_FIN | TCP_RST) && ctx->rule) {
         struct rule_dpif *rule = ctx->rule;
 
-        reduce_timeout(ntohs(naft->fin_idle_timeout), &rule->up.idle_timeout);
-        reduce_timeout(ntohs(naft->fin_hard_timeout), &rule->up.hard_timeout);
+        reduce_timeout(oft->fin_idle_timeout, &rule->up.idle_timeout);
+        reduce_timeout(oft->fin_hard_timeout, &rule->up.hard_timeout);
     }
 }
 
@@ -5359,13 +5366,11 @@ may_receive(const struct ofport_dpif *port, struct action_xlate_ctx *ctx)
 }
 
 static void
-do_xlate_actions(const union ofp_action *in, size_t n_in,
-                 struct action_xlate_ctx *ctx)
+do_xlate_actions(const struct ofpact *ofpacts, struct action_xlate_ctx *ctx)
 {
     const struct ofport_dpif *port;
-    const union ofp_action *ia;
     bool was_evictable = true;
-    size_t left;
+    const struct ofpact *a;
 
     port = get_ofp_port(ctx->ofproto, ctx->flow.in_port);
     if (port && !may_receive(port, ctx)) {
@@ -5378,184 +5383,149 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
         was_evictable = ctx->rule->up.evictable;
         ctx->rule->up.evictable = false;
     }
-    OFPUTIL_ACTION_FOR_EACH_UNSAFE (ia, left, in, n_in) {
-        const struct ofp_action_dl_addr *oada;
-        const struct nx_action_resubmit *nar;
-        const struct nx_action_set_tunnel *nast;
-        const struct nx_action_set_queue *nasq;
-        const struct nx_action_multipath *nam;
-        const struct nx_action_autopath *naa;
-        const struct nx_action_bundle *nab;
-        const struct nx_action_output_reg *naor;
-        const struct nx_action_controller *nac;
-        enum ofputil_action_code code;
-        ovs_be64 tun_id;
+    OFPACT_FOR_EACH (a, ofpacts) {
+        struct ofpact_controller *controller;
 
         if (ctx->exit) {
             break;
         }
 
-        code = ofputil_decode_action_unsafe(ia);
-        switch (code) {
-        case OFPUTIL_ACTION_INVALID:
-            NOT_REACHED();
+        switch (a->type) {
+        case OFPACT_END:
+            goto out;
+
+        case OFPACT_OUTPUT:
+            xlate_output_action(ctx, ofpact_get_OUTPUT(a)->port,
+                                ofpact_get_OUTPUT(a)->max_len);
+            break;
+
+        case OFPACT_CONTROLLER:
+            controller = ofpact_get_CONTROLLER(a);
+            execute_controller_action(ctx, controller->max_len,
+                                      controller->reason,
+                                      controller->controller_id);
+            break;
 
-        case OFPUTIL_OFPAT10_OUTPUT:
-            xlate_output_action(ctx, &ia->output);
+        case OFPACT_ENQUEUE:
+            xlate_enqueue_action(ctx, ofpact_get_ENQUEUE(a));
             break;
 
-        case OFPUTIL_OFPAT10_SET_VLAN_VID:
+        case OFPACT_SET_VLAN_VID:
             ctx->flow.vlan_tci &= ~htons(VLAN_VID_MASK);
-            ctx->flow.vlan_tci |= ia->vlan_vid.vlan_vid | htons(VLAN_CFI);
+            ctx->flow.vlan_tci |= (htons(ofpact_get_SET_VLAN_VID(a)->vlan_vid)
+                                   | htons(VLAN_CFI));
             break;
 
-        case OFPUTIL_OFPAT10_SET_VLAN_PCP:
+        case OFPACT_SET_VLAN_PCP:
             ctx->flow.vlan_tci &= ~htons(VLAN_PCP_MASK);
-            ctx->flow.vlan_tci |= htons(
-                (ia->vlan_pcp.vlan_pcp << VLAN_PCP_SHIFT) | VLAN_CFI);
+            ctx->flow.vlan_tci |= htons((ofpact_get_SET_VLAN_PCP(a)->vlan_pcp
+                                         << VLAN_PCP_SHIFT)
+                                        | VLAN_CFI);
             break;
 
-        case OFPUTIL_OFPAT10_STRIP_VLAN:
+        case OFPACT_STRIP_VLAN:
             ctx->flow.vlan_tci = htons(0);
             break;
 
-        case OFPUTIL_OFPAT10_SET_DL_SRC:
-            oada = ((struct ofp_action_dl_addr *) ia);
-            memcpy(ctx->flow.dl_src, oada->dl_addr, ETH_ADDR_LEN);
+        case OFPACT_SET_ETH_SRC:
+            memcpy(ctx->flow.dl_src, ofpact_get_SET_ETH_SRC(a)->mac,
+                   ETH_ADDR_LEN);
             break;
 
-        case OFPUTIL_OFPAT10_SET_DL_DST:
-            oada = ((struct ofp_action_dl_addr *) ia);
-            memcpy(ctx->flow.dl_dst, oada->dl_addr, ETH_ADDR_LEN);
+        case OFPACT_SET_ETH_DST:
+            memcpy(ctx->flow.dl_dst, ofpact_get_SET_ETH_DST(a)->mac,
+                   ETH_ADDR_LEN);
             break;
 
-        case OFPUTIL_OFPAT10_SET_NW_SRC:
-            ctx->flow.nw_src = ia->nw_addr.nw_addr;
+        case OFPACT_SET_IPV4_SRC:
+            ctx->flow.nw_src = ofpact_get_SET_IPV4_SRC(a)->ipv4;
             break;
 
-        case OFPUTIL_OFPAT10_SET_NW_DST:
-            ctx->flow.nw_dst = ia->nw_addr.nw_addr;
+        case OFPACT_SET_IPV4_DST:
+            ctx->flow.nw_dst = ofpact_get_SET_IPV4_DST(a)->ipv4;
             break;
 
-        case OFPUTIL_OFPAT10_SET_NW_TOS:
+        case OFPACT_SET_IPV4_DSCP:
             /* OpenFlow 1.0 only supports IPv4. */
             if (ctx->flow.dl_type == htons(ETH_TYPE_IP)) {
                 ctx->flow.nw_tos &= ~IP_DSCP_MASK;
-                ctx->flow.nw_tos |= ia->nw_tos.nw_tos & IP_DSCP_MASK;
+                ctx->flow.nw_tos |= ofpact_get_SET_IPV4_DSCP(a)->dscp;
             }
             break;
 
-        case OFPUTIL_OFPAT10_SET_TP_SRC:
-            ctx->flow.tp_src = ia->tp_port.tp_port;
-            break;
-
-        case OFPUTIL_OFPAT10_SET_TP_DST:
-            ctx->flow.tp_dst = ia->tp_port.tp_port;
+        case OFPACT_SET_L4_SRC_PORT:
+            ctx->flow.tp_src = htons(ofpact_get_SET_L4_SRC_PORT(a)->port);
             break;
 
-        case OFPUTIL_OFPAT10_ENQUEUE:
-            xlate_enqueue_action(ctx, (const struct ofp_action_enqueue *) ia);
+        case OFPACT_SET_L4_DST_PORT:
+            ctx->flow.tp_dst = htons(ofpact_get_SET_L4_DST_PORT(a)->port);
             break;
 
-        case OFPUTIL_NXAST_RESUBMIT:
-            nar = (const struct nx_action_resubmit *) ia;
-            xlate_table_action(ctx, ntohs(nar->in_port), ctx->table_id);
+        case OFPACT_RESUBMIT:
+            xlate_ofpact_resubmit(ctx, ofpact_get_RESUBMIT(a));
             break;
 
-        case OFPUTIL_NXAST_RESUBMIT_TABLE:
-            xlate_resubmit_table(ctx, (const struct nx_action_resubmit *) ia);
+        case OFPACT_SET_TUNNEL:
+            ctx->flow.tun_id = htonll(ofpact_get_SET_TUNNEL(a)->tun_id);
             break;
 
-        case OFPUTIL_NXAST_SET_TUNNEL:
-            nast = (const struct nx_action_set_tunnel *) ia;
-            tun_id = htonll(ntohl(nast->tun_id));
-            ctx->flow.tun_id = tun_id;
+        case OFPACT_SET_QUEUE:
+            xlate_set_queue_action(ctx, ofpact_get_SET_QUEUE(a)->queue_id);
             break;
 
-        case OFPUTIL_NXAST_SET_QUEUE:
-            nasq = (const struct nx_action_set_queue *) ia;
-            xlate_set_queue_action(ctx, nasq);
-            break;
-
-        case OFPUTIL_NXAST_POP_QUEUE:
+        case OFPACT_POP_QUEUE:
             ctx->flow.skb_priority = ctx->orig_skb_priority;
             break;
 
-        case OFPUTIL_NXAST_REG_MOVE:
-            nxm_execute_reg_move((const struct nx_action_reg_move *) ia,
-                                 &ctx->flow);
+        case OFPACT_REG_MOVE:
+            nxm_execute_reg_move(ofpact_get_REG_MOVE(a), &ctx->flow);
             break;
 
-        case OFPUTIL_NXAST_REG_LOAD:
-            nxm_execute_reg_load((const struct nx_action_reg_load *) ia,
-                                 &ctx->flow);
+        case OFPACT_REG_LOAD:
+            nxm_execute_reg_load(ofpact_get_REG_LOAD(a), &ctx->flow);
             break;
 
-        case OFPUTIL_NXAST_NOTE:
-            /* Nothing to do. */
-            break;
-
-        case OFPUTIL_NXAST_SET_TUNNEL64:
-            tun_id = ((const struct nx_action_set_tunnel64 *) ia)->tun_id;
-            ctx->flow.tun_id = tun_id;
+        case OFPACT_DEC_TTL:
+            if (compose_dec_ttl(ctx)) {
+                goto out;
+            }
             break;
 
-        case OFPUTIL_NXAST_MULTIPATH:
-            nam = (const struct nx_action_multipath *) ia;
-            multipath_execute(nam, &ctx->flow);
+        case OFPACT_NOTE:
+            /* Nothing to do. */
             break;
 
-        case OFPUTIL_NXAST_AUTOPATH:
-            naa = (const struct nx_action_autopath *) ia;
-            xlate_autopath(ctx, naa);
+        case OFPACT_MULTIPATH:
+            multipath_execute(ofpact_get_MULTIPATH(a), &ctx->flow);
             break;
 
-        case OFPUTIL_NXAST_BUNDLE:
-            ctx->ofproto->has_bundle_action = true;
-            nab = (const struct nx_action_bundle *) ia;
-            xlate_output_action__(ctx, bundle_execute(nab, &ctx->flow,
-                                                      slave_enabled_cb,
-                                                      ctx->ofproto), 0);
+        case OFPACT_AUTOPATH:
+            xlate_autopath(ctx, ofpact_get_AUTOPATH(a));
             break;
 
-        case OFPUTIL_NXAST_BUNDLE_LOAD:
+        case OFPACT_BUNDLE:
             ctx->ofproto->has_bundle_action = true;
-            nab = (const struct nx_action_bundle *) ia;
-            bundle_execute_load(nab, &ctx->flow, slave_enabled_cb,
-                                ctx->ofproto);
+            xlate_bundle_action(ctx, ofpact_get_BUNDLE(a));
             break;
 
-        case OFPUTIL_NXAST_OUTPUT_REG:
-            naor = (const struct nx_action_output_reg *) ia;
-            xlate_output_reg_action(ctx, naor);
+        case OFPACT_OUTPUT_REG:
+            xlate_output_reg_action(ctx, ofpact_get_OUTPUT_REG(a));
             break;
 
-        case OFPUTIL_NXAST_LEARN:
+        case OFPACT_LEARN:
             ctx->has_learn = true;
             if (ctx->may_learn) {
-                xlate_learn_action(ctx, (const struct nx_action_learn *) ia);
+                xlate_learn_action(ctx, ofpact_get_LEARN(a));
             }
             break;
 
-        case OFPUTIL_NXAST_DEC_TTL:
-            if (compose_dec_ttl(ctx)) {
-                goto out;
-            }
-            break;
-
-        case OFPUTIL_NXAST_EXIT:
+        case OFPACT_EXIT:
             ctx->exit = true;
             break;
 
-        case OFPUTIL_NXAST_FIN_TIMEOUT:
+        case OFPACT_FIN_TIMEOUT:
             ctx->has_fin_timeout = true;
-            xlate_fin_timeout(ctx, (const struct nx_action_fin_timeout *) ia);
-            break;
-
-        case OFPUTIL_NXAST_CONTROLLER:
-            nac = (const struct nx_action_controller *) ia;
-            execute_controller_action(ctx, ntohs(nac->max_len), nac->reason,
-                                      ntohs(nac->controller_id));
+            xlate_fin_timeout(ctx, ofpact_get_FIN_TIMEOUT(a));
             break;
         }
     }
@@ -5594,8 +5564,7 @@ action_xlate_ctx_init(struct action_xlate_ctx *ctx,
 /* Translates the 'n_in' "union ofp_action"s in 'in' into datapath actions in
  * 'odp_actions', using 'ctx'. */
 static void
-xlate_actions(struct action_xlate_ctx *ctx,
-              const union ofp_action *in, size_t n_in,
+xlate_actions(struct action_xlate_ctx *ctx, const struct ofpact *ofpacts,
               struct ofpbuf *odp_actions)
 {
     /* Normally false.  Set to true if we ever hit MAX_RESUBMIT_RECURSION, so
@@ -5665,7 +5634,7 @@ xlate_actions(struct action_xlate_ctx *ctx,
         ovs_be16 initial_tci = ctx->base_flow.vlan_tci;
 
         add_sflow_action(ctx);
-        do_xlate_actions(in, n_in, ctx);
+        do_xlate_actions(ofpacts, ctx);
 
         if (ctx->max_resubmit_trigger && !ctx->resubmit_hook) {
             if (!hit_resubmit_limit) {
@@ -5704,13 +5673,13 @@ xlate_actions(struct action_xlate_ctx *ctx,
  * using 'ctx', and discards the datapath actions. */
 static void
 xlate_actions_for_side_effects(struct action_xlate_ctx *ctx,
-                               const union ofp_action *in, size_t n_in)
+                               const struct ofpact *in)
 {
     uint64_t odp_actions_stub[1024 / 8];
     struct ofpbuf odp_actions;
 
     ofpbuf_use_stub(&odp_actions, odp_actions_stub, sizeof odp_actions_stub);
-    xlate_actions(ctx, in, n_in, &odp_actions);
+    xlate_actions(ctx, in, &odp_actions);
     ofpbuf_uninit(&odp_actions);
 }
 
@@ -6370,8 +6339,7 @@ set_frag_handling(struct ofproto *ofproto_,
 
 static enum ofperr
 packet_out(struct ofproto *ofproto_, struct ofpbuf *packet,
-           const struct flow *flow,
-           const union ofp_action *ofp_actions, size_t n_ofp_actions)
+           const struct flow *flow, const struct ofpact *ofpacts)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     enum ofperr error;
@@ -6380,8 +6348,7 @@ packet_out(struct ofproto *ofproto_, struct ofpbuf *packet,
         return OFPERR_NXBRC_BAD_IN_PORT;
     }
 
-    error = validate_actions(ofp_actions, n_ofp_actions, flow,
-                             ofproto->max_ports);
+    error = ofpacts_check(ofpacts, flow, ofproto->max_ports);
     if (!error) {
         struct odputil_keybuf keybuf;
         struct dpif_flow_stats stats;
@@ -6403,7 +6370,7 @@ packet_out(struct ofproto *ofproto_, struct ofpbuf *packet,
 
         ofpbuf_use_stub(&odp_actions,
                         odp_actions_stub, sizeof odp_actions_stub);
-        xlate_actions(&ctx, ofp_actions, n_ofp_actions, &odp_actions);
+        xlate_actions(&ctx, ofpacts, &odp_actions);
         dpif_execute(ofproto->dpif, key.data, key.size,
                      odp_actions.data, odp_actions.size, packet);
         ofpbuf_uninit(&odp_actions);
@@ -6560,7 +6527,7 @@ trace_format_rule(struct ds *result, uint8_t table_id, int level,
 
     ds_put_char_multiple(result, '\t', level);
     ds_put_cstr(result, "OpenFlow ");
-    ofp_print_actions(result, rule->up.actions, rule->up.n_actions);
+    ofpacts_format(rule->up.ofpacts, result);
     ds_put_char(result, '\n');
 }
 
@@ -6768,8 +6735,7 @@ ofproto_trace(struct ofproto_dpif *ofproto, const struct flow *flow,
         action_xlate_ctx_init(&trace.ctx, ofproto, flow, initial_tci,
                               rule, tcp_flags, packet);
         trace.ctx.resubmit_hook = trace_resubmit;
-        xlate_actions(&trace.ctx, rule->up.actions, rule->up.n_actions,
-                      &odp_actions);
+        xlate_actions(&trace.ctx, rule->up.ofpacts, &odp_actions);
 
         ds_put_char(ds, '\n');
         trace_format_flow(ds, 0, "Final flow", &trace);
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 2cbb9ae..ae9ace5 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -29,6 +29,7 @@
 #include "shash.h"
 #include "timeval.h"
 
+struct ofpact;
 struct ofputil_flow_mod;
 struct simap;
 
@@ -184,8 +185,8 @@ struct rule {
     struct heap_node evg_node;   /* In eviction_group's "rules" heap. */
     struct eviction_group *eviction_group; /* NULL if not in any group. */
 
-    union ofp_action *actions;   /* OpenFlow actions. */
-    int n_actions;               /* Number of elements in actions[]. */
+    struct ofpact *ofpacts;      /* "OFPACT_*"s.  Ends with OFPACT_END. */
+    unsigned int ofpacts_len;    /* Size of 'ofpacts', in bytes. */
 };
 
 static inline struct rule *
@@ -781,13 +782,11 @@ struct ofproto_class {
      *     registers, then it is an error if 'rule->cr' does not wildcard all
      *     registers.
      *
-     *   - Validate that 'rule->actions' and 'rule->n_actions' are well-formed
-     *     OpenFlow actions that the datapath can correctly implement.  The
-     *     validate_actions() function (in ofp-util.c) can be useful as a model
-     *     for action validation, but it accepts all of the OpenFlow actions
-     *     that OVS understands.  If your ofproto implementation only
-     *     implements a subset of those, then you should implement your own
-     *     action validation.
+     *   - Validate that 'rule->ofpacts' is a sequence of well-formed actions
+     *     that the datapath can correctly implement.  If your ofproto
+     *     implementation only implements a subset of the actions that Open
+     *     vSwitch understands, then you should implement your own action
+     *     validation.
      *
      *   - If the rule is valid, update the datapath flow table, adding the new
      *     rule or replacing the existing one.
@@ -918,14 +917,13 @@ struct ofproto_class {
                               enum ofp_config_flags frag_handling);
 
     /* Implements the OpenFlow OFPT_PACKET_OUT command.  The datapath should
-     * execute the 'n_actions' in the 'actions' array on 'packet'.
+     * execute the sequence of 'ofpacts' (which ends with OFPACT_END).
      *
-     * The caller retains ownership of 'packet', so ->packet_out() should not
-     * modify or free it.
+     * The caller retains ownership of 'packet' and of 'ofpacts', so
+     * ->packet_out() should not modify or free them.
      *
-     * This function must validate that the 'n_actions' elements in 'actions'
-     * are well-formed OpenFlow actions that can be correctly implemented by
-     * the datapath.  If not, then it should return an OpenFlow error code.
+     * This function must validate that it can implement 'ofpacts'.  If not,
+     * then it should return an OpenFlow error code.
      *
      * 'flow' reflects the flow information for 'packet'.  All of the
      * information in 'flow' is extracted from 'packet', except for
@@ -957,8 +955,7 @@ struct ofproto_class {
      * Returns 0 if successful, otherwise an OpenFlow error code. */
     enum ofperr (*packet_out)(struct ofproto *ofproto, struct ofpbuf *packet,
                               const struct flow *flow,
-                              const union ofp_action *actions,
-                              size_t n_actions);
+                              const struct ofpact *ofpacts);
 
 /* ## ------------------------- ## */
 /* ## OFPP_NORMAL configuration ## */
@@ -1195,7 +1192,7 @@ BUILD_ASSERT_DECL(OFPROTO_POSTPONE < OFPERR_OFS);
 
 int ofproto_flow_mod(struct ofproto *, const struct ofputil_flow_mod *);
 void ofproto_add_flow(struct ofproto *, const struct cls_rule *,
-                      const union ofp_action *, size_t n_actions);
+                      const struct ofpact *ofpacts, size_t ofpacts_len);
 bool ofproto_delete_flow(struct ofproto *, const struct cls_rule *);
 void ofproto_flush_flows(struct ofproto *);
 
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 1a2f712..0d3fa1b 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -32,6 +32,7 @@
 #include "meta-flow.h"
 #include "netdev.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-print.h"
 #include "ofp-util.h"
@@ -118,8 +119,8 @@ struct ofoperation {
     struct rule *rule;          /* Rule being operated upon. */
     enum ofoperation_type type; /* Type of operation. */
     struct rule *victim;        /* OFOPERATION_ADDING: Replaced rule. */
-    union ofp_action *actions;  /* OFOPERATION_MODIFYING: Replaced actions. */
-    int n_actions;              /* OFOPERATION_MODIFYING: # of old actions. */
+    struct ofpact *ofpacts;     /* OFOPERATION_MODIFYING: Replaced actions. */
+    size_t ofpacts_len;         /* OFOPERATION_MODIFYING: Bytes of ofpacts. */
     ovs_be64 flow_cookie;       /* Rule's old flow cookie. */
 };
 
@@ -1378,27 +1379,28 @@ ofproto_port_del(struct ofproto *ofproto, uint16_t ofp_port)
  * (0...65535, inclusive) then the flow will be visible to OpenFlow
  * controllers; otherwise, it will be hidden.
  *
- * The caller retains ownership of 'cls_rule' and 'actions'.
+ * The caller retains ownership of 'cls_rule' and 'ofpacts'.
  *
  * This is a helper function for in-band control and fail-open. */
 void
 ofproto_add_flow(struct ofproto *ofproto, const struct cls_rule *cls_rule,
-                 const union ofp_action *actions, size_t n_actions)
+                 const struct ofpact *ofpacts, size_t ofpacts_len)
 {
     const struct rule *rule;
 
     rule = rule_from_cls_rule(classifier_find_rule_exactly(
                                     &ofproto->tables[0].cls, cls_rule));
-    if (!rule || !ofputil_actions_equal(rule->actions, rule->n_actions,
-                                        actions, n_actions)) {
+    if (!rule || !ofpacts_equal(rule->ofpacts, rule->ofpacts_len,
+                                ofpacts, ofpacts_len)) {
         struct ofputil_flow_mod fm;
 
         memset(&fm, 0, sizeof fm);
         fm.cr = *cls_rule;
         fm.buffer_id = UINT32_MAX;
-        fm.actions = (union ofp_action *) actions;
-        fm.n_actions = n_actions;
+        fm.ofpacts = xmemdup(ofpacts, ofpacts_len);
+        fm.ofpacts_len = ofpacts_len;
         add_flow(ofproto, NULL, &fm, NULL);
+        free(fm.ofpacts);
     }
 }
 
@@ -1857,7 +1859,7 @@ static void
 ofproto_rule_destroy__(struct rule *rule)
 {
     if (rule) {
-        free(rule->actions);
+        free(rule->ofpacts);
         rule->ofproto->ofproto_class->rule_dealloc(rule);
     }
 }
@@ -1879,23 +1881,11 @@ ofproto_rule_destroy(struct rule *rule)
 }
 
 /* Returns true if 'rule' has an OpenFlow OFPAT_OUTPUT or OFPAT_ENQUEUE action
- * that outputs to 'out_port' (output to OFPP_FLOOD and OFPP_ALL doesn't
- * count). */
+ * that outputs to 'port' (output to OFPP_FLOOD and OFPP_ALL doesn't count). */
 static bool
-rule_has_out_port(const struct rule *rule, uint16_t out_port)
+rule_has_out_port(const struct rule *rule, uint16_t port)
 {
-    const union ofp_action *oa;
-    size_t left;
-
-    if (out_port == OFPP_NONE) {
-        return true;
-    }
-    OFPUTIL_ACTION_FOR_EACH_UNSAFE (oa, left, rule->actions, rule->n_actions) {
-        if (action_outputs_to_port(oa, htons(out_port))) {
-            return true;
-        }
-    }
-    return false;
+    return port == OFPP_NONE || ofpacts_output_to_port(rule->ofpacts, port);
 }
 
 /* Executes the actions indicated by 'rule' on 'packet' and credits 'rule''s
@@ -2052,6 +2042,8 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_packet_out *opo)
     struct ofproto *p = ofconn_get_ofproto(ofconn);
     struct ofputil_packet_out po;
     struct ofpbuf *payload;
+    uint64_t ofpacts_stub[1024 / 8];
+    struct ofpbuf ofpacts;
     struct flow flow;
     enum ofperr error;
 
@@ -2059,20 +2051,21 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_packet_out *opo)
 
     error = reject_slave_controller(ofconn);
     if (error) {
-        return error;
+        goto exit;
     }
 
     /* Decode message. */
-    error = ofputil_decode_packet_out(&po, opo);
+    ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
+    error = ofputil_decode_packet_out(&po, opo, &ofpacts);
     if (error) {
-        return error;
+        goto exit_free_ofpacts;
     }
 
     /* Get payload. */
     if (po.buffer_id != UINT32_MAX) {
         error = ofconn_pktbuf_retrieve(ofconn, po.buffer_id, &payload, NULL);
         if (error || !payload) {
-            return error;
+            goto exit_free_ofpacts;
         }
     } else {
         payload = xmalloc(sizeof *payload);
@@ -2081,10 +2074,12 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_packet_out *opo)
 
     /* Send out packet. */
     flow_extract(payload, 0, 0, po.in_port, &flow);
-    error = p->ofproto_class->packet_out(p, payload, &flow,
-                                         po.actions, po.n_actions);
+    error = p->ofproto_class->packet_out(p, payload, &flow, po.ofpacts);
     ofpbuf_delete(payload);
 
+exit_free_ofpacts:
+    ofpbuf_uninit(&ofpacts);
+exit:
     return error;
 }
 
@@ -2486,8 +2481,7 @@ handle_flow_stats_request(struct ofconn *ofconn,
         fs.hard_age = age_secs(now - rule->modified);
         ofproto->ofproto_class->rule_get_stats(rule, &fs.packet_count,
                                                &fs.byte_count);
-        fs.actions = rule->actions;
-        fs.n_actions = rule->n_actions;
+        fs.ofpacts = rule->ofpacts;
         ofputil_append_flow_stats_reply(&fs, &replies);
     }
     ofconn_send_replies(ofconn, &replies);
@@ -2513,8 +2507,8 @@ flow_stats_ds(struct rule *rule, struct ds *results)
     ds_put_format(results, "n_bytes=%"PRIu64", ", byte_count);
     cls_rule_format(&rule->cr, results);
     ds_put_char(results, ',');
-    if (rule->n_actions > 0) {
-        ofp_print_actions(results, rule->actions, rule->n_actions);
+    if (rule->ofpacts->type != OFPACT_END) {
+        ofpacts_format(rule->ofpacts, results);
     } else {
         ds_put_cstr(results, "drop");
     }
@@ -2763,6 +2757,9 @@ is_flow_deletion_pending(const struct ofproto *ofproto,
  * error code on failure, or OFPROTO_POSTPONE if the operation cannot be
  * initiated now but may be retried later.
  *
+ * Upon successful return, takes ownership of 'fm->ofpacts'.  On failure,
+ * ownership remains with the caller.
+ *
  * 'ofconn' is used to retrieve the packet buffer specified in ofm->buffer_id,
  * if any. */
 static enum ofperr
@@ -2831,8 +2828,8 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
     rule->hard_timeout = fm->hard_timeout;
     rule->table_id = table - ofproto->tables;
     rule->send_flow_removed = (fm->flags & OFPFF_SEND_FLOW_REM) != 0;
-    rule->actions = ofputil_actions_clone(fm->actions, fm->n_actions);
-    rule->n_actions = fm->n_actions;
+    rule->ofpacts = xmemdup(fm->ofpacts, fm->ofpacts_len);
+    rule->ofpacts_len = fm->ofpacts_len;
     rule->evictable = true;
     rule->eviction_group = NULL;
 
@@ -2914,14 +2911,14 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn,
             continue;
         }
 
-        if (!ofputil_actions_equal(fm->actions, fm->n_actions,
-                                   rule->actions, rule->n_actions)) {
+        if (!ofpacts_equal(fm->ofpacts, fm->ofpacts_len,
+                           rule->ofpacts, rule->ofpacts_len)) {
             ofoperation_create(group, rule, OFOPERATION_MODIFY);
-            rule->pending->actions = rule->actions;
-            rule->pending->n_actions = rule->n_actions;
-            rule->actions = ofputil_actions_clone(fm->actions, fm->n_actions);
-            rule->n_actions = fm->n_actions;
-            ofproto->ofproto_class->rule_modify_actions(rule);
+            rule->pending->ofpacts = rule->ofpacts;
+            rule->pending->ofpacts_len = rule->ofpacts_len;
+            rule->ofpacts = xmemdup(fm->ofpacts, fm->ofpacts_len);
+            rule->ofpacts_len = fm->ofpacts_len;
+            rule->ofproto->ofproto_class->rule_modify_actions(rule);
         } else {
             rule->modified = time_msec();
         }
@@ -3119,30 +3116,35 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
 {
     struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
     struct ofputil_flow_mod fm;
+    uint64_t ofpacts_stub[1024 / 8];
+    struct ofpbuf ofpacts;
     enum ofperr error;
     long long int now;
 
     error = reject_slave_controller(ofconn);
     if (error) {
-        return error;
+        goto exit;
     }
 
-    error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_protocol(ofconn));
+    ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
+    error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_protocol(ofconn),
+                                    &ofpacts);
     if (error) {
-        return error;
+        goto exit_free_ofpacts;
     }
 
     /* We do not support the OpenFlow 1.0 emergency flow cache, which is not
      * required in OpenFlow 1.0.1 and removed from OpenFlow 1.1. */
     if (fm.flags & OFPFF_EMERG) {
-        /* There isn't a good fit for an error code, so just state that the
-         * flow table is full. */
-        return OFPERR_OFPFMFC_ALL_TABLES_FULL;
+        /* We do not support the emergency flow cache.  It will hopefully get
+         * dropped from OpenFlow in the near future.  There is no good error
+         * code, so just state that the flow table is full. */
+        error = OFPERR_OFPFMFC_ALL_TABLES_FULL;
+    } else {
+        error = handle_flow_mod__(ofconn_get_ofproto(ofconn), ofconn, &fm, oh);
     }
-
-    error = handle_flow_mod__(ofconn_get_ofproto(ofconn), ofconn, &fm, oh);
     if (error) {
-        return error;
+        goto exit_free_ofpacts;
     }
 
     /* Record the operation for logging a summary report. */
@@ -3171,7 +3173,10 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
     }
     ofproto->last_op = now;
 
-    return 0;
+exit_free_ofpacts:
+    ofpbuf_uninit(&ofpacts);
+exit:
+    return error;
 }
 
 static enum ofperr
@@ -3607,7 +3612,7 @@ ofoperation_destroy(struct ofoperation *op)
         hmap_remove(&group->ofproto->deletions, &op->hmap_node);
     }
     list_remove(&op->group_node);
-    free(op->actions);
+    free(op->ofpacts);
     free(op);
 
     if (list_is_empty(&group->ops) && !list_is_empty(&group->ofproto_node)) {
@@ -3707,10 +3712,10 @@ ofoperation_complete(struct ofoperation *op, enum ofperr error)
         if (!error) {
             rule->modified = time_msec();
         } else {
-            free(rule->actions);
-            rule->actions = op->actions;
-            rule->n_actions = op->n_actions;
-            op->actions = NULL;
+            free(rule->ofpacts);
+            rule->ofpacts = op->ofpacts;
+            rule->ofpacts_len = op->ofpacts_len;
+            op->ofpacts = NULL;
         }
         break;
 
diff --git a/tests/learn.at b/tests/learn.at
index 9304861..da82f51 100644
--- a/tests/learn.at
+++ b/tests/learn.at
@@ -10,7 +10,7 @@ AT_CHECK([ovs-ofctl parse-flows flows.txt], [0],
 [[usable protocols: any
 chosen protocol: OpenFlow10-table_id
 OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1)
-OFPT_FLOW_MOD (xid=0x2): ADD actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[],load:0x000a->NXM_NX_REG0[5..10])
+OFPT_FLOW_MOD (xid=0x2): ADD actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[],load:0xa->NXM_NX_REG0[5..10])
 OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,idle_timeout=10,hard_timeout=20,fin_idle_timeout=5,fin_hard_timeout=10,priority=10,cookie=0xfedcba9876543210,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
 ]])
 AT_CLEANUP
@@ -42,7 +42,7 @@ ip,actions=learn(eth_type=0x800,OXM_OF_IPV4_DST[])
 AT_CHECK([ovs-ofctl parse-flows flows.txt], [0],
 [[usable protocols: any
 chosen protocol: OpenFlow10-table_id
-OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1,eth_type=0x800,load:0x00000005->NXM_OF_IP_DST[])
+OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1,eth_type=0x800,load:0x5->NXM_OF_IP_DST[])
 OFPT_FLOW_MOD (xid=0x2): ADD ip actions=learn(table=1,load:NXM_OF_IP_DST[]->NXM_NX_REG1[])
 OFPT_FLOW_MOD (xid=0x3): ADD ip actions=learn(table=1,eth_type=0x800,NXM_OF_IP_DST[])
 ]])
diff --git a/tests/test-bundle.c b/tests/test-bundle.c
index 672c426..f2d9b82 100644
--- a/tests/test-bundle.c
+++ b/tests/test-bundle.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011 Nicira, Inc.
+/* Copyright (c) 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 #include <stdlib.h>
 
 #include "flow.h"
+#include "ofp-actions.h"
 #include "ofpbuf.h"
 #include "random.h"
 
@@ -64,22 +65,24 @@ slave_enabled_cb(uint16_t slave_id, void *aux)
     return slave ? slave->enabled : false;
 }
 
-static struct nx_action_bundle *
+static struct ofpact_bundle *
 parse_bundle_actions(char *actions)
 {
-    struct nx_action_bundle *nab;
-    struct ofpbuf b;
+    struct ofpact_bundle *bundle;
+    struct ofpbuf ofpacts;
+    struct ofpact *action;
 
-    ofpbuf_init(&b, 0);
-    bundle_parse_load(&b, actions);
-    nab = ofpbuf_steal_data(&b);
-    ofpbuf_uninit(&b);
+    ofpbuf_init(&ofpacts, 0);
+    bundle_parse_load(actions, &ofpacts);
+    action = ofpacts.data;
+    bundle = ofpact_get_BUNDLE(xmemdup(action, action->len));
+    ofpbuf_uninit(&ofpacts);
 
-    if (ntohs(nab->n_slaves) > MAX_SLAVES) {
+    if (bundle->n_slaves > MAX_SLAVES) {
         ovs_fatal(0, "At most %u slaves are supported", MAX_SLAVES);
     }
 
-    return nab;
+    return bundle;
 }
 
 static const char *
@@ -101,7 +104,7 @@ int
 main(int argc, char *argv[])
 {
     bool ok = true;
-    struct nx_action_bundle *nab;
+    struct ofpact_bundle *bundle;
     struct flow *flows;
     size_t i, n_permute, old_n_enabled;
     struct slave_group sg;
@@ -114,12 +117,12 @@ main(int argc, char *argv[])
         ovs_fatal(0, "usage: %s bundle_action", program_name);
     }
 
-    nab = parse_bundle_actions(argv[1]);
+    bundle = parse_bundle_actions(argv[1]);
 
     /* Generate 'slaves' array. */
     sg.n_slaves = 0;
-    for (i = 0; i < ntohs(nab->n_slaves); i++) {
-        uint16_t slave_id = bundle_get_slave(nab, i);
+    for (i = 0; i < bundle->n_slaves; i++) {
+        uint16_t slave_id = bundle->slaves[i];
 
         if (slave_lookup(&sg, slave_id)) {
             ovs_fatal(0, "Redundant slaves are not supported. ");
@@ -136,10 +139,6 @@ main(int argc, char *argv[])
         flows[i].regs[0] = OFPP_NONE;
     }
 
-    if (bundle_check(nab, 1024, flows)) {
-        ovs_fatal(0, "Bundle action fails to check.");
-    }
-
     /* Cycles through each possible liveness permutation for the given
      * n_slaves.  The initial state is equivalent to all slaves down, so we
      * skip it by starting at i = 1. We do one extra iteration to cover
@@ -188,23 +187,19 @@ main(int argc, char *argv[])
             uint16_t old_slave_id, ofp_port;
 
             old_slave_id = flow->regs[0];
-            ofp_port = bundle_execute(nab, flow, slave_enabled_cb, &sg);
-            bundle_execute_load(nab, flow, slave_enabled_cb, &sg);
-            if (flow->regs[0] != ofp_port) {
-                ovs_fatal(0, "bundle_execute_load() and bundle_execute() "
-                          "disagree");
-            }
+            ofp_port = bundle_execute(bundle, flow, slave_enabled_cb, &sg);
+            flow->regs[0] = ofp_port;
 
-            if (flow->regs[0] != OFPP_NONE) {
-                slave_lookup(&sg, flow->regs[0])->flow_count++;
+            if (ofp_port != OFPP_NONE) {
+                slave_lookup(&sg, ofp_port)->flow_count++;
             }
 
-            if (old_slave_id != flow->regs[0]) {
+            if (old_slave_id != ofp_port) {
                 changed++;
             }
         }
 
-        if (nab->algorithm == htons(NX_BD_ALG_ACTIVE_BACKUP)) {
+        if (bundle->algorithm == NX_BD_ALG_ACTIVE_BACKUP) {
             perfect = active == old_active ? 0.0 : 1.0;
         } else {
             if (old_n_enabled || n_enabled) {
@@ -229,7 +224,7 @@ main(int argc, char *argv[])
             if (slave->enabled) {
                 double perfect_fp;
 
-                if (nab->algorithm == htons(NX_BD_ALG_ACTIVE_BACKUP)) {
+                if (bundle->algorithm == NX_BD_ALG_ACTIVE_BACKUP) {
                     perfect_fp = j == active ? 1.0 : 0.0;
                 } else {
                     perfect_fp = 1.0 / n_enabled;
@@ -262,7 +257,7 @@ main(int argc, char *argv[])
         old_n_enabled = n_enabled;
     }
 
-    free(nab);
+    free(bundle);
     free(flows);
     return ok ? 0 : 1;
 }
diff --git a/tests/test-multipath.c b/tests/test-multipath.c
index 483eb3d..8a35567 100644
--- a/tests/test-multipath.c
+++ b/tests/test-multipath.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Nicira, Inc.
+ * Copyright (c) 2010, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 
 #include "flow.h"
+#include "ofp-actions.h"
 #include "random.h"
 #include "util.h"
 
@@ -32,7 +33,7 @@ int
 main(int argc, char *argv[])
 {
     enum { MP_MAX_LINKS = 63 };
-    struct nx_action_multipath mp;
+    struct ofpact_multipath mp;
     bool ok = true;
     int n;
 
@@ -60,11 +61,11 @@ main(int argc, char *argv[])
 
             random_bytes(&flow, sizeof flow);
 
-            mp.max_link = htons(n - 1);
+            mp.max_link = n - 1;
             multipath_execute(&mp, &flow);
             old_link = flow.regs[0];
 
-            mp.max_link = htons(n);
+            mp.max_link = n;
             multipath_execute(&mp, &flow);
             new_link = flow.regs[0];
 
@@ -91,7 +92,7 @@ main(int argc, char *argv[])
                "stddev/expected=%.4f\n",
                n, n + 1, disruption, perfect, distribution);
 
-        switch (ntohs(mp.algorithm)) {
+        switch (mp.algorithm) {
         case NX_MP_ALG_MODULO_N:
             if (disruption < (n < 2 ? .25 : .5)) {
                 fprintf(stderr, "%d -> %d: disruption=%.2f < .5\n",
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 7413455..e10b966 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -35,9 +35,9 @@
 #include "compiler.h"
 #include "dirs.h"
 #include "dynamic-string.h"
-#include "netlink.h"
 #include "nx-match.h"
 #include "odp-util.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-parse.h"
 #include "ofp-print.h"
@@ -861,7 +861,7 @@ do_flow_mod__(const char *remote, struct ofputil_flow_mod *fms, size_t n_fms)
         struct ofputil_flow_mod *fm = &fms[i];
 
         transact_noreply(vconn, ofputil_encode_flow_mod(fm, protocol));
-        free(fm->actions);
+        free(fm->ofpacts);
     }
     vconn_close(vconn);
 }
@@ -1233,19 +1233,19 @@ static void
 do_packet_out(int argc, char *argv[])
 {
     struct ofputil_packet_out po;
-    struct ofpbuf actions;
+    struct ofpbuf ofpacts;
     struct vconn *vconn;
     int i;
 
-    ofpbuf_init(&actions, sizeof(union ofp_action));
-    parse_ofp_actions(argv[3], &actions);
+    ofpbuf_init(&ofpacts, 64);
+    parse_ofpacts(argv[3], &ofpacts);
 
     po.buffer_id = UINT32_MAX;
     po.in_port = (!strcasecmp(argv[2], "none") ? OFPP_NONE
                   : !strcasecmp(argv[2], "local") ? OFPP_LOCAL
                   : str_to_port_no(argv[1], argv[2]));
-    po.actions = actions.data;
-    po.n_actions = actions.size / sizeof(union ofp_action);
+    po.ofpacts = ofpacts.data;
+    po.ofpacts_len = ofpacts.size;
 
     open_vconn(argv[1], &vconn);
     for (i = 4; i < argc; i++) {
@@ -1264,7 +1264,7 @@ do_packet_out(int argc, char *argv[])
         ofpbuf_delete(packet);
     }
     vconn_close(vconn);
-    ofpbuf_uninit(&actions);
+    ofpbuf_uninit(&ofpacts);
 }
 
 static void
@@ -1482,8 +1482,8 @@ struct fte_version {
     uint16_t idle_timeout;
     uint16_t hard_timeout;
     uint16_t flags;
-    union ofp_action *actions;
-    size_t n_actions;
+    struct ofpact *ofpacts;
+    size_t ofpacts_len;
 };
 
 /* Frees 'version' and the data that it owns. */
@@ -1491,7 +1491,7 @@ static void
 fte_version_free(struct fte_version *version)
 {
     if (version) {
-        free(version->actions);
+        free(version->ofpacts);
         free(version);
     }
 }
@@ -1506,9 +1506,8 @@ fte_version_equals(const struct fte_version *a, const struct fte_version *b)
     return (a->cookie == b->cookie
             && a->idle_timeout == b->idle_timeout
             && a->hard_timeout == b->hard_timeout
-            && a->n_actions == b->n_actions
-            && !memcmp(a->actions, b->actions,
-                       a->n_actions * sizeof *a->actions));
+            && ofpacts_equal(a->ofpacts, a->ofpacts_len,
+                             b->ofpacts, b->ofpacts_len));
 }
 
 /* Prints 'version' on stdout.  Expects the caller to have printed the rule
@@ -1529,7 +1528,7 @@ fte_version_print(const struct fte_version *version)
     }
 
     ds_init(&s);
-    ofp_print_actions(&s, version->actions, version->n_actions);
+    ofpacts_format(version->ofpacts, &s);
     printf(" %s\n", ds_cstr(&s));
     ds_destroy(&s);
 }
@@ -1617,8 +1616,8 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
         version->idle_timeout = fm.idle_timeout;
         version->hard_timeout = fm.hard_timeout;
         version->flags = fm.flags & (OFPFF_SEND_FLOW_REM | OFPFF_EMERG);
-        version->actions = fm.actions;
-        version->n_actions = fm.n_actions;
+        version->ofpacts = fm.ofpacts;
+        version->ofpacts_len = fm.ofpacts_len;
 
         usable_protocols &= ofputil_usable_protocols(&fm.cr);
 
@@ -1684,10 +1683,14 @@ read_flows_from_switch(struct vconn *vconn,
             for (;;) {
                 struct fte_version *version;
                 struct ofputil_flow_stats fs;
+                struct ofpbuf ofpacts;
                 int retval;
 
-                retval = ofputil_decode_flow_stats_reply(&fs, reply, false);
+                ofpbuf_init(&ofpacts, 64);
+                retval = ofputil_decode_flow_stats_reply(&fs, reply, false,
+                                                         &ofpacts);
                 if (retval) {
+                    ofpbuf_uninit(&ofpacts);
                     if (retval != EOF) {
                         ovs_fatal(0, "parse error in reply");
                     }
@@ -1699,9 +1702,8 @@ read_flows_from_switch(struct vconn *vconn,
                 version->idle_timeout = fs.idle_timeout;
                 version->hard_timeout = fs.hard_timeout;
                 version->flags = 0;
-                version->n_actions = fs.n_actions;
-                version->actions = xmemdup(fs.actions,
-                                           fs.n_actions * sizeof *fs.actions);
+                version->ofpacts = ofpbuf_steal_data(&ofpacts);
+                version->ofpacts_len = ofpacts.size;
 
                 fte_insert(cls, &fs.rule, version, index);
             }
@@ -1734,11 +1736,11 @@ fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
     fm.flags = version->flags;
     if (command == OFPFC_ADD || command == OFPFC_MODIFY ||
         command == OFPFC_MODIFY_STRICT) {
-        fm.actions = version->actions;
-        fm.n_actions = version->n_actions;
+        fm.ofpacts = version->ofpacts;
+        fm.ofpacts_len = version->ofpacts_len;
     } else {
-        fm.actions = NULL;
-        fm.n_actions = 0;
+        fm.ofpacts = NULL;
+        fm.ofpacts_len = 0;
     }
 
     ofm = ofputil_encode_flow_mod(&fm, protocol);
@@ -1891,7 +1893,7 @@ do_parse_flows__(struct ofputil_flow_mod *fms, size_t n_fms)
         ofp_print(stdout, msg->data, msg->size, verbosity);
         ofpbuf_delete(msg);
 
-        free(fm->actions);
+        free(fm->ofpacts);
     }
 }
 
-- 
1.7.2.5




More information about the dev mailing list