[ovs-dev] [PATCH v3] datapath: Refactor actions in terms of match fields.

Pravin B Shelar pshelar at nicira.com
Thu Oct 20 23:55:44 UTC 2011


changes:
        - datapath validation for SET and PUSH actions.
        - fixed comments.
        - cleanup commit_odp_* functions.
v2:
Fixed according to comments from Jesse.

--8<--------------------------cut here-------------------------->8--
Almost all current actions can be expressed in the form of
push/pop/set <field>, where field is one of the match fields. We can
create three base actions and take a field. This has both a nice
symmetry and avoids inconsistencies where we can match on the vlan
TPID but not set it.
Following patch converts all actions to this new format.

Signed-off-by: Pravin B Shelar <pshelar at nicira.com>
Bug #7115
---
 datapath/actions.c          |  206 +++++++++++++++++++++------------------
 datapath/datapath.c         |  131 +++++++++++++++++++-------
 datapath/flow.c             |   10 +-
 datapath/flow.h             |    1 +
 include/linux/openvswitch.h |   38 +++++--
 lib/dpif-netdev.c           |  222 ++++++++++++++++++++++++-------------------
 lib/odp-util.c              |   76 +++++----------
 lib/odp-util.h              |    3 -
 ofproto/ofproto-dpif.c      |  205 ++++++++++++++++++++++++++++-----------
 tests/ofproto-dpif.at       |   92 +++++++++---------
 10 files changed, 587 insertions(+), 397 deletions(-)

diff --git a/datapath/actions.c b/datapath/actions.c
index a28e986..70f48ea 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -97,7 +97,7 @@ static int pop_vlan(struct sk_buff *skb)
 	return 0;
 }
 
-static int push_vlan(struct sk_buff *skb, __be16 new_tci)
+static int push_vlan(struct sk_buff *skb, const struct ovs_key_8021q *q_key)
 {
 	if (unlikely(vlan_tx_tag_present(skb))) {
 		u16 current_tag;
@@ -113,117 +113,127 @@ static int push_vlan(struct sk_buff *skb, __be16 new_tci)
 					+ ETH_HLEN, VLAN_HLEN, 0));
 
 	}
-	__vlan_hwaccel_put_tag(skb, ntohs(new_tci));
+	__vlan_hwaccel_put_tag(skb, ntohs(q_key->q_tci));
 	return 0;
 }
 
-static bool is_ip(struct sk_buff *skb)
+static int set_eth_addr(struct sk_buff *skb,
+			const struct ovs_key_ethernet *eth_key)
 {
-	return (OVS_CB(skb)->flow->key.eth.type == htons(ETH_P_IP) &&
-		skb->transport_header > skb->network_header);
+	int err;
+	err = make_writable(skb, ETH_HLEN);
+	if (unlikely(err))
+		return err;
+
+	memcpy(eth_hdr(skb)->h_source, eth_key->eth_src, ETH_HLEN);
+	memcpy(eth_hdr(skb)->h_dest, eth_key->eth_dst, ETH_HLEN);
+
+	return 0;
 }
 
-static __sum16 *get_l4_checksum(struct sk_buff *skb)
+static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh,
+				__be32 *addr, __be32 new_addr)
 {
-	u8 nw_proto = OVS_CB(skb)->flow->key.ip.proto;
 	int transport_len = skb->len - skb_transport_offset(skb);
-	if (nw_proto == IPPROTO_TCP) {
+
+	if (nh->protocol == IPPROTO_TCP) {
 		if (likely(transport_len >= sizeof(struct tcphdr)))
-			return &tcp_hdr(skb)->check;
-	} else if (nw_proto == IPPROTO_UDP) {
+			inet_proto_csum_replace4(&tcp_hdr(skb)->check, skb,
+						 *addr, new_addr, 1);
+	} else if (nh->protocol == IPPROTO_UDP) {
 		if (likely(transport_len >= sizeof(struct udphdr)))
-			return &udp_hdr(skb)->check;
+			inet_proto_csum_replace4(&udp_hdr(skb)->check, skb,
+						 *addr, new_addr, 1);
 	}
-	return NULL;
+
+	csum_replace4(&nh->check, *addr, new_addr);
+	skb_clear_rxhash(skb);
+	*addr = new_addr;
 }
 
-static int set_nw_addr(struct sk_buff *skb, const struct nlattr *a)
+static void set_ip_tos(struct sk_buff *skb, struct iphdr *nh, u8 new_tos)
+{
+	u8 old, new;
+
+	/* Set the DSCP bits and preserve the ECN bits. */
+	old = nh->tos;
+	new = new_tos | (nh->tos & INET_ECN_MASK);
+	csum_replace4(&nh->check, (__force __be32)old,
+				  (__force __be32)new);
+	nh->tos = new;
+}
+
+static int set_ipv4(struct sk_buff *skb, const struct ovs_key_ipv4 *ipv4_key)
 {
-	__be32 new_nwaddr = nla_get_be32(a);
 	struct iphdr *nh;
-	__sum16 *check;
-	__be32 *nwaddr;
 	int err;
 
-	if (unlikely(!is_ip(skb)))
-		return 0;
-
 	err = make_writable(skb, skb_network_offset(skb) +
 				 sizeof(struct iphdr));
 	if (unlikely(err))
 		return err;
 
 	nh = ip_hdr(skb);
-	nwaddr = nla_type(a) == OVS_ACTION_ATTR_SET_NW_SRC ? &nh->saddr : &nh->daddr;
 
-	check = get_l4_checksum(skb);
-	if (likely(check))
-		inet_proto_csum_replace4(check, skb, *nwaddr, new_nwaddr, 1);
-	csum_replace4(&nh->check, *nwaddr, new_nwaddr);
+	if (ipv4_key->ipv4_src != nh->saddr)
+		set_ip_addr(skb, nh, &nh->saddr, ipv4_key->ipv4_src);
 
-	skb_clear_rxhash(skb);
+	if (ipv4_key->ipv4_dst != nh->daddr)
+		set_ip_addr(skb, nh, &nh->daddr, ipv4_key->ipv4_dst);
 
-	*nwaddr = new_nwaddr;
+	if (ipv4_key->ipv4_tos != nh->tos)
+		set_ip_tos(skb, nh, ipv4_key->ipv4_tos);
 
 	return 0;
 }
 
-static int set_nw_tos(struct sk_buff *skb, u8 nw_tos)
+/* Must follow make_writable() since that can move the skb data. */
+static void set_tp_port(struct sk_buff *skb, __be16 *port,
+			 __be16 new_port, __sum16 *check)
 {
-	struct iphdr *nh = ip_hdr(skb);
-	u8 old, new;
-	int err;
+	inet_proto_csum_replace2(check, skb, *port, new_port, 0);
+	*port = new_port;
+	skb_clear_rxhash(skb);
+}
 
-	if (unlikely(!is_ip(skb)))
-		return 0;
+static int set_udp_port(struct sk_buff *skb,
+			const struct ovs_key_udp *udp_port_key)
+{
+	struct udphdr *uh;
+	int err;
 
-	err = make_writable(skb, skb_network_offset(skb) +
-				 sizeof(struct iphdr));
+	err = make_writable(skb, skb_transport_offset(skb) +
+				 sizeof(struct udphdr));
 	if (unlikely(err))
 		return err;
 
-	/* Set the DSCP bits and preserve the ECN bits. */
-	old = nh->tos;
-	new = nw_tos | (nh->tos & INET_ECN_MASK);
-	csum_replace4(&nh->check, (__force __be32)old,
-				  (__force __be32)new);
-	nh->tos = new;
+	uh = udp_hdr(skb);
+	if (udp_port_key->udp_src != uh->source)
+		set_tp_port(skb, &uh->source, udp_port_key->udp_src, &uh->check);
+
+	if (udp_port_key->udp_dst != uh->dest)
+		set_tp_port(skb, &uh->dest, udp_port_key->udp_dst, &uh->check);
 
 	return 0;
 }
 
-static int set_tp_port(struct sk_buff *skb, const struct nlattr *a)
+static int set_tcp_port(struct sk_buff *skb,
+			const struct ovs_key_tcp *tcp_port_key)
 {
-	struct udphdr *th;
-	__sum16 *check;
-	__be16 *port;
+	struct tcphdr *th;
 	int err;
 
-	if (unlikely(!is_ip(skb)))
-		return 0;
-
 	err = make_writable(skb, skb_transport_offset(skb) +
 				 sizeof(struct tcphdr));
 	if (unlikely(err))
 		return err;
 
-	/* Must follow make_writable() since that can move the skb data. */
-	check = get_l4_checksum(skb);
-	if (unlikely(!check))
-		return 0;
+	th = tcp_hdr(skb);
+	if (tcp_port_key->tcp_src != th->source)
+		set_tp_port(skb, &th->source, tcp_port_key->tcp_src, &th->check);
 
-	/*
-	 * Update port and checksum.
-	 *
-	 * This is OK because source and destination port numbers are at the
-	 * same offsets in both UDP and TCP headers, and get_l4_checksum() only
-	 * supports those protocols.
-	 */
-	th = udp_hdr(skb);
-	port = nla_type(a) == OVS_ACTION_ATTR_SET_TP_SRC ? &th->source : &th->dest;
-	inet_proto_csum_replace2(check, skb, *port, nla_get_be16(a), 0);
-	*port = nla_get_be16(a);
-	skb_clear_rxhash(skb);
+	if (tcp_port_key->tcp_dst != th->dest)
+		set_tp_port(skb, &th->dest, tcp_port_key->tcp_dst, &th->check);
 
 	return 0;
 }
@@ -298,6 +308,36 @@ static int sample(struct datapath *dp, struct sk_buff *skb,
 						 nla_len(acts_list), true);
 }
 
+static int execute_set_action(struct sk_buff *skb,
+				 const struct nlattr *nested_attr)
+{
+	int err;
+
+	switch (nla_type(nested_attr)) {
+	case OVS_KEY_ATTR_TUN_ID:
+		OVS_CB(skb)->tun_id = nla_get_be64(nested_attr);
+		err = 0;
+		break;
+
+	case OVS_KEY_ATTR_ETHERNET:
+		err = set_eth_addr(skb, nla_data(nested_attr));
+		break;
+
+	case OVS_KEY_ATTR_IPV4:
+		err = set_ipv4(skb, nla_data(nested_attr));
+		break;
+
+	case OVS_KEY_ATTR_TCP:
+		err = set_tcp_port(skb, nla_data(nested_attr));
+		break;
+
+	case OVS_KEY_ATTR_UDP:
+		err = set_udp_port(skb, nla_data(nested_attr));
+		break;
+	}
+	return err;
+}
+
 /* Execute a list of actions against 'skb'. */
 static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 			const struct nlattr *attr, int len, bool keep_skb)
@@ -329,44 +369,20 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 			output_userspace(dp, skb, a);
 			break;
 
-		case OVS_ACTION_ATTR_SET_TUNNEL:
-			OVS_CB(skb)->tun_id = nla_get_be64(a);
-			break;
-
-		case OVS_ACTION_ATTR_PUSH_VLAN:
-			err = push_vlan(skb, nla_get_be16(a));
-			if (unlikely(err)) /* skb already freed */
+		case OVS_ACTION_ATTR_PUSH:
+			/* Only supported push action is on vlan tag. */
+			err = push_vlan(skb, nla_data(nla_data(a)));
+			if (unlikely(err)) /* skb already freed. */
 				return err;
 			break;
 
-		case OVS_ACTION_ATTR_POP_VLAN:
+		case OVS_ACTION_ATTR_POP:
+			/* Only supported pop action is on vlan tag. */
 			err = pop_vlan(skb);
 			break;
 
-		case OVS_ACTION_ATTR_SET_DL_SRC:
-			err = make_writable(skb, ETH_HLEN);
-			if (likely(!err))
-				memcpy(eth_hdr(skb)->h_source, nla_data(a), ETH_ALEN);
-			break;
-
-		case OVS_ACTION_ATTR_SET_DL_DST:
-			err = make_writable(skb, ETH_HLEN);
-			if (likely(!err))
-				memcpy(eth_hdr(skb)->h_dest, nla_data(a), ETH_ALEN);
-			break;
-
-		case OVS_ACTION_ATTR_SET_NW_SRC:
-		case OVS_ACTION_ATTR_SET_NW_DST:
-			err = set_nw_addr(skb, a);
-			break;
-
-		case OVS_ACTION_ATTR_SET_NW_TOS:
-			err = set_nw_tos(skb, nla_get_u8(a));
-			break;
-
-		case OVS_ACTION_ATTR_SET_TP_SRC:
-		case OVS_ACTION_ATTR_SET_TP_DST:
-			err = set_tp_port(skb, a);
+		case OVS_ACTION_ATTR_SET:
+			err = execute_set_action(skb, nla_data(a));
 			break;
 
 		case OVS_ACTION_ATTR_SET_PRIORITY:
diff --git a/datapath/datapath.c b/datapath/datapath.c
index cd29482..35612b0 100644
--- a/datapath/datapath.c
+++ b/datapath/datapath.c
@@ -494,9 +494,11 @@ static int flush_flows(int dp_ifindex)
 	return 0;
 }
 
-static int validate_actions(const struct nlattr *attr, int depth);
+static int validate_actions(const struct nlattr *attr,
+				const struct sw_flow_key *key, int depth);
 
-static int validate_sample(const struct nlattr *attr, int depth)
+static int validate_sample(const struct nlattr *attr,
+				const struct sw_flow_key *key, int depth)
 {
 	static const struct nla_policy sample_policy[OVS_SAMPLE_ATTR_MAX + 1] =
 	{
@@ -515,7 +517,77 @@ static int validate_sample(const struct nlattr *attr, int depth)
 	if (!a[OVS_SAMPLE_ATTR_ACTIONS])
 		return -EINVAL;
 
-	return validate_actions(a[OVS_SAMPLE_ATTR_ACTIONS], (depth + 1));
+	return validate_actions(a[OVS_SAMPLE_ATTR_ACTIONS], key, (depth + 1));
+}
+
+static int validate_action_key(int act_type, const struct nlattr *ovs_key, int act_len,
+		const struct sw_flow_key *flow_key)
+{
+	int key_type = nla_type(ovs_key);
+
+	/* There can be only one key in a action */
+	if (nla_total_size(nla_len(ovs_key)) != act_len)
+		return -EINVAL;
+
+	if (key_type > OVS_KEY_ATTR_MAX ||
+	    nla_len(ovs_key) != ovs_key_lens[key_type])
+		return -EINVAL;
+
+#define ACTION(act, key)	((act << 8) | key)
+
+	switch(ACTION(act_type, key_type)) {
+	const struct ovs_key_ipv4 *ipv4_key;
+	const struct ovs_key_8021q *q_key;
+
+	case ACTION(OVS_ACTION_ATTR_SET, OVS_KEY_ATTR_TUN_ID):
+	case ACTION(OVS_ACTION_ATTR_SET, OVS_KEY_ATTR_ETHERNET):
+			break;
+
+	case ACTION(OVS_ACTION_ATTR_PUSH, OVS_KEY_ATTR_8021Q):
+		q_key = nla_data(ovs_key);
+		if (q_key->q_tpid != htons(ETH_P_8021Q))
+			return -EINVAL;
+
+		if (q_key->q_tci & htons(VLAN_TAG_PRESENT))
+			return -EINVAL;
+		break;
+
+	case ACTION(OVS_ACTION_ATTR_SET, OVS_KEY_ATTR_IPV4):
+		if (flow_key->eth.type != htons(ETH_P_IP))
+			return -EINVAL;
+
+		if (!flow_key->ipv4.addr.src || !flow_key->ipv4.addr.dst)
+			return -EINVAL;
+
+		ipv4_key = nla_data(ovs_key);
+		if (ipv4_key->ipv4_proto != flow_key->ip.proto)
+			return -EINVAL;
+
+		if (ipv4_key->ipv4_tos & INET_ECN_MASK)
+			return -EINVAL;
+		break;
+
+	case ACTION(OVS_ACTION_ATTR_SET, OVS_KEY_ATTR_TCP):
+		if (flow_key->ip.proto != IPPROTO_TCP)
+			return -EINVAL;
+
+		if (!flow_key->ipv4.tp.src || !flow_key->ipv4.tp.dst)
+			return -EINVAL;
+
+		break;
+
+	case ACTION(OVS_ACTION_ATTR_SET, OVS_KEY_ATTR_UDP):
+		if (flow_key->ip.proto != IPPROTO_UDP)
+			return -EINVAL;
+
+		if (!flow_key->ipv4.tp.src || !flow_key->ipv4.tp.dst)
+			return -EINVAL;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
 }
 
 static int validate_userspace(const struct nlattr *attr)
@@ -538,7 +610,8 @@ static int validate_userspace(const struct nlattr *attr)
 	return 0;
 }
 
-static int validate_actions(const struct nlattr *attr, int depth)
+static int validate_actions(const struct nlattr *attr,
+				const struct sw_flow_key *key,  int depth)
 {
 	const struct nlattr *a;
 	int rem, err;
@@ -551,16 +624,9 @@ static int validate_actions(const struct nlattr *attr, int depth)
 		static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = {
 			[OVS_ACTION_ATTR_OUTPUT] = 4,
 			[OVS_ACTION_ATTR_USERSPACE] = (u32)-1,
-			[OVS_ACTION_ATTR_PUSH_VLAN] = 2,
-			[OVS_ACTION_ATTR_POP_VLAN] = 0,
-			[OVS_ACTION_ATTR_SET_DL_SRC] = ETH_ALEN,
-			[OVS_ACTION_ATTR_SET_DL_DST] = ETH_ALEN,
-			[OVS_ACTION_ATTR_SET_NW_SRC] = 4,
-			[OVS_ACTION_ATTR_SET_NW_DST] = 4,
-			[OVS_ACTION_ATTR_SET_NW_TOS] = 1,
-			[OVS_ACTION_ATTR_SET_TP_SRC] = 2,
-			[OVS_ACTION_ATTR_SET_TP_DST] = 2,
-			[OVS_ACTION_ATTR_SET_TUNNEL] = 8,
+			[OVS_ACTION_ATTR_PUSH] = (u32)-1,
+			[OVS_ACTION_ATTR_POP] = 2,
+			[OVS_ACTION_ATTR_SET] = (u32)-1,
 			[OVS_ACTION_ATTR_SET_PRIORITY] = 4,
 			[OVS_ACTION_ATTR_POP_PRIORITY] = 0,
 			[OVS_ACTION_ATTR_SAMPLE] = (u32)-1
@@ -576,14 +642,6 @@ static int validate_actions(const struct nlattr *attr, int depth)
 		case OVS_ACTION_ATTR_UNSPEC:
 			return -EINVAL;
 
-		case OVS_ACTION_ATTR_POP_VLAN:
-		case OVS_ACTION_ATTR_SET_DL_SRC:
-		case OVS_ACTION_ATTR_SET_DL_DST:
-		case OVS_ACTION_ATTR_SET_NW_SRC:
-		case OVS_ACTION_ATTR_SET_NW_DST:
-		case OVS_ACTION_ATTR_SET_TP_SRC:
-		case OVS_ACTION_ATTR_SET_TP_DST:
-		case OVS_ACTION_ATTR_SET_TUNNEL:
 		case OVS_ACTION_ATTR_SET_PRIORITY:
 		case OVS_ACTION_ATTR_POP_PRIORITY:
 			/* No validation needed. */
@@ -600,24 +658,28 @@ static int validate_actions(const struct nlattr *attr, int depth)
 				return -EINVAL;
 			break;
 
-		case OVS_ACTION_ATTR_PUSH_VLAN:
-			if (nla_get_be16(a) & htons(VLAN_CFI_MASK))
+
+		case OVS_ACTION_ATTR_POP:
+			if (nla_get_u16(a) != OVS_KEY_ATTR_8021Q)
 				return -EINVAL;
 			break;
 
-		case OVS_ACTION_ATTR_SET_NW_TOS:
-			if (nla_get_u8(a) & INET_ECN_MASK)
-				return -EINVAL;
+		case OVS_ACTION_ATTR_SET:
+		case OVS_ACTION_ATTR_PUSH:
+			err = validate_action_key(type, nla_data(a),
+						 nla_len(a), key);
+			if (err)
+				return err;
 			break;
 
 		case OVS_ACTION_ATTR_SAMPLE:
-			err = validate_sample(a, depth);
+			err = validate_sample(a, key, depth);
 			if (err)
 				return err;
 			break;
 
 		default:
-			return -EOPNOTSUPP;
+			return -EINVAL;
 		}
 	}
 
@@ -626,6 +688,7 @@ static int validate_actions(const struct nlattr *attr, int depth)
 
 	return 0;
 }
+
 static void clear_stats(struct sw_flow *flow)
 {
 	flow->used = 0;
@@ -654,10 +717,6 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
 	    nla_len(a[OVS_PACKET_ATTR_PACKET]) < ETH_HLEN)
 		goto err;
 
-	err = validate_actions(a[OVS_PACKET_ATTR_ACTIONS], 0);
-	if (err)
-		goto err;
-
 	len = nla_len(a[OVS_PACKET_ATTR_PACKET]);
 	packet = __dev_alloc_skb(NET_IP_ALIGN + len, GFP_KERNEL);
 	err = -ENOMEM;
@@ -694,6 +753,10 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
 	if (err)
 		goto err_flow_put;
 
+	err = validate_actions(a[OVS_PACKET_ATTR_ACTIONS], &flow->key, 0);
+	if (err)
+		goto err_flow_put;
+
 	flow->hash = flow_hash(&flow->key, key_len);
 
 	acts = flow_actions_alloc(a[OVS_PACKET_ATTR_ACTIONS]);
@@ -914,7 +977,7 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
 
 	/* Validate actions. */
 	if (a[OVS_FLOW_ATTR_ACTIONS]) {
-		error = validate_actions(a[OVS_FLOW_ATTR_ACTIONS], 0);
+		error = validate_actions(a[OVS_FLOW_ATTR_ACTIONS], &key,  0);
 		if (error)
 			goto error;
 	} else if (info->genlhdr->cmd == OVS_FLOW_CMD_NEW) {
diff --git a/datapath/flow.c b/datapath/flow.c
index 7322295..7b9cb61 100644
--- a/datapath/flow.c
+++ b/datapath/flow.c
@@ -769,7 +769,7 @@ void flow_tbl_remove(struct flow_table *table, struct sw_flow *flow)
 }
 
 /* The size of the argument for each %OVS_KEY_ATTR_* Netlink attribute.  */
-static const u32 key_lens[OVS_KEY_ATTR_MAX + 1] = {
+const u32 ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = {
 	[OVS_KEY_ATTR_TUN_ID] = 8,
 	[OVS_KEY_ATTR_IN_PORT] = 4,
 	[OVS_KEY_ATTR_ETHERNET] = sizeof(struct ovs_key_ethernet),
@@ -827,8 +827,8 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, int *key_lenp,
 
                 int type = nla_type(nla);
 
-                if (type > OVS_KEY_ATTR_MAX || nla_len(nla) != key_lens[type])
-                        goto invalid;
+		if (type > OVS_KEY_ATTR_MAX || nla_len(nla) != ovs_key_lens[type])
+			goto invalid;
 
 #define TRANSITION(PREV_TYPE, TYPE) (((PREV_TYPE) << 16) | (TYPE))
 		switch (TRANSITION(prev_type, type)) {
@@ -1069,8 +1069,8 @@ int flow_metadata_from_nlattrs(u16 *in_port, __be64 *tun_id,
 	nla_for_each_nested(nla, attr, rem) {
                 int type = nla_type(nla);
 
-                if (type > OVS_KEY_ATTR_MAX || nla_len(nla) != key_lens[type])
-                        return -EINVAL;
+		if (type > OVS_KEY_ATTR_MAX || nla_len(nla) != ovs_key_lens[type])
+			return -EINVAL;
 
 		switch (TRANSITION(prev_type, type)) {
 		case TRANSITION(OVS_KEY_ATTR_UNSPEC, OVS_KEY_ATTR_TUN_ID):
diff --git a/datapath/flow.h b/datapath/flow.h
index ade8ac8..96b3b4f 100644
--- a/datapath/flow.h
+++ b/datapath/flow.h
@@ -180,5 +180,6 @@ void flow_tbl_remove(struct flow_table *table, struct sw_flow *flow);
 u32 flow_hash(const struct sw_flow_key *key, int key_len);
 
 struct sw_flow *flow_tbl_next(struct flow_table *table, u32 *bucket, u32 *idx);
+extern const u32 ovs_key_lens[OVS_KEY_ATTR_MAX + 1];
 
 #endif /* flow.h */
diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index c077f62..4d7126f 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -423,21 +423,37 @@ enum ovs_userspace_attr {
 
 #define OVS_USERSPACE_ATTR_MAX (__OVS_USERSPACE_ATTR_MAX - 1)
 
-/* Action types. */
+/**
+ * enum ovs_action_attr -  Action types.
+ *
+ * @OVS_ACTION_ATTR_OUTPUT: Output packet to port passed as NLA data.
+ * @OVS_ACTION_ATTR_USERSPACE: Nested %OVS_USERSPACE_ATTR_ attributes specifying
+ * PID.
+ * @OVS_ACTION_ATTR_PUSH: Nested %OVS_KEY_ATTR_* attribute specifying header to
+ * push to given packet.
+ * E.g. Push vlan tag action would be NLA of %OVS_ACTION_ATTR_PUSH type with
+ * nested attribute of type %OVS_KEY_ATTR_8021Q with struct ovs_key_8021q
+ * as NLA data.
+ * @OVS_ACTION_ATTR_POP: Pop header according to %OVS_KEY_ATTR_ sent as
+ * attribute data.
+ * @OVS_ACTION_ATTR_SET: Nested %OVS_KEY_ATTR_* attribute specifying the
+ * field to set to given packet.
+ * @OVS_ACTION_ATTR_SET_PRIORITY: A set skb->priority to 32-bit number passed
+ * as NLA data.
+ * @OVS_ACTION_ATTR_POP_PRIORITY: Restore skb->priority to original value.
+ * @OVS_ACTION_ATTR_SAMPLE: Execute set of actions according to probability
+ * %OVS_SAMPLE_ATTR_PROBABILITY.
+ *
+ * Only a single field can be set with a single %OVS_ACTION_ATTR_{SET,PUSH}.
+ */
+
 enum ovs_action_attr {
 	OVS_ACTION_ATTR_UNSPEC,
 	OVS_ACTION_ATTR_OUTPUT,	      /* Output to switch port. */
 	OVS_ACTION_ATTR_USERSPACE,    /* Nested OVS_USERSPACE_ATTR_*. */
-	OVS_ACTION_ATTR_PUSH_VLAN,    /* Set the 802.1q TCI value. */
-	OVS_ACTION_ATTR_POP_VLAN,     /* Strip the 802.1q header. */
-	OVS_ACTION_ATTR_SET_DL_SRC,   /* Ethernet source address. */
-	OVS_ACTION_ATTR_SET_DL_DST,   /* Ethernet destination address. */
-	OVS_ACTION_ATTR_SET_NW_SRC,   /* IPv4 source address. */
-	OVS_ACTION_ATTR_SET_NW_DST,   /* IPv4 destination address. */
-	OVS_ACTION_ATTR_SET_NW_TOS,   /* IP ToS/DSCP field (6 bits). */
-	OVS_ACTION_ATTR_SET_TP_SRC,   /* TCP/UDP source port. */
-	OVS_ACTION_ATTR_SET_TP_DST,   /* TCP/UDP destination port. */
-	OVS_ACTION_ATTR_SET_TUNNEL,   /* Set the encapsulating tunnel ID. */
+	OVS_ACTION_ATTR_PUSH,         /* Push packet header. */
+	OVS_ACTION_ATTR_POP,          /* Pop packet header. */
+	OVS_ACTION_ATTR_SET,          /* Set packet field(s). */
 	OVS_ACTION_ATTR_SET_PRIORITY, /* Set skb->priority. */
 	OVS_ACTION_ATTR_POP_PRIORITY, /* Restore original skb->priority. */
 	OVS_ACTION_ATTR_SAMPLE,       /* Nested OVS_SAMPLE_ATTR_*. */
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index f78cda2..31c2e27 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -149,10 +149,10 @@ static int dpif_netdev_open(const struct dpif_class *, const char *name,
 static int dp_netdev_output_userspace(struct dp_netdev *, const struct ofpbuf *,
                                     int queue_no, const struct flow *,
                                     uint64_t arg);
-static int dp_netdev_execute_actions(struct dp_netdev *,
-                                     struct ofpbuf *, struct flow *,
-                                     const struct nlattr *actions,
-                                     size_t actions_len);
+static void dp_netdev_execute_actions(struct dp_netdev *,
+                                      struct ofpbuf *, struct flow *,
+                                      const struct nlattr *actions,
+                                      size_t actions_len);
 
 static struct dpif_class dpif_dummy_class;
 
@@ -904,8 +904,8 @@ dpif_netdev_execute(struct dpif *dpif,
     flow_extract(&copy, 0, -1, &key);
     error = dpif_netdev_flow_from_nlattrs(key_attrs, key_len, &key);
     if (!error) {
-        error = dp_netdev_execute_actions(dp, &copy, &key,
-                                          actions, actions_len);
+        dp_netdev_execute_actions(dp, &copy, &key,
+                                  actions, actions_len);
     }
 
     ofpbuf_uninit(&copy);
@@ -1076,94 +1076,94 @@ dp_netdev_pop_vlan(struct ofpbuf *packet)
 }
 
 static void
-dp_netdev_set_dl_src(struct ofpbuf *packet, const uint8_t dl_addr[ETH_ADDR_LEN])
+dp_netdev_set_dl(struct ofpbuf *packet, const struct ovs_key_ethernet *eth_key)
 {
     struct eth_header *eh = packet->l2;
-    memcpy(eh->eth_src, dl_addr, sizeof eh->eth_src);
+
+    memcpy(eh->eth_src, eth_key->eth_src, sizeof eh->eth_src);
+    memcpy(eh->eth_dst, eth_key->eth_dst, sizeof eh->eth_dst);
 }
 
 static void
-dp_netdev_set_dl_dst(struct ofpbuf *packet, const uint8_t dl_addr[ETH_ADDR_LEN])
+dp_netdev_set_ip_addr(struct ofpbuf *packet, ovs_be32 *addr, ovs_be32 new_addr)
 {
-    struct eth_header *eh = packet->l2;
-    memcpy(eh->eth_dst, dl_addr, sizeof eh->eth_dst);
+    struct ip_header *nh = packet->l3;
+
+    if (nh->ip_proto == IPPROTO_TCP && packet->l7) {
+        struct tcp_header *th = packet->l4;
+        th->tcp_csum = recalc_csum32(th->tcp_csum, *addr, new_addr);
+    } else if (nh->ip_proto == IPPROTO_UDP && packet->l7) {
+        struct udp_header *uh = packet->l4;
+        if (uh->udp_csum) {
+            uh->udp_csum = recalc_csum32(uh->udp_csum, *addr, new_addr);
+            if (!uh->udp_csum) {
+                uh->udp_csum = htons(0xffff);
+            }
+        }
+    }
+    nh->ip_csum = recalc_csum32(nh->ip_csum, *addr, new_addr);
+    *addr = new_addr;
 }
 
-static bool
-is_ip(const struct ofpbuf *packet, const struct flow *key)
+static void
+dp_netdev_set_ip_tos(struct ip_header *nh, uint8_t new_tos)
 {
-    return key->dl_type == htons(ETH_TYPE_IP) && packet->l4;
+    uint8_t *field = &nh->ip_tos;
+
+    /* Set the DSCP bits and preserve the ECN bits. */
+    uint8_t new = new_tos | (nh->ip_tos & IP_ECN_MASK);
+
+    nh->ip_csum = recalc_csum16(nh->ip_csum, htons((uint16_t)*field),
+			            htons((uint16_t) new));
+    *field = new;
 }
 
 static void
-dp_netdev_set_nw_addr(struct ofpbuf *packet, const struct flow *key,
-                      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);
-        ovs_be32 *field;
-
-        field = type == OVS_ACTION_ATTR_SET_NW_SRC ? &nh->ip_src : &nh->ip_dst;
-        if (key->nw_proto == IPPROTO_TCP && packet->l7) {
-            struct tcp_header *th = packet->l4;
-            th->tcp_csum = recalc_csum32(th->tcp_csum, *field, ip);
-        } else if (key->nw_proto == IPPROTO_UDP && packet->l7) {
-            struct udp_header *uh = packet->l4;
-            if (uh->udp_csum) {
-                uh->udp_csum = recalc_csum32(uh->udp_csum, *field, ip);
-                if (!uh->udp_csum) {
-                    uh->udp_csum = htons(0xffff);
-                }
-            }
-        }
-        nh->ip_csum = recalc_csum32(nh->ip_csum, *field, ip);
-        *field = ip;
+dp_netdev_set_ipv4(struct ofpbuf *packet, const struct ovs_key_ipv4 *ipv4_key)
+{
+    struct ip_header *nh = packet->l3;
+
+    if (nh->ip_src != ipv4_key->ipv4_src) {
+        dp_netdev_set_ip_addr(packet, &nh->ip_src, ipv4_key->ipv4_src);
+    }
+    if (nh->ip_dst != ipv4_key->ipv4_dst) {
+        dp_netdev_set_ip_addr(packet, &nh->ip_dst, ipv4_key->ipv4_dst);
+    }
+    if (nh->ip_tos != ipv4_key->ipv4_tos) {
+        dp_netdev_set_ip_tos(nh, ipv4_key->ipv4_tos);
     }
 }
 
 static void
-dp_netdev_set_nw_tos(struct ofpbuf *packet, const struct flow *key,
-                     uint8_t nw_tos)
+dp_netdev_set_port(ovs_be16 *port, ovs_be16 new_port, ovs_be16 *csum)
 {
-    if (is_ip(packet, key)) {
-        struct ip_header *nh = packet->l3;
-        uint8_t *field = &nh->ip_tos;
+    *csum = recalc_csum16(*csum, *port, new_port);
+    *port = new_port;
+}
 
-        /* Set the DSCP bits and preserve the ECN bits. */
-        uint8_t new = nw_tos | (nh->ip_tos & IP_ECN_MASK);
+static void
+dp_netdev_set_tcp_port(struct ofpbuf *packet, const struct ovs_key_tcp *tcp_key)
+{
+    struct tcp_header *th = packet->l4;
 
-        nh->ip_csum = recalc_csum16(nh->ip_csum, htons((uint16_t)*field),
-                htons((uint16_t) new));
-        *field = new;
+    if (th->tcp_src != tcp_key->tcp_src) {
+        dp_netdev_set_port(&th->tcp_src, tcp_key->tcp_src, &th->tcp_csum);
+    }
+    if (th->tcp_dst != tcp_key->tcp_dst) {
+        dp_netdev_set_port(&th->tcp_dst, tcp_key->tcp_dst, &th->tcp_csum);
     }
 }
 
 static void
-dp_netdev_set_tp_port(struct ofpbuf *packet, const struct flow *key,
-                      const struct nlattr *a)
-{
-	if (is_ip(packet, key)) {
-        uint16_t type = nl_attr_type(a);
-        ovs_be16 port = nl_attr_get_be16(a);
-        ovs_be16 *field;
-
-        if (key->nw_proto == IPPROTO_TCP && packet->l7) {
-            struct tcp_header *th = packet->l4;
-            field = (type == OVS_ACTION_ATTR_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 = (type == OVS_ACTION_ATTR_SET_TP_SRC
-                     ? &uh->udp_src : &uh->udp_dst);
-            uh->udp_csum = recalc_csum16(uh->udp_csum, *field, port);
-            *field = port;
-        } else {
-            return;
-        }
+dp_netdev_set_udp_port(struct ofpbuf *packet, const struct ovs_key_udp *udp_key)
+{
+    struct udp_header *uh = packet->l4;
+
+    if (uh->udp_src != udp_key->udp_src) {
+        dp_netdev_set_port(&uh->udp_src, udp_key->udp_src, &uh->udp_csum);
+    }
+    if (uh->udp_dst != udp_key->udp_dst) {
+        dp_netdev_set_port(&uh->udp_dst, udp_key->udp_dst, &uh->udp_csum);
     }
 }
 
@@ -1257,7 +1257,50 @@ dp_netdev_action_userspace(struct dp_netdev *dp,
     dp_netdev_output_userspace(dp, packet, DPIF_UC_ACTION, key, userdata);
 }
 
-static int
+static void
+execute_set_action(struct ofpbuf *packet, const struct nlattr *a)
+{
+    enum ovs_key_attr type = nl_attr_type(a);
+    switch (type) {
+    case OVS_KEY_ATTR_TUN_ID:
+        break;
+
+    case OVS_KEY_ATTR_ETHERNET:
+        dp_netdev_set_dl(packet,
+                   nl_attr_get_unspec(a, sizeof(struct ovs_key_ethernet)));
+        break;
+
+    case OVS_KEY_ATTR_IPV4:
+        dp_netdev_set_ipv4(packet,
+                   nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv4)));
+        break;
+
+    case OVS_KEY_ATTR_TCP:
+        dp_netdev_set_tcp_port(packet,
+                   nl_attr_get_unspec(a, sizeof(struct ovs_key_tcp)));
+        break;
+
+     case OVS_KEY_ATTR_UDP:
+        dp_netdev_set_udp_port(packet,
+                   nl_attr_get_unspec(a, sizeof(struct ovs_key_udp)));
+        break;
+
+     case OVS_KEY_ATTR_UNSPEC:
+     case OVS_KEY_ATTR_ETHERTYPE:
+     case OVS_KEY_ATTR_IPV6:
+     case OVS_KEY_ATTR_IN_PORT:
+     case OVS_KEY_ATTR_8021Q:
+     case OVS_KEY_ATTR_ICMP:
+     case OVS_KEY_ATTR_ICMPV6:
+     case OVS_KEY_ATTR_ARP:
+     case OVS_KEY_ATTR_ND:
+     case __OVS_KEY_ATTR_MAX:
+     default:
+        NOT_REACHED();
+    }
+}
+
+static void
 dp_netdev_execute_actions(struct dp_netdev *dp,
                           struct ofpbuf *packet, struct flow *key,
                           const struct nlattr *actions,
@@ -1267,6 +1310,8 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
     unsigned int left;
 
     NL_ATTR_FOR_EACH_UNSAFE (a, left, actions, actions_len) {
+        const struct nlattr *nested;
+        const struct ovs_key_8021q *q_key;
         int type = nl_attr_type(a);
 
         switch ((enum ovs_action_attr) type) {
@@ -1278,41 +1323,25 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
             dp_netdev_action_userspace(dp, packet, key, a);
             break;
 
-        case OVS_ACTION_ATTR_PUSH_VLAN:
-            eth_push_vlan(packet, nl_attr_get_be16(a));
+        case OVS_ACTION_ATTR_PUSH:
+            nested = nl_attr_get(a);
+            assert(nl_attr_type(nested) == OVS_KEY_ATTR_8021Q);
+            q_key = nl_attr_get_unspec(nested, sizeof(*q_key));
+            eth_push_vlan(packet, q_key->q_tci);
             break;
 
-        case OVS_ACTION_ATTR_POP_VLAN:
+        case OVS_ACTION_ATTR_POP:
             dp_netdev_pop_vlan(packet);
             break;
 
-        case OVS_ACTION_ATTR_SET_DL_SRC:
-            dp_netdev_set_dl_src(packet, nl_attr_get_unspec(a, ETH_ADDR_LEN));
-            break;
-
-        case OVS_ACTION_ATTR_SET_DL_DST:
-            dp_netdev_set_dl_dst(packet, nl_attr_get_unspec(a, ETH_ADDR_LEN));
-            break;
-
-        case OVS_ACTION_ATTR_SET_NW_SRC:
-        case OVS_ACTION_ATTR_SET_NW_DST:
-            dp_netdev_set_nw_addr(packet, key, a);
-            break;
-
-        case OVS_ACTION_ATTR_SET_NW_TOS:
-            dp_netdev_set_nw_tos(packet, key, nl_attr_get_u8(a));
-            break;
-
-        case OVS_ACTION_ATTR_SET_TP_SRC:
-        case OVS_ACTION_ATTR_SET_TP_DST:
-            dp_netdev_set_tp_port(packet, key, a);
+        case OVS_ACTION_ATTR_SET:
+            execute_set_action(packet, nl_attr_get(a));
             break;
 
         case OVS_ACTION_ATTR_SAMPLE:
             dp_netdev_sample(dp, packet, key, a);
             break;
 
-        case OVS_ACTION_ATTR_SET_TUNNEL:
         case OVS_ACTION_ATTR_SET_PRIORITY:
         case OVS_ACTION_ATTR_POP_PRIORITY:
             /* not implemented */
@@ -1323,7 +1352,6 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
             NOT_REACHED();
         }
     }
-    return 0;
 }
 
 const struct dpif_class dpif_netdev_class = {
diff --git a/lib/odp-util.c b/lib/odp-util.c
index a471099..9c516bf 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -33,6 +33,9 @@
 #include "timeval.h"
 #include "util.h"
 
+static void
+format_odp_key_attr(const struct nlattr *a, struct ds *ds);
+
 /* The interface between userspace and kernel uses an "OVS_*" prefix.
  * Since this is fairly non-specific for the OVS userspace components,
  * "ODP_*" (Open vSwitch Datapath) is used as the prefix for
@@ -48,7 +51,7 @@
  *   - For an action with a variable-length argument, returns -2.
  *
  *   - For an invalid 'type', returns -1. */
-int
+static int
 odp_action_len(uint16_t type)
 {
     if (type > OVS_ACTION_ATTR_MAX) {
@@ -58,16 +61,9 @@ odp_action_len(uint16_t type)
     switch ((enum ovs_action_attr) type) {
     case OVS_ACTION_ATTR_OUTPUT: return 4;
     case OVS_ACTION_ATTR_USERSPACE: return -2;
-    case OVS_ACTION_ATTR_PUSH_VLAN: return 2;
-    case OVS_ACTION_ATTR_POP_VLAN: return 0;
-    case OVS_ACTION_ATTR_SET_DL_SRC: return ETH_ADDR_LEN;
-    case OVS_ACTION_ATTR_SET_DL_DST: return ETH_ADDR_LEN;
-    case OVS_ACTION_ATTR_SET_NW_SRC: return 4;
-    case OVS_ACTION_ATTR_SET_NW_DST: return 4;
-    case OVS_ACTION_ATTR_SET_NW_TOS: return 1;
-    case OVS_ACTION_ATTR_SET_TP_SRC: return 2;
-    case OVS_ACTION_ATTR_SET_TP_DST: return 2;
-    case OVS_ACTION_ATTR_SET_TUNNEL: return 8;
+    case OVS_ACTION_ATTR_PUSH: return -2;
+    case OVS_ACTION_ATTR_POP: return 2;
+    case OVS_ACTION_ATTR_SET: return -2;
     case OVS_ACTION_ATTR_SET_PRIORITY: return 4;
     case OVS_ACTION_ATTR_POP_PRIORITY: return 0;
     case OVS_ACTION_ATTR_SAMPLE: return -2;
@@ -170,12 +166,11 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr)
 }
 
 
-void
+static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
 {
-    const uint8_t *eth;
     int expected_len;
-    ovs_be32 ip;
+    enum ovs_action_attr type = nl_attr_type(a);
 
     expected_len = odp_action_len(nl_attr_type(a));
     if (expected_len != -2 && nl_attr_get_size(a) != expected_len) {
@@ -185,49 +180,30 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
         return;
     }
 
-    switch (nl_attr_type(a)) {
+    switch (type) {
+
     case OVS_ACTION_ATTR_OUTPUT:
         ds_put_format(ds, "%"PRIu16, nl_attr_get_u32(a));
         break;
     case OVS_ACTION_ATTR_USERSPACE:
         format_odp_userspace_action(ds, a);
         break;
-    case OVS_ACTION_ATTR_SET_TUNNEL:
-        ds_put_format(ds, "set_tunnel(%#"PRIx64")",
-                      ntohll(nl_attr_get_be64(a)));
-        break;
-    case OVS_ACTION_ATTR_PUSH_VLAN:
-        ds_put_format(ds, "push_vlan(vid=%"PRIu16",pcp=%d)",
-                      vlan_tci_to_vid(nl_attr_get_be16(a)),
-                      vlan_tci_to_pcp(nl_attr_get_be16(a)));
-        break;
-    case OVS_ACTION_ATTR_POP_VLAN:
-        ds_put_format(ds, "pop_vlan");
+    case OVS_ACTION_ATTR_SET:
+        ds_put_cstr(ds, "set(");
+        format_odp_key_attr(nl_attr_get(a), ds);
+        ds_put_cstr(ds, ")");
         break;
-    case OVS_ACTION_ATTR_SET_DL_SRC:
-        eth = nl_attr_get_unspec(a, ETH_ADDR_LEN);
-        ds_put_format(ds, "set_dl_src("ETH_ADDR_FMT")", ETH_ADDR_ARGS(eth));
+    case OVS_ACTION_ATTR_PUSH:
+        ds_put_cstr(ds, "push(");
+        format_odp_key_attr(nl_attr_get(a), ds);
+        ds_put_cstr(ds, ")");
         break;
-    case OVS_ACTION_ATTR_SET_DL_DST:
-        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 OVS_ACTION_ATTR_SET_NW_SRC:
-        ip = nl_attr_get_be32(a);
-        ds_put_format(ds, "set_nw_src("IP_FMT")", IP_ARGS(&ip));
-        break;
-    case OVS_ACTION_ATTR_SET_NW_DST:
-        ip = nl_attr_get_be32(a);
-        ds_put_format(ds, "set_nw_dst("IP_FMT")", IP_ARGS(&ip));
-        break;
-    case OVS_ACTION_ATTR_SET_NW_TOS:
-        ds_put_format(ds, "set_nw_tos(%"PRIu8")", nl_attr_get_u8(a));
-        break;
-    case OVS_ACTION_ATTR_SET_TP_SRC:
-        ds_put_format(ds, "set_tp_src(%"PRIu16")", ntohs(nl_attr_get_be16(a)));
-        break;
-    case OVS_ACTION_ATTR_SET_TP_DST:
-        ds_put_format(ds, "set_tp_dst(%"PRIu16")", ntohs(nl_attr_get_be16(a)));
+    case OVS_ACTION_ATTR_POP:
+        if (nl_attr_get_u16(a) == OVS_KEY_ATTR_8021Q) {
+            ds_put_cstr(ds, "pop(vlan)");
+        } else {
+            ds_put_format(ds, "pop(key=%"PRIu16")", nl_attr_get_u16(a));
+        }
         break;
     case OVS_ACTION_ATTR_SET_PRIORITY:
         ds_put_format(ds, "set_priority(%#"PRIx32")", nl_attr_get_u32(a));
@@ -238,6 +214,8 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
     case OVS_ACTION_ATTR_SAMPLE:
         format_odp_sample_action(ds, a);
         break;
+    case OVS_ACTION_ATTR_UNSPEC:
+    case __OVS_ACTION_ATTR_MAX:
     default:
         format_generic_odp_action(ds, a);
         break;
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 04b8885..8c7d4d1 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -58,9 +58,6 @@ odp_port_to_ofp_port(uint16_t odp_port)
         return odp_port;
     }
 }
-
-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);
 
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 0eeda7e..ff607f7 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -2603,6 +2603,7 @@ facet_account(struct ofproto_dpif *ofproto, struct facet *facet)
         struct ofport_dpif *port;
 
         switch (nl_attr_type(a)) {
+        const struct nlattr *nested;
         case OVS_ACTION_ATTR_OUTPUT:
             port = get_odp_port(ofproto, nl_attr_get_u32(a));
             if (port && port->bundle && port->bundle->bond) {
@@ -2611,12 +2612,20 @@ facet_account(struct ofproto_dpif *ofproto, struct facet *facet)
             }
             break;
 
-        case OVS_ACTION_ATTR_POP_VLAN:
-            vlan_tci = htons(0);
+        case OVS_ACTION_ATTR_POP:
+            if (nl_attr_get_u16(a) == OVS_KEY_ATTR_8021Q) {
+                vlan_tci = htons(0);
+            }
             break;
 
-        case OVS_ACTION_ATTR_PUSH_VLAN:
-            vlan_tci = nl_attr_get_be16(a);
+        case OVS_ACTION_ATTR_PUSH:
+            nested = nl_attr_get(a);
+            if (nl_attr_type(nested) == OVS_KEY_ATTR_8021Q) {
+                const struct ovs_key_8021q *q_key;
+
+                q_key = nl_attr_get_unspec(nested, sizeof(*q_key));
+                vlan_tci = q_key->q_tci;
+            }
             break;
         }
     }
@@ -3279,85 +3288,167 @@ fix_sflow_action(struct action_xlate_ctx *ctx)
 }
 
 static void
-commit_vlan_tci(struct action_xlate_ctx *ctx, ovs_be16 vlan_tci)
+commit_action__(struct ofpbuf *odp_actions,
+                enum ovs_action_attr act_type,
+                enum ovs_key_attr key_type,
+                const void *key, size_t key_size)
 {
-    struct flow *base = &ctx->base_flow;
-    struct ofpbuf *odp_actions = ctx->odp_actions;
+    size_t offset = nl_msg_start_nested(odp_actions, act_type);
 
-    if (base->vlan_tci != vlan_tci) {
-        if (!(vlan_tci & htons(VLAN_CFI))) {
-            nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_VLAN);
-        } else {
-            if (base->vlan_tci != htons(0)) {
-                nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_VLAN);
-            }
-            nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_PUSH_VLAN,
-                            vlan_tci & ~htons(VLAN_CFI));
-        }
-        base->vlan_tci = vlan_tci;
+    nl_msg_put_unspec(odp_actions, key_type, key, key_size);
+    nl_msg_end_nested(odp_actions, offset);
+}
+
+static void
+commit_set_tun_id_action(const struct flow *flow, struct flow *base,
+                         struct ofpbuf *odp_actions)
+{
+    if (base->tun_id == flow->tun_id) {
+        return;
     }
+    base->tun_id = flow->tun_id;
+
+    commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
+             OVS_KEY_ATTR_TUN_ID, &base->tun_id, sizeof(base->tun_id));
 }
 
 static void
-commit_odp_actions(struct action_xlate_ctx *ctx)
+commit_set_ether_addr_action(const struct flow *flow, struct flow *base,
+                             struct ofpbuf *odp_actions)
+{
+    struct ovs_key_ethernet eth_key;
+
+    if (eth_addr_equals(base->dl_src, flow->dl_src) &&
+        eth_addr_equals(base->dl_dst, flow->dl_dst)) {
+        return;
+    }
+
+    memcpy(base->dl_src, flow->dl_src, ETH_ADDR_LEN);
+    memcpy(base->dl_dst, flow->dl_dst, ETH_ADDR_LEN);
+
+    memcpy(eth_key.eth_src, base->dl_src, ETH_ADDR_LEN);
+    memcpy(eth_key.eth_dst, base->dl_dst, ETH_ADDR_LEN);
+
+    commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
+             OVS_KEY_ATTR_ETHERNET, &eth_key, sizeof(eth_key));
+}
+
+static void
+commit_vlan_action(struct action_xlate_ctx *ctx, ovs_be16 new_tci)
 {
-    const struct flow *flow = &ctx->flow;
     struct flow *base = &ctx->base_flow;
-    struct ofpbuf *odp_actions = ctx->odp_actions;
 
-    if (base->tun_id != flow->tun_id) {
-        nl_msg_put_be64(odp_actions, OVS_ACTION_ATTR_SET_TUNNEL, flow->tun_id);
-        base->tun_id = flow->tun_id;
+    if (base->vlan_tci == new_tci) {
+        return;
     }
 
-    if (base->nw_src != flow->nw_src) {
-        nl_msg_put_be32(odp_actions, OVS_ACTION_ATTR_SET_NW_SRC, flow->nw_src);
-        base->nw_src = flow->nw_src;
+    if (base->vlan_tci & htons(VLAN_CFI)) {
+        nl_msg_put_u16(ctx->odp_actions, OVS_ACTION_ATTR_POP,
+                                       OVS_KEY_ATTR_8021Q);
     }
 
-    if (base->nw_dst != flow->nw_dst) {
-        nl_msg_put_be32(odp_actions, OVS_ACTION_ATTR_SET_NW_DST, flow->nw_dst);
-        base->nw_dst = flow->nw_dst;
+    if (new_tci & htons(VLAN_CFI)) {
+        struct ovs_key_8021q q_key;
+
+        q_key.q_tpid = htons(ETH_TYPE_VLAN);
+        q_key.q_tci = new_tci & ~htons(VLAN_CFI);
+
+        commit_action__(ctx->odp_actions, OVS_ACTION_ATTR_PUSH,
+                            OVS_KEY_ATTR_8021Q, &q_key, sizeof(q_key));
     }
+    base->vlan_tci = new_tci;
+}
 
-    if (base->nw_tos != flow->nw_tos) {
-        nl_msg_put_u8(odp_actions, OVS_ACTION_ATTR_SET_NW_TOS, flow->nw_tos);
-        base->nw_tos = flow->nw_tos;
+static void
+commit_set_nw_action(const struct flow *flow, struct flow *base,
+                     struct ofpbuf *odp_actions)
+{
+    struct ovs_key_ipv4 ipv4_key;
+
+    if (base->dl_type != htons(ETH_TYPE_IP) ||
+        !base->nw_src || !base->nw_dst) {
+        return;
+    }
+
+    if (base->nw_src == flow->nw_src &&
+        base->nw_dst == flow->nw_dst &&
+        base->nw_tos == flow->nw_tos) {
+        return;
     }
 
-    commit_vlan_tci(ctx, flow->vlan_tci);
+    memset(&ipv4_key, 0, sizeof(ipv4_key));
+    ipv4_key.ipv4_src = base->nw_src = flow->nw_src;
+    ipv4_key.ipv4_dst = base->nw_dst = flow->nw_dst;
+    ipv4_key.ipv4_tos = base->nw_tos = flow->nw_tos;
 
-    if (base->tp_src != flow->tp_src) {
-        nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_SET_TP_SRC, flow->tp_src);
-        base->tp_src = flow->tp_src;
+    ipv4_key.ipv4_proto = base->nw_proto;
+
+    commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
+             OVS_KEY_ATTR_IPV4, &ipv4_key, sizeof(ipv4_key));
+}
+
+static void
+commit_set_port_action(const struct flow *flow, struct flow *base,
+                       struct ofpbuf *odp_actions)
+{
+    if (!base->tp_src || !base->tp_dst) {
+        return;
     }
 
-    if (base->tp_dst != flow->tp_dst) {
-        nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_SET_TP_DST, flow->tp_dst);
-        base->tp_dst = flow->tp_dst;
+    if (base->tp_src == flow->tp_src &&
+        base->tp_dst == flow->tp_dst) {
+        return;
     }
 
-    if (!eth_addr_equals(base->dl_src, flow->dl_src)) {
-        nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_SET_DL_SRC,
-                          flow->dl_src, ETH_ADDR_LEN);
-        memcpy(base->dl_src, flow->dl_src, ETH_ADDR_LEN);
+    if (flow->nw_proto == IPPROTO_TCP) {
+        struct ovs_key_tcp port_key;
+
+        port_key.tcp_src = base->tp_src = flow->tp_src;
+        port_key.tcp_dst = base->tp_dst = flow->tp_dst;
+
+        commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
+             OVS_KEY_ATTR_TCP, &port_key, sizeof(port_key));
+
+    } else if (flow->nw_proto == IPPROTO_UDP) {
+        struct ovs_key_udp port_key;
+
+        port_key.udp_src = base->tp_src = flow->tp_src;
+        port_key.udp_dst = base->tp_dst = flow->tp_dst;
+
+        commit_action__(odp_actions, OVS_ACTION_ATTR_SET,
+             OVS_KEY_ATTR_UDP, &port_key, sizeof(port_key));
     }
+}
 
-    if (!eth_addr_equals(base->dl_dst, flow->dl_dst)) {
-        nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_SET_DL_DST,
-                          flow->dl_dst, ETH_ADDR_LEN);
-        memcpy(base->dl_dst, flow->dl_dst, ETH_ADDR_LEN);
+static void
+commit_priority_action(struct action_xlate_ctx *ctx)
+{
+    if (ctx->base_priority == ctx->priority) {
+        return;
     }
 
-    if (ctx->base_priority != ctx->priority) {
-        if (ctx->priority) {
-            nl_msg_put_u32(odp_actions, OVS_ACTION_ATTR_SET_PRIORITY,
-                           ctx->priority);
-        } else {
-            nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_PRIORITY);
-        }
-        ctx->base_priority = ctx->priority;
+    if (ctx->priority) {
+        nl_msg_put_u32(ctx->odp_actions,
+                        OVS_ACTION_ATTR_SET_PRIORITY, ctx->priority);
+    } else {
+        nl_msg_put_flag(ctx->odp_actions, OVS_ACTION_ATTR_POP_PRIORITY);
     }
+    ctx->base_priority = ctx->priority;
+}
+
+static void
+commit_odp_actions(struct action_xlate_ctx *ctx)
+{
+    const struct flow *flow = &ctx->flow;
+    struct flow *base = &ctx->base_flow;
+    struct ofpbuf *odp_actions = ctx->odp_actions;
+
+    commit_set_tun_id_action(flow, base, odp_actions);
+    commit_set_ether_addr_action(flow, base, odp_actions);
+    commit_vlan_action(ctx, flow->vlan_tci);
+    commit_set_nw_action(flow, base, odp_actions);
+    commit_set_port_action(flow, base, odp_actions);
+    commit_priority_action(ctx);
 }
 
 static void
@@ -4240,7 +4331,7 @@ compose_actions(struct action_xlate_ctx *ctx, uint16_t vlan,
             if (tci) {
                 tci |= htons(VLAN_CFI);
             }
-            commit_vlan_tci(ctx, tci);
+            commit_vlan_action(ctx, tci);
 
             cur_vid = dst->vid;
         }
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index b5ca08c..59b14e4 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -75,7 +75,7 @@ in_port=5 actions=set_tunnel:5
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 AT_CHECK([ovs-appctl -t test-openflowd ofproto/trace br0 'tun_id(0x1),in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0),icmp(type=8,code=0)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: set_tunnel(0x1),1,2,set_tunnel(0x3),3,4
+  [Datapath actions: set(tun_id(0x1)),1,2,set(tun_id(0x3)),3,4
 ])
 OFPROTO_STOP
 AT_CLEANUP
@@ -124,84 +124,84 @@ for tuple in \
         "br0 none 0 drop" \
         "br0 0    0 drop" \
         "br0 0    1 drop" \
-        "br0 10   0 p1,p5,p6,p7,p8,pop_vlan,p2" \
-        "br0 10   1 p1,p5,p6,p7,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "br0 10   0 p1,p5,p6,p7,p8,pop(vlan),p2" \
+        "br0 10   1 p1,p5,p6,p7,p8,pop(vlan),push(vlan(vid=0,pcp=1)),p2" \
         "br0 11   0 p5,p7" \
         "br0 11   1 p5,p7" \
-        "br0 12   0 p1,p5,p6,pop_vlan,p3,p4,p7,p8" \
-        "br0 12   1 p1,p5,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7,p8" \
+        "br0 12   0 p1,p5,p6,pop(vlan),p3,p4,p7,p8" \
+        "br0 12   1 p1,p5,p6,pop(vlan),push(vlan(vid=0,pcp=1)),p3,p4,p7,p8" \
         "p1  none 0 drop" \
         "p1  0    0 drop" \
         "p1  0    1 drop" \
-        "p1  10   0 br0,p5,p6,p7,p8,pop_vlan,p2" \
-        "p1  10   1 br0,p5,p6,p7,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p1  10   0 br0,p5,p6,p7,p8,pop(vlan),p2" \
+        "p1  10   1 br0,p5,p6,p7,p8,pop(vlan),push(vlan(vid=0,pcp=1)),p2" \
         "p1  11   0 drop" \
         "p1  11   1 drop" \
-        "p1  12   0 br0,p5,p6,pop_vlan,p3,p4,p7,p8" \
-        "p1  12   1 br0,p5,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7,p8" \
-        "p2  none 0 push_vlan(vid=10,pcp=0),br0,p1,p5,p6,p7,p8" \
-        "p2  0    0 pop_vlan,push_vlan(vid=10,pcp=0),br0,p1,p5,p6,p7,p8" \
-        "p2  0    1 pop_vlan,push_vlan(vid=10,pcp=1),br0,p1,p5,p6,p7,p8" \
+        "p1  12   0 br0,p5,p6,pop(vlan),p3,p4,p7,p8" \
+        "p1  12   1 br0,p5,p6,pop(vlan),push(vlan(vid=0,pcp=1)),p3,p4,p7,p8" \
+        "p2  none 0 push(vlan(vid=10,pcp=0)),br0,p1,p5,p6,p7,p8" \
+        "p2  0    0 pop(vlan),push(vlan(vid=10,pcp=0)),br0,p1,p5,p6,p7,p8" \
+        "p2  0    1 pop(vlan),push(vlan(vid=10,pcp=1)),br0,p1,p5,p6,p7,p8" \
         "p2  10   0 drop" \
         "p2  10   1 drop" \
         "p2  11   0 drop" \
         "p2  11   1 drop" \
         "p2  12   0 drop" \
         "p2  12   1 drop" \
-        "p3  none 0 p4,p7,p8,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
-        "p3  0    0 p4,p7,p8,pop_vlan,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
-        "p3  0    1 p4,p7,p8,pop_vlan,push_vlan(vid=12,pcp=1),br0,p1,p5,p6" \
+        "p3  none 0 p4,p7,p8,push(vlan(vid=12,pcp=0)),br0,p1,p5,p6" \
+        "p3  0    0 p4,p7,p8,pop(vlan),push(vlan(vid=12,pcp=0)),br0,p1,p5,p6" \
+        "p3  0    1 p4,p7,p8,pop(vlan),push(vlan(vid=12,pcp=1)),br0,p1,p5,p6" \
         "p3  10   0 drop" \
         "p3  10   1 drop" \
         "p3  11   0 drop" \
         "p3  11   1 drop" \
         "p3  12   0 drop" \
         "p3  12   1 drop" \
-        "p4  none 0 p3,p7,p8,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
-        "p4  0    0 p3,p7,p8,pop_vlan,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
-        "p4  0    1 p3,p7,p8,pop_vlan,push_vlan(vid=12,pcp=1),br0,p1,p5,p6" \
+        "p4  none 0 p3,p7,p8,push(vlan(vid=12,pcp=0)),br0,p1,p5,p6" \
+        "p4  0    0 p3,p7,p8,pop(vlan),push(vlan(vid=12,pcp=0)),br0,p1,p5,p6" \
+        "p4  0    1 p3,p7,p8,pop(vlan),push(vlan(vid=12,pcp=1)),br0,p1,p5,p6" \
         "p4  10   0 drop" \
         "p4  10   1 drop" \
         "p4  11   0 drop" \
         "p4  11   1 drop" \
         "p4  12   0 drop" \
         "p4  12   1 drop" \
-        "p5  none 0 p2,push_vlan(vid=10,pcp=0),br0,p1,p6,p7,p8" \
-        "p5  0    0 p2,pop_vlan,push_vlan(vid=10,pcp=0),br0,p1,p6,p7,p8" \
-        "p5  0    1 p2,pop_vlan,push_vlan(vid=10,pcp=1),br0,p1,p6,p7,p8" \
-        "p5  10   0 br0,p1,p6,p7,p8,pop_vlan,p2" \
-        "p5  10   1 br0,p1,p6,p7,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p5  none 0 p2,push(vlan(vid=10,pcp=0)),br0,p1,p6,p7,p8" \
+        "p5  0    0 p2,pop(vlan),push(vlan(vid=10,pcp=0)),br0,p1,p6,p7,p8" \
+        "p5  0    1 p2,pop(vlan),push(vlan(vid=10,pcp=1)),br0,p1,p6,p7,p8" \
+        "p5  10   0 br0,p1,p6,p7,p8,pop(vlan),p2" \
+        "p5  10   1 br0,p1,p6,p7,p8,pop(vlan),push(vlan(vid=0,pcp=1)),p2" \
         "p5  11   0 br0,p7" \
         "p5  11   1 br0,p7" \
-        "p5  12   0 br0,p1,p6,pop_vlan,p3,p4,p7,p8" \
-        "p5  12   1 br0,p1,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7,p8" \
-        "p6  none 0 p2,push_vlan(vid=10,pcp=0),br0,p1,p5,p7,p8" \
-        "p6  0    0 p2,pop_vlan,push_vlan(vid=10,pcp=0),br0,p1,p5,p7,p8" \
-        "p6  0    1 p2,pop_vlan,push_vlan(vid=10,pcp=1),br0,p1,p5,p7,p8" \
-        "p6  10   0 br0,p1,p5,p7,p8,pop_vlan,p2" \
-        "p6  10   1 br0,p1,p5,p7,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p5  12   0 br0,p1,p6,pop(vlan),p3,p4,p7,p8" \
+        "p5  12   1 br0,p1,p6,pop(vlan),push(vlan(vid=0,pcp=1)),p3,p4,p7,p8" \
+        "p6  none 0 p2,push(vlan(vid=10,pcp=0)),br0,p1,p5,p7,p8" \
+        "p6  0    0 p2,pop(vlan),push(vlan(vid=10,pcp=0)),br0,p1,p5,p7,p8" \
+        "p6  0    1 p2,pop(vlan),push(vlan(vid=10,pcp=1)),br0,p1,p5,p7,p8" \
+        "p6  10   0 br0,p1,p5,p7,p8,pop(vlan),p2" \
+        "p6  10   1 br0,p1,p5,p7,p8,pop(vlan),push(vlan(vid=0,pcp=1)),p2" \
         "p6  11   0 drop" \
         "p6  11   1 drop" \
-        "p6  12   0 br0,p1,p5,pop_vlan,p3,p4,p7,p8" \
-        "p6  12   1 br0,p1,p5,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7,p8" \
-        "p7  none 0 p3,p4,p8,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
-        "p7  0    0 p3,p4,p8,pop_vlan,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
-        "p7  0    1 p3,p4,p8,pop_vlan,push_vlan(vid=12,pcp=1),br0,p1,p5,p6" \
-        "p7  10   0 br0,p1,p5,p6,p8,pop_vlan,p2" \
-        "p7  10   1 br0,p1,p5,p6,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p6  12   0 br0,p1,p5,pop(vlan),p3,p4,p7,p8" \
+        "p6  12   1 br0,p1,p5,pop(vlan),push(vlan(vid=0,pcp=1)),p3,p4,p7,p8" \
+        "p7  none 0 p3,p4,p8,push(vlan(vid=12,pcp=0)),br0,p1,p5,p6" \
+        "p7  0    0 p3,p4,p8,pop(vlan),push(vlan(vid=12,pcp=0)),br0,p1,p5,p6" \
+        "p7  0    1 p3,p4,p8,pop(vlan),push(vlan(vid=12,pcp=1)),br0,p1,p5,p6" \
+        "p7  10   0 br0,p1,p5,p6,p8,pop(vlan),p2" \
+        "p7  10   1 br0,p1,p5,p6,p8,pop(vlan),push(vlan(vid=0,pcp=1)),p2" \
         "p7  11   0 br0,p5" \
         "p7  11   1 br0,p5" \
-        "p7  12   0 br0,p1,p5,p6,pop_vlan,p3,p4,p8" \
-        "p7  12   1 br0,p1,p5,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p8" \
-        "p8  none 0 p3,p4,p7,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
-        "p8  0    0 p3,p4,p7,pop_vlan,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
-        "p8  0    1 p3,p4,p7,pop_vlan,push_vlan(vid=12,pcp=1),br0,p1,p5,p6" \
-        "p8  10   0 br0,p1,p5,p6,p7,pop_vlan,p2" \
-        "p8  10   1 br0,p1,p5,p6,p7,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p7  12   0 br0,p1,p5,p6,pop(vlan),p3,p4,p8" \
+        "p7  12   1 br0,p1,p5,p6,pop(vlan),push(vlan(vid=0,pcp=1)),p3,p4,p8" \
+        "p8  none 0 p3,p4,p7,push(vlan(vid=12,pcp=0)),br0,p1,p5,p6" \
+        "p8  0    0 p3,p4,p7,pop(vlan),push(vlan(vid=12,pcp=0)),br0,p1,p5,p6" \
+        "p8  0    1 p3,p4,p7,pop(vlan),push(vlan(vid=12,pcp=1)),br0,p1,p5,p6" \
+        "p8  10   0 br0,p1,p5,p6,p7,pop(vlan),p2" \
+        "p8  10   1 br0,p1,p5,p6,p7,pop(vlan),push(vlan(vid=0,pcp=1)),p2" \
         "p8  11   0 drop" \
         "p8  11   1 drop" \
-        "p8  12   0 br0,p1,p5,p6,pop_vlan,p3,p4,p7" \
-        "p8  12   1 br0,p1,p5,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7"
+        "p8  12   0 br0,p1,p5,p6,pop(vlan),p3,p4,p7" \
+        "p8  12   1 br0,p1,p5,p6,pop(vlan),push(vlan(vid=0,pcp=1)),p3,p4,p7"
 do
   set $tuple
   in_port=$1
-- 
1.7.1




More information about the dev mailing list