[ovs-dev] [of1.1 draft v2 2/7] Introduce ofpacts, an abstraction of OpenFlow actions.

Ben Pfaff blp at nicira.com
Fri Feb 3 20:43:50 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.

The following improvements are necessary before this commit is ready:

  - The current commit formats internal actions as Netlink.  This is
    an imperfect choice because Netlink attributes are only 4-byte
    aligned, which makes it awkward to include 64-bit integers and
    pointers inside actions.  It would be nice to figure out a way to
    do this, either by adding a way to 64-bit align Netlink attributes
    or to use a different framing format.

  - Add comments in various places.

  - The new functions in netlink, meta-flow, and ofp-util might be easier
    to review as separate commits.

  - A few minor points marked with XXX.
---
 include/openflow/openflow.h |   10 +-
 lib/automake.mk             |    2 +
 lib/autopath.c              |   70 ++--
 lib/autopath.h              |   14 +-
 lib/bundle.c                |  282 +++++++-------
 lib/bundle.h                |   25 +-
 lib/learn.c                 |  626 ++++++++++++++-----------------
 lib/learn.h                 |   14 +-
 lib/multipath.c             |  145 ++++----
 lib/multipath.h             |   17 +-
 lib/netlink.c               |   45 ++-
 lib/netlink.h               |    5 +
 lib/nx-match.c              |  188 +++++----
 lib/nx-match.h              |   32 ++-
 lib/ofp-actions.c           |  898 +++++++++++++++++++++++++++++++++++++++++++
 lib/ofp-actions.h           |  175 +++++++++
 lib/ofp-parse.c             |  240 ++++++------
 lib/ofp-print.c             |  275 ++------------
 lib/ofp-print.h             |    3 +-
 lib/ofp-util.c              |  333 +++++------------
 lib/ofp-util.h              |   22 +-
 ofproto/connmgr.c           |   16 +-
 ofproto/fail-open.c         |   15 +-
 ofproto/ofproto-dpif.c      |  330 +++++++---------
 ofproto/ofproto-provider.h  |   12 +-
 ofproto/ofproto.c           |  152 ++++----
 tests/learn.at              |    4 +-
 tests/ofp-print.at          |    2 +-
 tests/ovs-ofctl.at          |    2 +-
 tests/test-bundle.c         |   59 ++--
 tests/test-multipath.c      |   11 +-
 utilities/ovs-ofctl.c       |   35 +-
 32 files changed, 2387 insertions(+), 1672 deletions(-)
 create mode 100644 lib/ofp-actions.c
 create mode 100644 lib/ofp-actions.h

diff --git a/include/openflow/openflow.h b/include/openflow/openflow.h
index cd30d32..e624450 100644
--- a/include/openflow/openflow.h
+++ b/include/openflow/openflow.h
@@ -453,10 +453,12 @@ struct ofp_packet_out {
     ovs_be32 buffer_id;           /* ID assigned by datapath (-1 if none). */
     ovs_be16 in_port;             /* Packet's input port (OFPP_NONE if none). */
     ovs_be16 actions_len;         /* Size of action array in bytes. */
-    struct ofp_action_header actions[0]; /* Actions. */
-    /* uint8_t data[0]; */        /* Packet data.  The length is inferred
-                                     from the length field in the header.
-                                     (Only meaningful if buffer_id == -1.) */
+    /* Followed by:
+     *   - Exactly 'actions_len' bytes (possibly 0 bytes, and always a multiple
+     *     of 8) containing actions.
+     *   - If 'buffer_id' != -1, packet data to fill out the remainder of the
+     *     message length.
+     */
 };
 OFP_ASSERT(sizeof(struct ofp_packet_out) == 16);
 
diff --git a/lib/automake.mk b/lib/automake.mk
index 3531ba9..f082c01 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -94,6 +94,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 b8ba3c1..4eb4ba9 100644
--- a/lib/autopath.c
+++ b/lib/autopath.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Nicira Networks.
+ * Copyright (c) 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.
@@ -23,7 +23,9 @@
 
 #include "flow.h"
 #include "meta-flow.h"
+#include "netlink.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "openflow/nicira-ext.h"
@@ -31,32 +33,19 @@
 
 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;
 
     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 +54,52 @@ 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);
+    /* XXX check field is writable */
+    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_to_ofpact(const struct nx_action_autopath *nap,
+                   struct ofpact_autopath *autopath)
 {
-    struct mf_subfield dst;
+    memset(autopath, 0, sizeof *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_openflow10(const struct nlattr *ofpact, struct ofpbuf *openflow)
+{
+    const struct ofpact_autopath *autopath = nl_attr_get(ofpact);
+    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 19e2d07..ccb32ea 100644
--- a/lib/autopath.h
+++ b/lib/autopath.h
@@ -21,16 +21,22 @@
 #include "ofp-errors.h"
 
 struct flow;
+struct nlattr;
 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_to_ofpact(const struct nx_action_autopath *,
+                               struct ofpact_autopath *);
+enum ofperr autopath_check(const struct ofpact_autopath *,
                            const struct flow *);
+void autopath_to_openflow10(const struct nlattr *ofpact,
+                            struct ofpbuf *openflow);
 
 #endif /* autopath.h */
diff --git a/lib/bundle.c b/lib/bundle.c
index 733d79a..9149c1c 100644
--- a/lib/bundle.c
+++ b/lib/bundle.c
@@ -23,8 +23,10 @@
 #include "dynamic-string.h"
 #include "multipath.h"
 #include "meta-flow.h"
+#include "netlink.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 +37,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 +53,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 +74,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 +101,44 @@ 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_to_ofpact(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;
+    size_t start_ofs;
+    struct ofpact_bundle *bundle;
+    uint16_t subtype;
     uint32_t slave_type;
     size_t slaves_size, i;
     enum ofperr error;
 
+    start_ofs = nl_msg_start_attr(ofpacts, OFPACT_BUNDLE);
+    bundle = ofpbuf_put_zeros(ofpacts, sizeof *bundle);
+
     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 +147,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);
+    }
+
+    nl_msg_end_attr(ofpacts, start_ofs);
+    bundle = ofpbuf_at_assert(ofpacts, start_ofs, sizeof *bundle);
+
+    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 +209,51 @@ 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_openflow10(const struct ofpact_bundle *bundle, struct ofpbuf *of10)
+{
+    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(of10)
+           : ofputil_put_NXAST_BUNDLE(of10));
+    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(of10, 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;
+    size_t start_ofs;
 
     if (!slave_delim) {
         ovs_fatal(0, "%s: not enough arguments to bundle action", s);
@@ -212,72 +264,58 @@ 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);
+    start_ofs = nl_msg_start_attr(ofpacts, OFPACT_BUNDLE);
+    bundle = ofpbuf_put_zeros(ofpacts, sizeof *bundle);
 
-    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 = ofpbuf_at_assert(ofpacts, start_ofs, sizeof *bundle);
+        bundle->n_slaves++;
     }
+    nl_msg_end_attr(ofpacts, start_ofs);
+    bundle = ofpbuf_at_assert(ofpacts, start_ofs, sizeof *bundle);
 
-    /* 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 +328,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 +350,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 +376,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 580ecf8..4f2a162 100644
--- a/lib/bundle.h
+++ b/lib/bundle.h
@@ -27,29 +27,24 @@
 
 struct ds;
 struct flow;
+struct nlattr;
+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_to_ofpact(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_openflow10(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/learn.c b/lib/learn.c
index 1676462..d43482f 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -21,7 +21,9 @@
 #include "byte-order.h"
 #include "dynamic-string.h"
 #include "meta-flow.h"
+#include "netlink.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -59,14 +61,6 @@ get_bits(int n_bits, const void **p)
     return value;
 }
 
-static void
-get_subfield(int n_bits, const void **p, struct mf_subfield *sf)
-{
-    sf->field = mf_from_nxm_header(ntohl(get_be32(p)));
-    sf->ofs = ntohs(get_be16(p));
-    sf->n_bits = n_bits;
-}
-
 static unsigned int
 learn_min_len(uint16_t header)
 {
@@ -90,170 +84,129 @@ learn_min_len(uint16_t header)
     return min_len;
 }
 
-static enum ofperr
-learn_check_header(uint16_t header, size_t len)
-{
-    int src_type = header & NX_LEARN_SRC_MASK;
-    int dst_type = header & NX_LEARN_DST_MASK;
-
-    /* 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 {
-        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;
-    }
-
-    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)
+learn_to_ofpact(const struct nx_action_learn *nal, struct ofpbuf *ofpacts)
 {
-    struct cls_rule rule;
+    struct ofpact_learn *learn;
     const void *p, *end;
+    size_t start_ofs;
 
-    cls_rule_init_catchall(&rule, 0);
+    if (!is_all_zeros(nal->pad, sizeof nal->pad)) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
 
-    if (learn->flags & ~htons(OFPFF_SEND_FLOW_REM)
-        || !is_all_zeros(learn->pad, sizeof learn->pad)
-        || learn->table_id == 0xff) {
+    start_ofs = nl_msg_start_attr(ofpacts, OFPACT_LEARN);
+    learn = ofpbuf_put_zeros(ofpacts, sizeof *learn);
+
+    learn->idle_timeout = ntohs(nal->idle_timeout);
+    learn->hard_timeout = ntohs(nal->hard_timeout);
+    learn->priority = ntohs(nal->priority);
+    put_32aligned_be64(&learn->cookie, nal->cookie);
+    learn->flags = ntohs(nal->flags);
+    learn->table_id = nal->table_id;
+
+    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 = ofpbuf_at_assert(ofpacts, start_ofs, sizeof *learn);
+        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;
 
-            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) {
+            spec->src.field = mf_from_nxm_header(ntohl(get_be32(&p)));
+            spec->src.ofs = ntohs(get_be16(&p));
+            spec->src.n_bits = n_bits;
+        } else {
+            put_32aligned_u64(&spec->imm, get_bits(n_bits, &p));
+        }
 
-            if (dst_type == NX_LEARN_DST_MATCH
-                && src_type == NX_LEARN_SRC_IMMEDIATE) {
-                mf_set_subfield(&dst, value, &rule);
-            }
+        /* Get the destination. */
+        if (spec->dst_type == NX_LEARN_DST_MATCH ||
+            spec->dst_type == NX_LEARN_DST_LOAD) {
+            spec->dst.field = mf_from_nxm_header(ntohl(get_be32(&p)));
+            spec->dst.ofs = ntohs(get_be16(&p));
+            spec->dst.n_bits = n_bits;
         }
     }
     if (!is_all_zeros(p, (char *) end - (char *) p)) {
         return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
 
+    nl_msg_end_attr(ofpacts, start_ofs);
+
     return 0;
 }
 
-void
-learn_execute(const struct nx_action_learn *learn, const struct flow *flow,
-              struct ofputil_flow_mod *fm)
+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 = 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);
-
-    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;
-        uint64_t value;
-
-        struct nx_action_reg_load *load;
-        struct mf_subfield dst;
-
-        if (!header) {
-            break;
-        }
+    const struct ofpact_learn_spec *spec;
+    struct cls_rule rule;
 
-        if (src_type == NX_LEARN_SRC_FIELD) {
-            struct mf_subfield src;
+    cls_rule_init_catchall(&rule, 0);
+    for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
+        /* Check the source. */
+        if (spec->src_type == NX_LEARN_SRC_FIELD) {
+            enum ofperr error;
 
-            get_subfield(n_bits, &p, &src);
-            value = mf_get_subfield(&src, flow);
-        } else {
-            value = get_bits(n_bits, &p);
+            error = mf_check_src(&spec->src, flow);
+            if (error) {
+                return error;
+            }
         }
 
-        switch (dst_type) {
-        case NX_LEARN_DST_MATCH:
-            get_subfield(n_bits, &p, &dst);
-            mf_set_subfield(&dst, value, &fm->cr);
-            break;
+        /* Check the destination. */
+        if (spec->dst_type == NX_LEARN_DST_MATCH ||
+            spec->dst_type == NX_LEARN_DST_LOAD) {
+            enum ofperr error;
 
-        case NX_LEARN_DST_LOAD:
-            get_subfield(n_bits, &p, &dst);
-            load = ofputil_put_NXAST_REG_LOAD(&actions);
-            load->ofs_nbits = nxm_encode_ofs_nbits(dst.ofs, dst.n_bits);
-            load->dst = htonl(dst.field->nxm_header);
-            load->value = htonll(value);
-            break;
+            error = (spec->dst_type == NX_LEARN_DST_LOAD
+                     ? mf_check_dst(&spec->dst, &rule.flow)
+                     : mf_check_src(&spec->dst, &rule.flow));
+            if (error) {
+                return error;
+            }
 
-        case NX_LEARN_DST_OUTPUT:
-            ofputil_put_OFPAT_OUTPUT(&actions)->port = htons(value);
-            break;
+            if (spec->dst_type == NX_LEARN_DST_MATCH
+                && spec->src_type == NX_LEARN_SRC_IMMEDIATE) {
+                mf_set_subfield(&spec->dst, get_32aligned_u64(&spec->imm),
+                                &rule);
+            }
         }
     }
-
-    fm->actions = ofpbuf_steal_data(&actions);
-    fm->n_actions = actions.size / sizeof(struct ofp_action_header);
+    return 0;
 }
 
 static void
@@ -280,25 +233,121 @@ put_u32(struct ofpbuf *b, uint32_t x)
     put_be32(b, htonl(x));
 }
 
-struct learn_spec {
-    int n_bits;
+void
+learn_to_openflow10(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->priority = htons(learn->priority);
+    nal->cookie = get_32aligned_be64(&learn->cookie);
+    nal->flags = htons(learn->flags);
+    nal->table_id = learn->table_id;
+
+    for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
+        int n_bits;
+
+        n_bits = spec->src.field ? spec->src.n_bits : spec->dst.n_bits;
+        put_u16(openflow, 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 {
+            ovs_be64 imm = htonll(get_32aligned_u64(&spec->imm));
+            size_t n_dst_bytes = 2 * DIV_ROUND_UP(n_bits, 16);
+            uint8_t *bits = ofpbuf_put_zeros(openflow, n_dst_bytes);
+            bitwise_copy(&imm, sizeof imm, 0,
+                         bits, n_dst_bytes, 0,
+                         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);
+}
+
+void
+learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
+              struct ofputil_flow_mod *fm)
+{
+    const struct ofpact_learn_spec *spec;
+    struct ofpbuf ofpacts;
+
+    cls_rule_init_catchall(&fm->cr, learn->priority);
+    fm->cookie = get_32aligned_be64(&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;
+
+    ofpbuf_init(&ofpacts, 64);
+    for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
+        struct ofpact_reg_load *load;
+        uint64_t value;
+
+        value = (spec->src_type == NX_LEARN_SRC_FIELD
+                 ? mf_get_subfield(&spec->src, flow)
+                 : get_32aligned_u64(&spec->imm));
+
+        switch (spec->dst_type) {
+        case NX_LEARN_DST_MATCH:
+            mf_set_subfield(&spec->dst, value, &fm->cr);
+            break;
 
-    int src_type;
-    struct mf_subfield src;
-    uint8_t src_imm[sizeof(union mf_value)];
+        case NX_LEARN_DST_LOAD:
+            load = nl_msg_put_unspec_zeros(&ofpacts, OFPACT_REG_LOAD,
+                                           sizeof *load);
+            load->dst = spec->dst;
+            put_32aligned_u64(&load->value, value);
+            break;
 
-    int dst_type;
-    struct mf_subfield dst;
-};
+        case NX_LEARN_DST_OUTPUT:
+            if (value < OFPP_MAX
+                || value == OFPP_IN_PORT
+                || value == OFPP_FLOOD
+                || value == OFPP_LOCAL
+                || value == OFPP_ALL) {
+                nl_msg_put_u16(&ofpacts, OFPACT_OUTPUT, value);
+            }
+            break;
+        }
+    }
+    nl_msg_put_flag(&ofpacts, OFPACT_END);
+
+    fm->ofpacts_len = ofpacts.size;
+    fm->ofpacts = ofpbuf_steal_data(&ofpacts);
+}
 
 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;
+        ovs_be64 imm_be64;
         char *error;
 
         error = mf_parse_value(dst, value, &imm);
@@ -306,14 +355,21 @@ learn_parse_spec(const char *orig, char *name, char *value,
             ovs_fatal(0, "%s", error);
         }
 
-        spec->n_bits = dst->n_bits;
+        imm_be64 = htonll(0);
+        bitwise_copy(&imm, dst->n_bytes, 0,
+                     &imm_be64, sizeof imm_be64, 0,
+                     dst->n_bits);
+
         spec->src_type = NX_LEARN_SRC_IMMEDIATE;
-        memcpy(spec->src_imm, &imm, dst->n_bytes);
+        put_32aligned_u64(&spec->imm, ntohll(imm_be64));
         spec->dst_type = NX_LEARN_DST_MATCH;
         spec->dst.field = dst;
         spec->dst.ofs = 0;
-        spec->dst.n_bits = dst->n_bits;
+        spec->dst.n_bits = dst->n_bytes * 8;
     } else if (strchr(name, '[')) {
+        spec->src_type = NX_LEARN_SRC_FIELD;
+        spec->dst_type = NX_LEARN_DST_MATCH;
+
         /* Parse destination and check prerequisites. */
         if (mf_parse_subfield(&spec->dst, name)[0] != '\0') {
             ovs_fatal(0, "%s: syntax error after NXM field name `%s'",
@@ -327,87 +383,68 @@ learn_parse_spec(const char *orig, char *name, char *value,
                           orig, value);
             }
             if (spec->src.n_bits != spec->dst.n_bits) {
-                ovs_fatal(0, "%s: bit widths of %s (%u) and %s (%u) differ",
-                          orig, name, spec->src.n_bits, value,
-                          spec->dst.n_bits);
+                ovs_fatal(0, "%s: bit widths of %s (%d) and %s (%d) differ",
+                          orig, name, spec->dst.n_bits,
+                          value, spec->src.n_bits);
             }
         } else {
             spec->src = spec->dst;
         }
 
-        spec->n_bits = spec->src.n_bits;
-        spec->src_type = NX_LEARN_SRC_FIELD;
-        spec->dst_type = NX_LEARN_DST_MATCH;
     } else if (!strcmp(name, "load")) {
         if (value[strcspn(value, "[-")] == '-') {
-            struct nx_action_reg_load load;
-            int nbits, imm_bytes;
-            uint64_t imm;
-            int i;
+            struct ofpact_reg_load load;
 
             nxm_parse_reg_load(&load, value);
-            nbits = nxm_decode_n_bits(load.ofs_nbits);
-            imm_bytes = DIV_ROUND_UP(nbits, 8);
-            imm = ntohll(load.value);
 
-            spec->n_bits = nbits;
             spec->src_type = NX_LEARN_SRC_IMMEDIATE;
-            for (i = 0; i < imm_bytes; i++) {
-                spec->src_imm[i] = imm >> ((imm_bytes - i - 1) * 8);
-            }
+            spec->imm = load.value;
+
             spec->dst_type = NX_LEARN_DST_LOAD;
-            nxm_decode(&spec->dst, load.dst, load.ofs_nbits);
+            spec->dst = load.dst;
         } 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->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")) {
+        spec->src_type = NX_LEARN_SRC_FIELD;
+        spec->dst_type = NX_LEARN_DST_OUTPUT;
         if (mf_parse_subfield(&spec->src, value)[0] != '\0') {
             ovs_fatal(0, "%s: syntax error after NXM field name `%s'",
                       orig, name);
         }
-
-        spec->n_bits = spec->src.n_bits;
-        spec->src_type = NX_LEARN_SRC_FIELD;
-        spec->dst_type = NX_LEARN_DST_OUTPUT;
     } else {
         ovs_fatal(0, "%s: unknown keyword %s", orig, name);
     }
 }
 
 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;
+    size_t start_ofs;
 
-    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);
+    start_ofs = nl_msg_start_attr(ofpacts, OFPACT_LEARN);
+    learn = ofpbuf_put_zeros(ofpacts, sizeof *learn);
+    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) {
@@ -415,76 +452,51 @@ 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, "cookie")) {
-            learn->cookie = htonll(strtoull(value, NULL, 0));
+            put_32aligned_be64(&learn->cookie,
+                               htonll(strtoull(value, NULL, 0)));
         } else {
-            struct learn_spec spec;
+            struct ofpact_learn_spec *spec;
 
-            learn_parse_spec(orig, name, value, &spec);
+            spec = ofpbuf_put_zeros(ofpacts, sizeof *spec);
+            learn = ofpbuf_at_assert(ofpacts, start_ofs, sizeof *learn);
+            learn->n_specs++;
+
+            learn_parse_spec(orig, name, value, spec);
 
             /* Check prerequisites. */
-            if (spec.src_type == NX_LEARN_SRC_FIELD
-                && !mf_are_prereqs_ok(spec.src.field, flow)) {
+            if (spec->src_type == NX_LEARN_SRC_FIELD
+                && !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
-                && spec.dst.ofs == 0
-                && spec.n_bits == spec.dst.field->n_bytes * 8) {
-                union mf_value imm;
-
-                memcpy(&imm, spec.src_imm, spec.dst.field->n_bytes);
-                mf_set_value(spec.dst.field, &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, 8);
-                if (n_bytes % 2) {
-                    ofpbuf_put_zeros(b, 1);
-                }
-                ofpbuf_put(b, spec.src_imm, 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
+                && spec->dst.ofs == 0
+                && spec->dst.n_bits == spec->dst.field->n_bytes * 8) {
+                mf_set_subfield(&spec->dst, get_32aligned_u64(&spec->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);
+    nl_msg_end_attr(ofpacts, start_ofs);
 
     /* In theory the above should have caught any errors, but... */
     error = learn_check(learn, flow);
@@ -495,160 +507,80 @@ learn_parse(struct ofpbuf *b, char *arg, const struct flow *flow)
 }
 
 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->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 (get_32aligned_be64(&learn->cookie) != htonll(0)) {
+        ds_put_format(s, ",cookie=%#"PRIx64,
+                      ntohll(get_32aligned_be64(&learn->cookie)));
     }
-    if (learn->cookie != htonll(0)) {
-        ds_put_format(s, ",cookie=0x%"PRIx64, ntohll(learn->cookie));
-    }
-    if (!is_all_zeros(learn->pad, sizeof learn->pad)) {
-        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) {
+                ovs_be64 imm_be64 = htonll(get_32aligned_u64(&spec->imm));
                 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(&imm_be64, sizeof imm_be64, 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_format(s, "=%#"PRIx64, get_32aligned_u64(&spec->imm));
             }
             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_cstr(s, "->");
-            mf_format_subfield(&dst, s);
+            ds_put_format(s, "load:%#"PRIx64"->",
+                          get_32aligned_u64(&spec->imm));
+            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 b83bee2..45cd7ec 100644
--- a/lib/learn.h
+++ b/lib/learn.h
@@ -21,7 +21,9 @@
 
 struct ds;
 struct flow;
+struct nlattr;
 struct ofpbuf;
+struct ofpact_learn;
 struct ofputil_flow_mod;
 struct nx_action_learn;
 
@@ -30,11 +32,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 *,
+enum ofperr learn_to_ofpact(const struct nx_action_learn *,
+                            struct ofpbuf *ofpacts);
+enum ofperr learn_check(const struct ofpact_learn *, const struct flow *);
+void learn_to_openflow10(const struct ofpact_learn *, struct ofpbuf *openflow);
+
+void learn_execute(const struct ofpact_learn *, const struct flow *,
                    struct ofputil_flow_mod *);
 
-void learn_parse(struct ofpbuf *, char *, const struct flow *);
-void learn_format(const struct nx_action_learn *, struct ds *);
+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/multipath.c b/lib/multipath.c
index 46976fb..fa9a0cc 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -22,8 +22,9 @@
 #include <sys/types.h>
 #include <netinet/in.h>
 #include "dynamic-string.h"
-#include "meta-flow.h"
+#include "netlink.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "openflow/nicira-ext.h"
@@ -34,37 +35,62 @@ VLOG_DEFINE_THIS_MODULE(multipath);
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
-/* multipath_check(). */
+/* multipath_to_ofpact(). */
 enum ofperr
-multipath_check(const struct nx_action_multipath *mp, const struct flow *flow)
+multipath_to_ofpact(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) {
+    memset(mp, 0, sizeof *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);
+}
+
+enum ofperr
+multipath_check(const struct ofpact_multipath *mp,
+                const struct flow *flow)
+{
+    return mf_check_dst(&mp->dst, flow);
+}
+
+void
+multipath_to_openflow10(const struct nlattr *ofpact, struct ofpbuf *openflow)
+{
+    const struct ofpact_multipath *mp = nl_attr_get(ofpact);
+    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(). */
@@ -73,18 +99,14 @@ static uint16_t multipath_algorithm(uint32_t hash, enum nx_mp_algorithm,
                                     unsigned int n_links, unsigned int arg);
 
 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
@@ -165,12 +187,11 @@ multipath_algorithm(uint32_t hash, enum nx_mp_algorithm algorithm,
 /* multipath_parse(). */
 
 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 +199,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);
+    memset(mp, 0, sizeof *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 +229,27 @@ 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);
 }
 
 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 +267,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 3c4ff45..3d180f7 100644
--- a/lib/multipath.h
+++ b/lib/multipath.h
@@ -22,19 +22,26 @@
 
 struct ds;
 struct flow;
+struct nlattr;
 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_to_ofpact(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_openflow10(const struct nlattr *ofpact,
+                             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/netlink.c b/lib/netlink.c
index 6445049..d74862b 100644
--- a/lib/netlink.c
+++ b/lib/netlink.c
@@ -239,6 +239,14 @@ nl_msg_put_unspec_uninit(struct ofpbuf *msg, uint16_t type, size_t size)
     return nla + 1;
 }
 
+void *
+nl_msg_put_unspec_zeros(struct ofpbuf *msg, uint16_t type, size_t size)
+{
+    void *payload = nl_msg_put_unspec_uninit(msg, type, size);
+    memset(payload, 0, size);
+    return payload;
+}
+
 /* Appends a Netlink attribute of the given 'type' and the 'size' bytes of
  * 'data' as its payload, to the tail end of 'msg', reallocating and copying
  * its data if necessary.  Returns a pointer to the first byte of data in the
@@ -338,6 +346,14 @@ nl_msg_push_unspec_uninit(struct ofpbuf *msg, uint16_t type, size_t size)
     return nla + 1;
 }
 
+void *
+nl_msg_push_unspec_zeros(struct ofpbuf *msg, uint16_t type, size_t size)
+{
+    void *payload = nl_msg_push_unspec_uninit(msg, type, size);
+    memset(payload, 0, size);
+    return payload;
+}
+
 /* Prepends a Netlink attribute of the given 'type' and the 'size' bytes of
  * 'data' as its payload, to the head end of 'msg', reallocating and copying
  * its data if necessary.  Returns a pointer to the first byte of data in the
@@ -430,9 +446,8 @@ nl_msg_push_string(struct ofpbuf *msg, uint16_t type, const char *value)
 size_t
 nl_msg_start_nested(struct ofpbuf *msg, uint16_t type)
 {
-    size_t offset = msg->size;
     nl_msg_put_unspec(msg, type, NULL, 0);
-    return offset;
+    return msg->size;
 }
 
 /* Finalizes a nested Netlink attribute in 'msg'.  'offset' should be the value
@@ -440,7 +455,10 @@ nl_msg_start_nested(struct ofpbuf *msg, uint16_t type)
 void
 nl_msg_end_nested(struct ofpbuf *msg, size_t offset)
 {
-    struct nlattr *attr = ofpbuf_at_assert(msg, offset, sizeof *attr);
+    struct nlattr *attr;
+
+    offset -= NLA_HDRLEN;
+    attr = ofpbuf_at_assert(msg, offset, sizeof *attr);
     attr->nla_len = msg->size - offset;
 }
 
@@ -455,6 +473,27 @@ nl_msg_put_nested(struct ofpbuf *msg,
     nl_msg_end_nested(msg, offset);
 }
 
+/* Adds the header for a Netlink attribute to 'msg', with the specified 'type',
+ * and returns the header's offset within 'msg'.  The caller should add the
+ * content for the attribute to 'msg' (e.g. using ofpbuf_put()), and then pass
+ * the returned offset to nl_msg_end_attr() to finish up the attribute. */
+size_t
+nl_msg_start_attr(struct ofpbuf *msg, uint16_t type)
+{
+    return nl_msg_start_nested(msg, type);
+}
+
+/* Finalizes an attribute in 'msg'.  'offset' should be the value returned by
+ * nl_msg_start_attr(). */
+void
+nl_msg_end_attr(struct ofpbuf *msg, size_t offset)
+{
+    nl_msg_end_nested(msg, offset);
+    if (msg->size % NLA_ALIGNTO) {
+        ofpbuf_put_zeros(msg, NLA_ALIGNTO - msg->size % NLA_ALIGNTO);
+    }
+}
+
 /* If 'buffer' begins with a valid "struct nlmsghdr", pulls the header and its
  * payload off 'buffer', stores header and payload in 'msg->data' and
  * 'msg->size', and returns a pointer to the header.
diff --git a/lib/netlink.h b/lib/netlink.h
index 7eced42..5165171 100644
--- a/lib/netlink.h
+++ b/lib/netlink.h
@@ -59,6 +59,7 @@ void *nl_msg_push_uninit(struct ofpbuf *, size_t);
 
 /* Appending attributes. */
 void *nl_msg_put_unspec_uninit(struct ofpbuf *, uint16_t type, size_t);
+void *nl_msg_put_unspec_zeros(struct ofpbuf *, uint16_t type, size_t);
 void nl_msg_put_unspec(struct ofpbuf *, uint16_t type, const void *, size_t);
 void nl_msg_put_flag(struct ofpbuf *, uint16_t type);
 void nl_msg_put_u8(struct ofpbuf *, uint16_t type, uint8_t value);
@@ -75,8 +76,12 @@ void nl_msg_end_nested(struct ofpbuf *, size_t offset);
 void nl_msg_put_nested(struct ofpbuf *, uint16_t type,
                        const void *data, size_t size);
 
+size_t nl_msg_start_attr(struct ofpbuf *, uint16_t type);
+void nl_msg_end_attr(struct ofpbuf *, size_t offset);
+
 /* Prepending attributes. */
 void *nl_msg_push_unspec_uninit(struct ofpbuf *, uint16_t type, size_t);
+void *nl_msg_push_unspec_zeros(struct ofpbuf *, uint16_t type, size_t);
 void nl_msg_push_unspec(struct ofpbuf *, uint16_t type, const void *, size_t);
 void nl_msg_push_flag(struct ofpbuf *, uint16_t type);
 void nl_msg_push_u8(struct ofpbuf *, uint16_t type, uint8_t value);
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 41c65fb..ccc72b6 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"
@@ -744,160 +745,171 @@ 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);
+    put_32aligned_u64(&load->value, value);
     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 && (value >> load->dst.n_bits) != 0) {
+        ovs_fatal(0, "%s: value %"PRIu64" does not fit into %d bits",
+                  full_s, 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)
 {
-    struct mf_subfield dst;
-
-    ds_put_format(s, "load:%#"PRIx64"->", ntohll(load->value));
-
-    nxm_decode(&dst, load->dst, load->ofs_nbits);
-    mf_format_subfield(&dst, s);
+    ds_put_format(s, "load:%#"PRIx64"->", get_32aligned_u64(&load->value));
+    mf_format_subfield(&load->dst, s);
 }
 
-/* nxm_check_reg_move(), nxm_check_reg_load(). */
+/* nxm_reg_move_check(), nxm_reg_load_check(). */
 
 enum ofperr
-nxm_check_reg_move(const struct nx_action_reg_move *action,
-                   const struct flow *flow)
+nxm_reg_move_to_ofpact(const struct nx_action_reg_move *narm,
+                       struct ofpact_reg_move *move)
 {
-    struct mf_subfield src;
-    struct mf_subfield dst;
-    int error;
+    memset(move, 0, sizeof *move);
+    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_discrete(&src, action->src, action->src_ofs, action->n_bits);
-    error = mf_check_src(&src, flow);
-    if (error) {
-        return error;
+    return nxm_reg_move_check(move, NULL);
+}
+
+enum ofperr
+nxm_reg_load_to_ofpact(const struct nx_action_reg_load *narl,
+                       struct ofpact_reg_load *load)
+{
+    memset(load, 0, sizeof *load);
+    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);
+    put_32aligned_u64(&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
+        && get_32aligned_u64(&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_openflow10(const struct ofpact_reg_move *move,
+                           struct ofpbuf *of10)
+{
+    struct nx_action_reg_move *narm;
+
+    narm = ofputil_put_NXAST_REG_MOVE(of10);
+    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_openflow10(const struct ofpact_reg_load *load,
+                           struct ofpbuf *of10)
+{
+    struct nx_action_reg_load *narl;
+
+    narl = ofputil_put_NXAST_REG_LOAD(of10);
+    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(get_32aligned_u64(&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, get_32aligned_u64(&load->value), flow);
 }
 
 /* Initializes 'sf->field' with the field corresponding to the given NXM
@@ -913,13 +925,21 @@ nxm_execute_reg_load(const struct nx_action_reg_load *action,
  * 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
  * 'header' and 'sf->ofs' and 'sf->n_bits' from 'ofs' and 'n_bits',
  * respectively.
diff --git a/lib/nx-match.h b/lib/nx-match.h
index 3406e04..fdeb30a 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 #include <sys/types.h>
 #include <netinet/in.h>
+#include "ofp-errors.h"
 #include "openvswitch/types.h"
 #include "ofp-errors.h"
 
@@ -27,6 +28,9 @@ struct cls_rule;
 struct ds;
 struct flow;
 struct mf_subfield;
+struct nlattr;
+struct ofpact_reg_move;
+struct ofpact_reg_load;
 struct ofpbuf;
 struct nx_action_reg_load;
 struct nx_action_reg_move;
@@ -48,19 +52,31 @@ int nx_put_match(struct ofpbuf *, 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_to_ofpact(const struct nx_action_reg_move *,
+                                   struct ofpact_reg_move *);
+enum ofperr nxm_reg_load_to_ofpact(const struct nx_action_reg_load *,
+                                   struct ofpact_reg_load *);
 
-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_openflow10(const struct ofpact_reg_move *,
+                                struct ofpbuf *of10);
+void nxm_reg_load_to_openflow10(const struct ofpact_reg_load *,
+                                struct ofpbuf *of10);
+
+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..36d0d2a
--- /dev/null
+++ b/lib/ofp-actions.c
@@ -0,0 +1,898 @@
+/*
+ * 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 "netlink.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);
+
+static enum ofperr
+enqueue_to_ofpact(const struct ofp_action_enqueue *oae, struct ofpbuf *out)
+{
+    struct ofpact_enqueue *enqueue;
+
+    enqueue = nl_msg_put_unspec_zeros(out, OFPACT_ENQUEUE, sizeof *enqueue);
+    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_to_ofpact(const struct nx_action_resubmit *nar, struct ofpbuf *out)
+{
+    struct ofpact_resubmit *resubmit;
+
+    resubmit = nl_msg_put_unspec_zeros(out, OFPACT_RESUBMIT, sizeof *resubmit);
+    resubmit->in_port = ntohs(nar->in_port);
+    resubmit->table_id = 0xff;
+}
+
+static enum ofperr
+resubmit_table_to_ofpact(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 = nl_msg_put_unspec_zeros(out, OFPACT_RESUBMIT, sizeof *resubmit);
+    resubmit->in_port = ntohs(nar->in_port);
+    resubmit->table_id = nar->table;
+    return 0;
+}
+
+static enum ofperr
+output_reg_to_ofpact(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 = nl_msg_put_unspec_zeros(out, OFPACT_OUTPUT_REG,
+                                         sizeof *output_reg);
+    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);
+
+    return mf_check_src(&output_reg->src, NULL);
+}
+
+static enum ofperr
+openflow10_to_ofpact__(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_reg_move *narm;
+    const struct nx_action_reg_load *narl;
+    const struct nx_action_note *nan;
+    const struct nx_action_set_tunnel64 *nast64;
+    const struct nx_action_multipath *nam;
+    const struct nx_action_autopath *naa;
+    enum ofperr error;
+    void *ofpact;
+    int code;
+
+    code = ofputil_decode_action(a);
+    if (code < 0) {
+        return -code;
+    }
+
+    error = 0;
+    switch ((enum ofputil_action_code) code) {
+    case OFPUTIL_OFPAT_OUTPUT:
+        error = ofputil_check_output_port(ntohs(a->output.port), OFPP_MAX);
+        if (error) {
+            return error;
+        }
+
+        if (a->output.port != htons(OFPP_CONTROLLER)) {
+            nl_msg_put_u16(out, OFPACT_OUTPUT, ntohs(a->output.port));
+        } else {
+            nl_msg_put_u16(out, OFPACT_CONTROLLER,
+                           ntohs(a->output.max_len));
+        }
+        break;
+
+    case OFPUTIL_OFPAT_SET_VLAN_VID:
+        if (a->vlan_vid.vlan_vid & ~htons(0xfff)) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        nl_msg_put_be16(out, OFPACT_SET_VLAN_VID, a->vlan_vid.vlan_vid);
+        break;
+
+    case OFPUTIL_OFPAT_SET_VLAN_PCP:
+        if (a->vlan_pcp.vlan_pcp & ~7) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        nl_msg_put_u8(out, OFPACT_SET_VLAN_PCP, a->vlan_pcp.vlan_pcp);
+        break;
+
+    case OFPUTIL_OFPAT_STRIP_VLAN:
+        nl_msg_put_flag(out, OFPACT_STRIP_VLAN);
+        break;
+
+    case OFPUTIL_OFPAT_SET_DL_SRC:
+        nl_msg_put_unspec(out, OFPACT_SET_ETH_SRC,
+                          ((const struct ofp_action_dl_addr *) a)->dl_addr,
+                          ETH_ADDR_LEN);
+        break;
+
+    case OFPUTIL_OFPAT_SET_DL_DST:
+        nl_msg_put_unspec(out, OFPACT_SET_ETH_DST,
+                          ((const struct ofp_action_dl_addr *) a)->dl_addr,
+                          ETH_ADDR_LEN);
+        break;
+
+    case OFPUTIL_OFPAT_SET_NW_SRC:
+        nl_msg_put_be32(out, OFPACT_SET_NW_SRC, a->nw_addr.nw_addr);
+        break;
+
+    case OFPUTIL_OFPAT_SET_NW_DST:
+        nl_msg_put_be32(out, OFPACT_SET_NW_DST, a->nw_addr.nw_addr);
+        break;
+
+    case OFPUTIL_OFPAT_SET_NW_TOS:
+        if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        nl_msg_put_u8(out, OFPACT_SET_NW_TOS, a->nw_tos.nw_tos);
+        break;
+
+    case OFPUTIL_OFPAT_SET_TP_SRC:
+        nl_msg_put_be16(out, OFPACT_SET_TP_SRC,
+                        ((const struct ofp_action_tp_port *) a)->tp_port);
+        break;
+
+    case OFPUTIL_OFPAT_SET_TP_DST:
+        nl_msg_put_be16(out, OFPACT_SET_TP_DST,
+                        ((const struct ofp_action_tp_port *) a)->tp_port);
+        break;
+
+    case OFPUTIL_OFPAT_ENQUEUE:
+        error = enqueue_to_ofpact((const struct ofp_action_enqueue *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_RESUBMIT:
+        resubmit_to_ofpact((const struct nx_action_resubmit *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_SET_TUNNEL:
+        nast = (const struct nx_action_set_tunnel *) a;
+        nl_msg_put_be64(out, OFPACT_SET_TUNNEL, htonll(ntohl(nast->tun_id)));
+        break;
+
+    case OFPUTIL_NXAST_SET_QUEUE:
+        nasq = (const struct nx_action_set_queue *) a;
+        nl_msg_put_u32(out, OFPACT_SET_QUEUE, ntohl(nasq->queue_id));
+        break;
+
+    case OFPUTIL_NXAST_POP_QUEUE:
+        nl_msg_put_flag(out, OFPACT_POP_QUEUE);
+        break;
+
+    case OFPUTIL_NXAST_REG_MOVE:
+        narm = (const struct nx_action_reg_move *) a;
+        ofpact = nl_msg_put_unspec_uninit(out, OFPACT_REG_MOVE,
+                                          sizeof(struct ofpact_reg_move));
+        error = nxm_reg_move_to_ofpact(narm, ofpact);
+        break;
+
+    case OFPUTIL_NXAST_REG_LOAD:
+        narl = (const struct nx_action_reg_load *) a;
+        ofpact = nl_msg_put_unspec_uninit(out, OFPACT_REG_LOAD,
+                                          sizeof(struct ofpact_reg_load));
+        error = nxm_reg_load_to_ofpact(narl, ofpact);
+        break;
+
+    case OFPUTIL_NXAST_NOTE:
+        nan = (const struct nx_action_note *) a;
+        nl_msg_put_unspec(out, OFPACT_NOTE, nan->note,
+                          ntohs(nan->len) - offsetof(struct nx_action_note,
+                                                     note));
+        break;
+
+    case OFPUTIL_NXAST_SET_TUNNEL64:
+        nast64 = (const struct nx_action_set_tunnel64 *) a;
+        nl_msg_put_be64(out, OFPACT_SET_TUNNEL, nast64->tun_id);
+        break;
+
+    case OFPUTIL_NXAST_MULTIPATH:
+        nam = (const struct nx_action_multipath *) a;
+        ofpact = nl_msg_put_unspec_uninit(out, OFPACT_MULTIPATH,
+                                          sizeof(struct ofpact_multipath));
+        error = multipath_to_ofpact(nam, ofpact);
+        break;
+
+    case OFPUTIL_NXAST_AUTOPATH:
+        naa = (const struct nx_action_autopath *) a;
+        ofpact = nl_msg_put_unspec_uninit(out, OFPACT_AUTOPATH,
+                                          sizeof(struct ofpact_autopath));
+        error = autopath_to_ofpact(naa, ofpact);
+        break;
+
+    case OFPUTIL_NXAST_BUNDLE:
+    case OFPUTIL_NXAST_BUNDLE_LOAD:
+        error = bundle_to_ofpact((const struct nx_action_bundle *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_OUTPUT_REG:
+        error = output_reg_to_ofpact((const struct nx_action_output_reg *) a,
+                                     out);
+        break;
+
+    case OFPUTIL_NXAST_RESUBMIT_TABLE:
+        nar = (const struct nx_action_resubmit *) a;
+        error = resubmit_table_to_ofpact(nar, out);
+        break;
+
+    case OFPUTIL_NXAST_LEARN:
+        error = learn_to_ofpact((const struct nx_action_learn *) a, out);
+        break;
+
+    case OFPUTIL_NXAST_EXIT:
+        nl_msg_put_flag(out, OFPACT_EXIT);
+        break;
+
+    case OFPUTIL_NXAST_DEC_TTL:
+        nl_msg_put_flag(out, OFPACT_DEC_TTL);
+        break;
+    }
+
+    return error;
+}
+
+static enum ofperr
+openflow10_to_ofpact(const union ofp_action *in, size_t n_in,
+                     struct ofpbuf *out)
+{
+    const union ofp_action *a;
+    size_t left;
+
+    OFPUTIL_ACTION_FOR_EACH (a, left, in, n_in) {
+        enum ofperr error = openflow10_to_ofpact__(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;
+    }
+
+    nl_msg_put_flag(out, OFPACT_END);
+
+    return 0;
+}
+
+enum ofperr
+ofpacts_from_openflow(const union ofp_action *in, size_t n_in,
+                      enum ofputil_protocol ff OVS_UNUSED,
+                      struct ofpbuf *out)
+{
+    return openflow10_to_ofpact(in, n_in, out);
+}
+
+/* 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
+ofpacts_pull_openflow(struct ofpbuf *b, unsigned int actions_len,
+                      struct nlattr **ofpacts, size_t *ofpacts_len)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+    const union ofp_action *actions;
+    struct ofpbuf ofpacts_buf;
+    enum ofperr error;
+
+    *ofpacts = NULL;
+    *ofpacts_len = 0;
+
+    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(b, actions_len);
+    if (actions == NULL) {
+        VLOG_WARN_RL(&rl, "OpenFlow message actions length %u exceeds "
+                     "remaining message length (%zu)", actions_len, b->size);
+        return OFPERR_OFPBRC_BAD_LEN;
+    }
+
+    ofpbuf_init(&ofpacts_buf, actions_len);
+    error = ofpacts_from_openflow(actions, actions_len / OFP_ACTION_ALIGN,
+                                  OFPUTIL_P_OF10, &ofpacts_buf);
+    if (error) {
+        ofpbuf_uninit(&ofpacts_buf);
+        return error;
+    }
+
+    *ofpacts_len = ofpacts_buf.size;
+    *ofpacts = ofpbuf_steal_data(&ofpacts_buf);
+    return 0;
+}
+
+static enum ofperr
+ofpact_check__(const struct nlattr *a, const struct flow *flow, int max_ports)
+{
+    const struct ofpact_enqueue *enqueue;
+    const struct ofpact_output_reg *output_reg;
+    int type = nl_attr_type(a);
+
+    switch ((enum ofpact_type) type) {
+    case OFPACT_END:
+        return 0;
+
+    case OFPACT_OUTPUT:
+        return ofputil_check_output_port(nl_attr_get_u16(a), max_ports);
+
+    case OFPACT_CONTROLLER:
+        return 0;
+
+    case OFPACT_ENQUEUE:
+        enqueue = nl_attr_get(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:
+        output_reg = nl_attr_get(a);
+        return mf_check_src(&output_reg->src, flow);
+
+    case OFPACT_BUNDLE:
+        return bundle_check(nl_attr_get(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_NW_SRC:
+    case OFPACT_SET_NW_DST:
+    case OFPACT_SET_NW_TOS:
+    case OFPACT_SET_TP_SRC:
+    case OFPACT_SET_TP_DST:
+        return 0;
+
+    case OFPACT_REG_MOVE:
+        return nxm_reg_move_check(nl_attr_get(a), flow);
+
+    case OFPACT_REG_LOAD:
+        return nxm_reg_load_check(nl_attr_get(a), flow);
+
+    case OFPACT_DEC_TTL:
+    case OFPACT_SET_TUNNEL:
+    case OFPACT_SET_QUEUE:
+    case OFPACT_POP_QUEUE:
+    case OFPACT_RESUBMIT:
+        return 0;
+
+    case OFPACT_LEARN:
+        return learn_check(nl_attr_get(a), flow);
+
+    case OFPACT_MULTIPATH:
+        return multipath_check(nl_attr_get(a), flow);
+
+    case OFPACT_AUTOPATH:
+        return autopath_check(nl_attr_get(a), flow);
+
+    case OFPACT_NOTE:
+    case OFPACT_EXIT:
+        return 0;
+
+    default:
+        NOT_REACHED();
+    }
+
+}
+
+enum ofperr
+ofpacts_check(const struct nlattr *ofpacts,
+              const struct flow *flow, int max_ports)
+{
+    const struct nlattr *a;
+    enum ofperr error;
+
+    OFPACT_FOR_EACH (a, ofpacts) {
+        error = ofpact_check__(a, flow, max_ports);
+        if (error) {
+            return error;
+        }
+    }
+
+    return 0;
+}
+
+static void
+ofpact_enqueue_to_openflow10(const struct nlattr *a, struct ofpbuf *out)
+{
+    const struct ofpact_enqueue *enqueue = nl_attr_get(a);
+    struct ofp_action_enqueue *oae;
+
+    oae = ofputil_put_OFPAT_ENQUEUE(out);
+    oae->port = htons(enqueue->port);
+    oae->queue_id = htonl(enqueue->queue);
+}
+
+static void
+ofpact_output_reg_to_openflow10(const struct nlattr *a, struct ofpbuf *out)
+{
+    const struct ofpact_output_reg *output_reg = nl_attr_get(a);
+    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_openflow10(const struct nlattr *a, struct ofpbuf *out)
+{
+    const struct ofpact_resubmit *resubmit = nl_attr_get(a);
+    struct nx_action_resubmit *nar;
+
+    if (resubmit->table_id == 0xff) {
+        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_openflow10(const struct nlattr *a, struct ofpbuf *out)
+{
+    uint64_t tun_id = ntohll(nl_attr_get_be64(a));
+
+    if (tun_id <= UINT32_MAX) {
+        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_openflow10(const struct nlattr *a, 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, nl_attr_get(a), nl_attr_get_size(a));
+
+    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_to_openflow10__(const struct nlattr *a, struct ofpbuf *out)
+{
+    struct ofp_action_output *oao;
+    int type = nl_attr_type(a);
+
+    switch ((enum ofpact_type) type) {
+    case OFPACT_END:
+        NOT_REACHED();
+
+    case OFPACT_OUTPUT:
+        oao = ofputil_put_OFPAT_OUTPUT(out);
+        oao->port = htons(nl_attr_get_u16(a));
+        break;
+
+    case OFPACT_CONTROLLER:
+        oao = ofputil_put_OFPAT_OUTPUT(out);
+        oao->port = htons(OFPP_CONTROLLER);
+        oao->max_len = htons(nl_attr_get_u16(a));
+        break;
+
+    case OFPACT_ENQUEUE:
+        ofpact_enqueue_to_openflow10(a, out);
+        break;
+
+    case OFPACT_OUTPUT_REG:
+        ofpact_output_reg_to_openflow10(a, out);
+        break;
+
+    case OFPACT_BUNDLE:
+        bundle_to_openflow10(nl_attr_get(a), out);
+        break;
+
+    case OFPACT_SET_VLAN_VID:
+        ofputil_put_OFPAT_SET_VLAN_VID(out)->vlan_vid = nl_attr_get_be16(a);
+        break;
+
+    case OFPACT_SET_VLAN_PCP:
+        ofputil_put_OFPAT_SET_VLAN_PCP(out)->vlan_pcp = nl_attr_get_u8(a);
+        break;
+
+    case OFPACT_STRIP_VLAN:
+        ofputil_put_OFPAT_STRIP_VLAN(out);
+        break;
+
+    case OFPACT_SET_ETH_SRC:
+        memcpy(ofputil_put_OFPAT_SET_DL_SRC(out)->dl_addr, nl_attr_get(a),
+               ETH_ADDR_LEN);
+        break;
+
+    case OFPACT_SET_ETH_DST:
+        memcpy(ofputil_put_OFPAT_SET_DL_DST(out)->dl_addr, nl_attr_get(a),
+               ETH_ADDR_LEN);
+        break;
+
+    case OFPACT_SET_NW_SRC:
+        ofputil_put_OFPAT_SET_NW_SRC(out)->nw_addr = nl_attr_get_be32(a);
+        break;
+
+    case OFPACT_SET_NW_DST:
+        ofputil_put_OFPAT_SET_NW_DST(out)->nw_addr = nl_attr_get_be32(a);
+        break;
+
+    case OFPACT_SET_NW_TOS:
+        ofputil_put_OFPAT_SET_NW_TOS(out)->nw_tos = nl_attr_get_u8(a);
+        break;
+
+    case OFPACT_SET_TP_SRC:
+        ofputil_put_OFPAT_SET_TP_SRC(out)->tp_port = nl_attr_get_be16(a);
+        break;
+
+    case OFPACT_SET_TP_DST:
+        ofputil_put_OFPAT_SET_TP_DST(out)->tp_port = nl_attr_get_be16(a);
+        break;
+
+    case OFPACT_REG_MOVE:
+        nxm_reg_move_to_openflow10(nl_attr_get(a), out);
+        break;
+
+    case OFPACT_REG_LOAD:
+        nxm_reg_load_to_openflow10(nl_attr_get(a), out);
+        break;
+
+    case OFPACT_DEC_TTL:
+        ofputil_put_NXAST_DEC_TTL(out);
+        break;
+
+    case OFPACT_SET_TUNNEL:
+        ofpact_set_tunnel_to_openflow10(a, out);
+        break;
+
+    case OFPACT_SET_QUEUE:
+        ofputil_put_NXAST_SET_QUEUE(out)->queue_id = htonl(nl_attr_get_u32(a));
+        break;
+
+    case OFPACT_POP_QUEUE:
+        ofputil_put_NXAST_POP_QUEUE(out);
+        break;
+
+    case OFPACT_RESUBMIT:
+        ofpact_resubmit_to_openflow10(a, out);
+        break;
+
+    case OFPACT_LEARN:
+        learn_to_openflow10(nl_attr_get(a), out);
+        break;
+
+    case OFPACT_MULTIPATH:
+        multipath_to_openflow10(a, out);
+        break;
+
+    case OFPACT_AUTOPATH:
+        autopath_to_openflow10(a, out);
+        break;
+
+    case OFPACT_NOTE:
+        ofpact_note_to_openflow10(a, out);
+        break;
+
+    case OFPACT_EXIT:
+        ofputil_put_NXAST_EXIT(out);
+        break;
+    }
+}
+
+static void
+ofpact_to_openflow10(const struct nlattr *ofpacts, struct ofpbuf *out)
+{
+    const struct nlattr *a;
+
+    OFPACT_FOR_EACH (a, ofpacts) {
+        ofpact_to_openflow10__(a, out);
+    }
+}
+
+void
+ofpacts_to_openflow(const struct nlattr *ofpact,
+                    enum ofputil_protocol protocol OVS_UNUSED,
+                    struct ofpbuf *openflow)
+{
+    return ofpact_to_openflow10(ofpact, openflow);
+}
+
+/* Returns true if 'action' outputs to 'port', false otherwise. */
+static bool
+ofpact_outputs_to_port(const struct nlattr *ofpact, uint16_t port)
+{
+    const struct ofpact_enqueue *enqueue;
+
+    switch (nl_attr_type(ofpact)) {
+    case OFPAT_OUTPUT:
+        return nl_attr_get_u16(ofpact) == port;
+    case OFPAT_ENQUEUE:
+        enqueue = nl_attr_get(ofpact);
+        return enqueue->port == port;
+    default:
+        return false;
+    }
+}
+
+/* Returns true if any action in 'ofpacts' outputs to 'port', false
+ * otherwise. */
+bool
+ofpacts_output_to_port(const struct nlattr *ofpacts, uint16_t port)
+{
+    const struct nlattr *a;
+
+    OFPACT_FOR_EACH (a, ofpacts) {
+        if (ofpact_outputs_to_port(a, port)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+ofpacts_equal(const struct nlattr *a, size_t a_len,
+              const struct nlattr *b, size_t b_len)
+{
+    return a_len == b_len && !memcmp(a, b, a_len);
+}
+
+static void
+print_note(const uint8_t *note, size_t len, struct ds *string)
+{
+    size_t i;
+
+    ds_put_cstr(string, "note:");
+    for (i = 0; i < len; i++) {
+        if (i) {
+            ds_put_char(string, '.');
+        }
+        ds_put_format(string, "%02"PRIx8, note[i]);
+    }
+}
+
+static void
+ofpact_format(const struct nlattr *a, struct ds *s)
+{
+    const struct ofpact_enqueue *enqueue;
+    const struct ofpact_output_reg *output_reg;
+    const struct ofpact_resubmit *resubmit;
+    const struct ofpact_autopath *autopath;
+    int type = nl_attr_type(a);
+    ovs_be32 ip;
+    uint16_t port;
+
+    switch ((enum ofpact_type) type) {
+    case OFPACT_END:
+        NOT_REACHED();
+
+    case OFPACT_OUTPUT:
+        port = nl_attr_get_u16(a);
+        if (port < OFPP_MAX) {
+            ds_put_format(s, "output:%"PRIu16, port);
+        } else {
+            ofputil_format_port(port, s);
+        }
+        break;
+
+    case OFPACT_CONTROLLER:
+        ds_put_format(s, "CONTROLLER:%"PRIu16, nl_attr_get_u16(a));
+        break;
+
+    case OFPACT_ENQUEUE:
+        enqueue = nl_attr_get(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:
+        output_reg = nl_attr_get(a);
+        ds_put_cstr(s, "output:");
+        mf_format_subfield(&output_reg->src, s);
+        break;
+
+    case OFPACT_BUNDLE:
+        bundle_format(nl_attr_get(a), s);
+        break;
+
+    case OFPACT_SET_VLAN_VID:
+        ds_put_format(s, "mod_vlan_vid:%"PRIu16, ntohs(nl_attr_get_be16(a)));
+        break;
+
+    case OFPACT_SET_VLAN_PCP:
+        ds_put_format(s, "mod_vlan_pcp:%"PRIu8, nl_attr_get_u8(a));
+        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((uint8_t *) nl_attr_get(a)));
+        break;
+
+    case OFPACT_SET_ETH_DST:
+        ds_put_format(s, "mod_dl_dst:"ETH_ADDR_FMT,
+                      ETH_ADDR_ARGS((uint8_t *) nl_attr_get(a)));
+        break;
+
+    case OFPACT_SET_NW_SRC:
+        ip = nl_attr_get_be32(a);
+        ds_put_format(s, "mod_nw_src:"IP_FMT, IP_ARGS(&ip));
+        break;
+
+    case OFPACT_SET_NW_DST:
+        ip = nl_attr_get_be32(a);
+        ds_put_format(s, "mod_nw_dst:"IP_FMT, IP_ARGS(&ip));
+        break;
+
+    case OFPACT_SET_NW_TOS:
+        ds_put_format(s, "mod_nw_tos:%d", nl_attr_get_u8(a));
+        break;
+
+    case OFPACT_SET_TP_SRC:
+        ds_put_format(s, "mod_tp_src:%d", ntohs(nl_attr_get_be16(a)));
+        break;
+
+    case OFPACT_SET_TP_DST:
+        ds_put_format(s, "mod_tp_dst:%d", ntohs(nl_attr_get_be16(a)));
+        break;
+
+    case OFPACT_REG_MOVE:
+        nxm_format_reg_move(nl_attr_get(a), s);
+        break;
+
+    case OFPACT_REG_LOAD:
+        nxm_format_reg_load(nl_attr_get(a), s);
+        break;
+
+    case OFPACT_DEC_TTL:
+        ds_put_cstr(s, "dec_ttl");
+        break;
+
+    case OFPACT_SET_TUNNEL:
+        ds_put_format(s, "set_tunnel:%#"PRIx64, ntohll(nl_attr_get_be64(a)));
+        break;
+
+    case OFPACT_SET_QUEUE:
+        ds_put_format(s, "set_queue:%u", nl_attr_get_u32(a));
+        break;
+
+    case OFPACT_POP_QUEUE:
+        ds_put_cstr(s, "pop_queue");
+        break;
+
+    case OFPACT_RESUBMIT:
+        resubmit = nl_attr_get(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(nl_attr_get(a), s);
+        break;
+
+    case OFPACT_MULTIPATH:
+        multipath_format(nl_attr_get(a), s);
+        break;
+
+    case OFPACT_AUTOPATH:
+        autopath = nl_attr_get(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(nl_attr_get(a), nl_attr_get_size(a), s);
+        break;
+
+    case OFPACT_EXIT:
+        ds_put_cstr(s, "exit");
+        break;
+    }
+}
+
+void
+ofpacts_format(const struct nlattr *ofpacts, struct ds *string)
+{
+    ds_put_cstr(string, "actions=");
+    if (nl_attr_type(ofpacts) == OFPACT_END) {
+        ds_put_cstr(string, "drop");
+    } else {
+        const struct nlattr *a;
+
+        OFPACT_FOR_EACH (a, ofpacts) {
+            if (a != ofpacts) {
+                ds_put_cstr(string, ",");
+            }
+            ofpact_format(a, string);
+        }
+    }
+}
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
new file mode 100644
index 0000000..dcd3072
--- /dev/null
+++ b/lib/ofp-actions.h
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+#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"
+
+struct nlattr;
+
+enum ofpact_type {
+    OFPACT_END,                 /* none: Sentinel to end a list of actions. */
+
+    /* Output. */
+    OFPACT_OUTPUT,              /* uint16_t: output to OpenFlow port. */
+    OFPACT_CONTROLLER,          /* uint16_t: output to OpenFlow controller. */
+    OFPACT_ENQUEUE,             /* struct ofpact_enqueue. */
+    OFPACT_OUTPUT_REG,          /* struct ofpact_output_reg. */
+    OFPACT_BUNDLE,              /* struct ofpact_bundle. */
+
+    /* Header changes. */
+    OFPACT_SET_VLAN_VID,        /* ovs_be16: Set 802.1q VLAN id. */
+    OFPACT_SET_VLAN_PCP,        /* uint8_t: Set 802.1q priority. */
+    OFPACT_STRIP_VLAN,          /* none: Strip 802.1q header. */
+    OFPACT_SET_ETH_SRC,         /* uint8_t[6]: Set Ethernet source address. */
+    OFPACT_SET_ETH_DST,         /* uint8_t[6]: Set Ethernet dest address. */
+    OFPACT_SET_NW_SRC,          /* ovs_be32: Set IP source address. */
+    OFPACT_SET_NW_DST,          /* ovs_be32: Set IP destination address. */
+    OFPACT_SET_NW_TOS,          /* uint8_t: Set IP ToS. */
+    OFPACT_SET_TP_SRC,          /* ovs_be16: Set TCP/UDP source port. */
+    OFPACT_SET_TP_DST,          /* ovs_be16: Set TCP/UDP destination port. */
+    OFPACT_REG_MOVE,            /* struct ofpact_reg_move. */
+    OFPACT_REG_LOAD,            /* struct ofpact_reg_load. */
+    OFPACT_DEC_TTL,             /* none: Decrement IPv4/IPv6 TTL. */
+
+    /* Metadata. */
+    OFPACT_SET_TUNNEL,          /* ovs_be64: Set Tunnel ID. */
+    OFPACT_SET_QUEUE,           /* uint32_t: Set default output queue. */
+    OFPACT_POP_QUEUE,           /* none: Restore original default queue. */
+
+    /* Flow table interaction. */
+    OFPACT_RESUBMIT,            /* struct ofpact_resubmit. */
+    OFPACT_LEARN,               /* struct ofpact_learn. */
+
+    /* Arithmetic. */
+    OFPACT_MULTIPATH,           /* struct ofpact_multipath. */
+    OFPACT_AUTOPATH,            /* struct ofpact_autopath. */
+
+    /* Other. */
+    OFPACT_NOTE,                /* uint8_t[]. */
+    OFPACT_EXIT                 /* none: Stop executing actions. */
+};
+
+struct ofpact_enqueue {
+    uint16_t port;
+    uint32_t queue;
+};
+
+struct ofpact_output_reg {
+    struct mf_subfield src;
+    uint16_t max_len;
+};
+
+struct ofpact_bundle {
+    /* 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[];
+};
+
+struct ofpact_reg_move {
+    struct mf_subfield src;
+    struct mf_subfield dst;
+};
+
+struct ofpact_reg_load {
+    struct mf_subfield dst;
+    ovs_32aligned_u64 value;
+};
+
+struct ofpact_resubmit {
+    uint16_t in_port;
+    uint8_t table_id;
+};
+
+struct ofpact_learn_spec {
+    int src_type;
+    ovs_32aligned_u64 imm;      /* XXX should be byte array */
+    struct mf_subfield src;
+
+    int dst_type;
+    struct mf_subfield dst;
+};
+
+struct ofpact_learn {
+    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. */
+    ovs_32aligned_be64 cookie;  /* Cookie for new flow. */
+    uint16_t flags;             /* Either 0 or OFPFF_SEND_FLOW_REM. */
+    uint8_t table_id;           /* Table to insert flow entry. */
+
+    unsigned int n_specs;
+    struct ofpact_learn_spec specs[];
+};
+
+struct ofpact_multipath {
+    /* 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;
+};
+
+struct ofpact_autopath {
+    struct mf_subfield dst;
+    uint32_t port;
+};
+
+#define OFPACT_FOR_EACH(NLA, OFPACTS)                           \
+    for ((NLA) = (OFPACTS); nl_attr_type(NLA) != OFPACT_END;    \
+         (NLA) = nl_attr_next(NLA))
+
+enum ofperr ofpacts_from_openflow(const union ofp_action *, size_t n_actions,
+                                  enum ofputil_protocol, struct ofpbuf *);
+enum ofperr ofpacts_pull_openflow(struct ofpbuf *openflow,
+                                  unsigned int actions_len,
+                                  struct nlattr **ofpacts,
+                                  size_t *ofpacts_len);
+enum ofperr ofpacts_check(const struct nlattr *ofpacts,
+                          const struct flow *, int max_ports);
+
+void ofpacts_to_openflow(const struct nlattr *ofpacts,
+                         enum ofputil_protocol, struct ofpbuf *openflow);
+
+bool ofpacts_output_to_port(const struct nlattr *ofpacts, uint16_t port);
+bool ofpacts_equal(const struct nlattr *a, size_t a_len,
+                   const struct nlattr *b, size_t b_len);
+
+void ofpacts_format(const struct nlattr *ofpacts, struct ds *);
+
+#endif /* ofp-actions.h */
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index cb6ab01..fc41419 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -28,9 +28,11 @@
 #include "dynamic-string.h"
 #include "learn.h"
 #include "meta-flow.h"
-#include "netdev.h"
 #include "multipath.h"
+#include "netdev.h"
+#include "netlink.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
@@ -119,107 +121,77 @@ 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_OFPAT_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_OFPAT_ENQUEUE(b);
-    oae->port = htons(str_to_u32(port));
-    oae->queue_id = htonl(str_to_u32(queue));
+    enqueue.port = str_to_u32(port);
+    enqueue.queue = str_to_u32(queue);
+    nl_msg_put_unspec(ofpacts, OFPACT_ENQUEUE, &enqueue, sizeof enqueue);
 }
 
 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);
+        mf_parse_subfield(&output_reg.src, arg);
+        output_reg.max_len = UINT16_MAX;
+        nl_msg_put_unspec(ofpacts, OFPACT_OUTPUT_REG,
+                          &output_reg, sizeof output_reg);
     } else {
-        put_output_action(b, str_to_u32(arg));
+        uint16_t port = str_to_u32(arg);
+
+        if (port != OFPP_CONTROLLER) {
+            nl_msg_put_u16(ofpacts, OFPACT_OUTPUT, port);
+        } else {
+            nl_msg_put_u16(ofpacts, OFPACT_CONTROLLER, UINT16_MAX);
+        }
     }
 }
 
 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;
 
     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);
-    }
+    nl_msg_put_unspec(ofpacts, OFPACT_RESUBMIT, &resubmit, sizeof resubmit);
 }
 
 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;
+    size_t start_ofs = ofpacts->size;
 
-    nan = ofputil_put_NXAST_NOTE(b);
+    start_ofs = nl_msg_start_attr(ofpacts, OFPACT_NOTE);
 
-    b->size -= sizeof nan->note;
     while (*arg != '\0') {
         uint8_t byte;
         bool ok;
@@ -235,121 +207,144 @@ 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;
     }
 
-    len = b->size - start_ofs;
-    remainder = len % OFP_ACTION_ALIGN;
-    if (remainder) {
-        ofpbuf_put_zeros(b, OFP_ACTION_ALIGN - remainder);
-    }
-    nan = (struct nx_action_note *)((char *)b->data + start_ofs);
-    nan->len = htons(b->size - start_ofs);
+    nl_msg_end_attr(ofpacts, start_ofs);
 }
 
 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_reg_move reg_move;
+    struct ofpact_reg_load reg_load;
+    struct ofpact_multipath multipath;
+    struct ofpact_autopath autopath;
+    uint8_t mac[ETH_ADDR_LEN];
+    uint16_t vid;
+    ovs_be32 ip;
+    uint8_t pcp;
+    uint8_t tos;
 
     switch (code) {
     case OFPUTIL_OFPAT_OUTPUT:
-        parse_output(b, arg);
+        parse_output(arg, ofpacts);
         break;
 
     case OFPUTIL_OFPAT_SET_VLAN_VID:
-        oavv = ofputil_put_OFPAT_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);
+        }
+        nl_msg_put_be16(ofpacts, OFPACT_SET_VLAN_VID, htons(vid));
         break;
 
     case OFPUTIL_OFPAT_SET_VLAN_PCP:
-        oavp = ofputil_put_OFPAT_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);
+        }
+        nl_msg_put_u8(ofpacts, OFPACT_SET_VLAN_PCP, pcp);
         break;
 
     case OFPUTIL_OFPAT_STRIP_VLAN:
-        ofputil_put_OFPAT_STRIP_VLAN(b);
+        nl_msg_put_flag(ofpacts, OFPACT_STRIP_VLAN);
         break;
 
     case OFPUTIL_OFPAT_SET_DL_SRC:
+        str_to_mac(arg, mac);
+        nl_msg_put_unspec(ofpacts, OFPACT_SET_ETH_SRC, mac, ETH_ADDR_LEN);
+        break;
+
     case OFPUTIL_OFPAT_SET_DL_DST:
-        oada = ofputil_put_action(code, b);
-        str_to_mac(arg, oada->dl_addr);
+        str_to_mac(arg, mac);
+        nl_msg_put_unspec(ofpacts, OFPACT_SET_ETH_DST, mac, ETH_ADDR_LEN);
         break;
 
     case OFPUTIL_OFPAT_SET_NW_SRC:
+        str_to_ip(arg, &ip);
+        nl_msg_put_be32(ofpacts, OFPACT_SET_NW_SRC, ip);
+        break;
+
     case OFPUTIL_OFPAT_SET_NW_DST:
-        oana = ofputil_put_action(code, b);
-        str_to_ip(arg, &oana->nw_addr);
+        str_to_ip(arg, &ip);
+        nl_msg_put_be32(ofpacts, OFPACT_SET_NW_DST, ip);
         break;
 
     case OFPUTIL_OFPAT_SET_NW_TOS:
-        ofputil_put_OFPAT_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);
+        }
+        nl_msg_put_u8(ofpacts, OFPACT_SET_NW_TOS, tos);
         break;
 
     case OFPUTIL_OFPAT_SET_TP_SRC:
+        nl_msg_put_be16(ofpacts, OFPACT_SET_TP_SRC, htons(str_to_u32(arg)));
+        break;
+
     case OFPUTIL_OFPAT_SET_TP_DST:
-        oata = ofputil_put_action(code, b);
-        oata->tp_port = htons(str_to_u32(arg));
+        nl_msg_put_be16(ofpacts, OFPACT_SET_TP_DST, htons(str_to_u32(arg)));
         break;
 
     case OFPUTIL_OFPAT_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:
+        nl_msg_put_be64(ofpacts, OFPACT_SET_TUNNEL, htonll(str_to_u64(arg)));
         break;
 
     case OFPUTIL_NXAST_SET_QUEUE:
-        ofputil_put_NXAST_SET_QUEUE(b)->queue_id = htonl(str_to_u32(arg));
+        nl_msg_put_u32(ofpacts, OFPACT_SET_QUEUE, str_to_u32(arg));
         break;
 
     case OFPUTIL_NXAST_POP_QUEUE:
-        ofputil_put_NXAST_POP_QUEUE(b);
+        nl_msg_put_flag(ofpacts, OFPACT_POP_QUEUE);
         break;
 
     case OFPUTIL_NXAST_REG_MOVE:
-        nxm_parse_reg_move(ofputil_put_NXAST_REG_MOVE(b), arg);
+        nxm_parse_reg_move(&reg_move, arg);
+        nl_msg_put_unspec(ofpacts, OFPACT_REG_MOVE,
+                          &reg_move, sizeof reg_move);
         break;
 
     case OFPUTIL_NXAST_REG_LOAD:
-        nxm_parse_reg_load(ofputil_put_NXAST_REG_LOAD(b), arg);
+        nxm_parse_reg_load(&reg_load, arg);
+        nl_msg_put_unspec(ofpacts, OFPACT_REG_LOAD,
+                          &reg_load, sizeof reg_load);
         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(&multipath, arg);
+        nl_msg_put_unspec(ofpacts, OFPACT_MULTIPATH,
+                          &multipath, sizeof multipath);
         break;
 
     case OFPUTIL_NXAST_AUTOPATH:
-        autopath_parse(ofputil_put_NXAST_AUTOPATH(b), arg);
+        autopath_parse(&autopath, arg);
+        nl_msg_put_unspec(ofpacts, OFPACT_AUTOPATH,
+                          &autopath, sizeof autopath);
         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:
@@ -357,21 +352,21 @@ 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);
+        nl_msg_put_flag(ofpacts, OFPACT_EXIT);
         break;
 
     case OFPUTIL_NXAST_DEC_TTL:
-        ofputil_put_NXAST_DEC_TTL(b);
+        nl_msg_put_flag(ofpacts, OFPACT_DEC_TTL);
         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;
@@ -384,7 +379,7 @@ 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. */
@@ -397,23 +392,20 @@ str_to_action(const struct flow *flow, char *str, struct ofpbuf *b)
             }
             break;
         } else if (!strcasecmp(act, "CONTROLLER")) {
-            struct ofp_action_output *oao;
-            oao = put_output_action(b, OFPP_CONTROLLER);
-
             /* Unless a numeric argument is specified, we send the whole
              * packet to the controller. */
-            if (arg[0] && (strspn(arg, "0123456789") == strlen(arg))) {
-               oao->max_len = htons(str_to_u32(arg));
-            } else {
-                oao->max_len = htons(UINT16_MAX);
-            }
+            uint16_t max_len = (isdigit((unsigned char) arg[0])
+                                ? str_to_u32(arg)
+                                : UINT16_MAX);
+            nl_msg_put_u16(ofpacts, OFPACT_CONTROLLER, max_len);
         } else if (ofputil_port_from_string(act, &port)) {
-            put_output_action(b, port);
+            nl_msg_put_u16(ofpacts, OFPACT_OUTPUT, port);
         } else {
             ovs_fatal(0, "Unknown action: %s", act);
         }
         n_actions++;
     }
+    nl_msg_put_flag(ofpacts, OFPACT_END);
 }
 
 struct protocol {
@@ -606,15 +598,15 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
         }
     }
     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);
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 4d9ff88..058ac07 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -35,6 +35,7 @@
 #include "multipath.h"
 #include "meta-flow.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -158,268 +159,38 @@ 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;
-    struct mf_subfield subfield;
-    uint16_t port;
-
-    switch (code) {
-    case OFPUTIL_OFPAT_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_OFPAT_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_OFPAT_SET_VLAN_VID:
-        ds_put_format(s, "mod_vlan_vid:%"PRIu16,
-                      ntohs(a->vlan_vid.vlan_vid));
-        break;
-
-    case OFPUTIL_OFPAT_SET_VLAN_PCP:
-        ds_put_format(s, "mod_vlan_pcp:%"PRIu8, a->vlan_pcp.vlan_pcp);
-        break;
-
-    case OFPUTIL_OFPAT_STRIP_VLAN:
-        ds_put_cstr(s, "strip_vlan");
-        break;
-
-    case OFPUTIL_OFPAT_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_OFPAT_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_OFPAT_SET_NW_SRC:
-        ds_put_format(s, "mod_nw_src:"IP_FMT, IP_ARGS(&a->nw_addr.nw_addr));
-        break;
-
-    case OFPUTIL_OFPAT_SET_NW_DST:
-        ds_put_format(s, "mod_nw_dst:"IP_FMT, IP_ARGS(&a->nw_addr.nw_addr));
-        break;
-
-    case OFPUTIL_OFPAT_SET_NW_TOS:
-        ds_put_format(s, "mod_nw_tos:%d", a->nw_tos.nw_tos);
-        break;
-
-    case OFPUTIL_OFPAT_SET_TP_SRC:
-        ds_put_format(s, "mod_tp_src:%d", ntohs(a->tp_port.tp_port));
-        break;
-
-    case OFPUTIL_OFPAT_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;
-
-    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)
 {
-    size_t len = ntohs(opo->header.length);
-    size_t actions_len = ntohs(opo->actions_len);
-
-    ds_put_cstr(string, " in_port=");
-    ofputil_format_port(ntohs(opo->in_port), string);
+    struct ofputil_packet_out po;
+    enum ofperr error;
 
-    ds_put_format(string, " actions_len=%zu ", actions_len);
-    if (actions_len > (ntohs(opo->header.length) - sizeof *opo)) {
-        ds_put_format(string, "***packet too short for action length***\n");
+    error = ofputil_decode_packet_out(&po, opo);
+    if (error) {
+        ofp_print_error(string, error);
         return;
     }
-    if (actions_len % sizeof(union ofp_action)) {
-        ds_put_format(string, "***action length not a multiple of %zu***\n",
-                      sizeof(union ofp_action));
-    }
-    ofp_print_actions(string, (const union ofp_action *) opo->actions,
-                      actions_len / sizeof(union ofp_action));
 
-    if (ntohl(opo->buffer_id) == UINT32_MAX) {
-        int data_len = len - sizeof *opo - actions_len;
-        ds_put_format(string, " data_len=%d", data_len);
-        if (verbosity > 0 && len > sizeof *opo) {
-            char *packet = ofp_packet_to_string(
-                    (uint8_t *) opo->actions + actions_len, data_len);
+    ds_put_cstr(string, " in_port=");
+    ofputil_format_port(po.in_port, string);
+
+    ds_put_char(string, ' ');
+    ofpacts_format(po.ofpacts, string);
+
+    if (po.buffer_id == UINT32_MAX) {
+        ds_put_format(string, " data_len=%d", po.packet_len);
+        if (verbosity > 0 && po.packet_len > 0) {
+            char *packet = ofp_packet_to_string(po.packet, po.packet_len);
             ds_put_char(string, '\n');
             ds_put_cstr(string, packet);
             free(packet);
         }
     } else {
-        ds_put_format(string, " buffer=0x%08"PRIx32, ntohl(opo->buffer_id));
+        ds_put_format(string, " buffer=0x%08"PRIx32, po.buffer_id);
     }
     ds_put_char(string, '\n');
+
+    free(po.ofpacts);
 }
 
 /* qsort comparison function. */
@@ -844,7 +615,8 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
         ds_put_format(s, "flags:0x%"PRIx16" ", fm.flags);
     }
 
-    ofp_print_actions(s, fm.actions, fm.n_actions);
+    ofpacts_format(fm.ofpacts, s);
+    free(fm.ofpacts);
 }
 
 static void
@@ -1049,7 +821,8 @@ 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);
+        free(fs.ofpacts);
      }
 }
 
diff --git a/lib/ofp-print.h b/lib/ofp-print.h
index 428f5ce..5a83c86 100644
--- a/lib/ofp-print.h
+++ b/lib/ofp-print.h
@@ -25,7 +25,7 @@
 struct ofp_flow_mod;
 struct ofp_match;
 struct ds;
-union ofp_action;
+struct nlattr;
 
 #ifdef  __cplusplus
 extern "C" {
@@ -34,7 +34,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 ofp_print_match(struct ds *, const struct ofp_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 0a53695..4269c26 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -31,6 +31,7 @@
 #include "multipath.h"
 #include "meta-flow.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -1277,12 +1278,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
@@ -1297,6 +1294,13 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
         ofputil_cls_rule_from_match(&ofm->match, priority, &fm->cr);
         ofputil_normalize_rule(&fm->cr);
 
+        /* Now get the actions. */
+        error = ofpacts_pull_openflow(&b, b.size,
+                                      &fm->ofpacts, &fm->ofpacts_len);
+        if (error) {
+            return error;
+        }
+
         /* Translate the message. */
         fm->cookie = ofm->cookie;
         fm->cookie_mask = htonll(UINT64_MAX);
@@ -1318,7 +1322,8 @@ 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,
+                                      &fm->ofpacts, &fm->ofpacts_len);
         if (error) {
             return error;
         }
@@ -1365,7 +1370,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;
@@ -1379,7 +1383,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, OFPT_FLOW_MOD, msg);
         ofputil_cls_rule_to_match(&fm->cr, &ofm->match);
         ofm->cookie = fm->cookie;
@@ -1394,7 +1398,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);
@@ -1419,7 +1423,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, protocol, msg);
+    }
     update_openflow_length(msg);
     return msg;
 }
@@ -1638,8 +1644,8 @@ 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,
+                                  &fs->ofpacts, &fs->ofpacts_len)) {
             return EINVAL;
         }
 
@@ -1655,7 +1661,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) {
@@ -1676,9 +1682,9 @@ 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,
+                                  &fs->ofpacts, &fs->ofpacts_len)) {
             return EINVAL;
         }
 
@@ -1714,16 +1720,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_match(&fs->rule, &ofs->match);
@@ -1738,17 +1742,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, OFPUTIL_P_OF10, 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);
@@ -1756,16 +1757,20 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
         nfs->priority = htons(fs->rule.priority);
         nfs->idle_timeout = htons(fs->idle_timeout);
         nfs->hard_timeout = htons(fs->hard_timeout);
-        nfs->match_len = htons(nx_put_match(msg, &fs->rule, 0, 0));
+        nfs->match_len = htons(nx_put_match(reply, &fs->rule, 0, 0));
         memset(nfs->pad2, 0, sizeof nfs->pad2);
         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, OFPUTIL_P_NXM, 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
@@ -2050,6 +2055,41 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
     return packet;
 }
 
+enum ofperr
+ofputil_decode_packet_out(struct ofputil_packet_out *po,
+                          const struct ofp_packet_out *opo)
+{
+    enum ofperr error;
+    struct ofpbuf b;
+
+    po->buffer_id = ntohl(opo->buffer_id);
+    po->in_port = ntohs(opo->in_port);
+    if (po->in_port >= OFPP_MAX && po->in_port != OFPP_NONE) {
+        VLOG_WARN_RL(&bad_ofmsg_rl, "packet-out has bad input port %#"PRIx16,
+                     po->in_port);
+        return OFPERR_NXBRC_BAD_IN_PORT;
+    }
+
+    ofpbuf_use_const(&b, opo, ntohs(opo->header.length));
+    ofpbuf_pull(&b, sizeof *opo);
+
+    error = ofpacts_pull_openflow(&b, ntohs(opo->actions_len),
+                                  &po->ofpacts, &po->ofpacts_len);
+    if (error) {
+        return error;
+    }
+
+    if (po->buffer_id == UINT32_MAX) {
+        po->packet = b.data;
+        po->packet_len = b.size;
+    } else {
+        po->packet = NULL;
+        po->packet_len = 0;
+    }
+
+    return 0;
+}
+
 /* Returns a string representing the message type of 'type'.  The string is the
  * enumeration constant for the type, e.g. "OFPT_HELLO".  For statistics
  * messages, the constant is followed by "request" or "reply",
@@ -2318,6 +2358,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)
@@ -2623,154 +2677,6 @@ ofputil_format_port(uint16_t port, struct ds *s)
     ds_put_cstr(s, name);
 }
 
-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_OFPAT_OUTPUT:
-            error = ofputil_check_output_port(ntohs(a->output.port),
-                                              max_ports);
-            break;
-
-        case OFPUTIL_OFPAT_SET_VLAN_VID:
-            if (a->vlan_vid.vlan_vid & ~htons(0xfff)) {
-                error = OFPERR_OFPBAC_BAD_ARGUMENT;
-            }
-            break;
-
-        case OFPUTIL_OFPAT_SET_VLAN_PCP:
-            if (a->vlan_pcp.vlan_pcp & ~7) {
-                error = OFPERR_OFPBAC_BAD_ARGUMENT;
-            }
-            break;
-
-        case OFPUTIL_OFPAT_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_OFPAT_STRIP_VLAN:
-        case OFPUTIL_OFPAT_SET_NW_SRC:
-        case OFPUTIL_OFPAT_SET_NW_DST:
-        case OFPUTIL_OFPAT_SET_NW_TOS:
-        case OFPUTIL_OFPAT_SET_TP_SRC:
-        case OFPUTIL_OFPAT_SET_TP_DST:
-        case OFPUTIL_OFPAT_SET_DL_SRC:
-        case OFPUTIL_OFPAT_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:
-            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;
@@ -2965,20 +2871,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 (ntohs(action->type)) {
-    case OFPAT_OUTPUT:
-        return action->output.port == port;
-    case OFPAT_ENQUEUE:
-        return ((const struct ofp_action_enqueue *) action)->port == port;
-    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
@@ -3086,57 +2978,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 136dfd2..5991594 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -193,8 +193,8 @@ struct ofputil_flow_mod {
     uint32_t buffer_id;
     uint16_t out_port;
     uint16_t flags;
-    union ofp_action *actions;
-    size_t n_actions;
+    struct nlattr *ofpacts;     /* Series of OFPACT_* attributes. */
+    size_t ofpacts_len;         /* Length of ofpacts, in bytes. */
 };
 
 enum ofperr ofputil_decode_flow_mod(struct ofputil_flow_mod *,
@@ -234,8 +234,8 @@ struct ofputil_flow_stats {
     uint16_t hard_timeout;
     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 nlattr *ofpacts;
+    size_t ofpacts_len;
 };
 
 int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *,
@@ -294,6 +294,19 @@ struct ofpbuf *ofputil_encode_packet_in(const struct ofputil_packet_in *,
 int ofputil_decode_packet_in(struct ofputil_packet_in *pi,
                              const struct ofp_header *oh);
 
+/* Abstract packet-out message. */
+struct ofputil_packet_out {
+    const void *packet;         /* Packet data, if buffer_id == UINT32_MAX. */
+    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 or OFPP_NONE. */
+    struct nlattr *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 *);
+
 /* OpenFlow protocol utility functions. */
 void *make_openflow(size_t openflow_len, uint8_t type, struct ofpbuf **);
 void *make_nxmsg(size_t openflow_len, uint32_t subtype, struct ofpbuf **);
@@ -323,6 +336,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 *);
 
 const void *ofputil_stats_body(const struct ofp_header *);
 size_t ofputil_stats_body_len(const struct ofp_header *);
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index c4bba99..874a170 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -24,7 +24,9 @@
 #include "coverage.h"
 #include "fail-open.h"
 #include "in-band.h"
+#include "netlink.h"
 #include "odp-util.h"
+#include "ofp-actions.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
 #include "ofproto-provider.h"
@@ -1466,15 +1468,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(OFPAT_OUTPUT);
-        action.output.len = htons(sizeof action);
-        action.output.port = htons(OFPP_NORMAL);
+        ofpbuf_init(&ofpacts, NL_A_U16_SIZE + NL_A_FLAG_SIZE);
+        nl_msg_put_u16(&ofpacts, OFPACT_OUTPUT, OFPP_NORMAL);
+        nl_msg_put_flag(&ofpacts, OFPACT_END);
+
         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 541bb05..d2b0322 100644
--- a/ofproto/fail-open.c
+++ b/ofproto/fail-open.c
@@ -22,7 +22,9 @@
 #include "connmgr.h"
 #include "flow.h"
 #include "mac-learning.h"
+#include "netlink.h"
 #include "odp-util.h"
+#include "ofp-actions.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
 #include "ofproto.h"
@@ -210,18 +212,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(OFPAT_OUTPUT);
-        action.output.len = htons(sizeof action);
-        action.output.port = htons(OFPP_NORMAL);
+        ofpbuf_init(&ofpacts, NL_A_U16_SIZE + NL_A_FLAG_SIZE);
+        nl_msg_put_u16(&ofpacts, OFPACT_OUTPUT, OFPP_NORMAL);
+        nl_msg_put_flag(&ofpacts, OFPACT_END);
 
         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 28f0434..dd87441 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-print.h"
 #include "ofproto-dpif-sflow.h"
 #include "poll-loop.h"
@@ -252,7 +253,7 @@ static void action_xlate_ctx_init(struct action_xlate_ctx *,
                                   ovs_be16 initial_tci, struct rule_dpif *,
                                   const struct ofpbuf *);
 static struct ofpbuf *xlate_actions(struct action_xlate_ctx *,
-                                    const union ofp_action *in, size_t n_in);
+                                    const struct nlattr *ofpact);
 
 /* An exact-match instantiation of an OpenFlow flow.
  *
@@ -3232,8 +3233,7 @@ facet_account(struct facet *facet)
                               facet->flow.vlan_tci,
                               facet->rule, NULL);
         ctx.may_learn = true;
-        ofpbuf_delete(xlate_actions(&ctx, facet->rule->up.actions,
-                                    facet->rule->up.n_actions));
+        ofpbuf_delete(xlate_actions(&ctx, facet->rule->up.ofpacts));
     }
 
     if (!facet->has_normal || !ofproto->has_bonded_bundles) {
@@ -3283,10 +3283,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 nlattr *ofpact = facet->rule->up.ofpacts;
+
+        if (nl_attr_type(ofpact) == OFPACT_CONTROLLER &&
+            nl_attr_type(nl_attr_next(ofpact)) == OFPACT_END) {
+            return true;
+        }
+    }
+    return false;
 }
 
 /* Folds all of 'facet''s statistics into its rule.  Also updates the
@@ -3421,8 +3426,7 @@ facet_check_consistency(struct facet *facet)
 
         action_xlate_ctx_init(&ctx, ofproto, &facet->flow,
                               subfacet->initial_tci, rule, NULL);
-        odp_actions = xlate_actions(&ctx, rule->up.actions,
-                                    rule->up.n_actions);
+        odp_actions = xlate_actions(&ctx, rule->up.ofpacts);
 
         should_install = (ctx.may_set_up_flow
                           && subfacet->key_fitness != ODP_FIT_TOO_LITTLE);
@@ -3540,8 +3544,7 @@ facet_revalidate(struct facet *facet)
 
         action_xlate_ctx_init(&ctx, ofproto, &facet->flow,
                               subfacet->initial_tci, new_rule, NULL);
-        odp_actions = xlate_actions(&ctx, new_rule->up.actions,
-                                    new_rule->up.n_actions);
+        odp_actions = xlate_actions(&ctx, new_rule->up.ofpacts);
         actions_changed = (subfacet->actions_len != odp_actions->size
                            || memcmp(subfacet->actions, odp_actions->data,
                                      subfacet->actions_len));
@@ -3690,8 +3693,7 @@ flow_push_stats(struct rule_dpif *rule,
     action_xlate_ctx_init(&push.ctx, ofproto, flow, flow->vlan_tci, rule,
                           NULL);
     push.ctx.resubmit_hook = push_resubmit;
-    ofpbuf_delete(xlate_actions(&push.ctx,
-                                rule->up.actions, rule->up.n_actions));
+    ofpbuf_delete(xlate_actions(&push.ctx, rule->up.ofpacts));
 }
 
 /* Subfacets. */
@@ -3834,7 +3836,7 @@ subfacet_make_actions(struct subfacet *subfacet, const struct ofpbuf *packet)
 
     action_xlate_ctx_init(&ctx, ofproto, &facet->flow, subfacet->initial_tci,
                           rule, packet);
-    odp_actions = xlate_actions(&ctx, rule->up.actions, rule->up.n_actions);
+    odp_actions = xlate_actions(&ctx, rule->up.ofpacts);
     facet->tags = ctx.tags;
     facet->may_install = ctx.may_set_up_flow;
     facet->has_learn = ctx.has_learn;
@@ -4029,8 +4031,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;
     }
@@ -4114,7 +4116,7 @@ rule_execute(struct rule *rule_, const struct flow *flow,
 
     action_xlate_ctx_init(&ctx, ofproto, flow, flow->vlan_tci,
                           rule, packet);
-    odp_actions = xlate_actions(&ctx, rule->up.actions, rule->up.n_actions);
+    odp_actions = xlate_actions(&ctx, rule->up.ofpacts);
     size = packet->size;
     if (execute_odp_actions(ofproto, flow, odp_actions->data,
                             odp_actions->size, packet)) {
@@ -4135,8 +4137,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;
@@ -4189,8 +4191,8 @@ 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 nlattr *ofpact,
+                             struct action_xlate_ctx *);
 static void xlate_normal(struct action_xlate_ctx *);
 
 static size_t
@@ -4387,7 +4389,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--;
         }
@@ -4402,16 +4404,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 nlattr *a)
 {
+    const struct ofpact_resubmit *resubmit = nl_attr_get(a);
     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);
 }
@@ -4518,8 +4525,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;
 
@@ -4568,44 +4575,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) {
@@ -4627,21 +4622,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 {
@@ -4651,11 +4641,12 @@ 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);
-    struct ofport_dpif *port = get_ofp_port(ctx->ofproto, ofp_port);
+    struct ofport_dpif *port;
+    uint32_t ofp_port;
 
+    port = get_ofp_port(ctx->ofproto, ap->port);
     if (!port || !port->bundle) {
         ofp_port = OFPP_NONE;
     } else if (port->bundle->bond) {
@@ -4666,7 +4657,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
@@ -4692,8 +4683,22 @@ 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;
@@ -4706,8 +4711,6 @@ xlate_learn_action(struct action_xlate_ctx *ctx,
         VLOG_WARN("learning action failed to modify flow table (%s)",
                   ofperr_get_name(error));
     }
-
-    free(fm.actions);
 }
 
 static bool
@@ -4732,13 +4735,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 nlattr *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 nlattr *a;
 
     port = get_ofp_port(ctx->ofproto, ctx->flow.in_port);
     if (port && !may_receive(port, ctx)) {
@@ -4751,168 +4752,135 @@ 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;
-        enum ofputil_action_code code;
-        ovs_be64 tun_id;
+    OFPACT_FOR_EACH (a, ofpacts) {
+        enum ofpact_type type = nl_attr_type(a);
 
         if (ctx->exit) {
             break;
         }
 
-        code = ofputil_decode_action_unsafe(ia);
-        switch (code) {
-        case OFPUTIL_OFPAT_OUTPUT:
-            xlate_output_action(ctx, &ia->output);
+        switch (type) {
+        case OFPACT_END:
+            return;
+
+        case OFPACT_OUTPUT:
+            xlate_output_action(ctx, nl_attr_get_u16(a), 0);
             break;
 
-        case OFPUTIL_OFPAT_SET_VLAN_VID:
+        case OFPACT_CONTROLLER:
+            xlate_output_action(ctx, OFPP_CONTROLLER, nl_attr_get_u16(a));
+            break;
+
+        case OFPACT_ENQUEUE:
+            xlate_enqueue_action(ctx, nl_attr_get(a));
+            break;
+
+        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 |= nl_attr_get_be16(a) | htons(VLAN_CFI);
             break;
 
-        case OFPUTIL_OFPAT_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((nl_attr_get_u8(a) << VLAN_PCP_SHIFT)
+                                        | VLAN_CFI);
             break;
 
-        case OFPUTIL_OFPAT_STRIP_VLAN:
+        case OFPACT_STRIP_VLAN:
             ctx->flow.vlan_tci = htons(0);
             break;
 
-        case OFPUTIL_OFPAT_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, nl_attr_get(a), ETH_ADDR_LEN);
             break;
 
-        case OFPUTIL_OFPAT_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, nl_attr_get(a), ETH_ADDR_LEN);
             break;
 
-        case OFPUTIL_OFPAT_SET_NW_SRC:
-            ctx->flow.nw_src = ia->nw_addr.nw_addr;
+        case OFPACT_SET_NW_SRC:
+            ctx->flow.nw_src = nl_attr_get_be32(a);
             break;
 
-        case OFPUTIL_OFPAT_SET_NW_DST:
-            ctx->flow.nw_dst = ia->nw_addr.nw_addr;
+        case OFPACT_SET_NW_DST:
+            ctx->flow.nw_dst = nl_attr_get_be32(a);
             break;
 
-        case OFPUTIL_OFPAT_SET_NW_TOS:
+        case OFPACT_SET_NW_TOS:
             /* 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 |= nl_attr_get_u8(a) & IP_DSCP_MASK;
             }
             break;
 
-        case OFPUTIL_OFPAT_SET_TP_SRC:
-            ctx->flow.tp_src = ia->tp_port.tp_port;
-            break;
-
-        case OFPUTIL_OFPAT_SET_TP_DST:
-            ctx->flow.tp_dst = ia->tp_port.tp_port;
-            break;
-
-        case OFPUTIL_OFPAT_ENQUEUE:
-            xlate_enqueue_action(ctx, (const struct ofp_action_enqueue *) ia);
+        case OFPACT_SET_TP_SRC:
+            ctx->flow.tp_src = nl_attr_get_be16(a);
             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_SET_TP_DST:
+            ctx->flow.tp_dst = nl_attr_get_be16(a);
             break;
 
-        case OFPUTIL_NXAST_RESUBMIT_TABLE:
-            xlate_resubmit_table(ctx, (const struct nx_action_resubmit *) ia);
+        case OFPACT_RESUBMIT:
+            xlate_ofpact_resubmit(ctx, a);
             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_TUNNEL:
+            ctx->flow.tun_id = nl_attr_get_be64(a);
             break;
 
-        case OFPUTIL_NXAST_SET_QUEUE:
-            nasq = (const struct nx_action_set_queue *) ia;
-            xlate_set_queue_action(ctx, nasq);
+        case OFPACT_SET_QUEUE:
+            xlate_set_queue_action(ctx, nl_attr_get_u32(a));
             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);
-            break;
-
-        case OFPUTIL_NXAST_REG_LOAD:
-            nxm_execute_reg_load((const struct nx_action_reg_load *) ia,
-                                 &ctx->flow);
+        case OFPACT_REG_MOVE:
+            nxm_execute_reg_move(nl_attr_get(a), &ctx->flow);
             break;
 
-        case OFPUTIL_NXAST_NOTE:
-            /* Nothing to do. */
+        case OFPACT_REG_LOAD:
+            nxm_execute_reg_load(nl_attr_get(a), &ctx->flow);
             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(nl_attr_get(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, nl_attr_get(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, nl_attr_get(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, nl_attr_get(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);
-            }
-            break;
-
-        case OFPUTIL_NXAST_DEC_TTL:
-            if (compose_dec_ttl(ctx)) {
-                goto out;
+                xlate_learn_action(ctx, nl_attr_get(a));
             }
             break;
 
-        case OFPUTIL_NXAST_EXIT:
+        case OFPACT_EXIT:
             ctx->exit = true;
             break;
         }
@@ -4948,8 +4916,7 @@ action_xlate_ctx_init(struct action_xlate_ctx *ctx,
 }
 
 static struct ofpbuf *
-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 nlattr *ofpacts)
 {
     struct flow orig_flow = ctx->flow;
 
@@ -4996,7 +4963,7 @@ xlate_actions(struct action_xlate_ctx *ctx,
         return ctx->odp_actions;
     } else {
         add_sflow_action(ctx);
-        do_xlate_actions(in, n_in, ctx);
+        do_xlate_actions(ofpacts, ctx);
 
         if (!connmgr_may_set_up_flow(ctx->ofproto->up.connmgr, &ctx->flow,
                                      ctx->odp_actions->data,
@@ -5709,8 +5676,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 nlattr *ofpacts)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     enum ofperr error;
@@ -5719,8 +5685,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 ofpbuf *odp_actions;
@@ -5740,7 +5705,7 @@ packet_out(struct ofproto *ofproto_, struct ofpbuf *packet,
         push.used = time_msec();
         push.ctx.resubmit_hook = push_resubmit;
 
-        odp_actions = xlate_actions(&push.ctx, ofp_actions, n_ofp_actions);
+        odp_actions = xlate_actions(&push.ctx, ofpacts);
         dpif_execute(ofproto->dpif, key.data, key.size,
                      odp_actions->data, odp_actions->size, packet);
         ofpbuf_delete(odp_actions);
@@ -5898,7 +5863,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');
 }
 
@@ -6039,8 +6004,7 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[],
         action_xlate_ctx_init(&trace.ctx, ofproto, &flow, initial_tci,
                               rule, packet);
         trace.ctx.resubmit_hook = trace_resubmit;
-        odp_actions = xlate_actions(&trace.ctx,
-                                    rule->up.actions, rule->up.n_actions);
+        odp_actions = xlate_actions(&trace.ctx, rule->up.ofpacts);
 
         ds_put_char(&result, '\n');
         trace_format_flow(&result, 0, "Final flow", &trace);
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index cb97188..e9a6802 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -28,6 +28,7 @@
 #include "shash.h"
 #include "timeval.h"
 
+struct ofpact;
 struct ofputil_flow_mod;
 
 /* An OpenFlow switch.
@@ -175,8 +176,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 nlattr *ofpacts;      /* Actions as a series of OFPACT_*. */
+    unsigned int ofpacts_len;    /* Size of 'ofpacts', in bytes. */
 };
 
 static inline struct rule *
@@ -924,8 +925,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 nlattr *ofpact);
 
 /* ## ------------------------- ## */
 /* ## OFPP_NORMAL configuration ## */
@@ -1148,9 +1148,9 @@ int ofproto_class_unregister(const struct ofproto_class *);
 enum { OFPROTO_POSTPONE = 1 << 16 };
 BUILD_ASSERT_DECL(OFPROTO_POSTPONE < OFPERR_OFS);
 
-int ofproto_flow_mod(struct ofproto *, const struct ofputil_flow_mod *);
+int ofproto_flow_mod(struct ofproto *, struct ofputil_flow_mod *);
 void ofproto_add_flow(struct ofproto *, const struct cls_rule *,
-                      const union ofp_action *, size_t n_actions);
+                      const struct nlattr *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 f23b0dd..3b7eac1 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -31,7 +31,9 @@
 #include "hmap.h"
 #include "meta-flow.h"
 #include "netdev.h"
+#include "netlink.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-print.h"
 #include "ofp-util.h"
@@ -118,8 +120,8 @@ struct ofoperation {
     enum ofoperation_type type; /* Type of operation. */
     int status;                 /* -1 if pending, otherwise 0 or error code. */
     struct rule *victim;        /* OFOPERATION_ADDING: Replaced rule. */
-    union ofp_action *actions;  /* OFOPERATION_MODIFYING: Replaced actions. */
-    int n_actions;              /* OFOPERATION_MODIFYING: # of old actions. */
+    struct nlattr *ofpacts;     /* OFOPERATION_MODIFYING: Replaced actions. */
+    size_t ofpacts_len;         /* OFOPERATION_MODIFYING: # of old actions. */
     ovs_be64 flow_cookie;       /* Rule's old flow cookie. */
 };
 
@@ -184,12 +186,12 @@ static bool rule_is_hidden(const struct rule *);
 
 /* OpenFlow. */
 static enum ofperr add_flow(struct ofproto *, struct ofconn *,
-                            const struct ofputil_flow_mod *,
+                            struct ofputil_flow_mod *,
                             const struct ofp_header *);
 static void delete_flow__(struct rule *, struct ofopgroup *);
 static bool handle_openflow(struct ofconn *, struct ofpbuf *);
 static enum ofperr handle_flow_mod__(struct ofproto *, struct ofconn *,
-                                     const struct ofputil_flow_mod *,
+                                     struct ofputil_flow_mod *,
                                      const struct ofp_header *);
 
 /* ofproto. */
@@ -1297,27 +1299,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 nlattr *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);
     }
 }
 
@@ -1327,7 +1330,7 @@ ofproto_add_flow(struct ofproto *ofproto, const struct cls_rule *cls_rule,
  *
  * This is a helper function for in-band control and fail-open. */
 int
-ofproto_flow_mod(struct ofproto *ofproto, const struct ofputil_flow_mod *fm)
+ofproto_flow_mod(struct ofproto *ofproto, struct ofputil_flow_mod *fm)
 {
     return handle_flow_mod__(ofproto, NULL, fm, NULL);
 }
@@ -1763,7 +1766,7 @@ static void
 ofproto_rule_destroy__(struct rule *rule)
 {
     if (rule) {
-        free(rule->actions);
+        free(rule->ofpacts);
         rule->ofproto->ofproto_class->rule_dealloc(rule);
     }
 }
@@ -1785,23 +1788,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
@@ -1953,17 +1944,14 @@ reject_slave_controller(struct ofconn *ofconn)
 }
 
 static enum ofperr
-handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh)
+handle_packet_out(struct ofconn *ofconn, const struct ofp_packet_out *opo)
 {
     struct ofproto *p = ofconn_get_ofproto(ofconn);
-    struct ofp_packet_out *opo;
-    struct ofpbuf payload, *buffer;
-    union ofp_action *ofp_actions;
-    struct ofpbuf request;
+    struct ofputil_packet_out po;
+    struct ofpbuf *payload;
+    struct nlattr *ofpacts;
     struct flow flow;
-    size_t n_ofp_actions;
     enum ofperr error;
-    uint16_t in_port;
 
     COVERAGE_INC(ofproto_packet_out);
 
@@ -1972,28 +1960,21 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh)
         return error;
     }
 
-    /* Get ofp_packet_out. */
-    ofpbuf_use_const(&request, oh, ntohs(oh->length));
-    opo = ofpbuf_pull(&request, offsetof(struct ofp_packet_out, actions));
-
-    /* Get actions. */
-    error = ofputil_pull_actions(&request, ntohs(opo->actions_len),
-                                 &ofp_actions, &n_ofp_actions);
+    /* Decode message. */
+    error = ofputil_decode_packet_out(&po, opo);
     if (error) {
         return error;
     }
 
     /* Get payload. */
-    if (opo->buffer_id != htonl(UINT32_MAX)) {
-        error = ofconn_pktbuf_retrieve(ofconn, ntohl(opo->buffer_id),
-                                       &buffer, NULL);
-        if (error || !buffer) {
+    if (po.buffer_id != UINT32_MAX) {
+        error = ofconn_pktbuf_retrieve(ofconn, po.buffer_id, &payload, NULL);
+        if (error || !payload) {
             return error;
         }
-        payload = *buffer;
     } else {
-        payload = request;
-        buffer = NULL;
+        payload = xmalloc(sizeof *payload);
+        ofpbuf_use_const(payload, po.packet, po.packet_len);
     }
 
     /* Get in_port and partially validate it.
@@ -2001,16 +1982,15 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh)
      * We don't know what range of ports the ofproto actually implements, but
      * we do know that only certain reserved ports (numbered OFPP_MAX and
      * above) are valid. */
-    in_port = ntohs(opo->in_port);
-    if (in_port >= OFPP_MAX && in_port != OFPP_LOCAL && in_port != OFPP_NONE) {
+    if (po.in_port >= OFPP_MAX && po.in_port != OFPP_LOCAL
+        && po.in_port != OFPP_NONE) {
         return OFPERR_NXBRC_BAD_IN_PORT;
     }
 
     /* Send out packet. */
-    flow_extract(&payload, 0, 0, in_port, &flow);
-    error = p->ofproto_class->packet_out(p, &payload, &flow,
-                                         ofp_actions, n_ofp_actions);
-    ofpbuf_delete(buffer);
+    flow_extract(payload, 0, 0, po.in_port, &flow);
+    error = p->ofproto_class->packet_out(p, payload, &flow, ofpacts);
+    ofpbuf_delete(payload);
 
     return error;
 }
@@ -2373,8 +2353,7 @@ handle_flow_stats_request(struct ofconn *ofconn,
         fs.hard_timeout = rule->hard_timeout;
         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);
@@ -2400,8 +2379,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 (nl_attr_type(rule->ofpacts) != OFPACT_END) {
+        ofpacts_format(rule->ofpacts, results);
     } else {
         ds_put_cstr(results, "drop");
     }
@@ -2640,7 +2619,7 @@ is_flow_deletion_pending(const struct ofproto *ofproto,
  * if any. */
 static enum ofperr
 add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
-         const struct ofputil_flow_mod *fm, const struct ofp_header *request)
+         struct ofputil_flow_mod *fm, const struct ofp_header *request)
 {
     struct oftable *table;
     struct ofopgroup *group;
@@ -2704,10 +2683,12 @@ 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 = fm->ofpacts;
+    rule->ofpacts_len = fm->ofpacts_len;
     rule->evictable = true;
     rule->eviction_group = NULL;
+    fm->ofpacts = NULL;
+    fm->ofpacts_len = 0;
 
     /* Insert new rule. */
     victim = oftable_replace_rule(rule);
@@ -2787,14 +2768,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();
         }
@@ -2812,7 +2793,7 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn,
  * if any. */
 static enum ofperr
 modify_flows_loose(struct ofproto *ofproto, struct ofconn *ofconn,
-                   const struct ofputil_flow_mod *fm,
+                   struct ofputil_flow_mod *fm,
                    const struct ofp_header *request)
 {
     struct list rules;
@@ -2833,7 +2814,7 @@ modify_flows_loose(struct ofproto *ofproto, struct ofconn *ofconn,
  * if any. */
 static enum ofperr
 modify_flow_strict(struct ofproto *ofproto, struct ofconn *ofconn,
-                   const struct ofputil_flow_mod *fm,
+                   struct ofputil_flow_mod *fm,
                    const struct ofp_header *request)
 {
     struct list rules;
@@ -2991,20 +2972,25 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
         return error;
     }
 
-    /* We do not support the emergency flow cache.  It will hopefully get
-     * dropped from OpenFlow in the near future. */
     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);
     }
 
-    return handle_flow_mod__(ofconn_get_ofproto(ofconn), ofconn, &fm, oh);
+    /* handle_flow_mod__() might have taken fm.ofpacts for itself (and set it
+     * to NULL) but in case it didn't we need to free it. */
+    free(fm.ofpacts);
+
+    return error;
 }
 
 static enum ofperr
 handle_flow_mod__(struct ofproto *ofproto, struct ofconn *ofconn,
-                  const struct ofputil_flow_mod *fm,
+                  struct ofputil_flow_mod *fm,
                   const struct ofp_header *oh)
 {
     if (ofproto->n_pending >= 50) {
@@ -3180,7 +3166,7 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
         return handle_set_config(ofconn, msg->data);
 
     case OFPUTIL_OFPT_PACKET_OUT:
-        return handle_packet_out(ofconn, oh);
+        return handle_packet_out(ofconn, msg->data);
 
     case OFPUTIL_OFPT_PORT_MOD:
         return handle_port_mod(ofconn, oh);
@@ -3395,7 +3381,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)) {
@@ -3495,10 +3481,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 86cfa60..ffabd7a 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=1,hard_timeout=2,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,NXM_OF_IP_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/ofp-print.at b/tests/ofp-print.at
index 85562b6..cfa8d83 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -315,7 +315,7 @@ b9 7c c0 a8 00 02 c0 a8 00 01 00 00 2b 60 00 00 \
 00 00 6a 4f 2b 58 50 14 00 00 6d 75 00 00 00 00 \
 00 00 00 00 \
 "], [0], [dnl
-OFPT_PACKET_OUT (xid=0x0): in_port=1 actions_len=8 actions=output:3 buffer=0x00000114
+OFPT_PACKET_OUT (xid=0x0): in_port=1 actions=output:3 buffer=0x00000114
 ])
 AT_CLEANUP
 
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index cb12263..2ba5a15 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -69,7 +69,7 @@ NXT_FLOW_MOD: ADD table:255 udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,o
 NXT_FLOW_MOD: ADD table:255 priority=60000 cookie:0x123456789abcdef hard:10 actions=CONTROLLER:65535
 NXT_FLOW_MOD: ADD table:255 actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00
 NXT_FLOW_MOD: ADD table:255 tcp,tun_id=0x1234,tp_src=0x1230/0xfff0 cookie:0x5678 actions=FLOOD
-NXT_FLOW_MOD: ADD table:255 actions=set_tunnel:0x1234,set_tunnel64:0x9876,set_tunnel64:0x123456789
+NXT_FLOW_MOD: ADD table:255 actions=set_tunnel:0x1234,set_tunnel:0x9876,set_tunnel:0x123456789
 NXT_FLOW_MOD: ADD table:255 actions=multipath(eth_src,50,hrw,12,0,NXM_NX_REG0[0..3]),multipath(symmetric_l4,1024,iter_hash,5000,5050,NXM_NX_REG0[0..12])
 NXT_FLOW_MOD: ADD table:1 actions=drop
 NXT_FLOW_MOD: ADD table:255 tun_id=0x1234000056780000/0xffff0000ffff0000 actions=drop
diff --git a/tests/test-bundle.c b/tests/test-bundle.c
index 16e264d..83c2528 100644
--- a/tests/test-bundle.c
+++ b/tests/test-bundle.c
@@ -21,6 +21,8 @@
 #include <stdlib.h>
 
 #include "flow.h"
+#include "netlink.h"
+#include "ofp-actions.h"
 #include "ofpbuf.h"
 #include "random.h"
 
@@ -64,22 +66,25 @@ 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;
-
-    ofpbuf_init(&b, 0);
-    bundle_parse_load(&b, actions);
-    nab = ofpbuf_steal_data(&b);
-    ofpbuf_uninit(&b);
-
-    if (ntohs(nab->n_slaves) > MAX_SLAVES) {
+    struct ofpact_bundle *bundle;
+    struct ofpbuf ofpacts;
+    struct nlattr *attr;
+
+    ofpbuf_init(&ofpacts, 0);
+    bundle_parse_load(actions, &ofpacts);
+    attr = ofpacts.data;
+    assert(nl_attr_type(attr) == OFPACT_BUNDLE);
+    bundle = xmemdup(nl_attr_get(attr), nl_attr_get_size(attr));
+    ofpbuf_uninit(&ofpacts);
+
+    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 +106,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 +119,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 +141,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 +189,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 +226,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 +259,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 03a666f..fc1c0ef 100644
--- a/tests/test-multipath.c
+++ b/tests/test-multipath.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Nicira Networks.
+ * Copyright (c) 2010, 2011 Nicira Networks.
  *
  * 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 0cfb6ba..9650bda 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -37,6 +37,7 @@
 #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"
@@ -774,7 +775,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);
 }
@@ -1185,8 +1186,8 @@ struct fte_version {
     uint16_t idle_timeout;
     uint16_t hard_timeout;
     uint16_t flags;
-    union ofp_action *actions;
-    size_t n_actions;
+    struct nlattr *ofpacts;
+    size_t ofpacts_len;
 };
 
 /* Frees 'version' and the data that it owns. */
@@ -1194,7 +1195,7 @@ static void
 fte_version_free(struct fte_version *version)
 {
     if (version) {
-        free(version->actions);
+        free(version->ofpacts);
         free(version);
     }
 }
@@ -1209,9 +1210,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
@@ -1232,7 +1232,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);
 }
@@ -1319,8 +1319,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);
 
@@ -1401,9 +1401,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 = xmemdup(fs.ofpacts, fs.ofpacts_len);
+                version->ofpacts_len = fs.ofpacts_len;
 
                 fte_insert(cls, &fs.rule, version, index);
             }
@@ -1434,11 +1433,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);
@@ -1590,7 +1589,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