[ovs-dev] [PATCH 11/12] datapath: Replace "struct odp_action" by Netlink attributes.

Ben Pfaff blp at nicira.com
Tue Dec 7 19:00:33 UTC 2010


In the medium term, we plan to migrate the datapath to use Netlink as its
communication channel.  In the short term, we need to be able to have
actions with 64-bit arguments but "struct odp_action" only has room for
48 bits.  So this patch shifts to variable-length arguments using Netlink
attributes, which starts in on the Netlink transition and makes 64-bit
arguments possible at the same time.

Signed-off-by: Ben Pfaff <blp at nicira.com>
---
 datapath/actions.c                      |  101 ++++++------
 datapath/actions.h                      |    3 +-
 datapath/datapath.c                     |  149 ++++++++++--------
 datapath/flow.c                         |   12 +-
 datapath/flow.h                         |    6 +-
 datapath/loop_counter.c                 |    2 +-
 datapath/odp-compat.h                   |    4 +-
 include/openvswitch/datapath-protocol.h |  135 ++++------------
 lib/dpif-linux.c                        |    6 +-
 lib/dpif-netdev.c                       |  131 +++++++++-------
 lib/dpif-provider.h                     |    9 +-
 lib/dpif.c                              |   30 ++--
 lib/dpif.h                              |    5 +-
 lib/odp-util.c                          |  141 ++++++++++++-----
 lib/odp-util.h                          |   33 +----
 lib/ofp-util.c                          |    2 +-
 ofproto/in-band.c                       |   12 +-
 ofproto/in-band.h                       |    4 +-
 ofproto/ofproto-sflow.c                 |   67 ++++-----
 ofproto/ofproto.c                       |  265 +++++++++++++++----------------
 ofproto/ofproto.h                       |    7 +-
 utilities/ovs-dpctl.c                   |    6 +-
 vswitchd/bridge.c                       |   35 ++--
 23 files changed, 579 insertions(+), 586 deletions(-)

diff --git a/datapath/actions.c b/datapath/actions.c
index ec6a460..6e350db 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -73,9 +73,9 @@ static struct sk_buff *vlan_pull_tag(struct sk_buff *skb)
 
 static struct sk_buff *modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
 				       const struct odp_flow_key *key,
-				       const union odp_action *a, int n_actions)
+				       const struct nlattr *a, int actions_len)
 {
-	__be16 tci = a->dl_tci.tci;
+	__be16 tci = nla_get_be16(a);
 
 	skb = make_writable(skb, VLAN_HLEN);
 	if (!skb)
@@ -132,6 +132,8 @@ static struct sk_buff *modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
 		 * support hardware-accelerated VLAN tagging without VLAN
 		 * groups configured). */
 		if (skb_is_gso(skb)) {
+			const struct nlattr *actions_left;
+			int actions_len_left;
 			struct sk_buff *segs;
 
 			segs = skb_gso_segment(skb, 0);
@@ -139,6 +141,9 @@ static struct sk_buff *modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
 			if (unlikely(IS_ERR(segs)))
 				return ERR_CAST(segs);
 
+			actions_len_left = actions_len;
+			actions_left = nla_next(a, &actions_len_left);
+
 			do {
 				struct sk_buff *nskb = segs->next;
 				int err;
@@ -151,9 +156,9 @@ static struct sk_buff *modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
 				segs = __vlan_put_tag(segs, ntohs(tci));
 				err = -ENOMEM;
 				if (segs) {
-					err = execute_actions(dp, segs,
-							      key, a + 1,
-							      n_actions - 1);
+					err = execute_actions(
+						dp, segs, key, actions_left,
+						actions_len_left);
 				}
 
 				if (unlikely(err)) {
@@ -199,20 +204,6 @@ static struct sk_buff *strip_vlan(struct sk_buff *skb)
 	return skb;
 }
 
-static struct sk_buff *set_dl_addr(struct sk_buff *skb,
-				   const struct odp_action_dl_addr *a)
-{
-	skb = make_writable(skb, 0);
-	if (skb) {
-		struct ethhdr *eh = eth_hdr(skb);
-		if (a->type == ODPAT_SET_DL_SRC)
-			memcpy(eh->h_source, a->dl_addr, ETH_ALEN);
-		else
-			memcpy(eh->h_dest, a->dl_addr, ETH_ALEN);
-	}
-	return skb;
-}
-
 /* Updates 'sum', which is a field in 'skb''s data, given that a 4-byte field
  * covered by the sum has been changed from 'from' to 'to'.  If set,
  * 'pseudohdr' indicates that the field is in the TCP or UDP pseudo-header.
@@ -254,8 +245,9 @@ static __sum16 *get_l4_checksum(struct sk_buff *skb, const struct odp_flow_key *
 
 static struct sk_buff *set_nw_addr(struct sk_buff *skb,
 				   const struct odp_flow_key *key,
-				   const struct odp_action_nw_addr *a)
+				   const struct nlattr *a)
 {
+	__be32 new_nwaddr = nla_get_be32(a);
 	struct iphdr *nh;
 	__sum16 *check;
 	__be32 *nwaddr;
@@ -268,21 +260,21 @@ static struct sk_buff *set_nw_addr(struct sk_buff *skb,
 		return NULL;
 
 	nh = ip_hdr(skb);
-	nwaddr = a->type == ODPAT_SET_NW_SRC ? &nh->saddr : &nh->daddr;
+	nwaddr = nla_type(a) == ODPAT_SET_NW_SRC ? &nh->saddr : &nh->daddr;
 
 	check = get_l4_checksum(skb, key);
 	if (likely(check))
-		update_csum(check, skb, *nwaddr, a->nw_addr, 1);
-	update_csum(&nh->check, skb, *nwaddr, a->nw_addr, 0);
+		update_csum(check, skb, *nwaddr, new_nwaddr, 1);
+	update_csum(&nh->check, skb, *nwaddr, new_nwaddr, 0);
 
-	*nwaddr = a->nw_addr;
+	*nwaddr = new_nwaddr;
 
 	return skb;
 }
 
 static struct sk_buff *set_nw_tos(struct sk_buff *skb,
-				   const struct odp_flow_key *key,
-				   const struct odp_action_nw_tos *a)
+				  const struct odp_flow_key *key,
+				  u8 nw_tos)
 {
 	if (unlikely(!is_ip(skb, key)))
 		return skb;
@@ -295,7 +287,7 @@ static struct sk_buff *set_nw_tos(struct sk_buff *skb,
 		u8 new;
 
 		/* Set the DSCP bits and preserve the ECN bits. */
-		new = a->nw_tos | (nh->tos & INET_ECN_MASK);
+		new = nw_tos | (nh->tos & INET_ECN_MASK);
 		update_csum(&nh->check, skb, htons((u16)old),
 			    htons((u16)new), 0);
 		*f = new;
@@ -305,7 +297,7 @@ static struct sk_buff *set_nw_tos(struct sk_buff *skb,
 
 static struct sk_buff *set_tp_port(struct sk_buff *skb,
 				   const struct odp_flow_key *key,
-				   const struct odp_action_tp_port *a)
+				   const struct nlattr *a)
 {
 	struct udphdr *th;
 	__sum16 *check;
@@ -331,9 +323,9 @@ static struct sk_buff *set_tp_port(struct sk_buff *skb,
 	 * supports those protocols.
 	 */
 	th = udp_hdr(skb);
-	port = a->type == ODPAT_SET_TP_SRC ? &th->source : &th->dest;
-	update_csum(check, skb, *port, a->tp_port, 0);
-	*port = a->tp_port;
+	port = nla_type(a) == ODPAT_SET_TP_SRC ? &th->source : &th->dest;
+	update_csum(check, skb, *port, nla_get_be16(a), 0);
+	*port = nla_get_be16(a);
 
 	return skb;
 }
@@ -396,21 +388,20 @@ static int output_control(struct datapath *dp, struct sk_buff *skb, u32 arg)
 /* Send a copy of this packet up to the sFlow agent, along with extra
  * information about what happened to it. */
 static void sflow_sample(struct datapath *dp, struct sk_buff *skb,
-			 const union odp_action *a, int n_actions,
+			 const struct nlattr *a, int actions_len,
 			 struct vport *vport)
 {
 	struct odp_sflow_sample_header *hdr;
-	unsigned int actlen = n_actions * sizeof(union odp_action);
 	unsigned int hdrlen = sizeof(struct odp_sflow_sample_header);
 	struct sk_buff *nskb;
 
-	nskb = skb_copy_expand(skb, actlen + hdrlen, 0, GFP_ATOMIC);
+	nskb = skb_copy_expand(skb, actions_len + hdrlen, 0, GFP_ATOMIC);
 	if (!nskb)
 		return;
 
-	memcpy(__skb_push(nskb, actlen), a, actlen);
+	memcpy(__skb_push(nskb, actions_len), a, actions_len);
 	hdr = (struct odp_sflow_sample_header*)__skb_push(nskb, hdrlen);
-	hdr->n_actions = n_actions;
+	hdr->actions_len = actions_len;
 	hdr->sample_pool = atomic_read(&vport->sflow_pool);
 	dp_output_control(dp, nskb, _ODPL_SFLOW_NR, 0);
 }
@@ -418,7 +409,7 @@ static void sflow_sample(struct datapath *dp, struct sk_buff *skb,
 /* Execute a list of actions against 'skb'. */
 int execute_actions(struct datapath *dp, struct sk_buff *skb,
 		    const struct odp_flow_key *key,
-		    const union odp_action *a, int n_actions)
+		    const struct nlattr *actions, int actions_len)
 {
 	/* Every output action needs a separate clone of 'skb', but the common
 	 * case is just a single output action, so that doing a clone and
@@ -426,7 +417,8 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
 	 * is slightly obscure just to avoid that. */
 	int prev_port = -1;
 	u32 priority = skb->priority;
-	int err;
+	const struct nlattr *a;
+	int rem, err;
 
 	if (dp->sflow_probability) {
 		struct vport *p = OVS_CB(skb)->vport;
@@ -434,25 +426,25 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
 			atomic_inc(&p->sflow_pool);
 			if (dp->sflow_probability == UINT_MAX ||
 			    net_random() < dp->sflow_probability)
-				sflow_sample(dp, skb, a, n_actions, p);
+				sflow_sample(dp, skb, actions, actions_len, p);
 		}
 	}
 
 	OVS_CB(skb)->tun_id = 0;
 
-	for (; n_actions > 0; a++, n_actions--) {
+	for (a = actions, rem = actions_len; rem > 0; a = nla_next(a, &rem)) {
 		if (prev_port != -1) {
 			do_output(dp, skb_clone(skb, GFP_ATOMIC), prev_port);
 			prev_port = -1;
 		}
 
-		switch (a->type) {
+		switch (nla_type(a)) {
 		case ODPAT_OUTPUT:
-			prev_port = a->output.port;
+			prev_port = nla_get_u32(a);
 			break;
 
 		case ODPAT_CONTROLLER:
-			err = output_control(dp, skb, a->controller.arg);
+			err = output_control(dp, skb, nla_get_u32(a));
 			if (err) {
 				kfree_skb(skb);
 				return err;
@@ -460,11 +452,11 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
 			break;
 
 		case ODPAT_SET_TUNNEL:
-			OVS_CB(skb)->tun_id = a->tunnel.tun_id;
+			OVS_CB(skb)->tun_id = nla_get_be32(a);
 			break;
 
 		case ODPAT_SET_DL_TCI:
-			skb = modify_vlan_tci(dp, skb, key, a, n_actions);
+			skb = modify_vlan_tci(dp, skb, key, a, rem);
 			if (IS_ERR(skb))
 				return PTR_ERR(skb);
 			break;
@@ -474,26 +466,35 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
 			break;
 
 		case ODPAT_SET_DL_SRC:
+			skb = make_writable(skb, 0);
+			if (!skb)
+				return -ENOMEM;
+			memcpy(eth_hdr(skb)->h_source, nla_data(a), ETH_ALEN);
+			break;
+
 		case ODPAT_SET_DL_DST:
-			skb = set_dl_addr(skb, &a->dl_addr);
+			skb = make_writable(skb, 0);
+			if (!skb)
+				return -ENOMEM;
+			memcpy(eth_hdr(skb)->h_dest, nla_data(a), ETH_ALEN);
 			break;
 
 		case ODPAT_SET_NW_SRC:
 		case ODPAT_SET_NW_DST:
-			skb = set_nw_addr(skb, key, &a->nw_addr);
+			skb = set_nw_addr(skb, key, a);
 			break;
 
 		case ODPAT_SET_NW_TOS:
-			skb = set_nw_tos(skb, key, &a->nw_tos);
+			skb = set_nw_tos(skb, key, nla_get_u8(a));
 			break;
 
 		case ODPAT_SET_TP_SRC:
 		case ODPAT_SET_TP_DST:
-			skb = set_tp_port(skb, key, &a->tp_port);
+			skb = set_tp_port(skb, key, a);
 			break;
 
 		case ODPAT_SET_PRIORITY:
-			skb->priority = a->priority.priority;
+			skb->priority = nla_get_u32(a);
 			break;
 
 		case ODPAT_POP_PRIORITY:
diff --git a/datapath/actions.h b/datapath/actions.h
index a258551..bd39bcb 100644
--- a/datapath/actions.h
+++ b/datapath/actions.h
@@ -15,10 +15,9 @@
 struct datapath;
 struct sk_buff;
 struct odp_flow_key;
-union odp_action;
 
 int execute_actions(struct datapath *dp, struct sk_buff *skb,
 		    const struct odp_flow_key *key,
-		    const union odp_action *, int n_actions);
+		    const struct nlattr *, int actions_len);
 
 #endif /* actions.h */
diff --git a/datapath/datapath.c b/datapath/datapath.c
index c8b5088..3a21236 100644
--- a/datapath/datapath.c
+++ b/datapath/datapath.c
@@ -516,7 +516,7 @@ void dp_process_received_packet(struct vport *p, struct sk_buff *skb)
 
 	/* Execute actions. */
 	execute_actions(dp, skb, &OVS_CB(skb)->flow->key, acts->actions,
-			acts->n_actions);
+			acts->actions_len);
 	stats_counter_off = offsetof(struct dp_stats_percpu, n_hit);
 
 	/* Check whether sub-actions looped too much. */
@@ -658,50 +658,77 @@ static int flush_flows(struct datapath *dp)
 	return 0;
 }
 
-static int validate_actions(const struct sw_flow_actions *actions)
+static int validate_actions(const struct nlattr *actions,
+			    unsigned int actions_len)
 {
-	unsigned int i;
-
-	for (i = 0; i < actions->n_actions; i++) {
-		const union odp_action *a = &actions->actions[i];
-
-		switch (a->type) {
-		case ODPAT_CONTROLLER:
-		case ODPAT_STRIP_VLAN:
-		case ODPAT_SET_DL_SRC:
-		case ODPAT_SET_DL_DST:
-		case ODPAT_SET_NW_SRC:
-		case ODPAT_SET_NW_DST:
-		case ODPAT_SET_TP_SRC:
-		case ODPAT_SET_TP_DST:
-		case ODPAT_SET_TUNNEL:
-		case ODPAT_SET_PRIORITY:
-		case ODPAT_POP_PRIORITY:
-		case ODPAT_DROP_SPOOFED_ARP:
-			/* No validation needed. */
-			break;
+        const struct nlattr *a;
+        int rem;
+
+        nla_for_each_attr (a, actions, actions_len, rem) {
+                static const int action_lens[ODPAT_MAX + 1] = {
+                        [ODPAT_OUTPUT] = 4,
+                        [ODPAT_CONTROLLER] = 4,
+                        [ODPAT_SET_DL_TCI] = 2,
+                        [ODPAT_STRIP_VLAN] = 0,
+                        [ODPAT_SET_DL_SRC] = ETH_ALEN,
+                        [ODPAT_SET_DL_DST] = ETH_ALEN,
+                        [ODPAT_SET_NW_SRC] = 4,
+                        [ODPAT_SET_NW_DST] = 4,
+                        [ODPAT_SET_NW_TOS] = 1,
+                        [ODPAT_SET_TP_SRC] = 2,
+                        [ODPAT_SET_TP_DST] = 2,
+                        [ODPAT_SET_TUNNEL] = 8,
+                        [ODPAT_SET_PRIORITY] = 4,
+                        [ODPAT_POP_PRIORITY] = 0,
+                        [ODPAT_DROP_SPOOFED_ARP] = 0,
+                };
+                int type = nla_type(a);
+
+                if (type > ODPAT_MAX || nla_len(a) != action_lens[type])
+                        return -EINVAL;
+
+                switch (type) {
+		case ODPAT_UNSPEC:
+			return -EINVAL;
 
-		case ODPAT_OUTPUT:
-			if (a->output.port >= DP_MAX_PORTS)
+                case ODPAT_CONTROLLER:
+                case ODPAT_STRIP_VLAN:
+                case ODPAT_SET_DL_SRC:
+                case ODPAT_SET_DL_DST:
+                case ODPAT_SET_NW_SRC:
+                case ODPAT_SET_NW_DST:
+                case ODPAT_SET_TP_SRC:
+                case ODPAT_SET_TP_DST:
+                case ODPAT_SET_TUNNEL:
+                case ODPAT_SET_PRIORITY:
+                case ODPAT_POP_PRIORITY:
+                case ODPAT_DROP_SPOOFED_ARP:
+                        /* No validation needed. */
+                        break;
+
+                case ODPAT_OUTPUT:
+                        if (nla_get_u32(a) >= DP_MAX_PORTS)
+                                return -EINVAL;
+
+                case ODPAT_SET_DL_TCI:
+			if (nla_get_be16(a) & htons(VLAN_CFI_MASK))
 				return -EINVAL;
-			break;
+                        break;
 
-		case ODPAT_SET_DL_TCI:
-			if (a->dl_tci.tci & htons(VLAN_CFI_MASK))
-				return -EINVAL;
-			break;
+                case ODPAT_SET_NW_TOS:
+                        if (nla_get_u8(a) & INET_ECN_MASK)
+                                return -EINVAL;
+                        break;
 
-		case ODPAT_SET_NW_TOS:
-			if (a->nw_tos.nw_tos & INET_ECN_MASK)
-				return -EINVAL;
-			break;
+                default:
+                        return -EOPNOTSUPP;
+                }
+        }
 
-		default:
-			return -EOPNOTSUPP;
-		}
-	}
+        if (rem > 0)
+                return -EINVAL;
 
-	return 0;
+        return 0;
 }
 
 static struct sw_flow_actions *get_actions(const struct odp_flow *flow)
@@ -709,16 +736,14 @@ static struct sw_flow_actions *get_actions(const struct odp_flow *flow)
 	struct sw_flow_actions *actions;
 	int error;
 
-	actions = flow_actions_alloc(flow->n_actions);
+	actions = flow_actions_alloc(flow->actions_len);
 	error = PTR_ERR(actions);
 	if (IS_ERR(actions))
 		goto error;
 
-	error = -EFAULT;
-	if (copy_from_user(actions->actions, flow->actions,
-			   flow->n_actions * sizeof(union odp_action)))
+	if (copy_from_user(actions->actions, flow->actions, flow->actions_len))
 		goto error_free_actions;
-	error = validate_actions(actions);
+	error = validate_actions(actions->actions, actions->actions_len);
 	if (error)
 		goto error_free_actions;
 
@@ -842,9 +867,9 @@ static int do_put_flow(struct datapath *dp, struct odp_flow_put *uf,
 		if (IS_ERR(new_acts))
 			goto error;
 		old_acts = rcu_dereference(flow->sf_acts);
-		if (old_acts->n_actions != new_acts->n_actions ||
+		if (old_acts->actions_len != new_acts->actions_len ||
 		    memcmp(old_acts->actions, new_acts->actions,
-			   sizeof(union odp_action) * old_acts->n_actions)) {
+			   old_acts->actions_len)) {
 			rcu_assign_pointer(flow->sf_acts, new_acts);
 			flow_deferred_free_acts(old_acts);
 		} else {
@@ -892,12 +917,12 @@ static int put_flow(struct datapath *dp, struct odp_flow_put __user *ufp)
 
 static int do_answer_query(struct sw_flow *flow, u32 query_flags,
 			   struct odp_flow_stats __user *ustats,
-			   union odp_action __user *actions,
-			   u32 __user *n_actionsp)
+			   struct nlattr __user *actions,
+			   unsigned int __user *actions_lenp)
 {
 	struct sw_flow_actions *sf_acts;
 	struct odp_flow_stats stats;
-	u32 n_actions;
+	unsigned int actions_len;
 
 	spin_lock_bh(&flow->lock);
 	get_stats(flow, &stats);
@@ -907,17 +932,16 @@ static int do_answer_query(struct sw_flow *flow, u32 query_flags,
 	spin_unlock_bh(&flow->lock);
 
 	if (copy_to_user(ustats, &stats, sizeof(struct odp_flow_stats)) ||
-	    get_user(n_actions, n_actionsp))
+	    get_user(actions_len, actions_lenp))
 		return -EFAULT;
 
-	if (!n_actions)
+	if (!actions_len)
 		return 0;
 
 	sf_acts = rcu_dereference(flow->sf_acts);
-	if (put_user(sf_acts->n_actions, n_actionsp) ||
+	if (put_user(sf_acts->actions_len, actions_lenp) ||
 	    (actions && copy_to_user(actions, sf_acts->actions,
-				     sizeof(union odp_action) *
-				     min(sf_acts->n_actions, n_actions))))
+				     min(sf_acts->actions_len, actions_len))))
 		return -EFAULT;
 
 	return 0;
@@ -926,13 +950,13 @@ static int do_answer_query(struct sw_flow *flow, u32 query_flags,
 static int answer_query(struct sw_flow *flow, u32 query_flags,
 			struct odp_flow __user *ufp)
 {
-	union odp_action *actions;
+	struct nlattr *actions;
 
 	if (get_user(actions, &ufp->actions))
 		return -EFAULT;
 
 	return do_answer_query(flow, query_flags, 
-			       &ufp->stats, actions, &ufp->n_actions);
+			       &ufp->stats, actions, &ufp->actions_len);
 }
 
 static struct sw_flow *do_del_flow(struct datapath *dp, struct odp_flow_key *key)
@@ -1073,18 +1097,17 @@ static int do_execute(struct datapath *dp, const struct odp_execute *execute)
 	if (execute->length < ETH_HLEN || execute->length > 65535)
 		goto error;
 
-	actions = flow_actions_alloc(execute->n_actions);
+	actions = flow_actions_alloc(execute->actions_len);
 	if (IS_ERR(actions)) {
 		err = PTR_ERR(actions);
 		goto error;
 	}
 
 	err = -EFAULT;
-	if (copy_from_user(actions->actions, execute->actions,
-			   execute->n_actions * sizeof *execute->actions))
+	if (copy_from_user(actions->actions, execute->actions, execute->actions_len))
 		goto error_free_actions;
 
-	err = validate_actions(actions);
+	err = validate_actions(actions->actions, execute->actions_len);
 	if (err)
 		goto error_free_actions;
 
@@ -1114,7 +1137,7 @@ static int do_execute(struct datapath *dp, const struct odp_execute *execute)
 		goto error_free_skb;
 
 	rcu_read_lock();
-	err = execute_actions(dp, skb, &key, actions->actions, actions->n_actions);
+	err = execute_actions(dp, skb, &key, actions->actions, actions->actions_len);
 	rcu_read_unlock();
 
 	kfree(actions);
@@ -1498,7 +1521,7 @@ static int compat_get_flow(struct odp_flow *flow, const struct compat_odp_flow _
 	    __copy_from_user(&flow->stats, &compat->stats, sizeof(struct odp_flow_stats)) ||
 	    __copy_from_user(&flow->key, &compat->key, sizeof(struct odp_flow_key)) ||
 	    __get_user(actions, &compat->actions) ||
-	    __get_user(flow->n_actions, &compat->n_actions) ||
+	    __get_user(flow->actions_len, &compat->actions_len) ||
 	    __get_user(flow->flags, &compat->flags))
 		return -EFAULT;
 
@@ -1536,7 +1559,7 @@ static int compat_answer_query(struct sw_flow *flow, u32 query_flags,
 		return -EFAULT;
 
 	return do_answer_query(flow, query_flags, &ufp->stats,
-			       compat_ptr(actions), &ufp->n_actions);
+			       compat_ptr(actions), &ufp->actions_len);
 }
 
 static int compat_del_flow(struct datapath *dp, struct compat_odp_flow __user *ufp)
@@ -1659,7 +1682,7 @@ static int compat_execute(struct datapath *dp, const struct compat_odp_execute _
 
 	if (!access_ok(VERIFY_READ, uexecute, sizeof(struct compat_odp_execute)) ||
 	    __get_user(actions, &uexecute->actions) ||
-	    __get_user(execute.n_actions, &uexecute->n_actions) ||
+	    __get_user(execute.actions_len, &uexecute->actions_len) ||
 	    __get_user(data, &uexecute->data) ||
 	    __get_user(execute.length, &uexecute->length))
 		return -EFAULT;
diff --git a/datapath/flow.c b/datapath/flow.c
index d30fb73..865e74a 100644
--- a/datapath/flow.c
+++ b/datapath/flow.c
@@ -104,22 +104,24 @@ void flow_used(struct sw_flow *flow, struct sk_buff *skb)
 	spin_unlock_bh(&flow->lock);
 }
 
-struct sw_flow_actions *flow_actions_alloc(size_t n_actions)
+struct sw_flow_actions *flow_actions_alloc(unsigned int actions_len)
 {
 	struct sw_flow_actions *sfa;
 
+	if (actions_len % NLA_ALIGNTO)
+		return ERR_PTR(-EINVAL);
+
 	/* At least DP_MAX_PORTS actions are required to be able to flood a
 	 * packet to every port.  Factor of 2 allows for setting VLAN tags,
 	 * etc. */
-	if (n_actions > 2 * DP_MAX_PORTS)
+	if (actions_len > 2 * DP_MAX_PORTS * nla_total_size(4))
 		return ERR_PTR(-EINVAL);
 
-	sfa = kmalloc(sizeof *sfa + n_actions * sizeof(union odp_action),
-		      GFP_KERNEL);
+	sfa = kmalloc(sizeof *sfa + actions_len, GFP_KERNEL);
 	if (!sfa)
 		return ERR_PTR(-ENOMEM);
 
-	sfa->n_actions = n_actions;
+	sfa->actions_len = actions_len;
 	return sfa;
 }
 
diff --git a/datapath/flow.h b/datapath/flow.h
index b1e8005..24ac7b7 100644
--- a/datapath/flow.h
+++ b/datapath/flow.h
@@ -24,8 +24,8 @@ struct sk_buff;
 
 struct sw_flow_actions {
 	struct rcu_head rcu;
-	unsigned int n_actions;
-	union odp_action actions[];
+	unsigned int actions_len;
+	struct nlattr actions[];
 };
 
 struct sw_flow {
@@ -67,7 +67,7 @@ struct sw_flow *flow_alloc(void);
 void flow_deferred_free(struct sw_flow *);
 void flow_free_tbl(struct tbl_node *);
 
-struct sw_flow_actions *flow_actions_alloc(size_t n_actions);
+struct sw_flow_actions *flow_actions_alloc(unsigned int actions_len);
 void flow_deferred_free_acts(struct sw_flow_actions *);
 
 void flow_hold(struct sw_flow *);
diff --git a/datapath/loop_counter.c b/datapath/loop_counter.c
index fbfbdcf..491305d 100644
--- a/datapath/loop_counter.c
+++ b/datapath/loop_counter.c
@@ -20,7 +20,7 @@ void loop_suppress(struct datapath *dp, struct sw_flow_actions *actions)
 	if (net_ratelimit())
 		pr_warn("%s: flow looped %d times, dropping\n",
 			dp_name(dp), MAX_LOOPS);
-	actions->n_actions = 0;
+	actions->actions_len = 0;
 }
 
 #ifndef CONFIG_PREEMPT_RT
diff --git a/datapath/odp-compat.h b/datapath/odp-compat.h
index 5c9eb81..40229ed 100644
--- a/datapath/odp-compat.h
+++ b/datapath/odp-compat.h
@@ -32,7 +32,7 @@ struct compat_odp_flow {
 	struct odp_flow_stats stats;
 	struct odp_flow_key key;
 	compat_uptr_t actions;
-	u32 n_actions;
+	unsigned int actions_len;
 	u32 flags;
 };
 
@@ -48,7 +48,7 @@ struct compat_odp_flowvec {
 
 struct compat_odp_execute {
 	compat_uptr_t actions;
-	u32 n_actions;
+	unsigned int actions_len;
 
 	compat_uptr_t data;
 	u32 length;
diff --git a/include/openvswitch/datapath-protocol.h b/include/openvswitch/datapath-protocol.h
index 8e07b8b..8658d59 100644
--- a/include/openvswitch/datapath-protocol.h
+++ b/include/openvswitch/datapath-protocol.h
@@ -152,12 +152,12 @@ struct odp_stats {
  * encapsulated this packet. It is 0 if the packet was not received on a tunnel.
  *
  * For @type == %_ODPL_ACTION_NR, the header is followed by packet data.  The
- * @arg member is copied from the &struct odp_action_controller that caused
- * the &struct odp_msg to be composed.
+ * @arg member is copied from the %ODPAT_CONTROLLER action that caused the
+ * &struct odp_msg to be composed.
  *
  * For @type == %_ODPL_SFLOW_NR, the header is followed by &struct
- * odp_sflow_sample_header, then by an array of &union odp_action (the number
- * of which is specified in &struct odp_sflow_sample_header), then by packet
+ * odp_sflow_sample_header, then by a series of Netlink attributes (whose
+ * length is specified in &struct odp_sflow_sample_header), then by packet
  * data.
  */
 struct odp_msg {
@@ -172,15 +172,16 @@ struct odp_msg {
  * struct odp_sflow_sample_header - header added to sFlow sampled packet.
  * @sample_pool: Number of packets that were candidates for sFlow sampling,
  * regardless of whether they were actually chosen and sent down to userspace.
- * @n_actions: Number of "union odp_action"s immediately following this header.
+ * @actions_len: Number of bytes of actions immediately following this header.
  *
  * This header follows &struct odp_msg when that structure's @type is
- * %_ODPL_SFLOW_NR, and it is itself followed by an array of &union odp_action
- * (the number of which is specified in @n_actions) and then by packet data.
+ * %_ODPL_SFLOW_NR, and it is itself followed by a series of Netlink attributes
+ * (the number of bytes of which is specified in @actions_len) and then by
+ * packet data.
  */
 struct odp_sflow_sample_header {
     uint32_t sample_pool;
-    uint32_t n_actions;
+    uint32_t actions_len;
 };
 
 #define VPORT_TYPE_SIZE     16
@@ -240,8 +241,8 @@ struct odp_flow_key {
 struct odp_flow {
     struct odp_flow_stats stats;
     struct odp_flow_key key;
-    union odp_action *actions;
-    uint32_t n_actions;
+    struct nlattr *actions;
+    unsigned int actions_len;
     uint32_t flags;
 };
 
@@ -262,101 +263,31 @@ struct odp_flowvec {
 };
 
 /* Action types. */
-#define ODPAT_OUTPUT            0    /* Output to switch port. */
-#define ODPAT_CONTROLLER        2    /* Send copy to controller. */
-#define ODPAT_SET_DL_TCI        3    /* Set the 802.1q TCI value. */
-#define ODPAT_STRIP_VLAN        5    /* Strip the 802.1q header. */
-#define ODPAT_SET_DL_SRC        6    /* Ethernet source address. */
-#define ODPAT_SET_DL_DST        7    /* Ethernet destination address. */
-#define ODPAT_SET_NW_SRC        8    /* IP source address. */
-#define ODPAT_SET_NW_DST        9    /* IP destination address. */
-#define ODPAT_SET_NW_TOS        10   /* IP ToS/DSCP field (6 bits). */
-#define ODPAT_SET_TP_SRC        11   /* TCP/UDP source port. */
-#define ODPAT_SET_TP_DST        12   /* TCP/UDP destination port. */
-#define ODPAT_SET_TUNNEL        13   /* Set the encapsulating tunnel ID. */
-#define ODPAT_SET_PRIORITY      14   /* Set skb->priority. */
-#define ODPAT_POP_PRIORITY      15   /* Restore original skb->priority. */
-#define ODPAT_DROP_SPOOFED_ARP  16   /* Drop ARPs with spoofed source MAC. */
-#define ODPAT_N_ACTIONS         17
-
-struct odp_action_output {
-    uint16_t type;              /* ODPAT_OUTPUT. */
-    uint16_t port;              /* Output port. */
-    uint16_t reserved1;
-    uint16_t reserved2;
-};
-
-struct odp_action_controller {
-    uint16_t type;              /* ODPAT_OUTPUT_CONTROLLER. */
-    uint16_t reserved;
-    uint32_t arg;               /* Copied to struct odp_msg 'arg' member. */
-};
-
-struct odp_action_tunnel {
-    uint16_t type;              /* ODPAT_SET_TUNNEL. */
-    uint16_t reserved;
-    ovs_be32 tun_id;            /* Tunnel ID. */
-};
-
-/* Action structure for ODPAT_SET_DL_TCI. */
-struct odp_action_dl_tci {
-    uint16_t type;              /* ODPAT_SET_DL_TCI. */
-    ovs_be16 tci;               /* New TCI.  CFI bit must be zero. */
-    uint32_t reserved;
-};
-
-/* Action structure for ODPAT_SET_DL_SRC/DST. */
-struct odp_action_dl_addr {
-    uint16_t type;              /* ODPAT_SET_DL_SRC/DST. */
-    uint8_t dl_addr[6];         /* Ethernet address. */
-};
-
-/* Action structure for ODPAT_SET_NW_SRC/DST. */
-struct odp_action_nw_addr {
-    uint16_t type;              /* ODPAT_SET_TW_SRC/DST. */
-    uint16_t reserved;
-    ovs_be32 nw_addr;           /* IP address. */
-};
-
-struct odp_action_nw_tos {
-    uint16_t type;              /* ODPAT_SET_NW_TOS. */
-    uint8_t nw_tos;             /* IP ToS/DSCP field (6 bits). */
-    uint8_t reserved1;
-    uint16_t reserved2;
-    uint16_t reserved3;
-};
-
-/* Action structure for ODPAT_SET_TP_SRC/DST. */
-struct odp_action_tp_port {
-    uint16_t type;              /* ODPAT_SET_TP_SRC/DST. */
-    ovs_be16 tp_port;           /* TCP/UDP port. */
-    uint16_t reserved1;
-    uint16_t reserved2;
-};
-
-/* Action structure for ODPAT_SET_PRIORITY. */
-struct odp_action_priority {
-    uint16_t type;              /* ODPAT_SET_PRIORITY. */
-    uint16_t reserved;
-    uint32_t priority;          /* skb->priority value. */
-};
-
-union odp_action {
-    uint16_t type;
-    struct odp_action_output output;
-    struct odp_action_controller controller;
-    struct odp_action_tunnel tunnel;
-    struct odp_action_dl_tci dl_tci;
-    struct odp_action_dl_addr dl_addr;
-    struct odp_action_nw_addr nw_addr;
-    struct odp_action_nw_tos nw_tos;
-    struct odp_action_tp_port tp_port;
-    struct odp_action_priority priority;
+enum odp_action_type {
+    ODPAT_UNSPEC,
+    ODPAT_OUTPUT = 1,            /* Output to switch port. */
+    ODPAT_CONTROLLER = 2,        /* Send copy to controller. */
+    ODPAT_SET_DL_TCI = 3,        /* Set the 802.1q TCI value. */
+    ODPAT_STRIP_VLAN = 4,        /* Strip the 802.1q header. */
+    ODPAT_SET_DL_SRC = 5,        /* Ethernet source address. */
+    ODPAT_SET_DL_DST = 6,        /* Ethernet destination address. */
+    ODPAT_SET_NW_SRC = 7,        /* IP source address. */
+    ODPAT_SET_NW_DST = 8,        /* IP destination address. */
+    ODPAT_SET_NW_TOS = 9,        /* IP ToS/DSCP field (6 bits). */
+    ODPAT_SET_TP_SRC = 10,        /* TCP/UDP source port. */
+    ODPAT_SET_TP_DST = 11,       /* TCP/UDP destination port. */
+    ODPAT_SET_TUNNEL = 12,       /* Set the encapsulating tunnel ID. */
+    ODPAT_SET_PRIORITY = 13,     /* Set skb->priority. */
+    ODPAT_POP_PRIORITY = 14,     /* Restore original skb->priority. */
+    ODPAT_DROP_SPOOFED_ARP = 15, /* Drop ARPs with spoofed source MAC. */
+
+    __ODPAT_MAX,
+    ODPAT_MAX = __ODPAT_MAX - 1
 };
 
 struct odp_execute {
-    union odp_action *actions;
-    uint32_t n_actions;
+    struct nlattr *actions;
+    unsigned int actions_len;
 
     const void *data;
     uint32_t length;
diff --git a/lib/dpif-linux.c b/lib/dpif-linux.c
index 89cee1f..6aa3335 100644
--- a/lib/dpif-linux.c
+++ b/lib/dpif-linux.c
@@ -392,13 +392,13 @@ dpif_linux_flow_list(const struct dpif *dpif_, struct odp_flow flows[], int n)
 
 static int
 dpif_linux_execute(struct dpif *dpif_,
-                   const union odp_action actions[], int n_actions,
+                   const struct nlattr *actions, unsigned int actions_len,
                    const struct ofpbuf *buf)
 {
     struct odp_execute execute;
     memset(&execute, 0, sizeof execute);
-    execute.actions = (union odp_action *) actions;
-    execute.n_actions = n_actions;
+    execute.actions = (struct nlattr *) actions;
+    execute.actions_len = actions_len;
     execute.data = buf->data;
     execute.length = buf->size;
     return do_ioctl(dpif_, ODP_EXECUTE, &execute);
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index eddd18b..9415811 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -25,6 +25,7 @@
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <net/if.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
@@ -39,6 +40,7 @@
 #include "hmap.h"
 #include "list.h"
 #include "netdev.h"
+#include "netlink.h"
 #include "odp-util.h"
 #include "ofp-print.h"
 #include "ofpbuf.h"
@@ -106,8 +108,8 @@ struct dp_netdev_flow {
     uint16_t tcp_ctl;           /* Bitwise-OR of seen tcp_ctl values. */
 
     /* Actions. */
-    union odp_action *actions;
-    unsigned int n_actions;
+    struct nlattr *actions;
+    unsigned int actions_len;
 };
 
 /* Interface to netdev-based datapath. */
@@ -139,7 +141,8 @@ static int dp_netdev_output_control(struct dp_netdev *, const struct ofpbuf *,
                                     int queue_no, int port_no, uint32_t arg);
 static int dp_netdev_execute_actions(struct dp_netdev *,
                                      struct ofpbuf *, struct flow *,
-                                     const union odp_action *, int n);
+                                     const struct nlattr *actions,
+                                     unsigned int actions_len);
 
 static struct dpif_class dpif_dummy_class;
 
@@ -584,11 +587,10 @@ answer_flow_query(struct dp_netdev_flow *flow, uint32_t query_flags,
         odp_flow->stats.tcp_flags = TCP_FLAGS(flow->tcp_ctl);
         odp_flow->stats.reserved = 0;
         odp_flow->stats.error = 0;
-        if (odp_flow->n_actions > 0) {
-            unsigned int n = MIN(odp_flow->n_actions, flow->n_actions);
+        if (odp_flow->actions_len > 0) {
             memcpy(odp_flow->actions, flow->actions,
-                   n * sizeof *odp_flow->actions);
-            odp_flow->n_actions = flow->n_actions;
+                   MIN(odp_flow->actions_len, flow->actions_len));
+            odp_flow->actions_len = flow->actions_len;
         }
 
         if (query_flags & ODPFF_ZERO_TCP_FLAGS) {
@@ -618,34 +620,42 @@ dpif_netdev_flow_get(const struct dpif *dpif, struct odp_flow flows[], int n)
 }
 
 static int
-dpif_netdev_validate_actions(const union odp_action *actions, int n_actions,
-                             bool *mutates)
+dpif_netdev_validate_actions(const struct nlattr *actions,
+                             unsigned int actions_len, bool *mutates)
 {
-    unsigned int i;
+    const struct nlattr *a;
+    unsigned int left;
 
     *mutates = false;
-    for (i = 0; i < n_actions; i++) {
-        const union odp_action *a = &actions[i];
-        switch (a->type) {
+    NL_ATTR_FOR_EACH (a, left, actions, actions_len) {
+        uint16_t type = nl_attr_type(a);
+        int len = odp_action_len(type);
+
+        if (len != nl_attr_get_size(a)) {
+            return EINVAL;
+        }
+
+        switch (type) {
         case ODPAT_OUTPUT:
-            if (a->output.port >= MAX_PORTS) {
+            if (nl_attr_get_u32(a) >= MAX_PORTS) {
                 return EINVAL;
             }
             break;
 
         case ODPAT_CONTROLLER:
+        case ODPAT_DROP_SPOOFED_ARP:
             break;
 
         case ODPAT_SET_DL_TCI:
             *mutates = true;
-            if (a->dl_tci.tci & htons(VLAN_CFI)) {
+            if (nl_attr_get_be16(a) & htons(VLAN_CFI)) {
                 return EINVAL;
             }
             break;
 
         case ODPAT_SET_NW_TOS:
             *mutates = true;
-            if (a->nw_tos.nw_tos & IP_ECN_MASK) {
+            if (nl_attr_get_u8(a) & IP_ECN_MASK) {
                 return EINVAL;
             }
             break;
@@ -660,6 +670,9 @@ dpif_netdev_validate_actions(const union odp_action *actions, int n_actions,
             *mutates = true;
             break;
 
+        case ODPAT_SET_TUNNEL:
+        case ODPAT_SET_PRIORITY:
+        case ODPAT_POP_PRIORITY:
         default:
             return EOPNOTSUPP;
         }
@@ -670,23 +683,18 @@ dpif_netdev_validate_actions(const union odp_action *actions, int n_actions,
 static int
 set_flow_actions(struct dp_netdev_flow *flow, struct odp_flow *odp_flow)
 {
-    size_t n_bytes;
     bool mutates;
     int error;
 
-    if (odp_flow->n_actions >= 4096 / sizeof *odp_flow->actions) {
-        return EINVAL;
-    }
     error = dpif_netdev_validate_actions(odp_flow->actions,
-                                         odp_flow->n_actions, &mutates);
+                                         odp_flow->actions_len, &mutates);
     if (error) {
         return error;
     }
 
-    n_bytes = odp_flow->n_actions * sizeof *flow->actions;
-    flow->actions = xrealloc(flow->actions, n_bytes);
-    flow->n_actions = odp_flow->n_actions;
-    memcpy(flow->actions, odp_flow->actions, n_bytes);
+    flow->actions = xrealloc(flow->actions, odp_flow->actions_len);
+    flow->actions_len = odp_flow->actions_len;
+    memcpy(flow->actions, odp_flow->actions, odp_flow->actions_len);
     return 0;
 }
 
@@ -793,7 +801,7 @@ dpif_netdev_flow_list(const struct dpif *dpif, struct odp_flow flows[], int n)
 
 static int
 dpif_netdev_execute(struct dpif *dpif,
-                    const union odp_action actions[], int n_actions,
+                    const struct nlattr *actions, unsigned int actions_len,
                     const struct ofpbuf *packet)
 {
     struct dp_netdev *dp = get_dp_netdev(dpif);
@@ -806,7 +814,7 @@ dpif_netdev_execute(struct dpif *dpif,
         return EINVAL;
     }
 
-    error = dpif_netdev_validate_actions(actions, n_actions, &mutates);
+    error = dpif_netdev_validate_actions(actions, actions_len, &mutates);
     if (error) {
         return error;
     }
@@ -825,7 +833,7 @@ dpif_netdev_execute(struct dpif *dpif,
         copy = *packet;
     }
     flow_extract(&copy, 0, -1, &key);
-    error = dp_netdev_execute_actions(dp, &copy, &key, actions, n_actions);
+    error = dp_netdev_execute_actions(dp, &copy, &key, actions, actions_len);
     if (mutates) {
         ofpbuf_uninit(&copy);
     }
@@ -928,7 +936,7 @@ dp_netdev_port_input(struct dp_netdev *dp, struct dp_netdev_port *port,
     if (flow) {
         dp_netdev_flow_used(flow, &key, packet);
         dp_netdev_execute_actions(dp, packet, &key,
-                                  flow->actions, flow->n_actions);
+                                  flow->actions, flow->actions_len);
         dp->n_hit++;
     } else {
         dp->n_missed++;
@@ -1053,63 +1061,67 @@ is_ip(const struct ofpbuf *packet, const struct flow *key)
 
 static void
 dp_netdev_set_nw_addr(struct ofpbuf *packet, struct flow *key,
-                      const struct odp_action_nw_addr *a)
+                      const struct nlattr *a)
 {
     if (is_ip(packet, key)) {
         struct ip_header *nh = packet->l3;
+        ovs_be32 ip = nl_attr_get_be32(a);
+        uint16_t type = nl_attr_type(a);
         uint32_t *field;
 
-        field = a->type == ODPAT_SET_NW_SRC ? &nh->ip_src : &nh->ip_dst;
+        field = type == ODPAT_SET_NW_SRC ? &nh->ip_src : &nh->ip_dst;
         if (key->nw_proto == IP_TYPE_TCP && packet->l7) {
             struct tcp_header *th = packet->l4;
-            th->tcp_csum = recalc_csum32(th->tcp_csum, *field, a->nw_addr);
+            th->tcp_csum = recalc_csum32(th->tcp_csum, *field, ip);
         } else if (key->nw_proto == IP_TYPE_UDP && packet->l7) {
             struct udp_header *uh = packet->l4;
             if (uh->udp_csum) {
-                uh->udp_csum = recalc_csum32(uh->udp_csum, *field, a->nw_addr);
+                uh->udp_csum = recalc_csum32(uh->udp_csum, *field, ip);
                 if (!uh->udp_csum) {
                     uh->udp_csum = 0xffff;
                 }
             }
         }
-        nh->ip_csum = recalc_csum32(nh->ip_csum, *field, a->nw_addr);
-        *field = a->nw_addr;
+        nh->ip_csum = recalc_csum32(nh->ip_csum, *field, ip);
+        *field = ip;
     }
 }
 
 static void
-dp_netdev_set_nw_tos(struct ofpbuf *packet, struct flow *key,
-                     const struct odp_action_nw_tos *a)
+dp_netdev_set_nw_tos(struct ofpbuf *packet, struct flow *key, uint8_t nw_tos)
 {
     if (is_ip(packet, key)) {
         struct ip_header *nh = packet->l3;
         uint8_t *field = &nh->ip_tos;
 
         /* Set the DSCP bits and preserve the ECN bits. */
-        uint8_t new = a->nw_tos | (nh->ip_tos & IP_ECN_MASK);
+        uint8_t new = nw_tos | (nh->ip_tos & IP_ECN_MASK);
 
         nh->ip_csum = recalc_csum16(nh->ip_csum, htons((uint16_t)*field),
-                htons((uint16_t)a->nw_tos));
+                                    htons(nw_tos));
         *field = new;
     }
 }
 
 static void
 dp_netdev_set_tp_port(struct ofpbuf *packet, struct flow *key,
-                      const struct odp_action_tp_port *a)
+                      const struct nlattr *a)
 {
 	if (is_ip(packet, key)) {
+        uint16_t type = nl_attr_type(a);
+        ovs_be16 port = nl_attr_get_be16(a);
         uint16_t *field;
+
         if (key->nw_proto == IPPROTO_TCP && packet->l7) {
             struct tcp_header *th = packet->l4;
-            field = a->type == ODPAT_SET_TP_SRC ? &th->tcp_src : &th->tcp_dst;
-            th->tcp_csum = recalc_csum16(th->tcp_csum, *field, a->tp_port);
-            *field = a->tp_port;
+            field = type == ODPAT_SET_TP_SRC ? &th->tcp_src : &th->tcp_dst;
+            th->tcp_csum = recalc_csum16(th->tcp_csum, *field, port);
+            *field = port;
         } else if (key->nw_proto == IPPROTO_UDP && packet->l7) {
             struct udp_header *uh = packet->l4;
-            field = a->type == ODPAT_SET_TP_SRC ? &uh->udp_src : &uh->udp_dst;
-            uh->udp_csum = recalc_csum16(uh->udp_csum, *field, a->tp_port);
-            *field = a->tp_port;
+            field = type == ODPAT_SET_TP_SRC ? &uh->udp_src : &uh->udp_dst;
+            uh->udp_csum = recalc_csum16(uh->udp_csum, *field, port);
+            *field = port;
         } else {
             return;
         }
@@ -1184,24 +1196,25 @@ dp_netdev_is_spoofed_arp(struct ofpbuf *packet, const struct flow *key)
 static int
 dp_netdev_execute_actions(struct dp_netdev *dp,
                           struct ofpbuf *packet, struct flow *key,
-                          const union odp_action *actions, int n_actions)
+                          const struct nlattr *actions,
+                          unsigned int actions_len)
 {
-    int i;
-    for (i = 0; i < n_actions; i++) {
-        const union odp_action *a = &actions[i];
+    const struct nlattr *a;
+    unsigned int left;
 
-        switch (a->type) {
+    NL_ATTR_FOR_EACH_UNSAFE (a, left, actions, actions_len) {
+        switch (nl_attr_type(a)) {
         case ODPAT_OUTPUT:
-            dp_netdev_output_port(dp, packet, a->output.port);
+            dp_netdev_output_port(dp, packet, nl_attr_get_u32(a));
             break;
 
         case ODPAT_CONTROLLER:
             dp_netdev_output_control(dp, packet, _ODPL_ACTION_NR,
-                                     key->in_port, a->controller.arg);
+                                     key->in_port, nl_attr_get_u32(a));
             break;
 
         case ODPAT_SET_DL_TCI:
-            dp_netdev_set_dl_tci(packet, a->dl_tci.tci);
+            dp_netdev_set_dl_tci(packet, nl_attr_get_be16(a));
             break;
 
         case ODPAT_STRIP_VLAN:
@@ -1209,25 +1222,25 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
             break;
 
         case ODPAT_SET_DL_SRC:
-            dp_netdev_set_dl_src(packet, a->dl_addr.dl_addr);
+            dp_netdev_set_dl_src(packet, nl_attr_get_unspec(a, ETH_ADDR_LEN));
             break;
 
         case ODPAT_SET_DL_DST:
-            dp_netdev_set_dl_dst(packet, a->dl_addr.dl_addr);
+            dp_netdev_set_dl_dst(packet, nl_attr_get_unspec(a, ETH_ADDR_LEN));
             break;
 
         case ODPAT_SET_NW_SRC:
         case ODPAT_SET_NW_DST:
-            dp_netdev_set_nw_addr(packet, key, &a->nw_addr);
+            dp_netdev_set_nw_addr(packet, key, a);
             break;
 
         case ODPAT_SET_NW_TOS:
-            dp_netdev_set_nw_tos(packet, key, &a->nw_tos);
+            dp_netdev_set_nw_tos(packet, key, nl_attr_get_u8(a));
             break;
 
         case ODPAT_SET_TP_SRC:
         case ODPAT_SET_TP_DST:
-            dp_netdev_set_tp_port(packet, key, &a->tp_port);
+            dp_netdev_set_tp_port(packet, key, a);
             break;
 
         case ODPAT_DROP_SPOOFED_ARP:
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
index 26cd6b0..deb3bf2 100644
--- a/lib/dpif-provider.h
+++ b/lib/dpif-provider.h
@@ -247,11 +247,10 @@ struct dpif_class {
      * 'n' flows).  On failure, returns a negative errno value. */
     int (*flow_list)(const struct dpif *dpif, struct odp_flow flows[], int n);
 
-    /* Performs the 'n_actions' actions in 'actions' on the Ethernet frame
-     * specified in 'packet'. */
-    int (*execute)(struct dpif *dpif,
-                   const union odp_action actions[], int n_actions,
-                   const struct ofpbuf *packet);
+    /* Performs the 'actions_len' bytes of actions in 'actions' on the Ethernet
+     * frame specified in 'packet'. */
+    int (*execute)(struct dpif *dpif, const struct nlattr *actions,
+                   size_t actions_len, const struct ofpbuf *packet);
 
     /* Retrieves 'dpif''s "listen mask" into '*listen_mask'.  Each ODPL_* bit
      * set in '*listen_mask' indicates the 'dpif' will receive messages of the
diff --git a/lib/dpif.c b/lib/dpif.c
index 03e13ac..91d849d 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -687,7 +687,7 @@ dpif_flow_get(const struct dpif *dpif, struct odp_flow *flow)
     if (error) {
         /* Make the results predictable on error. */
         memset(&flow->stats, 0, sizeof flow->stats);
-        flow->n_actions = 0;
+        flow->actions_len = 0;
     }
     if (should_log_flow_message(error)) {
         log_flow_operation(dpif, "flow_get", error, flow);
@@ -811,7 +811,7 @@ dpif_flow_list(const struct dpif *dpif, struct odp_flow flows[], size_t n,
     } else {
         for (i = 0; i < n; i++) {
             flows[i].actions = NULL;
-            flows[i].n_actions = 0;
+            flows[i].actions_len = 0;
         }
     }
     retval = dpif->dpif_class->flow_list(dpif, flows, n);
@@ -879,14 +879,14 @@ dpif_flow_list_all(const struct dpif *dpif,
  * Returns 0 if successful, otherwise a positive errno value. */
 int
 dpif_execute(struct dpif *dpif,
-             const union odp_action actions[], size_t n_actions,
+             const struct nlattr *actions, size_t actions_len,
              const struct ofpbuf *buf)
 {
     int error;
 
     COVERAGE_INC(dpif_execute);
-    if (n_actions > 0) {
-        error = dpif->dpif_class->execute(dpif, actions, n_actions, buf);
+    if (actions_len > 0) {
+        error = dpif->dpif_class->execute(dpif, actions, actions_len, buf);
     } else {
         error = 0;
     }
@@ -895,7 +895,7 @@ dpif_execute(struct dpif *dpif,
         struct ds ds = DS_EMPTY_INITIALIZER;
         char *packet = ofp_packet_to_string(buf->data, buf->size, buf->size);
         ds_put_format(&ds, "%s: execute ", dpif_name(dpif));
-        format_odp_actions(&ds, actions, n_actions);
+        format_odp_actions(&ds, actions, actions_len);
         if (error) {
             ds_put_format(&ds, " failed (%s)", strerror(error));
         }
@@ -1132,7 +1132,7 @@ static void
 log_flow_message(const struct dpif *dpif, int error, const char *operation,
                  const struct odp_flow_key *flow,
                  const struct odp_flow_stats *stats,
-                 const union odp_action *actions, size_t n_actions)
+                 const struct nlattr *actions, unsigned int actions_len)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     ds_put_format(&ds, "%s: ", dpif_name(dpif));
@@ -1148,9 +1148,9 @@ log_flow_message(const struct dpif *dpif, int error, const char *operation,
         ds_put_cstr(&ds, ", ");
         format_odp_flow_stats(&ds, stats);
     }
-    if (actions || n_actions) {
+    if (actions || actions_len) {
         ds_put_cstr(&ds, ", actions:");
-        format_odp_actions(&ds, actions, n_actions);
+        format_odp_actions(&ds, actions, actions_len);
     }
     vlog(THIS_MODULE, flow_message_log_level(error), "%s", ds_cstr(&ds));
     ds_destroy(&ds);
@@ -1161,11 +1161,11 @@ log_flow_operation(const struct dpif *dpif, const char *operation, int error,
                    struct odp_flow *flow)
 {
     if (error) {
-        flow->n_actions = 0;
+        flow->actions_len = 0;
     }
     log_flow_message(dpif, error, operation, &flow->key,
                      !error ? &flow->stats : NULL,
-                     flow->actions, flow->n_actions);
+                     flow->actions, flow->actions_len);
 }
 
 static void
@@ -1190,12 +1190,12 @@ log_flow_put(struct dpif *dpif, int error, const struct odp_flow_put *put)
     }
     log_flow_message(dpif, error, ds_cstr(&s), &put->flow.key,
                      !error ? &put->flow.stats : NULL,
-                     put->flow.actions, put->flow.n_actions);
+                     put->flow.actions, put->flow.actions_len);
     ds_destroy(&s);
 }
 
 /* There is a tendency to construct odp_flow objects on the stack and to
- * forget to properly initialize their "actions" and "n_actions" members.
+ * forget to properly initialize their "actions" and "actions_len" members.
  * When this happens, we get memory corruption because the kernel
  * writes through the random pointer that is in the "actions" member.
  *
@@ -1208,12 +1208,12 @@ log_flow_put(struct dpif *dpif, int error, const struct odp_flow_put *put)
  *        easy-to-identify error later if it is dereferenced, etc.
  *
  *      - Triggering a warning on uninitialized memory from Valgrind if
- *        "actions" or "n_actions" was not initialized.
+ *        "actions" or "actions_len" was not initialized.
  */
 static void
 check_rw_odp_flow(struct odp_flow *flow)
 {
-    if (flow->n_actions) {
+    if (flow->actions_len) {
         memset(&flow->actions[0], 0xcc, sizeof flow->actions[0]);
     }
 }
diff --git a/lib/dpif.h b/lib/dpif.h
index 927776c..825a00c 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -31,6 +31,7 @@ extern "C" {
 
 struct dpif;
 struct netdev;
+struct nlattr;
 struct ofpbuf;
 struct svec;
 struct dpif_class;
@@ -83,8 +84,8 @@ int dpif_flow_list(const struct dpif *, struct odp_flow[], size_t n,
 int dpif_flow_list_all(const struct dpif *,
                        struct odp_flow **flowsp, size_t *np);
 
-int dpif_execute(struct dpif *, const union odp_action[], size_t n_actions,
-                 const struct ofpbuf *);
+int dpif_execute(struct dpif *, const struct nlattr *actions,
+                 size_t actions_len, const struct ofpbuf *);
 
 /* Minimum number of bytes of headroom for a packet returned by dpif_recv()
  * member function.  This headroom allows "struct odp_msg" to be replaced by
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 6b4f5fa..8089cd3 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -19,26 +19,15 @@
 #include <inttypes.h>
 #include <stdlib.h>
 #include <string.h>
+#include "byte-order.h"
 #include "coverage.h"
 #include "dynamic-string.h"
 #include "flow.h"
+#include "netlink.h"
 #include "packets.h"
 #include "timeval.h"
 #include "util.h"
 
-union odp_action *
-odp_actions_add(struct odp_actions *actions, uint16_t type)
-{
-    union odp_action *a;
-    size_t idx;
-
-    idx = actions->n_actions++ & (MAX_ODP_ACTIONS - 1);
-    a = &actions->actions[idx];
-    memset(a, 0, sizeof *a);
-    a->type = type;
-    return a;
-}
-
 void
 format_odp_flow_key(struct ds *ds, const struct odp_flow_key *key)
 {
@@ -59,54 +48,114 @@ format_odp_flow_key(struct ds *ds, const struct odp_flow_key *key)
                   ntohs(key->tp_src), ntohs(key->tp_dst));
 }
 
+int
+odp_action_len(uint16_t type)
+{
+    if (type > ODPAT_MAX) {
+        return -1;
+    }
+
+    switch ((enum odp_action_type) type) {
+    case ODPAT_OUTPUT: return 4;
+    case ODPAT_CONTROLLER: return 4;
+    case ODPAT_SET_DL_TCI: return 2;
+    case ODPAT_STRIP_VLAN: return 0;
+    case ODPAT_SET_DL_SRC: return ETH_ADDR_LEN;
+    case ODPAT_SET_DL_DST: return ETH_ADDR_LEN;
+    case ODPAT_SET_NW_SRC: return 4;
+    case ODPAT_SET_NW_DST: return 4;
+    case ODPAT_SET_NW_TOS: return 1;
+    case ODPAT_SET_TP_SRC: return 2;
+    case ODPAT_SET_TP_DST: return 2;
+    case ODPAT_SET_TUNNEL: return 8;
+    case ODPAT_SET_PRIORITY: return 4;
+    case ODPAT_POP_PRIORITY: return 0;
+    case ODPAT_DROP_SPOOFED_ARP: return 0;
+
+    case ODPAT_UNSPEC:
+    case __ODPAT_MAX:
+        return -1;
+    }
+
+    return -1;
+}
+
+static void
+format_generic_odp_action(struct ds *ds, const struct nlattr *a)
+{
+    ds_put_format(ds, "action%"PRId16, nl_attr_type(a));
+    if (a->nla_len) {
+        const uint8_t *unspec;
+        unsigned int i;
+
+        unspec = nl_attr_get(a);
+        for (i = 0; i < a->nla_len; i++) {
+            ds_put_char(ds, i ? ' ': '(');
+            ds_put_format(ds, "%02x", unspec[i]);
+        }
+        ds_put_char(ds, ')');
+    }
+}
+
 void
-format_odp_action(struct ds *ds, const union odp_action *a)
+format_odp_action(struct ds *ds, const struct nlattr *a)
 {
-    switch (a->type) {
+    const uint8_t *eth;
+    ovs_be32 ip;
+
+    if (nl_attr_get_size(a) != odp_action_len(a->nla_len)) {
+        ds_put_format(ds, "***bad action: length is %zu, expected %d*** ",
+                      nl_attr_get_size(a), odp_action_len(a->nla_len));
+        format_generic_odp_action(ds, a);
+        return;
+    }
+
+    switch (nl_attr_type(a)) {
     case ODPAT_OUTPUT:
-        ds_put_format(ds, "%"PRIu16, a->output.port);
+        ds_put_format(ds, "%"PRIu16, nl_attr_get_u32(a));
         break;
     case ODPAT_CONTROLLER:
-        ds_put_format(ds, "ctl(%"PRIu32")", a->controller.arg);
+        ds_put_format(ds, "ctl(%"PRIu32")", nl_attr_get_u32(a));
         break;
     case ODPAT_SET_TUNNEL:
-        ds_put_format(ds, "set_tunnel(%#"PRIx32")", ntohl(a->tunnel.tun_id));
+        ds_put_format(ds, "set_tunnel(%#"PRIx32")",
+                      ntohl(nl_attr_get_be32(a)));
         break;
     case ODPAT_SET_DL_TCI:
         ds_put_format(ds, "set_tci(vid=%"PRIu16",pcp=%d)",
-                      vlan_tci_to_vid(a->dl_tci.tci),
-                      vlan_tci_to_pcp(a->dl_tci.tci));
+                      vlan_tci_to_vid(nl_attr_get_be16(a)),
+                      vlan_tci_to_pcp(nl_attr_get_be16(a)));
         break;
     case ODPAT_STRIP_VLAN:
         ds_put_format(ds, "strip_vlan");
         break;
     case ODPAT_SET_DL_SRC:
-        ds_put_format(ds, "set_dl_src("ETH_ADDR_FMT")",
-               ETH_ADDR_ARGS(a->dl_addr.dl_addr));
+        eth = nl_attr_get_unspec(a, ETH_ADDR_LEN);
+        ds_put_format(ds, "set_dl_src("ETH_ADDR_FMT")", ETH_ADDR_ARGS(eth));
         break;
     case ODPAT_SET_DL_DST:
-        ds_put_format(ds, "set_dl_dst("ETH_ADDR_FMT")",
-               ETH_ADDR_ARGS(a->dl_addr.dl_addr));
+        eth = nl_attr_get_unspec(a, ETH_ADDR_LEN);
+        ds_put_format(ds, "set_dl_dst("ETH_ADDR_FMT")", ETH_ADDR_ARGS(eth));
         break;
     case ODPAT_SET_NW_SRC:
-        ds_put_format(ds, "set_nw_src("IP_FMT")",
-                      IP_ARGS(&a->nw_addr.nw_addr));
+        ip = nl_attr_get_be32(a);
+        ds_put_format(ds, "set_nw_src("IP_FMT")", IP_ARGS(&ip));
         break;
     case ODPAT_SET_NW_DST:
-        ds_put_format(ds, "set_nw_dst("IP_FMT")",
-                      IP_ARGS(&a->nw_addr.nw_addr));
+        ip = nl_attr_get_be32(a);
+        ds_put_format(ds, "set_nw_dst("IP_FMT")", IP_ARGS(&ip));
         break;
     case ODPAT_SET_NW_TOS:
-        ds_put_format(ds, "set_nw_tos(%"PRIu8")", a->nw_tos.nw_tos);
+        ds_put_format(ds, "set_nw_tos(%"PRIu8")", nl_attr_get_u8(a));
         break;
     case ODPAT_SET_TP_SRC:
-        ds_put_format(ds, "set_tp_src(%"PRIu16")", ntohs(a->tp_port.tp_port));
+        ds_put_format(ds, "set_tp_src(%"PRIu16")", ntohs(nl_attr_get_be16(a)));
         break;
     case ODPAT_SET_TP_DST:
-        ds_put_format(ds, "set_tp_dst(%"PRIu16")", ntohs(a->tp_port.tp_port));
+        ds_put_format(ds, "set_tp_dst(%"PRIu16")", ntohs(nl_attr_get_be16(a)));
         break;
     case ODPAT_SET_PRIORITY:
-        ds_put_format(ds, "set_priority(0x%"PRIx32")", a->priority.priority);
+        ds_put_format(ds, "set_priority(%#"PRIx32")", nl_attr_get_u32(a));
         break;
     case ODPAT_POP_PRIORITY:
         ds_put_cstr(ds, "pop_priority");
@@ -115,23 +164,29 @@ format_odp_action(struct ds *ds, const union odp_action *a)
         ds_put_cstr(ds, "drop_spoofed_arp");
         break;
     default:
-        ds_put_format(ds, "***bad action 0x%"PRIx16"***", a->type);
+        format_generic_odp_action(ds, a);
         break;
     }
 }
 
 void
-format_odp_actions(struct ds *ds, const union odp_action *actions,
-                   size_t n_actions)
+format_odp_actions(struct ds *ds, const struct nlattr *actions,
+                   unsigned int actions_len)
 {
-    size_t i;
-    for (i = 0; i < n_actions; i++) {
-        if (i) {
-            ds_put_char(ds, ',');
+    if (actions_len) {
+        const struct nlattr *a;
+        unsigned int left;
+
+        NL_ATTR_FOR_EACH (a, left, actions, actions_len) {
+            if (a != actions) {
+                ds_put_char(ds, ',');
+            }
+            format_odp_action(ds, a);
         }
-        format_odp_action(ds, &actions[i]);
-    }
-    if (!n_actions) {
+        if (left) {
+            ds_put_format(ds, " ***%u leftover bytes***", left);
+        }
+    } else {
         ds_put_cstr(ds, "drop");
     }
 }
@@ -157,7 +212,7 @@ format_odp_flow(struct ds *ds, const struct odp_flow *f)
     ds_put_cstr(ds, ", ");
     format_odp_flow_stats(ds, &f->stats);
     ds_put_cstr(ds, ", actions:");
-    format_odp_actions(ds, f->actions, f->n_actions);
+    format_odp_actions(ds, f->actions, f->actions_len);
 }
 
 void
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 110e8bc..6051c52 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -29,32 +29,6 @@
 struct ds;
 struct flow;
 
-/* The kernel datapaths limits actions to those that fit in a single page of
- * memory, so there is no point in allocating more than that.  */
-enum { MAX_ODP_ACTIONS = 65536 / sizeof(union odp_action) };
-
-/* odp_actions_add() assumes that MAX_ODP_ACTIONS is a power of 2. */
-BUILD_ASSERT_DECL(IS_POW2(MAX_ODP_ACTIONS));
-
-struct odp_actions {
-    size_t n_actions;
-    union odp_action actions[MAX_ODP_ACTIONS];
-};
-
-static inline void
-odp_actions_init(struct odp_actions *actions)
-{
-    actions->n_actions = 0;
-}
-
-union odp_action *odp_actions_add(struct odp_actions *actions, uint16_t type);
-
-static inline bool
-odp_actions_overflow(const struct odp_actions *actions)
-{
-    return actions->n_actions > MAX_ODP_ACTIONS;
-}
-
 static inline uint16_t
 ofp_port_to_odp_port(uint16_t ofp_port)
 {
@@ -82,9 +56,10 @@ odp_port_to_ofp_port(uint16_t odp_port)
 }
 
 void format_odp_flow_key(struct ds *, const struct odp_flow_key *);
-void format_odp_action(struct ds *, const union odp_action *);
-void format_odp_actions(struct ds *, const union odp_action *actions,
-                        size_t n_actions);
+int odp_action_len(uint16_t type);
+void format_odp_action(struct ds *, const struct nlattr *);
+void format_odp_actions(struct ds *, const struct nlattr *odp_actions,
+                        size_t actions_len);
 void format_odp_flow_stats(struct ds *, const struct odp_flow_stats *);
 void format_odp_flow(struct ds *, const struct odp_flow *);
 
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 9fabecb..93805ba 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -129,7 +129,7 @@ ofputil_cls_rule_from_match(const struct ofp_match *match,
         rule->flow.tun_id = htonl(ntohll(cookie) >> 32);
     } else {
         wc->wildcards |= FWW_TUN_ID;
-        rule->flow.tun_id = 0;
+        rule->flow.tun_id = htonl(0);
     }
 
     if (ofpfw & OFPFW_DL_DST) {
diff --git a/ofproto/in-band.c b/ofproto/in-band.c
index aebdb7e..9655f10 100644
--- a/ofproto/in-band.c
+++ b/ofproto/in-band.c
@@ -28,6 +28,7 @@
 #include "dpif.h"
 #include "flow.h"
 #include "netdev.h"
+#include "netlink.h"
 #include "odp-util.h"
 #include "ofproto.h"
 #include "ofpbuf.h"
@@ -428,7 +429,7 @@ in_band_msg_in_hook(struct in_band *in_band, const struct flow *flow,
  * allowed to be set up in the datapath. */
 bool
 in_band_rule_check(struct in_band *in_band, const struct flow *flow,
-                   const struct odp_actions *actions)
+                   const struct nlattr *actions, unsigned int actions_len)
 {
     if (!in_band) {
         return true;
@@ -440,11 +441,12 @@ in_band_rule_check(struct in_band *in_band, const struct flow *flow,
             && flow->nw_proto == IP_TYPE_UDP
             && flow->tp_src == htons(DHCP_SERVER_PORT)
             && flow->tp_dst == htons(DHCP_CLIENT_PORT)) {
-        int i;
+        const struct nlattr *a;
+        unsigned int left;
 
-        for (i=0; i<actions->n_actions; i++) {
-            if (actions->actions[i].output.type == ODPAT_OUTPUT
-                    && actions->actions[i].output.port == ODPP_LOCAL) {
+        NL_ATTR_FOR_EACH_UNSAFE (a, left, actions, actions_len) {
+            if (nl_attr_type(a) == ODPAT_OUTPUT
+                && nl_attr_get_u32(a) == ODPP_LOCAL) {
                 return true;
             }
         }
diff --git a/ofproto/in-band.h b/ofproto/in-band.h
index 23a30ce..972acaa 100644
--- a/ofproto/in-band.h
+++ b/ofproto/in-band.h
@@ -21,7 +21,6 @@
 
 struct dpif;
 struct in_band;
-struct odp_actions;
 struct ofproto;
 struct rconn;
 struct settings;
@@ -41,7 +40,8 @@ void in_band_wait(struct in_band *);
 bool in_band_msg_in_hook(struct in_band *, const struct flow *,
                          const struct ofpbuf *packet);
 bool in_band_rule_check(struct in_band *, const struct flow *,
-                        const struct odp_actions *);
+                        const struct nlattr *odp_actions,
+                        unsigned int actions_len);
 void in_band_flushed(struct in_band *);
 
 #endif /* in-band.h */
diff --git a/ofproto/ofproto-sflow.c b/ofproto/ofproto-sflow.c
index 801614d..77119fd 100644
--- a/ofproto/ofproto-sflow.c
+++ b/ofproto/ofproto-sflow.c
@@ -25,6 +25,7 @@
 #include "hash.h"
 #include "hmap.h"
 #include "netdev.h"
+#include "netlink.h"
 #include "ofpbuf.h"
 #include "ofproto.h"
 #include "packets.h"
@@ -484,41 +485,32 @@ ofproto_sflow_received(struct ofproto_sflow *os, struct odp_msg *msg)
     SFLFlow_sample_element switchElem;
     SFLSampler *sampler;
     const struct odp_sflow_sample_header *hdr;
-    const union odp_action *actions;
-    struct ofpbuf payload;
-    size_t n_actions, n_outputs;
+    const struct nlattr *actions, *a;
+    unsigned int left;
+    struct ofpbuf b;
+    size_t n_outputs;
     struct flow flow;
-    size_t min_size;
-    size_t i;
-
-    /* Get odp_sflow_sample_header. */
-    min_size = sizeof *msg + sizeof *hdr;
-    if (min_size > msg->length) {
-        VLOG_WARN_RL(&rl, "sFlow packet too small (%"PRIu32" < %zu)",
-                     msg->length, min_size);
-        return;
-    }
-    hdr = (const struct odp_sflow_sample_header *) (msg + 1);
 
-    /* Get actions. */
-    n_actions = hdr->n_actions;
-    if (n_actions > 65536 / sizeof *actions) {
-        VLOG_WARN_RL(&rl, "too many actions in sFlow packet (%zu > %zu)",
-                     65536 / sizeof *actions, n_actions);
+    /* Pull odp_msg header. */
+    ofpbuf_use_const(&b, msg, msg->length);
+    ofpbuf_pull(&b, sizeof *msg);
+
+    /* Pull odp_sflow_sample_header. */
+    hdr = ofpbuf_try_pull(&b, sizeof *hdr);
+    if (!hdr) {
+        VLOG_WARN_RL(&rl, "missing odp_sflow_sample_header");
         return;
     }
-    min_size += n_actions * sizeof *actions;
-    if (min_size > msg->length) {
-        VLOG_WARN_RL(&rl, "sFlow packet with %zu actions too small "
-                     "(%"PRIu32" < %zu)",
-                     n_actions, msg->length, min_size);
+
+    /* Pull actions. */
+    actions = ofpbuf_try_pull(&b, hdr->actions_len);
+    if (!actions) {
+        VLOG_WARN_RL(&rl, "missing odp actions");
         return;
     }
-    actions = (const union odp_action *) (hdr + 1);
 
-    /* Get packet payload and extract flow. */
-    ofpbuf_use_const(&payload, actions + n_actions, msg->length - min_size);
-    flow_extract(&payload, 0, msg->port, &flow);
+    /* Now only the payload is left. */
+    flow_extract(&b, 0, msg->port, &flow);
 
     /* Build a flow sample */
     memset(&fs, 0, sizeof fs);
@@ -543,12 +535,11 @@ ofproto_sflow_received(struct ofproto_sflow *os, struct odp_msg *msg)
     header->header_protocol = SFLHEADER_ETHERNET_ISO8023;
     /* The frame_length should include the Ethernet FCS (4 bytes),
        but it has already been stripped,  so we need to add 4 here. */
-    header->frame_length = payload.size + 4;
+    header->frame_length = b.size + 4;
     /* Ethernet FCS stripped off. */
     header->stripped = 4;
-    header->header_length = MIN(payload.size,
-                                sampler->sFlowFsMaximumHeaderSize);
-    header->header_bytes = payload.data;
+    header->header_length = MIN(b.size, sampler->sFlowFsMaximumHeaderSize);
+    header->header_bytes = b.data;
 
     /* Add extended switch element. */
     memset(&switchElem, 0, sizeof(switchElem));
@@ -562,18 +553,18 @@ ofproto_sflow_received(struct ofproto_sflow *os, struct odp_msg *msg)
 
     /* Figure out the output ports. */
     n_outputs = 0;
-    for (i = 0; i < n_actions; i++) {
-        const union odp_action *a = &actions[i];
-        uint16_t tci;
+    NL_ATTR_FOR_EACH_UNSAFE (a, left, actions, hdr->actions_len) {
+        ovs_be16 tci;
 
-        switch (a->type) {
+        switch (nl_attr_type(a)) {
         case ODPAT_OUTPUT:
-            fs.output = ofproto_sflow_odp_port_to_ifindex(os, a->output.port);
+            fs.output = ofproto_sflow_odp_port_to_ifindex(os,
+                                                          nl_attr_get_u32(a));
             n_outputs++;
             break;
 
         case ODPAT_SET_DL_TCI:
-            tci = a->dl_tci.tci;
+            tci = nl_attr_get_be16(a);
             switchElem.flowType.sw.dst_vlan = vlan_tci_to_vid(tci);
             switchElem.flowType.sw.dst_priority = vlan_tci_to_pcp(tci);
             break;
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 7a93b10..88b7124 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -37,6 +37,7 @@
 #include "mac-learning.h"
 #include "netdev.h"
 #include "netflow.h"
+#include "netlink.h"
 #include "nx-match.h"
 #include "odp-util.h"
 #include "ofp-print.h"
@@ -100,11 +101,11 @@ struct ofport {
 static void ofport_free(struct ofport *);
 static void hton_ofp_phy_port(struct ofp_phy_port *);
 
-static int xlate_actions(const union ofp_action *in, size_t n_in,
-                         const struct flow *, struct ofproto *,
-                         const struct ofpbuf *packet,
-                         struct odp_actions *out, tag_type *tags,
-                         bool *may_set_up_flow, uint16_t *nf_output_iface);
+static struct ofpbuf *xlate_actions(const union ofp_action *in, size_t n_in,
+                                    const struct flow *, struct ofproto *,
+                                    const struct ofpbuf *packet,
+                                    tag_type *tags, bool *may_set_up_flow,
+                                    uint16_t *nf_output_iface);
 
 /* An OpenFlow flow. */
 struct rule {
@@ -185,8 +186,8 @@ struct facet {
     bool installed;              /* Installed in datapath? */
     bool may_install;            /* True ordinarily; false if actions must
                                   * be reassessed for every packet. */
-    int n_actions;               /* Number of elements in actions[]. */
-    union odp_action *actions;   /* Datapath actions. */
+    unsigned int actions_len;    /* Number of bytes in actions[]. */
+    struct nlattr *actions;      /* Datapath actions. */
     tag_type tags;               /* Tags (set only by hooks). */
     struct netflow_flow nf_flow; /* Per-flow NetFlow tracking data. */
 };
@@ -1349,18 +1350,17 @@ ofproto_send_packet(struct ofproto *p, const struct flow *flow,
                     const union ofp_action *actions, size_t n_actions,
                     const struct ofpbuf *packet)
 {
-    struct odp_actions odp_actions;
-    int error;
+    struct ofpbuf *odp_actions;
 
-    error = xlate_actions(actions, n_actions, flow, p, packet, &odp_actions,
-                          NULL, NULL, NULL);
-    if (error) {
-        return error;
-    }
+    odp_actions = xlate_actions(actions, n_actions, flow, p, packet,
+                                NULL, NULL, NULL);
 
     /* XXX Should we translate the dpif_execute() errno value into an OpenFlow
      * error code? */
-    dpif_execute(p->dpif, odp_actions.actions, odp_actions.n_actions, packet);
+    dpif_execute(p->dpif, odp_actions->data, odp_actions->size, packet);
+
+    ofpbuf_delete(odp_actions);
+
     return 0;
 }
 
@@ -1999,10 +1999,11 @@ rule_has_out_port(const struct rule *rule, ovs_be16 out_port)
  * Takes ownership of 'packet'. */
 static bool
 execute_odp_actions(struct ofproto *ofproto, uint16_t in_port,
-                    const union odp_action *actions, size_t n_actions,
+                    const struct nlattr *odp_actions, unsigned int actions_len,
                     struct ofpbuf *packet)
 {
-    if (n_actions == 1 && actions[0].type == ODPAT_CONTROLLER) {
+    if (actions_len == NLA_ALIGN(NLA_HDRLEN + sizeof(uint32_t))
+        && odp_actions->nla_type == ODPAT_CONTROLLER) {
         /* As an optimization, avoid a round-trip from userspace to kernel to
          * userspace.  This also avoids possibly filling up kernel packet
          * buffers along the way. */
@@ -2013,7 +2014,7 @@ execute_odp_actions(struct ofproto *ofproto, uint16_t in_port,
         msg->length = sizeof(struct odp_msg) + packet->size;
         msg->port = in_port;
         msg->reserved = 0;
-        msg->arg = actions[0].controller.arg;
+        msg->arg = nl_attr_get_u32(odp_actions);
 
         send_packet_in(ofproto, packet);
 
@@ -2021,7 +2022,7 @@ execute_odp_actions(struct ofproto *ofproto, uint16_t in_port,
     } else {
         int error;
 
-        error = dpif_execute(ofproto->dpif, actions, n_actions, packet);
+        error = dpif_execute(ofproto->dpif, odp_actions, actions_len, packet);
         ofpbuf_delete(packet);
         return !error;
     }
@@ -2049,7 +2050,7 @@ facet_execute(struct ofproto *ofproto, struct facet *facet,
 
     flow_extract_stats(&facet->flow, packet, &stats);
     if (execute_odp_actions(ofproto, facet->flow.in_port,
-                            facet->actions, facet->n_actions, packet)) {
+                            facet->actions, facet->actions_len, packet)) {
         facet_update_stats(ofproto, facet, &stats);
         facet->used = time_msec();
         netflow_flow_update_time(ofproto->netflow,
@@ -2069,8 +2070,8 @@ static void
 rule_execute(struct ofproto *ofproto, struct rule *rule, uint16_t in_port,
              struct ofpbuf *packet)
 {
+    struct ofpbuf *odp_actions;
     struct facet *facet;
-    struct odp_actions a;
     struct flow flow;
     size_t size;
 
@@ -2096,18 +2097,16 @@ rule_execute(struct ofproto *ofproto, struct rule *rule, uint16_t in_port,
 
     /* We can't account anything to a facet.  If we were to try, then that
      * facet would have a non-matching rule, busting our invariants. */
-    if (xlate_actions(rule->actions, rule->n_actions, &flow, ofproto,
-                      packet, &a, NULL, 0, NULL)) {
-        ofpbuf_delete(packet);
-        return;
-    }
+    odp_actions = xlate_actions(rule->actions, rule->n_actions, &flow, ofproto,
+                                packet, NULL, 0, NULL);
     size = packet->size;
-    if (execute_odp_actions(ofproto, in_port,
-                            a.actions, a.n_actions, packet)) {
+    if (execute_odp_actions(ofproto, in_port, odp_actions->data,
+                            odp_actions->size, packet)) {
         rule->used = time_msec();
         rule->packet_count++;
         rule->byte_count += size;
     }
+    ofpbuf_delete(odp_actions);
 }
 
 /* Inserts 'rule' into 'p''s flow table. */
@@ -2195,20 +2194,20 @@ facet_make_actions(struct ofproto *p, struct facet *facet,
                    const struct ofpbuf *packet)
 {
     const struct rule *rule = facet->rule;
-    struct odp_actions a;
-    size_t actions_len;
+    struct ofpbuf *odp_actions;
 
-    xlate_actions(rule->actions, rule->n_actions, &facet->flow, p,
-                  packet, &a, &facet->tags, &facet->may_install,
-                  &facet->nf_flow.output_iface);
+    odp_actions = xlate_actions(rule->actions, rule->n_actions, &facet->flow,
+                                p, packet, &facet->tags, &facet->may_install,
+                                &facet->nf_flow.output_iface);
 
-    actions_len = a.n_actions * sizeof *a.actions;
-    if (facet->n_actions != a.n_actions
-        || memcmp(facet->actions, a.actions, actions_len)) {
+    if (facet->actions_len != odp_actions->size
+        || memcmp(facet->actions, odp_actions->data, odp_actions->size)) {
         free(facet->actions);
-        facet->n_actions = a.n_actions;
-        facet->actions = xmemdup(a.actions, actions_len);
+        facet->actions_len = odp_actions->size;
+        facet->actions = xmemdup(odp_actions->data, odp_actions->size);
     }
+
+    ofpbuf_delete(odp_actions);
 }
 
 static int
@@ -2218,7 +2217,7 @@ facet_put__(struct ofproto *ofproto, struct facet *facet, int flags,
     memset(&put->flow.stats, 0, sizeof put->flow.stats);
     odp_flow_key_from_flow(&put->flow.key, &facet->flow);
     put->flow.actions = facet->actions;
-    put->flow.n_actions = facet->n_actions;
+    put->flow.actions_len = facet->actions_len;
     put->flow.flags = 0;
     put->flags = flags;
     return dpif_flow_put(ofproto->dpif, put);
@@ -2256,7 +2255,7 @@ facet_account(struct ofproto *ofproto,
         && total_bytes > facet->accounted_bytes)
     {
         ofproto->ofhooks->account_flow_cb(
-            &facet->flow, facet->tags, facet->actions, facet->n_actions,
+            &facet->flow, facet->tags, facet->actions, facet->actions_len,
             total_bytes - facet->accounted_bytes, ofproto->aux);
         facet->accounted_bytes = total_bytes;
     }
@@ -2271,7 +2270,7 @@ facet_uninstall(struct ofproto *p, struct facet *facet)
 
         odp_flow_key_from_flow(&odp_flow.key, &facet->flow);
         odp_flow.actions = NULL;
-        odp_flow.n_actions = 0;
+        odp_flow.actions_len = 0;
         odp_flow.flags = 0;
         if (!dpif_flow_del(p->dpif, &odp_flow)) {
             facet_update_stats(p, facet, &odp_flow.stats);
@@ -2375,9 +2374,8 @@ facet_lookup_valid(struct ofproto *ofproto, const struct flow *flow)
 static bool
 facet_revalidate(struct ofproto *ofproto, struct facet *facet)
 {
+    struct ofpbuf *odp_actions;
     struct rule *new_rule;
-    struct odp_actions a;
-    size_t actions_len;
     uint16_t new_nf_output_iface;
     bool actions_changed;
 
@@ -2396,12 +2394,12 @@ facet_revalidate(struct ofproto *ofproto, struct facet *facet)
      * We are very cautious about actually modifying 'facet' state at this
      * point, because we might need to, e.g., emit a NetFlow expiration and, if
      * so, we need to have the old state around to properly compose it. */
-    xlate_actions(new_rule->actions, new_rule->n_actions, &facet->flow,
-                  ofproto, NULL, &a, &facet->tags, &facet->may_install,
-                  &new_nf_output_iface);
-    actions_len = a.n_actions * sizeof *a.actions;
-    actions_changed = (facet->n_actions != a.n_actions
-                       || memcmp(facet->actions, a.actions, actions_len));
+    odp_actions = xlate_actions(new_rule->actions, new_rule->n_actions,
+                                &facet->flow, ofproto, NULL, &facet->tags,
+                                &facet->may_install, &new_nf_output_iface);
+    actions_changed = (facet->actions_len != odp_actions->size
+                       || memcmp(facet->actions, odp_actions->data,
+                                 facet->actions_len));
 
     /* If the ODP actions changed or the installability changed, then we need
      * to talk to the datapath. */
@@ -2411,8 +2409,8 @@ facet_revalidate(struct ofproto *ofproto, struct facet *facet)
 
             memset(&put.flow.stats, 0, sizeof put.flow.stats);
             odp_flow_key_from_flow(&put.flow.key, &facet->flow);
-            put.flow.actions = a.actions;
-            put.flow.n_actions = a.n_actions;
+            put.flow.actions = odp_actions->data;
+            put.flow.actions_len = odp_actions->size;
             put.flow.flags = 0;
             put.flags = ODPPF_CREATE | ODPPF_MODIFY | ODPPF_ZERO_STATS;
             dpif_flow_put(ofproto->dpif, &put);
@@ -2427,12 +2425,14 @@ facet_revalidate(struct ofproto *ofproto, struct facet *facet)
         facet_flush_stats(ofproto, facet);
     }
 
+    ofpbuf_delete(odp_actions);
+
     /* Update 'facet' now that we've taken care of all the old state. */
     facet->nf_flow.output_iface = new_nf_output_iface;
     if (actions_changed) {
         free(facet->actions);
-        facet->n_actions = a.n_actions;
-        facet->actions = xmemdup(a.actions, actions_len);
+        facet->actions_len = odp_actions->size;
+        facet->actions = xmemdup(odp_actions->data, odp_actions->size);
     }
     if (facet->rule != new_rule) {
         COVERAGE_INC(facet_changed_rule);
@@ -2565,13 +2565,6 @@ handle_set_config(struct ofconn *ofconn, const struct ofp_switch_config *osc)
     return 0;
 }
 
-static void
-add_controller_action(struct odp_actions *actions, uint16_t max_len)
-{
-    union odp_action *a = odp_actions_add(actions, ODPAT_CONTROLLER);
-    a->controller.arg = max_len;
-}
-
 struct action_xlate_ctx {
     /* Input. */
     struct flow flow;           /* Flow to which these actions correspond. */
@@ -2582,7 +2575,9 @@ struct action_xlate_ctx {
                                   * without a packet to refer to. */
 
     /* Output. */
-    struct odp_actions *out;    /* Datapath actions. */
+    struct ofpbuf *odp_actions; /* Datapath actions. */
+    int last_pop_priority;      /* Offset in 'odp_actions' just past most
+                                 * recently * added ODPAT_SET_PRIORITY. */
     tag_type tags;              /* Tags associated with OFPP_NORMAL actions. */
     bool may_set_up_flow;       /* True ordinarily; false if the actions must
                                  * be reassessed for every packet. */
@@ -2614,7 +2609,7 @@ add_output_action(struct action_xlate_ctx *ctx, uint16_t port)
          */
     }
 
-    odp_actions_add(ctx->out, ODPAT_OUTPUT)->output.port = port;
+    nl_msg_put_u32(ctx->odp_actions, ODPAT_OUTPUT, port);
     ctx->nf_output_iface = port;
 }
 
@@ -2654,14 +2649,14 @@ xlate_table_action(struct action_xlate_ctx *ctx, uint16_t in_port)
 
 static void
 flood_packets(struct ofproto *ofproto, uint16_t odp_in_port, uint32_t mask,
-              uint16_t *nf_output_iface, struct odp_actions *actions)
+              uint16_t *nf_output_iface, struct ofpbuf *odp_actions)
 {
     struct ofport *ofport;
 
     HMAP_FOR_EACH (ofport, hmap_node, &ofproto->ports) {
         uint16_t odp_port = ofport->odp_port;
         if (odp_port != odp_in_port && !(ofport->opp.config & mask)) {
-            odp_actions_add(actions, ODPAT_OUTPUT)->output.port = odp_port;
+            nl_msg_put_u32(odp_actions, ODPAT_OUTPUT, odp_port);
         }
     }
     *nf_output_iface = NF_OUT_FLOOD;
@@ -2685,7 +2680,7 @@ xlate_output_action__(struct action_xlate_ctx *ctx,
         break;
     case OFPP_NORMAL:
         if (!ctx->ofproto->ofhooks->normal_cb(&ctx->flow, ctx->packet,
-                                              ctx->out, &ctx->tags,
+                                              ctx->odp_actions, &ctx->tags,
                                               &ctx->nf_output_iface,
                                               ctx->ofproto->aux)) {
             COVERAGE_INC(ofproto_uninstallable);
@@ -2694,14 +2689,14 @@ xlate_output_action__(struct action_xlate_ctx *ctx,
         break;
     case OFPP_FLOOD:
         flood_packets(ctx->ofproto, ctx->flow.in_port, OFPPC_NO_FLOOD,
-                      &ctx->nf_output_iface, ctx->out);
+                      &ctx->nf_output_iface, ctx->odp_actions);
         break;
     case OFPP_ALL:
         flood_packets(ctx->ofproto, ctx->flow.in_port, 0,
-                      &ctx->nf_output_iface, ctx->out);
+                      &ctx->nf_output_iface, ctx->odp_actions);
         break;
     case OFPP_CONTROLLER:
-        add_controller_action(ctx->out, max_len);
+        nl_msg_put_u32(ctx->odp_actions, ODPAT_CONTROLLER, max_len);
         break;
     case OFPP_LOCAL:
         add_output_action(ctx, ODPP_LOCAL);
@@ -2738,9 +2733,18 @@ xlate_output_action(struct action_xlate_ctx *ctx,
 static void
 remove_pop_action(struct action_xlate_ctx *ctx)
 {
-    size_t n = ctx->out->n_actions;
-    if (n > 0 && ctx->out->actions[n - 1].type == ODPAT_POP_PRIORITY) {
-        ctx->out->n_actions--;
+    if (ctx->odp_actions->size == ctx->last_pop_priority) {
+        ctx->odp_actions->size -= NLA_ALIGN(NLA_HDRLEN);
+        ctx->last_pop_priority = -1;
+    }
+}
+
+static void
+add_pop_action(struct action_xlate_ctx *ctx)
+{
+    if (ctx->odp_actions->size != ctx->last_pop_priority) {
+        nl_msg_put_flag(ctx->odp_actions, ODPAT_POP_PRIORITY);
+        ctx->last_pop_priority = ctx->odp_actions->size;
     }
 }
 
@@ -2770,10 +2774,9 @@ xlate_enqueue_action(struct action_xlate_ctx *ctx,
 
     /* Add ODP actions. */
     remove_pop_action(ctx);
-    odp_actions_add(ctx->out, ODPAT_SET_PRIORITY)->priority.priority
-        = priority;
+    nl_msg_put_u32(ctx->odp_actions, ODPAT_SET_PRIORITY, priority);
     add_output_action(ctx, odp_port);
-    odp_actions_add(ctx->out, ODPAT_POP_PRIORITY);
+    add_pop_action(ctx);
 
     /* Update NetFlow output port. */
     if (ctx->nf_output_iface == NF_OUT_DROP) {
@@ -2799,8 +2802,7 @@ xlate_set_queue_action(struct action_xlate_ctx *ctx,
     }
 
     remove_pop_action(ctx);
-    odp_actions_add(ctx->out, ODPAT_SET_PRIORITY)->priority.priority
-        = priority;
+    nl_msg_put_u32(ctx->odp_actions, ODPAT_SET_PRIORITY, priority);
 }
 
 static void
@@ -2808,10 +2810,10 @@ xlate_set_dl_tci(struct action_xlate_ctx *ctx)
 {
     ovs_be16 tci = ctx->flow.vlan_tci;
     if (!(tci & htons(VLAN_CFI))) {
-        odp_actions_add(ctx->out, ODPAT_STRIP_VLAN);
+        nl_msg_put_flag(ctx->odp_actions, ODPAT_STRIP_VLAN);
     } else {
-        union odp_action *oa = odp_actions_add(ctx->out, ODPAT_SET_DL_TCI);
-        oa->dl_tci.tci = tci & ~htons(VLAN_CFI);
+        nl_msg_put_be16(ctx->odp_actions, ODPAT_SET_DL_TCI,
+                        tci & ~htons(VLAN_CFI));
     }
 }
 
@@ -2835,7 +2837,6 @@ xlate_nicira_action(struct action_xlate_ctx *ctx,
     const struct nx_action_resubmit *nar;
     const struct nx_action_set_tunnel *nast;
     const struct nx_action_set_queue *nasq;
-    union odp_action *oa;
     int subtype = ntohs(nah->subtype);
 
     assert(nah->vendor == htonl(NX_VENDOR_ID));
@@ -2847,13 +2848,12 @@ xlate_nicira_action(struct action_xlate_ctx *ctx,
 
     case NXAST_SET_TUNNEL:
         nast = (const struct nx_action_set_tunnel *) nah;
-        oa = odp_actions_add(ctx->out, ODPAT_SET_TUNNEL);
-        ctx->flow.tun_id = oa->tunnel.tun_id = nast->tun_id;
+        nl_msg_put_be32(ctx->odp_actions, ODPAT_SET_TUNNEL, nast->tun_id);
         break;
 
     case NXAST_DROP_SPOOFED_ARP:
         if (ctx->flow.dl_type == htons(ETH_TYPE_ARP)) {
-            odp_actions_add(ctx->out, ODPAT_DROP_SPOOFED_ARP);
+            nl_msg_put_flag(ctx->odp_actions, ODPAT_DROP_SPOOFED_ARP);
         }
         break;
 
@@ -2863,7 +2863,7 @@ xlate_nicira_action(struct action_xlate_ctx *ctx,
         break;
 
     case NXAST_POP_QUEUE:
-        odp_actions_add(ctx->out, ODPAT_POP_PRIORITY);
+        add_pop_action(ctx);
         break;
 
     case NXAST_REG_MOVE:
@@ -2904,8 +2904,8 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
     }
 
     for (ia = actions_first(&iter, in, n_in); ia; ia = actions_next(&iter)) {
+        const struct ofp_action_dl_addr *oada;
         uint16_t type = ntohs(ia->type);
-        union odp_action *oa;
 
         switch (type) {
         case OFPAT_OUTPUT:
@@ -2931,44 +2931,47 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
             break;
 
         case OFPAT_SET_DL_SRC:
-            oa = odp_actions_add(ctx->out, ODPAT_SET_DL_SRC);
-            memcpy(oa->dl_addr.dl_addr,
-                   ((struct ofp_action_dl_addr *) ia)->dl_addr, ETH_ADDR_LEN);
-            memcpy(ctx->flow.dl_src,
-                   ((struct ofp_action_dl_addr *) ia)->dl_addr, ETH_ADDR_LEN);
+            oada = ((struct ofp_action_dl_addr *) ia);
+            nl_msg_put_unspec(ctx->odp_actions, ODPAT_SET_DL_SRC,
+                              oada->dl_addr, ETH_ADDR_LEN);
+            memcpy(ctx->flow.dl_src, oada->dl_addr, ETH_ADDR_LEN);
             break;
 
         case OFPAT_SET_DL_DST:
-            oa = odp_actions_add(ctx->out, ODPAT_SET_DL_DST);
-            memcpy(oa->dl_addr.dl_addr,
-                   ((struct ofp_action_dl_addr *) ia)->dl_addr, ETH_ADDR_LEN);
-            memcpy(ctx->flow.dl_dst,
-                   ((struct ofp_action_dl_addr *) ia)->dl_addr, ETH_ADDR_LEN);
+            oada = ((struct ofp_action_dl_addr *) ia);
+            nl_msg_put_unspec(ctx->odp_actions, ODPAT_SET_DL_DST,
+                              oada->dl_addr, ETH_ADDR_LEN);
+            memcpy(ctx->flow.dl_dst, oada->dl_addr, ETH_ADDR_LEN);
             break;
 
         case OFPAT_SET_NW_SRC:
-            oa = odp_actions_add(ctx->out, ODPAT_SET_NW_SRC);
-            ctx->flow.nw_src = oa->nw_addr.nw_addr = ia->nw_addr.nw_addr;
+            nl_msg_put_be32(ctx->odp_actions, ODPAT_SET_NW_SRC,
+                            ia->nw_addr.nw_addr);
+            ctx->flow.nw_src = ia->nw_addr.nw_addr;
             break;
 
         case OFPAT_SET_NW_DST:
-            oa = odp_actions_add(ctx->out, ODPAT_SET_NW_DST);
-            ctx->flow.nw_dst = oa->nw_addr.nw_addr = ia->nw_addr.nw_addr;
+            nl_msg_put_be32(ctx->odp_actions, ODPAT_SET_NW_DST,
+                            ia->nw_addr.nw_addr);
+            ctx->flow.nw_dst = ia->nw_addr.nw_addr;
             break;
 
         case OFPAT_SET_NW_TOS:
-            oa = odp_actions_add(ctx->out, ODPAT_SET_NW_TOS);
-            ctx->flow.nw_tos = oa->nw_tos.nw_tos = ia->nw_tos.nw_tos;
+            nl_msg_put_u8(ctx->odp_actions, ODPAT_SET_NW_TOS,
+                          ia->nw_tos.nw_tos);
+            ctx->flow.nw_tos = ia->nw_tos.nw_tos;
             break;
 
         case OFPAT_SET_TP_SRC:
-            oa = odp_actions_add(ctx->out, ODPAT_SET_TP_SRC);
-            ctx->flow.tp_src = oa->tp_port.tp_port = ia->tp_port.tp_port;
+            nl_msg_put_be16(ctx->odp_actions, ODPAT_SET_TP_SRC,
+                            ia->tp_port.tp_port);
+            ctx->flow.tp_src = ia->tp_port.tp_port;
             break;
 
         case OFPAT_SET_TP_DST:
-            oa = odp_actions_add(ctx->out, ODPAT_SET_TP_DST);
-            ctx->flow.tp_dst = oa->tp_port.tp_port = ia->tp_port.tp_port;
+            nl_msg_put_be16(ctx->odp_actions, ODPAT_SET_TP_DST,
+                            ia->tp_port.tp_port);
+            ctx->flow.tp_dst = ia->tp_port.tp_port;
             break;
 
         case OFPAT_VENDOR:
@@ -2986,22 +2989,21 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
     }
 }
 
-static int
+static struct ofpbuf *
 xlate_actions(const union ofp_action *in, size_t n_in,
               const struct flow *flow, struct ofproto *ofproto,
-              const struct ofpbuf *packet,
-              struct odp_actions *out, tag_type *tags, bool *may_set_up_flow,
-              uint16_t *nf_output_iface)
+              const struct ofpbuf *packet, tag_type *tags,
+              bool *may_set_up_flow, uint16_t *nf_output_iface)
 {
     struct action_xlate_ctx ctx;
 
     COVERAGE_INC(ofproto_ofp2odp);
-    odp_actions_init(out);
     ctx.flow = *flow;
     ctx.recurse = 0;
     ctx.ofproto = ofproto;
     ctx.packet = packet;
-    ctx.out = out;
+    ctx.odp_actions = ofpbuf_new(512);
+    ctx.last_pop_priority = -1;
     ctx.tags = 0;
     ctx.may_set_up_flow = true;
     ctx.nf_output_iface = NF_OUT_DROP;
@@ -3010,7 +3012,8 @@ xlate_actions(const union ofp_action *in, size_t n_in,
 
     /* Check with in-band control to see if we're allowed to set up this
      * flow. */
-    if (!in_band_rule_check(ofproto->in_band, flow, out)) {
+    if (!in_band_rule_check(ofproto->in_band, flow, ctx.odp_actions->data,
+                            ctx.odp_actions->size)) {
         ctx.may_set_up_flow = false;
     }
 
@@ -3023,12 +3026,8 @@ xlate_actions(const union ofp_action *in, size_t n_in,
     if (nf_output_iface) {
         *nf_output_iface = ctx.nf_output_iface;
     }
-    if (odp_actions_overflow(out)) {
-        COVERAGE_INC(odp_overflow);
-        odp_actions_init(out);
-        return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_TOO_MANY);
-    }
-    return 0;
+
+    return ctx.odp_actions;
 }
 
 /* Checks whether 'ofconn' is a slave controller.  If so, returns an OpenFlow
@@ -3057,7 +3056,7 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh)
     struct ofp_packet_out *opo;
     struct ofpbuf payload, *buffer;
     union ofp_action *ofp_actions;
-    struct odp_actions odp_actions;
+    struct ofpbuf *odp_actions;
     struct ofpbuf request;
     struct flow flow;
     size_t n_ofp_actions;
@@ -3107,12 +3106,10 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh)
     }
 
     /* Send. */
-    error = xlate_actions(ofp_actions, n_ofp_actions, &flow, p, &payload,
-                          &odp_actions, NULL, NULL, NULL);
-    if (!error) {
-        dpif_execute(p->dpif, odp_actions.actions, odp_actions.n_actions,
-                     &payload);
-    }
+    odp_actions = xlate_actions(ofp_actions, n_ofp_actions, &flow, p, &payload,
+                                NULL, NULL, NULL);
+    dpif_execute(p->dpif, odp_actions->data, odp_actions->size, &payload);
+    ofpbuf_delete(odp_actions);
 
 exit:
     ofpbuf_delete(buffer);
@@ -4398,12 +4395,14 @@ handle_odp_miss_msg(struct ofproto *p, struct ofpbuf *packet)
     /* Check with in-band control to see if this packet should be sent
      * to the local port regardless of the flow table. */
     if (in_band_msg_in_hook(p->in_band, &flow, &payload)) {
-        union odp_action action;
+        struct ofpbuf odp_actions;
+        uint64_t buf[1];
+
+        ofpbuf_use_stack(&odp_actions, buf, sizeof buf);
+        nl_msg_put_u32(&odp_actions, ODPAT_OUTPUT, ODPP_LOCAL);
+        assert(odp_actions.base == buf);
 
-        memset(&action, 0, sizeof(action));
-        action.output.type = ODPAT_OUTPUT;
-        action.output.port = ODPP_LOCAL;
-        dpif_execute(p->dpif, &action, 1, &payload);
+        dpif_execute(p->dpif, odp_actions.data, odp_actions.size, &payload);
     }
 
     facet = facet_lookup_valid(p, &flow);
@@ -4991,7 +4990,7 @@ pick_fallback_dpid(void)
 
 static bool
 default_normal_ofhook_cb(const struct flow *flow, const struct ofpbuf *packet,
-                         struct odp_actions *actions, tag_type *tags,
+                         struct ofpbuf *odp_actions, tag_type *tags,
                          uint16_t *nf_output_iface, void *ofproto_)
 {
     struct ofproto *ofproto = ofproto_;
@@ -5022,9 +5021,9 @@ default_normal_ofhook_cb(const struct flow *flow, const struct ofpbuf *packet,
                                        NULL);
     if (out_port < 0) {
         flood_packets(ofproto, flow->in_port, OFPPC_NO_FLOOD,
-                      nf_output_iface, actions);
+                      nf_output_iface, odp_actions);
     } else if (out_port != flow->in_port) {
-        odp_actions_add(actions, ODPAT_OUTPUT)->output.port = out_port;
+        nl_msg_put_u32(odp_actions, ODPAT_OUTPUT, out_port);
         *nf_output_iface = out_port;
     } else {
         /* Drop. */
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index fd089c5..eeaeb6f 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -31,7 +31,7 @@ extern "C" {
 #endif
 
 struct cls_rule;
-struct odp_actions;
+struct nlattr;
 struct ofhooks;
 struct ofproto;
 struct svec;
@@ -136,10 +136,11 @@ void ofproto_flush_flows(struct ofproto *);
 /* Hooks for ovs-vswitchd. */
 struct ofhooks {
     bool (*normal_cb)(const struct flow *, const struct ofpbuf *packet,
-                      struct odp_actions *, tag_type *,
+                      struct ofpbuf *odp_actions, tag_type *,
                       uint16_t *nf_output_iface, void *aux);
     void (*account_flow_cb)(const struct flow *, tag_type tags,
-                            const union odp_action *, size_t n_actions,
+                            const struct nlattr *odp_actions,
+                            size_t actions_len,
                             unsigned long long int n_bytes, void *aux);
     void (*account_checkpoint_cb)(void *aux);
 };
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index bed50fa..8f2a2bc 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -478,11 +478,11 @@ do_dump_flows(int argc OVS_UNUSED, char *argv[])
     ds_init(&ds);
     for (i = 0; i < n_flows; i++) {
         struct odp_flow *f = &flows[i];
-        enum { MAX_ACTIONS = 4096 / sizeof(union odp_action) };
-        union odp_action actions[MAX_ACTIONS];
+        enum { MAX_ACTIONS = 4096 }; /* An arbitrary but large number. */
+        struct nlattr actions[MAX_ACTIONS];
 
         f->actions = actions;
-        f->n_actions = MAX_ACTIONS;
+        f->actions_len = sizeof actions;
         if (!dpif_flow_get(dpif, f)) {
             ds_clear(&ds);
             format_odp_flow(&ds, f);
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index c098436..8ade873 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -45,6 +45,7 @@
 #include "list.h"
 #include "mac-learning.h"
 #include "netdev.h"
+#include "netlink.h"
 #include "odp-util.h"
 #include "ofp-print.h"
 #include "ofpbuf.h"
@@ -2424,7 +2425,7 @@ print_dsts(const struct dst *dsts, size_t n)
 static void
 compose_actions(struct bridge *br, const struct flow *flow, uint16_t vlan,
                 const struct port *in_port, const struct port *out_port,
-                tag_type *tags, struct odp_actions *actions,
+                tag_type *tags, struct ofpbuf *actions,
                 uint16_t *nf_output_iface)
 {
     struct dst dsts[DP_MAX_PORTS * (MAX_MIRRORS + 1)];
@@ -2440,19 +2441,18 @@ compose_actions(struct bridge *br, const struct flow *flow, uint16_t vlan,
         cur_vlan = OFP_VLAN_NONE;
     }
     for (p = dsts; p < &dsts[n_dsts]; p++) {
-        union odp_action *a;
         if (p->vlan != cur_vlan) {
             if (p->vlan == OFP_VLAN_NONE) {
-                odp_actions_add(actions, ODPAT_STRIP_VLAN);
+                nl_msg_put_flag(actions, ODPAT_STRIP_VLAN);
             } else {
-                a = odp_actions_add(actions, ODPAT_SET_DL_TCI);
-                a->dl_tci.tci = htons(p->vlan & VLAN_VID_MASK);
-                a->dl_tci.tci |= flow->vlan_tci & htons(VLAN_PCP_MASK);
+                ovs_be16 tci;
+                tci = htons(p->vlan & VLAN_VID_MASK);
+                tci |= flow->vlan_tci & htons(VLAN_PCP_MASK);
+                nl_msg_put_be16(actions, ODPAT_SET_DL_TCI, tci);
             }
             cur_vlan = p->vlan;
         }
-        a = odp_actions_add(actions, ODPAT_OUTPUT);
-        a->output.port = p->dp_ifidx;
+        nl_msg_put_u32(actions, ODPAT_OUTPUT, p->dp_ifidx);
     }
 }
 
@@ -2645,7 +2645,7 @@ is_admissible(struct bridge *br, const struct flow *flow, bool have_packet,
  * not at all, if 'packet' was NULL. */
 static bool
 process_flow(struct bridge *br, const struct flow *flow,
-             const struct ofpbuf *packet, struct odp_actions *actions,
+             const struct ofpbuf *packet, struct ofpbuf *actions,
              tag_type *tags, uint16_t *nf_output_iface)
 {
     struct port *in_port;
@@ -2696,7 +2696,7 @@ done:
 
 static bool
 bridge_normal_ofhook_cb(const struct flow *flow, const struct ofpbuf *packet,
-                        struct odp_actions *actions, tag_type *tags,
+                        struct ofpbuf *actions, tag_type *tags,
                         uint16_t *nf_output_iface, void *br_)
 {
     struct iface *iface;
@@ -2718,14 +2718,15 @@ bridge_normal_ofhook_cb(const struct flow *flow, const struct ofpbuf *packet,
 
 static void
 bridge_account_flow_ofhook_cb(const struct flow *flow, tag_type tags,
-                              const union odp_action *actions,
-                              size_t n_actions, unsigned long long int n_bytes,
-                              void *br_)
+                              const struct nlattr *actions,
+                              unsigned int actions_len,
+                              unsigned long long int n_bytes, void *br_)
 {
     struct bridge *br = br_;
-    const union odp_action *a;
+    const struct nlattr *a;
     struct port *in_port;
     tag_type dummy = 0;
+    unsigned int left;
     int vlan;
 
     /* Feed information from the active flows back into the learning table to
@@ -2743,9 +2744,9 @@ bridge_account_flow_ofhook_cb(const struct flow *flow, tag_type tags,
     if (!br->has_bonded_ports) {
         return;
     }
-    for (a = actions; a < &actions[n_actions]; a++) {
-        if (a->type == ODPAT_OUTPUT) {
-            struct port *out_port = port_from_dp_ifidx(br, a->output.port);
+    NL_ATTR_FOR_EACH_UNSAFE (a, left, actions, actions_len) {
+        if (nl_attr_type(a) == ODPAT_OUTPUT) {
+            struct port *out_port = port_from_dp_ifidx(br, nl_attr_get_u32(a));
             if (out_port && out_port->n_ifaces >= 2) {
                 uint16_t vlan = (flow->vlan_tci
                                  ? vlan_tci_to_vid(flow->vlan_tci)
-- 
1.7.1





More information about the dev mailing list