diff mbox

[ovs-dev,PATCHv6,1/3] ofp-actions: Add truncate action.

Message ID 1465365234-59223-2-git-send-email-u9012063@gmail.com
State Changes Requested
Headers show

Commit Message

William Tu June 8, 2016, 5:53 a.m. UTC
The patch adds a new action to support packet truncation.  The new action
is formatted as 'output(port=n,max_len=m)', as output to port n, with
packet size being MIN(original_size, m).

One use case is to enable port mirroring to send smaller packets to the
destination port so that only useful packet information is mirrored/copied,
saving some performance overhead of copying entire packet payload.  Example
use case is below as well as shown in the testcases:

    - Output to port 1 with max_len 100 bytes.
    - The output packet size on port 1 will be MIN(original_packet_size, 100).
    # ovs-ofctl add-flow br0 'actions=output(port=1,max_len=100)'

    - The scope of max_len is limited to output action itself.  The following
      packet size of output:1 and output:2 will be intact.
    # ovs-ofctl add-flow br0 \
            'actions=output(port=1,max_len=100),output:1,output:2'
    - The Datapath actions shows:
    # Datapath actions: trunc(100),1,1,2

Signed-off-by: William Tu <u9012063@gmail.com>
---
 datapath/actions.c                                |  36 ++++-
 datapath/datapath.c                               |  25 +++-
 datapath/datapath.h                               |   4 +
 datapath/flow_netlink.c                           |   9 ++
 datapath/linux/compat/include/linux/openvswitch.h |   8 ++
 datapath/vport.c                                  |   1 +
 include/openvswitch/ofp-actions.h                 |  10 ++
 lib/dp-packet.c                                   |   1 +
 lib/dp-packet.h                                   |  26 ++++
 lib/dpif-netdev.c                                 |  33 ++++-
 lib/dpif-netlink.c                                |   4 +-
 lib/dpif.c                                        |  23 +++
 lib/dpif.h                                        |   1 +
 lib/netdev-bsd.c                                  |   5 +
 lib/netdev-dpdk.c                                 |   6 +
 lib/netdev-dummy.c                                |   5 +
 lib/netdev-linux.c                                |   5 +
 lib/netdev.c                                      |   8 +-
 lib/odp-execute.c                                 |  11 ++
 lib/odp-util.c                                    |  23 +++
 lib/ofp-actions.c                                 | 108 ++++++++++++++
 ofproto/ofproto-dpif-sflow.c                      |   1 +
 ofproto/ofproto-dpif-upcall.c                     |  18 ++-
 ofproto/ofproto-dpif-xlate.c                      |  56 ++++++++
 ofproto/ofproto-dpif.c                            |  55 +++++++
 ofproto/ofproto-dpif.h                            |   3 +
 tests/odp.at                                      |   1 +
 tests/ofp-actions.at                              |   3 +
 tests/ofproto-dpif.at                             | 124 ++++++++++++++++
 tests/ovs-ofctl.at                                |   4 +
 tests/system-traffic.at                           | 168 ++++++++++++++++++++++
 31 files changed, 772 insertions(+), 13 deletions(-)

Comments

Pravin Shelar June 8, 2016, 9:22 p.m. UTC | #1
On Tue, Jun 7, 2016 at 10:53 PM, William Tu <u9012063@gmail.com> wrote:
> The patch adds a new action to support packet truncation.  The new action
> is formatted as 'output(port=n,max_len=m)', as output to port n, with
> packet size being MIN(original_size, m).
>
> One use case is to enable port mirroring to send smaller packets to the
> destination port so that only useful packet information is mirrored/copied,
> saving some performance overhead of copying entire packet payload.  Example
> use case is below as well as shown in the testcases:
>
>     - Output to port 1 with max_len 100 bytes.
>     - The output packet size on port 1 will be MIN(original_packet_size, 100).
>     # ovs-ofctl add-flow br0 'actions=output(port=1,max_len=100)'
>
>     - The scope of max_len is limited to output action itself.  The following
>       packet size of output:1 and output:2 will be intact.
>     # ovs-ofctl add-flow br0 \
>             'actions=output(port=1,max_len=100),output:1,output:2'
>     - The Datapath actions shows:
>     # Datapath actions: trunc(100),1,1,2
>
> Signed-off-by: William Tu <u9012063@gmail.com>
> ---
Can you send next version against net-next tree? All feature patches
needs to be pushed upstream OVS first.

...

> diff --git a/datapath/actions.c b/datapath/actions.c
> index dcf8591..92ee3f9 100644
> --- a/datapath/actions.c
> +++ b/datapath/actions.c
> @@ -778,6 +778,7 @@ static int output_userspace(struct datapath *dp, struct sk_buff *skb,
>         memset(&upcall, 0, sizeof(upcall));
>         upcall.cmd = OVS_PACKET_CMD_ACTION;
>         upcall.mru = OVS_CB(skb)->mru;
> +       upcall.cutlen = OVS_CB(skb)->cutlen;
>
>         for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
>                  a = nla_next(a, &rem)) {
> @@ -854,10 +855,17 @@ static int sample(struct datapath *dp, struct sk_buff *skb,
>                 return 0;
>
>         /* The only known usage of sample action is having a single user-space
> +        * action, or having a truncate action followed by a single user-space
>          * action. Treat this usage as a special case.
>          * The output_userspace() should clone the skb to be sent to the
> -        * user space. This skb will be consumed by its caller.
> -        */
> +        * user space. This skb will be consumed by its caller. */
> +       if (unlikely(nla_type(a) == OVS_ACTION_ATTR_TRUNC)) {
> +               struct ovs_action_trunc *trunc = nla_data(a);
> +               OVS_CB(skb)->cutlen = skb->len > trunc->max_len ?
> +                                               skb->len - trunc->max_len : 0;
> +               a = nla_next(a, &rem);
> +       }
> +
>         if (likely(nla_type(a) == OVS_ACTION_ATTR_USERSPACE &&
>                    nla_is_last(a, rem)))
>                 return output_userspace(dp, skb, key, a, actions, actions_len);
> @@ -1040,10 +1048,15 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
>         for (a = attr, rem = len; rem > 0;
>              a = nla_next(a, &rem)) {
>                 int err = 0;
> +               int cutlen = OVS_CB(skb)->cutlen;
>
>                 if (unlikely(prev_port != -1)) {
>                         struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);
>
> +                       if (cutlen > 0) {
> +                               pskb_trim(out_skb, out_skb->len - cutlen);
> +                               OVS_CB(skb)->cutlen = 0;
> +                       }
>                         if (out_skb)
>                                 do_output(dp, out_skb, prev_port, key);
>
Lets  move the pskb_trim() call inside do_output().  So that NULL skb
is not passed to the function.


> @@ -1055,6 +1068,16 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
>                         prev_port = nla_get_u32(a);
>                         break;
>
> +               case OVS_ACTION_ATTR_TRUNC: {
> +                       struct ovs_action_trunc *trunc = nla_data(a);
> +
> +                       if (trunc->max_len < ETH_MIN_FRAME_LEN)
> +                               return -EINVAL;
Does max_len still needs to be check after checking the at flow install.

> +                       OVS_CB(skb)->cutlen = skb->len > trunc->max_len ?
> +                                               skb->len - trunc->max_len : 0;
> +                       break;
> +               }
> +
>                 case OVS_ACTION_ATTR_USERSPACE:
>                         output_userspace(dp, skb, key, a, attr, len);
>                         break;
> @@ -1125,8 +1148,15 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
>                 }
>         }
>
> -       if (prev_port != -1)
> +       if (prev_port != -1) {
> +               uint32_t cutlen = OVS_CB(skb)->cutlen;
> +
> +               if (cutlen > 0) {
> +                       pskb_trim(skb, skb->len - cutlen);
> +                       OVS_CB(skb)->cutlen = 0;
> +               }
>                 do_output(dp, skb, prev_port, key);
> +       }
By moving pskb_trim() to do_output() we can avoid the duplicate code.

>         else
>                 consume_skb(skb);
>
> diff --git a/datapath/datapath.c b/datapath/datapath.c
> index 5bec072..958dfb8 100644
> --- a/datapath/datapath.c
> +++ b/datapath/datapath.c
> @@ -280,6 +280,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
>                 upcall.cmd = OVS_PACKET_CMD_MISS;
>                 upcall.portid = ovs_vport_find_upcall_portid(p, skb);
>                 upcall.mru = OVS_CB(skb)->mru;
> +               upcall.cutlen = OVS_CB(skb)->cutlen;
>                 error = ovs_dp_upcall(dp, skb, key, &upcall);
>                 if (unlikely(error))
>                         kfree_skb(skb);
> @@ -409,6 +410,10 @@ static size_t upcall_msg_size(const struct dp_upcall_info *upcall_info,
>         if (upcall_info->mru)
>                 size += nla_total_size(sizeof(upcall_info->mru));
>
> +       /* OVS_PACKET_ATTR_CUTLEN */
> +       if (upcall_info->cutlen)
> +               size += nla_total_size(sizeof(upcall_info->cutlen));
> +
I think this should be part of separate patch. This is not required
for truncate support.

>         return size;
>  }
>
> @@ -439,6 +444,7 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
>         size_t len;
>         unsigned int hlen;
>         int err, dp_ifindex;
> +       int cutlen = OVS_CB(skb)->cutlen;
>
>         dp_ifindex = get_dpifindex(dp);
>         if (!dp_ifindex)
> @@ -475,6 +481,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
>         else
>                 hlen = skb->len;
>
> +       if (cutlen > 0)
> +               hlen -= cutlen;
> +
>         len = upcall_msg_size(upcall_info, hlen);
>         user_skb = genlmsg_new_unicast(len, &info, GFP_ATOMIC);
>         if (!user_skb) {
> @@ -525,6 +534,16 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
>                 pad_packet(dp, user_skb);
>         }
>
> +       /* Add OVS_PACKET_ATTR_CUTLEN */
> +       if (upcall_info->cutlen) {
> +               if (nla_put_u16(user_skb, OVS_PACKET_ATTR_CUTLEN,
> +                               upcall_info->cutlen)) {
> +                       err = -ENOBUFS;
> +                       goto out;
> +               }
> +               pad_packet(dp, user_skb);
> +       }
> +
>         /* Only reserve room for attribute header, packet data is added
>          * in skb_zerocopy()
>          */
> @@ -532,9 +551,10 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
>                 err = -ENOBUFS;
>                 goto out;
>         }
> -       nla->nla_len = nla_attr_size(skb->len);
> +       nla->nla_len = nla_attr_size(skb->len - cutlen);
>
> -       err = skb_zerocopy(user_skb, skb, skb->len, hlen);
> +       err = skb_zerocopy(user_skb, skb,
> +                       (cutlen > 0) ? hlen : skb->len, hlen);
I do not think we need to compare cut-len with zero, we can just
subtract it from skb->len.
Ben Pfaff June 9, 2016, 11:50 p.m. UTC | #2
On Tue, Jun 07, 2016 at 10:53:52PM -0700, William Tu wrote:
> The patch adds a new action to support packet truncation.  The new action
> is formatted as 'output(port=n,max_len=m)', as output to port n, with
> packet size being MIN(original_size, m).
> 
> One use case is to enable port mirroring to send smaller packets to the
> destination port so that only useful packet information is mirrored/copied,
> saving some performance overhead of copying entire packet payload.  Example
> use case is below as well as shown in the testcases:
> 
>     - Output to port 1 with max_len 100 bytes.
>     - The output packet size on port 1 will be MIN(original_packet_size, 100).
>     # ovs-ofctl add-flow br0 'actions=output(port=1,max_len=100)'
> 
>     - The scope of max_len is limited to output action itself.  The following
>       packet size of output:1 and output:2 will be intact.
>     # ovs-ofctl add-flow br0 \
>             'actions=output(port=1,max_len=100),output:1,output:2'
>     - The Datapath actions shows:
>     # Datapath actions: trunc(100),1,1,2
> 
> Signed-off-by: William Tu <u9012063@gmail.com>

I'm a bit nervous about cutlen.  Can a packet change, for example by
popping a VLAN header, after cutlen is set?  If so, can this cause the
packet length to drop below 0, or below 14?

xlate_output_trunc_action() disallows all special OFPP_* ports.  Is
there a reason to disallow output_trunc to OFPP_LOCAL or OFPP_IN_PORT?

If special OFPP_* ports are disallowed, should we disallow them at
action decode time, in ofp-actions.c, instead of waiting until action
translation?

In xlate_output_trunc_action(), s/unipredicable/unpredictable/.  Also in
the same error message it's probably worth giving the name or the number
of the output port.

Thanks,

Ben.
William Tu June 10, 2016, 12:32 a.m. UTC | #3
Hi Ben,

Thanks for the feedback.

>> Signed-off-by: William Tu <u9012063@gmail.com>
>
> I'm a bit nervous about cutlen.  Can a packet change, for example by
> popping a VLAN header, after cutlen is set?  If so, can this cause the
> packet length to drop below 0, or below 14?
>

I don't want this happen. As a result, at OpenFlow side I only expose
output(max_len=n,port=m) and in datapath, making truncate action
immediately followed by output action. So, after cutlen is set, it
immediately outputs to a port.

However, if the output port is a patch port, then we might have other
actions come in between truncate and output action. And if it is
popping a vlan header, then packet length could drop to below 60 byte
or lower depends on number of vlan_pop. For this reason, the current
patch disallows output to the patch port.

For truncate and output to tunnel port (userspace datapath), we apply
cutlen on the cloned packet before push and pop.

> xlate_output_trunc_action() disallows all special OFPP_* ports.  Is
> there a reason to disallow output_trunc to OFPP_LOCAL or OFPP_IN_PORT?
>
In the beginning I couldn't think of any use case of OFPP_LOCAL or
OFPP_IN_PORT so I disallow them. I could enable it in next version.

> If special OFPP_* ports are disallowed, should we disallow them at
> action decode time, in ofp-actions.c, instead of waiting until action
> translation?
>
yes I will do that.

> In xlate_output_trunc_action(), s/unipredicable/unpredictable/.  Also in
> the same error message it's probably worth giving the name or the number
> of the output port.
>
Thanks,
William
Ben Pfaff June 10, 2016, 2:38 a.m. UTC | #4
On Thu, Jun 09, 2016 at 05:32:05PM -0700, William Tu wrote:
> >> Signed-off-by: William Tu <u9012063@gmail.com>
> >
> > I'm a bit nervous about cutlen.  Can a packet change, for example by
> > popping a VLAN header, after cutlen is set?  If so, can this cause the
> > packet length to drop below 0, or below 14?
> >
> 
> I don't want this happen. As a result, at OpenFlow side I only expose
> output(max_len=n,port=m) and in datapath, making truncate action
> immediately followed by output action. So, after cutlen is set, it
> immediately outputs to a port.

I understand that's what userspace does.  What about in the datapath?
Do the datapaths properly handle it if userspace adds a flow that does
"truncate, pop_vlan, ..., output"?  The kernel datapath, in particular,
should be able to handle malicious or buggy userspace.

> However, if the output port is a patch port, then we might have other
> actions come in between truncate and output action. And if it is
> popping a vlan header, then packet length could drop to below 60 byte
> or lower depends on number of vlan_pop. For this reason, the current
> patch disallows output to the patch port.

That restriction seems OK.  It can always be relaxed later.

> > xlate_output_trunc_action() disallows all special OFPP_* ports.  Is
> > there a reason to disallow output_trunc to OFPP_LOCAL or OFPP_IN_PORT?
> >
> In the beginning I couldn't think of any use case of OFPP_LOCAL or
> OFPP_IN_PORT so I disallow them. I could enable it in next version.

Thanks.
Pravin Shelar June 10, 2016, 4:25 a.m. UTC | #5
On Thu, Jun 9, 2016 at 7:38 PM, Ben Pfaff <blp@ovn.org> wrote:
> On Thu, Jun 09, 2016 at 05:32:05PM -0700, William Tu wrote:
>> >> Signed-off-by: William Tu <u9012063@gmail.com>
>> >
>> > I'm a bit nervous about cutlen.  Can a packet change, for example by
>> > popping a VLAN header, after cutlen is set?  If so, can this cause the
>> > packet length to drop below 0, or below 14?
>> >
>>
>> I don't want this happen. As a result, at OpenFlow side I only expose
>> output(max_len=n,port=m) and in datapath, making truncate action
>> immediately followed by output action. So, after cutlen is set, it
>> immediately outputs to a port.
>
> I understand that's what userspace does.  What about in the datapath?
> Do the datapaths properly handle it if userspace adds a flow that does
> "truncate, pop_vlan, ..., output"?  The kernel datapath, in particular,
> should be able to handle malicious or buggy userspace.
>

There is check in truncate action at flow install. we can check the
same at output.
William Tu June 10, 2016, 5:35 a.m. UTC | #6
>> I understand that's what userspace does.  What about in the datapath?
>> Do the datapaths properly handle it if userspace adds a flow that does
>> "truncate, pop_vlan, ..., output"?  The kernel datapath, in particular,
>> should be able to handle malicious or buggy userspace.
>>
>
> There is check in truncate action at flow install. we can check the
> same at output.

Thanks, I will add this check at output.
diff mbox

Patch

diff --git a/datapath/actions.c b/datapath/actions.c
index dcf8591..92ee3f9 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -778,6 +778,7 @@  static int output_userspace(struct datapath *dp, struct sk_buff *skb,
 	memset(&upcall, 0, sizeof(upcall));
 	upcall.cmd = OVS_PACKET_CMD_ACTION;
 	upcall.mru = OVS_CB(skb)->mru;
+	upcall.cutlen = OVS_CB(skb)->cutlen;
 
 	for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
 		 a = nla_next(a, &rem)) {
@@ -854,10 +855,17 @@  static int sample(struct datapath *dp, struct sk_buff *skb,
 		return 0;
 
 	/* The only known usage of sample action is having a single user-space
+	 * action, or having a truncate action followed by a single user-space
 	 * action. Treat this usage as a special case.
 	 * The output_userspace() should clone the skb to be sent to the
-	 * user space. This skb will be consumed by its caller.
-	 */
+	 * user space. This skb will be consumed by its caller. */
+	if (unlikely(nla_type(a) == OVS_ACTION_ATTR_TRUNC)) {
+		struct ovs_action_trunc *trunc = nla_data(a);
+		OVS_CB(skb)->cutlen = skb->len > trunc->max_len ?
+						skb->len - trunc->max_len : 0;
+		a = nla_next(a, &rem);
+	}
+
 	if (likely(nla_type(a) == OVS_ACTION_ATTR_USERSPACE &&
 		   nla_is_last(a, rem)))
 		return output_userspace(dp, skb, key, a, actions, actions_len);
@@ -1040,10 +1048,15 @@  static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 	for (a = attr, rem = len; rem > 0;
 	     a = nla_next(a, &rem)) {
 		int err = 0;
+		int cutlen = OVS_CB(skb)->cutlen;
 
 		if (unlikely(prev_port != -1)) {
 			struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);
 
+			if (cutlen > 0) {
+				pskb_trim(out_skb, out_skb->len - cutlen);
+				OVS_CB(skb)->cutlen = 0;
+			}
 			if (out_skb)
 				do_output(dp, out_skb, prev_port, key);
 
@@ -1055,6 +1068,16 @@  static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 			prev_port = nla_get_u32(a);
 			break;
 
+		case OVS_ACTION_ATTR_TRUNC: {
+			struct ovs_action_trunc *trunc = nla_data(a);
+
+			if (trunc->max_len < ETH_MIN_FRAME_LEN)
+				return -EINVAL;
+			OVS_CB(skb)->cutlen = skb->len > trunc->max_len ?
+						skb->len - trunc->max_len : 0;
+			break;
+		}
+
 		case OVS_ACTION_ATTR_USERSPACE:
 			output_userspace(dp, skb, key, a, attr, len);
 			break;
@@ -1125,8 +1148,15 @@  static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 		}
 	}
 
-	if (prev_port != -1)
+	if (prev_port != -1) {
+		uint32_t cutlen = OVS_CB(skb)->cutlen;
+
+		if (cutlen > 0) {
+			pskb_trim(skb, skb->len - cutlen);
+			OVS_CB(skb)->cutlen = 0;
+		}
 		do_output(dp, skb, prev_port, key);
+	}
 	else
 		consume_skb(skb);
 
diff --git a/datapath/datapath.c b/datapath/datapath.c
index 5bec072..958dfb8 100644
--- a/datapath/datapath.c
+++ b/datapath/datapath.c
@@ -280,6 +280,7 @@  void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
 		upcall.cmd = OVS_PACKET_CMD_MISS;
 		upcall.portid = ovs_vport_find_upcall_portid(p, skb);
 		upcall.mru = OVS_CB(skb)->mru;
+		upcall.cutlen = OVS_CB(skb)->cutlen;
 		error = ovs_dp_upcall(dp, skb, key, &upcall);
 		if (unlikely(error))
 			kfree_skb(skb);
@@ -409,6 +410,10 @@  static size_t upcall_msg_size(const struct dp_upcall_info *upcall_info,
 	if (upcall_info->mru)
 		size += nla_total_size(sizeof(upcall_info->mru));
 
+	/* OVS_PACKET_ATTR_CUTLEN */
+	if (upcall_info->cutlen)
+		size += nla_total_size(sizeof(upcall_info->cutlen));
+
 	return size;
 }
 
@@ -439,6 +444,7 @@  static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
 	size_t len;
 	unsigned int hlen;
 	int err, dp_ifindex;
+	int cutlen = OVS_CB(skb)->cutlen;
 
 	dp_ifindex = get_dpifindex(dp);
 	if (!dp_ifindex)
@@ -475,6 +481,9 @@  static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
 	else
 		hlen = skb->len;
 
+	if (cutlen > 0)
+		hlen -= cutlen;
+
 	len = upcall_msg_size(upcall_info, hlen);
 	user_skb = genlmsg_new_unicast(len, &info, GFP_ATOMIC);
 	if (!user_skb) {
@@ -525,6 +534,16 @@  static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
 		pad_packet(dp, user_skb);
 	}
 
+	/* Add OVS_PACKET_ATTR_CUTLEN */
+	if (upcall_info->cutlen) {
+		if (nla_put_u16(user_skb, OVS_PACKET_ATTR_CUTLEN,
+				upcall_info->cutlen)) {
+			err = -ENOBUFS;
+			goto out;
+		}
+		pad_packet(dp, user_skb);
+	}
+
 	/* Only reserve room for attribute header, packet data is added
 	 * in skb_zerocopy()
 	 */
@@ -532,9 +551,10 @@  static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
 		err = -ENOBUFS;
 		goto out;
 	}
-	nla->nla_len = nla_attr_size(skb->len);
+	nla->nla_len = nla_attr_size(skb->len - cutlen);
 
-	err = skb_zerocopy(user_skb, skb, skb->len, hlen);
+	err = skb_zerocopy(user_skb, skb,
+			(cutlen > 0) ? hlen : skb->len, hlen);
 	if (err)
 		goto out;
 
@@ -548,6 +568,7 @@  static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
 out:
 	if (err)
 		skb_tx_error(skb);
+	OVS_CB(skb)->cutlen = 0;
 	kfree_skb(user_skb);
 	kfree_skb(nskb);
 	return err;
diff --git a/datapath/datapath.h b/datapath/datapath.h
index ceb3372..38b5acf 100644
--- a/datapath/datapath.h
+++ b/datapath/datapath.h
@@ -97,11 +97,13 @@  struct datapath {
  * @input_vport: The original vport packet came in on. This value is cached
  * when a packet is received by OVS.
  * @mru: The maximum received fragement size; 0 if the packet is not
+ * @cutlen: The number of bytes from the packet end to be removed.
  * fragmented.
  */
 struct ovs_skb_cb {
 	struct vport		*input_vport;
 	u16			mru;
+	u16			cutlen;
 };
 #define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)
 
@@ -115,6 +117,7 @@  struct ovs_skb_cb {
  * counter.
  * @egress_tun_info: If nonnull, becomes %OVS_PACKET_ATTR_EGRESS_TUN_KEY.
  * @mru: If not zero, Maximum received IP fragment size.
+ * @cutlen: Number of bytes packet get truncated.
  */
 struct dp_upcall_info {
 	struct ip_tunnel_info *egress_tun_info;
@@ -125,6 +128,7 @@  struct dp_upcall_info {
 	u32 portid;
 	u8 cmd;
 	u16 mru;
+	u16 cutlen;
 };
 
 /**
diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c
index 6ffcc53..6d6cd37 100644
--- a/datapath/flow_netlink.c
+++ b/datapath/flow_netlink.c
@@ -2181,6 +2181,7 @@  static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
 			[OVS_ACTION_ATTR_SAMPLE] = (u32)-1,
 			[OVS_ACTION_ATTR_HASH] = sizeof(struct ovs_action_hash),
 			[OVS_ACTION_ATTR_CT] = (u32)-1,
+			[OVS_ACTION_ATTR_TRUNC] = sizeof(struct ovs_action_trunc),
 		};
 		const struct ovs_action_push_vlan *vlan;
 		int type = nla_type(a);
@@ -2207,6 +2208,14 @@  static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
 				return -EINVAL;
 			break;
 
+		case OVS_ACTION_ATTR_TRUNC: {
+			const struct ovs_action_trunc *trunc = nla_data(a);
+
+			if (trunc->max_len < ETH_MIN_FRAME_LEN)
+				return -EINVAL;
+			break;
+		}
+
 		case OVS_ACTION_ATTR_HASH: {
 			const struct ovs_action_hash *act_hash = nla_data(a);
 
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 3b39ebb..69ec486 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -207,6 +207,7 @@  enum ovs_packet_attr {
 	OVS_PACKET_ATTR_PROBE,      /* Packet operation is a feature probe,
 				       error logging should be suppressed. */
 	OVS_PACKET_ATTR_MRU,	    /* Maximum received IP fragment size. */
+	OVS_PACKET_ATTR_CUTLEN,     /* Number of bytes cut before upcall. */
 	__OVS_PACKET_ATTR_MAX
 };
 
@@ -600,6 +601,11 @@  enum ovs_userspace_attr {
 
 #define OVS_USERSPACE_ATTR_MAX (__OVS_USERSPACE_ATTR_MAX - 1)
 
+struct ovs_action_trunc {
+   uint32_t max_len; /* Max packet size in bytes. */
+};
+#define ETH_MIN_FRAME_LEN 60
+
 /**
  * struct ovs_action_push_mpls - %OVS_ACTION_ATTR_PUSH_MPLS action argument.
  * @mpls_lse: MPLS label stack entry to push.
@@ -742,6 +748,7 @@  enum ovs_nat_attr {
  * enum ovs_action_attr - Action types.
  *
  * @OVS_ACTION_ATTR_OUTPUT: Output packet to port.
+ * @OVS_ACTION_ATTR_TRUNC: Output packet to port with truncated packet size.
  * @OVS_ACTION_ATTR_USERSPACE: Send packet to userspace according to nested
  * %OVS_USERSPACE_ATTR_* attributes.
  * @OVS_ACTION_ATTR_PUSH_VLAN: Push a new outermost 802.1Q header onto the
@@ -802,6 +809,7 @@  enum ovs_action_attr {
 				       * The data must be zero for the unmasked
 				       * bits. */
 	OVS_ACTION_ATTR_CT,           /* Nested OVS_CT_ATTR_* . */
+	OVS_ACTION_ATTR_TRUNC,        /* u16 struct ovs_action_trunc. */
 
 #ifndef __KERNEL__
 	OVS_ACTION_ATTR_TUNNEL_PUSH,   /* struct ovs_action_push_tnl*/
diff --git a/datapath/vport.c b/datapath/vport.c
index 44b9dfb..3ba4c01 100644
--- a/datapath/vport.c
+++ b/datapath/vport.c
@@ -487,6 +487,7 @@  int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
 
 	OVS_CB(skb)->input_vport = vport;
 	OVS_CB(skb)->mru = 0;
+	OVS_CB(skb)->cutlen = 0;
 	if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
 		u32 mark;
 
diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index 038ef87..11e48ff 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -108,6 +108,7 @@ 
     OFPACT(UNROLL_XLATE,    ofpact_unroll_xlate, ofpact, "unroll_xlate") \
     OFPACT(CT,              ofpact_conntrack,   ofpact, "ct")           \
     OFPACT(NAT,             ofpact_nat,         ofpact, "nat")          \
+    OFPACT(OUTPUT_TRUNC,    ofpact_output_trunc,ofpact, "output_trunc") \
                                                                         \
     /* Debugging actions.                                               \
      *                                                                  \
@@ -290,6 +291,15 @@  struct ofpact_output_reg {
     struct mf_subfield src;
 };
 
+/* OFPACT_OUTPUT_TRUNC.
+ *
+ * Used for NXAST_OUTPUT_TRUNC. */
+struct ofpact_output_trunc {
+    struct ofpact ofpact;
+    ofp_port_t port;            /* Output port. */
+    uint16_t max_len;           /* Max send len. */
+};
+
 /* Bundle slave choice algorithm to apply.
  *
  * In the descriptions below, 'slaves' is the list of possible slaves in the
diff --git a/lib/dp-packet.c b/lib/dp-packet.c
index 0c85d50..4ee2f56 100644
--- a/lib/dp-packet.c
+++ b/lib/dp-packet.c
@@ -30,6 +30,7 @@  dp_packet_init__(struct dp_packet *b, size_t allocated, enum dp_packet_source so
     dp_packet_reset_offsets(b);
     pkt_metadata_init(&b->md, 0);
     dp_packet_rss_invalidate(b);
+    dp_packet_reset_cutlen(b);
 }
 
 static void
diff --git a/lib/dp-packet.h b/lib/dp-packet.h
index 118c84d..bad3bbb 100644
--- a/lib/dp-packet.h
+++ b/lib/dp-packet.h
@@ -60,6 +60,7 @@  struct dp_packet {
                                     * or UINT16_MAX. */
     uint16_t l4_ofs;               /* Transport-level header offset,
                                       or UINT16_MAX. */
+    uint16_t cutlen;               /* length in bytes to cut from the end. */
     union {
         struct pkt_metadata md;
         uint64_t data[DP_PACKET_CONTEXT_SIZE / 8];
@@ -494,6 +495,31 @@  dp_packet_set_allocated(struct dp_packet *b, uint16_t s)
 }
 #endif
 
+static inline void
+dp_packet_reset_cutlen(struct dp_packet *b)
+{
+    b->cutlen = 0;
+}
+
+static inline uint32_t
+dp_packet_set_cutlen(struct dp_packet *b, uint16_t max_len)
+{
+    if (max_len < ETH_MIN_FRAME_LEN ||
+        max_len >= dp_packet_size(b)) {
+        b->cutlen = 0;
+    }
+    else {
+        b->cutlen = dp_packet_size(b) - max_len;
+    }
+    return b->cutlen;
+}
+
+static inline uint32_t
+dp_packet_get_cutlen(struct dp_packet *b)
+{
+    return b->cutlen;
+}
+
 static inline void *
 dp_packet_data(const struct dp_packet *b)
 {
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 61a939a..96513a6 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -4047,6 +4047,7 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_TUNNEL_PUSH:
         if (*depth < MAX_RECIRC_DEPTH) {
             struct dp_packet_batch tnl_pkt;
+            struct dp_packet **orig_packets = packets_->packets;
             int err;
 
             if (!may_steal) {
@@ -4054,6 +4055,19 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
                 packets_ = &tnl_pkt;
             }
 
+            for (int i = 0; i < packets_->count; i++) {
+                /* if may_steal, then opacket == packet. */
+                struct dp_packet *orig_packet = orig_packets[i];
+                struct dp_packet *packet = packets_->packets[i];
+                uint32_t cutlen = dp_packet_get_cutlen(orig_packet);
+
+                if (cutlen > 0) {
+                    dp_packet_set_size(packet,
+                            dp_packet_size(packet) - cutlen);
+                    dp_packet_reset_cutlen(orig_packet);
+                }
+            }
+
             err = push_tnl_action(pmd, a, packets_);
             if (!err) {
                 (*depth)++;
@@ -4066,6 +4080,7 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
 
     case OVS_ACTION_ATTR_TUNNEL_POP:
         if (*depth < MAX_RECIRC_DEPTH) {
+            struct dp_packet **orig_packets = packets_->packets;
             odp_port_t portno = u32_to_odp(nl_attr_get_u32(a));
 
             p = pmd_tx_port_cache_lookup(pmd, portno);
@@ -4074,8 +4089,21 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
                 int i;
 
                 if (!may_steal) {
-                   dp_packet_batch_clone(&tnl_pkt, packets_);
-                   packets_ = &tnl_pkt;
+                    dp_packet_batch_clone(&tnl_pkt, packets_);
+                    packets_ = &tnl_pkt;
+                }
+
+                for (int i = 0; i < packets_->count; i++) {
+                    /* if may_steal, then opacket == packet. */
+                    struct dp_packet *orig_packet = orig_packets[i];
+                    struct dp_packet *packet = packets_->packets[i];
+                    uint32_t cutlen = dp_packet_get_cutlen(orig_packet);
+
+                    if (cutlen > 0) {
+                        dp_packet_set_size(packet,
+                                dp_packet_size(packet) - cutlen);
+                        dp_packet_reset_cutlen(orig_packet);
+                    }
                 }
 
                 netdev_pop_header(p->netdev, packets_);
@@ -4160,6 +4188,7 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_SAMPLE:
     case OVS_ACTION_ATTR_HASH:
     case OVS_ACTION_ATTR_UNSPEC:
+    case OVS_ACTION_ATTR_TRUNC:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
     }
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index 9bff3a8..0159804 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -1969,7 +1969,8 @@  parse_odp_packet(const struct dpif_netlink *dpif, struct ofpbuf *buf,
         [OVS_PACKET_ATTR_USERDATA] = { .type = NL_A_UNSPEC, .optional = true },
         [OVS_PACKET_ATTR_EGRESS_TUN_KEY] = { .type = NL_A_NESTED, .optional = true },
         [OVS_PACKET_ATTR_ACTIONS] = { .type = NL_A_NESTED, .optional = true },
-        [OVS_PACKET_ATTR_MRU] = { .type = NL_A_U16, .optional = true }
+        [OVS_PACKET_ATTR_MRU] = { .type = NL_A_U16, .optional = true },
+        [OVS_PACKET_ATTR_CUTLEN] = { .type = NL_A_U16, .optional = true },
     };
 
     struct ofpbuf b = ofpbuf_const_initializer(buf->data, buf->size);
@@ -2002,6 +2003,7 @@  parse_odp_packet(const struct dpif_netlink *dpif, struct ofpbuf *buf,
     upcall->out_tun_key = a[OVS_PACKET_ATTR_EGRESS_TUN_KEY];
     upcall->actions = a[OVS_PACKET_ATTR_ACTIONS];
     upcall->mru = a[OVS_PACKET_ATTR_MRU];
+    upcall->cutlen = a[OVS_PACKET_ATTR_CUTLEN];
 
     /* Allow overwriting the netlink attribute header without reallocating. */
     dp_packet_use_stub(&upcall->packet,
diff --git a/lib/dpif.c b/lib/dpif.c
index c4f24c7..92f37f8 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1092,6 +1092,7 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
     struct dpif_execute_helper_aux *aux = aux_;
     int type = nl_attr_type(action);
     struct dp_packet *packet = packets_->packets[0];
+    struct dp_packet *trunc_packet = NULL, *orig_packet;
 
     ovs_assert(packets_->count == 1);
 
@@ -1107,6 +1108,7 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
         uint64_t stub[256 / 8];
         struct pkt_metadata *md = &packet->md;
         bool dst_set;
+        uint32_t cutlen = dp_packet_get_cutlen(packet);
 
         dst_set = flow_tnl_dst_is_set(&md->tunnel);
         if (dst_set) {
@@ -1124,6 +1126,18 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
             execute.actions_len = NLA_ALIGN(action->nla_len);
         }
 
+        orig_packet = packet;
+
+        if (cutlen > 0) {
+            if (!may_steal) {
+               trunc_packet = dp_packet_clone(packet);
+               packet = trunc_packet;
+            }
+            /* Truncation applies to the clone packet or the original
+             * packet with may_steal == true. */
+            dp_packet_set_size(packet, dp_packet_size(orig_packet) - cutlen);
+        }
+
         execute.packet = packet;
         execute.flow = aux->flow;
         execute.needs_help = false;
@@ -1135,6 +1149,14 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
         if (dst_set) {
             ofpbuf_uninit(&execute_actions);
         }
+
+        /* Reset the truncation state so next output action is intact. */
+        if (cutlen > 0) {
+            dp_packet_reset_cutlen(orig_packet);
+            if (!may_steal) {
+                dp_packet_delete(trunc_packet);
+            }
+        }
         break;
     }
 
@@ -1146,6 +1168,7 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_SET:
     case OVS_ACTION_ATTR_SET_MASKED:
     case OVS_ACTION_ATTR_SAMPLE:
+    case OVS_ACTION_ATTR_TRUNC:
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
diff --git a/lib/dpif.h b/lib/dpif.h
index 6788301..981868c 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -784,6 +784,7 @@  struct dpif_upcall {
     size_t key_len;             /* Length of 'key' in bytes. */
     ovs_u128 ufid;              /* Unique flow identifier for 'key'. */
     struct nlattr *mru;         /* Maximum receive unit. */
+    struct nlattr *cutlen;      /* Number of bytes shrink from the end. */
 
     /* DPIF_UC_ACTION only. */
     struct nlattr *userdata;    /* Argument to OVS_ACTION_ATTR_USERSPACE. */
diff --git a/lib/netdev-bsd.c b/lib/netdev-bsd.c
index 43fa982..7fc0888 100644
--- a/lib/netdev-bsd.c
+++ b/lib/netdev-bsd.c
@@ -698,6 +698,11 @@  netdev_bsd_send(struct netdev *netdev_, int qid OVS_UNUSED,
     for (i = 0; i < cnt; i++) {
         const void *data = dp_packet_data(pkts[i]);
         size_t size = dp_packet_size(pkts[i]);
+        uint32_t cutlen = dp_packet_get_cutlen(pkts[i]);
+
+        if (cutlen > 0) {
+            size -= cutlen;
+        }
 
         while (!error) {
             ssize_t retval;
diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
index 22891b2..eb0f18c 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -1602,6 +1602,12 @@  netdev_dpdk_send__(struct netdev_dpdk *dev, int qid,
 
         for (i = 0; i < cnt; i++) {
             int size = dp_packet_size(pkts[i]);
+            uint32_t cutlen = dp_packet_get_cutlen(pkts[i]);
+
+            if (cutlen > 0) {
+                size -= cutlen;
+                dp_packet_set_size(pkts[i], size);
+            }
 
             if (OVS_UNLIKELY(size > dev->max_packet_len)) {
                 if (next_tx_idx != i) {
diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
index 54a1152..dd201f9 100644
--- a/lib/netdev-dummy.c
+++ b/lib/netdev-dummy.c
@@ -1028,6 +1028,11 @@  netdev_dummy_send(struct netdev *netdev, int qid OVS_UNUSED,
     for (i = 0; i < cnt; i++) {
         const void *buffer = dp_packet_data(pkts[i]);
         size_t size = dp_packet_size(pkts[i]);
+        uint32_t cutlen = dp_packet_get_cutlen(pkts[i]);
+
+        if (cutlen > 0) {
+            size -= cutlen;
+        }
 
         if (size < ETH_HEADER_LEN) {
             error = EMSGSIZE;
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index 82813ba..082ca06 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -1169,6 +1169,11 @@  netdev_linux_send(struct netdev *netdev_, int qid OVS_UNUSED,
         const void *data = dp_packet_data(pkts[i]);
         size_t size = dp_packet_size(pkts[i]);
         ssize_t retval;
+        uint32_t cutlen = dp_packet_get_cutlen(pkts[i]);
+
+        if (cutlen > 0) {
+            size -= cutlen;
+        }
 
         if (!is_tap_netdev(netdev_)) {
             /* Use our AF_PACKET socket to send to this device. */
diff --git a/lib/netdev.c b/lib/netdev.c
index 4be806d..f285ddc 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -681,7 +681,7 @@  netdev_set_tx_multiq(struct netdev *netdev, unsigned int n_txq)
     return error;
 }
 
-/* Sends 'buffers' on 'netdev'.  Returns 0 if successful (for every packet),
+/* Sends 'batch' on 'netdev'.  Returns 0 if successful (for every packet),
  * otherwise a positive errno value.  Returns EAGAIN without blocking if
  * at least one the packets cannot be queued immediately.  Returns EMSGSIZE
  * if a partial packet was transmitted or if a packet is too big or too small
@@ -717,6 +717,12 @@  netdev_send(struct netdev *netdev, int qid, struct dp_packet_batch *batch,
     if (!error) {
         COVERAGE_INC(netdev_sent);
     }
+
+    if (!may_steal) {
+        for (int i = 0; i < batch->count; i++) {
+            dp_packet_reset_cutlen(batch->packets[i]);
+        }
+    }
     return error;
 }
 
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 4239624..5ded210 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -503,6 +503,7 @@  requires_datapath_assistance(const struct nlattr *a)
     case OVS_ACTION_ATTR_HASH:
     case OVS_ACTION_ATTR_PUSH_MPLS:
     case OVS_ACTION_ATTR_POP_MPLS:
+    case OVS_ACTION_ATTR_TRUNC:
         return false;
 
     case OVS_ACTION_ATTR_UNSPEC:
@@ -625,6 +626,16 @@  odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,
             }
             break;
 
+        case OVS_ACTION_ATTR_TRUNC: {
+            const struct ovs_action_trunc *trunc =
+                        nl_attr_get_unspec(a, sizeof *trunc);
+
+            for (i = 0; i < cnt; i++) {
+                dp_packet_set_cutlen(packets[i], trunc->max_len);
+            }
+            break;
+        }
+
         case OVS_ACTION_ATTR_OUTPUT:
         case OVS_ACTION_ATTR_TUNNEL_PUSH:
         case OVS_ACTION_ATTR_TUNNEL_POP:
diff --git a/lib/odp-util.c b/lib/odp-util.c
index d9ace90..8195f21 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -107,6 +107,7 @@  odp_action_len(uint16_t type)
 
     switch ((enum ovs_action_attr) type) {
     case OVS_ACTION_ATTR_OUTPUT: return sizeof(uint32_t);
+    case OVS_ACTION_ATTR_TRUNC: return sizeof(struct ovs_action_trunc);
     case OVS_ACTION_ATTR_TUNNEL_PUSH: return ATTR_LEN_VARIABLE;
     case OVS_ACTION_ATTR_TUNNEL_POP: return sizeof(uint32_t);
     case OVS_ACTION_ATTR_USERSPACE: return ATTR_LEN_VARIABLE;
@@ -775,6 +776,14 @@  format_odp_action(struct ds *ds, const struct nlattr *a)
     case OVS_ACTION_ATTR_OUTPUT:
         ds_put_format(ds, "%"PRIu32, nl_attr_get_u32(a));
         break;
+    case OVS_ACTION_ATTR_TRUNC: {
+        const struct ovs_action_trunc *trunc =
+                       nl_attr_get_unspec(a, sizeof *trunc);
+
+        ds_put_format(ds, "trunc(%"PRIu16")", trunc->max_len);
+        break;
+    }
+    break;
     case OVS_ACTION_ATTR_TUNNEL_POP:
         ds_put_format(ds, "tnl_pop(%"PRIu32")", nl_attr_get_u32(a));
         break;
@@ -1523,6 +1532,20 @@  parse_odp_action(const char *s, const struct simap *port_names,
         }
     }
 
+    {
+        uint32_t max_len;
+        int n;
+
+        if (ovs_scan(s, "trunc(%"SCNi32")%n", &max_len, &n)) {
+            struct ovs_action_trunc *trunc;
+
+            trunc = nl_msg_put_unspec_uninit(actions,
+                     OVS_ACTION_ATTR_TRUNC, sizeof *trunc);
+            trunc->max_len = max_len;
+            return n;
+        }
+    }
+
     if (port_names) {
         int len = strcspn(s, delimiters);
         struct simap_node *node;
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 7ddadb8..7fb6869 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -299,6 +299,9 @@  enum ofp_raw_action_type {
     /* NX1.0+(36): struct nx_action_nat, ... */
     NXAST_RAW_NAT,
 
+    /* NX1.0+(38): struct nx_action_output_trunc. */
+    NXAST_RAW_OUTPUT_TRUNC,
+
 /* ## ------------------ ## */
 /* ## Debugging actions. ## */
 /* ## ------------------ ## */
@@ -379,6 +382,7 @@  ofpact_next_flattened(const struct ofpact *ofpact)
     case OFPACT_CONTROLLER:
     case OFPACT_ENQUEUE:
     case OFPACT_OUTPUT_REG:
+    case OFPACT_OUTPUT_TRUNC:
     case OFPACT_BUNDLE:
     case OFPACT_SET_FIELD:
     case OFPACT_SET_VLAN_VID:
@@ -536,6 +540,39 @@  encode_OUTPUT(const struct ofpact_output *output,
 }
 
 static char * OVS_WARN_UNUSED_RESULT
+parse_truncate_subfield(struct ofpact_output_trunc *output_trunc,
+                        const char *arg_)
+{
+    char *key, *value;
+    char *arg = CONST_CAST(char *, arg_);
+
+    while (ofputil_parse_key_value(&arg, &key, &value)) {
+        if (!strcmp(key, "port")) {
+            unsigned int port;
+
+            if (!str_to_uint(value, 10, &port)) {
+                return xasprintf("%s: named port is not supported", value);
+            }
+            if (!ofputil_port_from_string(value, &output_trunc->port)) {
+                return xasprintf("%s: output to unknown truncate port",
+                                  value);
+            }
+        } else if (!strcmp(key, "max_len")) {
+            char *err;
+
+            err = str_to_u16(value, key, &output_trunc->max_len);
+            if (err) {
+                return err;
+            }
+        } else {
+            return xasprintf("invalid key '%s' in output_trunc argument",
+                                key);
+        }
+    }
+    return NULL;
+}
+
+static char * OVS_WARN_UNUSED_RESULT
 parse_OUTPUT(const char *arg, struct ofpbuf *ofpacts,
              enum ofputil_protocol *usable_protocols OVS_UNUSED)
 {
@@ -545,6 +582,11 @@  parse_OUTPUT(const char *arg, struct ofpbuf *ofpacts,
         output_reg = ofpact_put_OUTPUT_REG(ofpacts);
         output_reg->max_len = UINT16_MAX;
         return mf_parse_subfield(&output_reg->src, arg);
+    } else if (strstr(arg, "port") && strstr(arg, "max_len")) {
+        struct ofpact_output_trunc *output_trunc;
+
+        output_trunc = ofpact_put_OUTPUT_TRUNC(ofpacts);
+        return parse_truncate_subfield(output_trunc, arg);
     } else {
         struct ofpact_output *output;
 
@@ -5540,6 +5582,64 @@  parse_NAT(char *arg, struct ofpbuf *ofpacts,
     return NULL;
 }
 
+/* Truncate output action. */
+struct nx_action_output_trunc {
+    ovs_be16 type;              /* OFPAT_VENDOR. */
+    ovs_be16 len;               /* At least 16. */
+    ovs_be32 vendor;            /* NX_VENDOR_ID. */
+    ovs_be16 subtype;           /* NXAST_OUTPUT_TRUNC. */
+    ovs_be16 max_len;           /* Truncate packet to size bytes */
+    ovs_be16 port;              /* Output port */
+    uint8_t pad[2];
+};
+OFP_ASSERT(sizeof(struct nx_action_output_trunc) == 16);
+
+static enum ofperr
+decode_NXAST_RAW_OUTPUT_TRUNC(const struct nx_action_output_trunc *natrc,
+                            enum ofp_version ofp_version OVS_UNUSED,
+                            struct ofpbuf *out)
+{
+    struct ofpact_output_trunc *output_trunc;
+
+    output_trunc = ofpact_put_OUTPUT_TRUNC(out);
+    output_trunc->max_len = ntohs(natrc->max_len);
+    output_trunc->port = u16_to_ofp(ntohs(natrc->port));
+
+    if (output_trunc->max_len < ETH_MIN_FRAME_LEN) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
+    return 0;
+}
+
+static void
+encode_OUTPUT_TRUNC(const struct ofpact_output_trunc *output_trunc,
+                  enum ofp_version ofp_version OVS_UNUSED,
+                  struct ofpbuf *out)
+{
+    struct nx_action_output_trunc *natrc = put_NXAST_OUTPUT_TRUNC(out);
+
+    natrc->max_len = htons(output_trunc->max_len);
+    natrc->port = htons(ofp_to_u16(output_trunc->port));
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_OUTPUT_TRUNC(const char *arg, struct ofpbuf *ofpacts OVS_UNUSED,
+                 enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+    /* Disable output_trunc parsing.  Expose as output(port=N,max_len=M) and
+     * reuse parse_OUTPUT to parse output_trunc action. */
+    return xasprintf("unknown action %s", arg);
+}
+
+static void
+format_OUTPUT_TRUNC(const struct ofpact_output_trunc *a, struct ds *s)
+{
+    if (ofp_to_u16(a->port) < ofp_to_u16(OFPP_MAX)) {
+         ds_put_format(s, "%soutput%s(port=%"PRIu16",max_len=%"PRIu16")",
+                       colors.special, colors.end, a->port, a->max_len);
+    }
+}
+
 
 /* Meter instruction. */
 
@@ -5934,6 +6034,7 @@  ofpact_is_set_or_move_action(const struct ofpact *a)
     case OFPACT_NOTE:
     case OFPACT_OUTPUT:
     case OFPACT_OUTPUT_REG:
+    case OFPACT_OUTPUT_TRUNC:
     case OFPACT_POP_MPLS:
     case OFPACT_POP_QUEUE:
     case OFPACT_PUSH_MPLS:
@@ -5962,6 +6063,7 @@  ofpact_is_allowed_in_actions_set(const struct ofpact *a)
     case OFPACT_DEC_TTL:
     case OFPACT_GROUP:
     case OFPACT_OUTPUT:
+    case OFPACT_OUTPUT_TRUNC:
     case OFPACT_POP_MPLS:
     case OFPACT_PUSH_MPLS:
     case OFPACT_PUSH_VLAN:
@@ -6186,6 +6288,7 @@  ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
     case OFPACT_CONTROLLER:
     case OFPACT_ENQUEUE:
     case OFPACT_OUTPUT_REG:
+    case OFPACT_OUTPUT_TRUNC:
     case OFPACT_BUNDLE:
     case OFPACT_SET_VLAN_VID:
     case OFPACT_SET_VLAN_PCP:
@@ -6614,6 +6717,10 @@  ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     case OFPACT_OUTPUT_REG:
         return mf_check_src(&ofpact_get_OUTPUT_REG(a)->src, flow);
 
+    case OFPACT_OUTPUT_TRUNC:
+        return ofpact_check_output_port(ofpact_get_OUTPUT_TRUNC(a)->port,
+                                        max_ports);
+
     case OFPACT_BUNDLE:
         return bundle_check(ofpact_get_BUNDLE(a), max_ports, flow);
 
@@ -7291,6 +7398,7 @@  ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
         return port == OFPP_CONTROLLER;
 
     case OFPACT_OUTPUT_REG:
+    case OFPACT_OUTPUT_TRUNC:
     case OFPACT_BUNDLE:
     case OFPACT_SET_VLAN_VID:
     case OFPACT_SET_VLAN_PCP:
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index fbc82b7..5d26b7c 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1140,6 +1140,7 @@  dpif_sflow_read_actions(const struct flow *flow,
 	    }
 	    break;
 
+	case OVS_ACTION_ATTR_TRUNC:
 	case OVS_ACTION_ATTR_USERSPACE:
 	case OVS_ACTION_ATTR_RECIRC:
 	case OVS_ACTION_ATTR_HASH:
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index 1374950..61e7494 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -207,6 +207,7 @@  struct upcall {
     ofp_port_t in_port;            /* OpenFlow in port, or OFPP_NONE. */
     uint16_t mru;                  /* If !0, Maximum receive unit of
                                       fragmented IP packet */
+    uint16_t cutlen;
 
     enum dpif_upcall_type type;    /* Datapath type of the upcall. */
     const struct nlattr *userdata; /* Userdata for DPIF_UC_ACTION Upcalls. */
@@ -350,7 +351,7 @@  static enum upcall_type classify_upcall(enum dpif_upcall_type type,
 static int upcall_receive(struct upcall *, const struct dpif_backer *,
                           const struct dp_packet *packet, enum dpif_upcall_type,
                           const struct nlattr *userdata, const struct flow *,
-                          const unsigned int mru,
+                          const unsigned int mru, const uint16_t cutlen,
                           const ovs_u128 *ufid, const unsigned pmd_id);
 static void upcall_uninit(struct upcall *);
 
@@ -746,6 +747,7 @@  recv_upcalls(struct handler *handler)
         struct upcall *upcall = &upcalls[n_upcalls];
         struct flow *flow = &flows[n_upcalls];
         unsigned int mru;
+        uint16_t cutlen;
         int error;
 
         ofpbuf_use_stub(recv_buf, recv_stubs[n_upcalls],
@@ -766,8 +768,15 @@  recv_upcalls(struct handler *handler)
             mru = 0;
         }
 
+        if (dupcall->cutlen) {
+            cutlen = nl_attr_get_u16(dupcall->cutlen);
+        } else {
+            cutlen = 0;
+        }
+
         error = upcall_receive(upcall, udpif->backer, &dupcall->packet,
-                               dupcall->type, dupcall->userdata, flow, mru,
+                               dupcall->type, dupcall->userdata,
+                               flow, mru, cutlen,
                                &dupcall->ufid, PMD_ID_NULL);
         if (error) {
             if (error == ENODEV) {
@@ -1009,7 +1018,7 @@  static int
 upcall_receive(struct upcall *upcall, const struct dpif_backer *backer,
                const struct dp_packet *packet, enum dpif_upcall_type type,
                const struct nlattr *userdata, const struct flow *flow,
-               const unsigned int mru,
+               const unsigned int mru, const uint16_t cutlen,
                const ovs_u128 *ufid, const unsigned pmd_id)
 {
     int error;
@@ -1039,6 +1048,7 @@  upcall_receive(struct upcall *upcall, const struct dpif_backer *backer,
     upcall->key = NULL;
     upcall->key_len = 0;
     upcall->mru = mru;
+    upcall->cutlen = cutlen;
 
     upcall->out_tun_key = NULL;
     upcall->actions = NULL;
@@ -1141,7 +1151,7 @@  upcall_cb(const struct dp_packet *packet, const struct flow *flow, ovs_u128 *ufi
     atomic_read_relaxed(&udpif->flow_limit, &flow_limit);
 
     error = upcall_receive(&upcall, udpif->backer, packet, type, userdata,
-                           flow, 0, ufid, pmd_id);
+                           flow, 0, packet->cutlen, ufid, pmd_id);
     if (error) {
         return error;
     }
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index cca5c5c..a2cf7ed 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -3990,6 +3990,55 @@  xlate_output_reg_action(struct xlate_ctx *ctx,
 }
 
 static void
+xlate_output_trunc_action(struct xlate_ctx *ctx,
+                    ofp_port_t port, uint16_t max_len)
+{
+    bool support_trunc = ctx->xbridge->support.trunc;
+    struct ovs_action_trunc *trunc;
+
+    switch (port) {
+    case OFPP_IN_PORT:
+    case OFPP_TABLE:
+    case OFPP_NORMAL:
+    case OFPP_FLOOD:
+    case OFPP_ALL:
+    /* Controller can use max_len in output
+     * action to truncate packets. */
+    case OFPP_CONTROLLER:
+    case OFPP_NONE:
+    case OFPP_LOCAL:
+        xlate_report(ctx, "output_trunc does not support named port");
+        break;
+    default:
+        if (port != ctx->xin->flow.in_port.ofp_port) {
+            const struct xport *xport = get_ofp_port(ctx->xbridge, port);
+
+            if (xport == NULL || xport->odp_port == ODPP_NONE) {
+                /* Since truncate happens at its following output action, if
+                 * the output port is a patch port, the behavior is more
+                 * unipredicable. For simpilicity, disallow this case. */
+                XLATE_REPORT_ERROR(ctx, "bridge %s: "
+                         "output_trunc does not support this output port",
+                          ctx->xbridge->name);
+                break;
+            }
+
+            trunc = nl_msg_put_unspec_uninit(ctx->odp_actions,
+                                OVS_ACTION_ATTR_TRUNC,
+                                sizeof *trunc);
+            trunc->max_len = max_len;
+            xlate_output_action(ctx, port, max_len, false);
+            if (!support_trunc) {
+                ctx->xout->slow |= SLOW_ACTION;
+            }
+        } else {
+            xlate_report(ctx, "skipping output to input port");
+        }
+        break;
+    }
+}
+
+static void
 xlate_enqueue_action(struct xlate_ctx *ctx,
                      const struct ofpact_enqueue *enqueue)
 {
@@ -4274,6 +4323,7 @@  freeze_unroll_actions(const struct ofpact *a, const struct ofpact *end,
     for (; a < end; a = ofpact_next(a)) {
         switch (a->type) {
         case OFPACT_OUTPUT_REG:
+        case OFPACT_OUTPUT_TRUNC:
         case OFPACT_GROUP:
         case OFPACT_OUTPUT:
         case OFPACT_CONTROLLER:
@@ -4525,6 +4575,7 @@  recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
 
     /* Output actions  do not require recirculation. */
     case OFPACT_OUTPUT:
+    case OFPACT_OUTPUT_TRUNC:
     case OFPACT_ENQUEUE:
     case OFPACT_OUTPUT_REG:
     /* Set actions that don't touch L3+ fields do not require recirculation. */
@@ -4874,6 +4925,11 @@  do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             xlate_output_reg_action(ctx, ofpact_get_OUTPUT_REG(a));
             break;
 
+        case OFPACT_OUTPUT_TRUNC:
+            xlate_output_trunc_action(ctx, ofpact_get_OUTPUT_TRUNC(a)->port,
+                                ofpact_get_OUTPUT_TRUNC(a)->max_len);
+            break;
+
         case OFPACT_LEARN:
             xlate_learn_action(ctx, ofpact_get_LEARN(a));
             break;
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 91529fe..0dd174d 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1212,6 +1212,60 @@  check_masked_set_action(struct dpif_backer *backer)
     return !error;
 }
 
+/* Tests whether 'backer''s datapath supports truncation of a packet in
+ * OVS_ACTION_ATTR_TRUNC.  We need to disable some features on older
+ * datapaths that don't support this feature. */
+static bool
+check_trunc_action(struct dpif_backer *backer)
+{
+    struct eth_header *eth;
+    struct ofpbuf actions;
+    struct dpif_execute execute;
+    struct dp_packet packet;
+    struct ovs_action_trunc *trunc;
+    struct flow flow;
+    int error;
+
+    /* Compose an action with output(port:1,
+     *              max_len:OVS_ACTION_OUTPUT_MIN + 1).
+     * This translates to one truncate action and one output action. */
+    ofpbuf_init(&actions, 64);
+    trunc = nl_msg_put_unspec_uninit(&actions,
+                            OVS_ACTION_ATTR_TRUNC, sizeof *trunc);
+
+    trunc->max_len = ETH_MIN_FRAME_LEN + 1;
+    nl_msg_put_odp_port(&actions, OVS_ACTION_ATTR_OUTPUT, u32_to_odp(1));
+
+    /* Compose a dummy Ethernet packet. */
+    dp_packet_init(&packet, ETH_HEADER_LEN);
+    eth = dp_packet_put_zeros(&packet, ETH_HEADER_LEN);
+    eth->eth_type = htons(0x1234);
+
+    flow_extract(&packet, &flow);
+
+    /* Execute the actions.  On older datapaths this fails with EINVAL, on
+     * newer datapaths it succeeds. */
+    execute.actions = actions.data;
+    execute.actions_len = actions.size;
+    execute.packet = &packet;
+    execute.flow = &flow;
+    execute.needs_help = false;
+    execute.probe = true;
+    execute.mtu = 0;
+
+    error = dpif_execute(backer->dpif, &execute);
+
+    dp_packet_uninit(&packet);
+    ofpbuf_uninit(&actions);
+
+    if (error) {
+        /* Truncate action is not supported. */
+        VLOG_INFO("%s: Datapath does not support truncate action",
+                  dpif_name(backer->dpif));
+    }
+    return !error;
+}
+
 #define CHECK_FEATURE__(NAME, SUPPORT, FIELD, VALUE)                        \
 static bool                                                                 \
 check_##NAME(struct dpif_backer *backer)                                    \
@@ -1263,6 +1317,7 @@  check_support(struct dpif_backer *backer)
     backer->support.odp.recirc = check_recirc(backer);
     backer->support.odp.max_mpls_depth = check_max_mpls_depth(backer);
     backer->support.masked_set_action = check_masked_set_action(backer);
+    backer->support.trunc = check_trunc_action(backer);
     backer->support.ufid = check_ufid(backer);
     backer->support.tnl_push_pop = dpif_supports_tnl_push_pop(backer->dpif);
 
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index 9e03b01..4034475 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -90,6 +90,9 @@  struct dpif_backer_support {
     /* True if the datapath supports OVS_FLOW_ATTR_UFID. */
     bool ufid;
 
+    /* True if the datapath supports OVS_ACTION_ATTR_TRUNC action. */
+    bool trunc;
+
     /* Each member represents support for related OVS_KEY_ATTR_* fields. */
     struct odp_support odp;
 };
diff --git a/tests/odp.at b/tests/odp.at
index 808a83b..9be730a 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -329,6 +329,7 @@  ct(commit,nat(src=fe80::20c:29ff:fe88:a18b,random))
 ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b,random))
 ct(commit,nat(src=[[fe80::20c:29ff:fe88:1]]-[[fe80::20c:29ff:fe88:a18b]]:255-4096,random))
 ct(commit,helper=ftp,nat(src=10.1.1.240-10.1.1.255))
+trunc(100)
 ])
 AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0],
   [`cat actions.txt`
diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at
index 83a2301..bd9cf41 100644
--- a/tests/ofp-actions.at
+++ b/tests/ofp-actions.at
@@ -238,6 +238,9 @@  fe800000 00000000 020c 29ff fe88 0001 dnl
 fe800000 00000000 020c 29ff fe88 a18b dnl
 00ff1000 00000000
 
+# actions=output(port=1,max_len=100)
+ffff 0010 00002320 0026 0064 00010000
+
 # bad OpenFlow10 actions: NXBRC_MUST_BE_ZERO
 ffff 0018 00002320 0025 0000 0005 0000 1122334455 000005
 
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index d0aacfa..399c895 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -5322,6 +5322,130 @@  PORTNAME
 	portName=p2
 ])])
 
+AT_SETUP([ofproto-dpif - basic truncate action])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2 3 4 5
+
+AT_CHECK([ovs-vsctl -- set Interface p1 type=dummy options:pcap=p1.pcap])
+AT_CHECK([ovs-vsctl -- set Interface p2 type=dummy options:pstream=punix:p2.sock])
+AT_CHECK([ovs-vsctl -- set Interface p3 type=dummy   options:stream=unix:p2.sock])
+AT_CHECK([ovs-vsctl -- set Interface p4 type=dummy options:pstream=punix:p4.sock])
+AT_CHECK([ovs-vsctl -- set Interface p5 type=dummy   options:stream=unix:p4.sock])
+
+AT_DATA([flows.txt], [dnl
+in_port=3,actions=drop
+in_port=5,actions=drop
+in_port=1,actions=output(port=2,max_len=64),output:4
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl Datapath actions
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=4,ttl=128,frag=no),tcp(src=8,dst=9)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+[Datapath actions: trunc(64),2,4
+])
+
+dnl An 170 byte packet
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '000c29c8a0a4005056c0000808004500009cb4a6000040019003c0a8da01c0a8da640800cb5fa762000556f431ad0009388e08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f'])
+
+AT_CHECK([ovs-ofctl parse-pcap p1.pcap], [0], [dnl
+icmp,in_port=ANY,vlan_tci=0x0000,dl_src=00:50:56:c0:00:08,dl_dst=00:0c:29:c8:a0:a4,nw_src=192.168.218.1,nw_dst=192.168.218.100,nw_tos=0,nw_ecn=0,nw_ttl=64,icmp_type=8,icmp_code=0
+])
+
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+dnl packet with truncated size
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=64
+])
+dnl packet with original size
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=170
+])
+
+dnl More complicated case
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_DATA([flows.txt], [dnl
+in_port=3,actions=drop
+in_port=5,actions=drop
+in_port=1,actions=output(port=2,max_len=64),output(port=2,max_len=128),output(port=4,max_len=60),output:2,output:4
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl Datapath actions
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=4,ttl=128,frag=no),tcp(src=8,dst=9)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+[Datapath actions: trunc(64),2,trunc(128),2,trunc(60),4,2,4
+])
+
+dnl An 170 byte packet
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '000c29c8a0a4005056c0000808004500009cb4a6000040019003c0a8da01c0a8da640800cb5fa762000556f431ad0009388e08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f'])
+
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+dnl packet size: 64 + 128 + 170 = 362
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=362
+])
+dnl packet size: 60 + 170 = 230
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=230
+])
+
+dnl syntax checking
+AT_CHECK([ovs-ofctl add-flow br0 'actions=output(port=ALL,max_len=100)'], [1], [], [dnl
+ovs-ofctl: ALL: named port is not supported
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - truncate and output to patch port])
+OVS_VSWITCHD_START([add-br br1 \
+-- set bridge br1 datapath-type=dummy fail-mode=secure \
+-- add-port br1 pbr1 -- set int pbr1 type=patch options:peer=pbr0 ofport_request=1 \
+-- add-port br0 pbr0 -- set int pbr0 type=patch options:peer=pbr1])
+
+add_of_ports br0 2
+
+AT_CHECK([ovs-ofctl add-flow br0 actions='output(port=1,max_len=100),output:2'])
+AT_CHECK([ovs-ofctl add-flow br1 actions=NORMAL])
+
+AT_CHECK([ovs-appctl ofproto/trace br0 in_port=LOCAL,dl_src=10:20:30:40:50:60],
+[0], [stdout])
+AT_CHECK([tail -1 stdout], [0], [Datapath actions: 2
+])
+dnl the output(port=1,max_len=100) fails the translation, only output:2 in datapath
+AT_CHECK([grep "output_trunc does not support this output port" stdout], [0], [stdout])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - truncate and output to gre tunnel])
+OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=gre \
+                    options:remote_ip=1.1.1.1 options:local_ip=2.2.2.2 \
+                    options:key=5 ofport_request=1\
+                    -- add-port br0 p2 -- set Interface p2 type=dummy \
+                    ofport_request=2])
+AT_DATA([flows.txt], [dnl
+actions=output(max_len=100, port=1)
+])
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
+		br0 65534/100: (dummy)
+		p1 1/1: (gre: key=5, local_ip=2.2.2.2, remote_ip=1.1.1.1)
+		p2 2/2: (dummy)
+])
+
+dnl Basic
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),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=6,tos=4,ttl=128,frag=no),tcp(src=8,dst=9)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: trunc(100),set(tunnel(tun_id=0x5,src=2.2.2.2,dst=1.1.1.1,ttl=64,flags(df|key))),1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto-dpif - sFlow packet sampling - IPv4 collector])
 CHECK_SFLOW_SAMPLING_PACKET([127.0.0.1])
 AT_CLEANUP
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index 8287cd2..fa7ed48 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -381,6 +381,8 @@  ip,actions=ct(commit,exec(load(0x1->NXM_NX_CT_LABEL[])))
 ip,actions=ct(commit,exec(load(0x1234567890ABCDEF->NXM_NX_CT_LABEL[32..95])))
 ip,actions=ct(commit,exec(set_field(0x1->ct_label)))
 ip,ct_state=+trk,ct_label=0x1234567890abcdef12345678,actions=ct(commit)
+actions=output(max_len=100,port=123)
+actions=output(port=100,max_len=123)
 ]])
 
 AT_CHECK([ovs-ofctl parse-flows flows.txt
@@ -423,6 +425,8 @@  NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[
 NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1234567890abcdef->NXM_NX_CT_LABEL[32..95]))
 NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[0..63],load:0->NXM_NX_CT_LABEL[64..127]))
 NXT_FLOW_MOD: ADD table:255 ct_state=+trk,ct_label=0x1234567890abcdef12345678,ip actions=ct(commit)
+NXT_FLOW_MOD: ADD table:255 actions=output(port=123,max_len=100)
+NXT_FLOW_MOD: ADD table:255 actions=output(port=100,max_len=123)
 ]])
 AT_CLEANUP
 
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index ceaba62..7c82514 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -250,6 +250,174 @@  NS_CHECK_EXEC([at_ns0], [ping -s 3200 -q -c 3 -i 0.3 -w 2 10.1.1.100 | FORMAT_PI
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([datapath - basic truncate action])
+OVS_TRAFFIC_VSWITCHD_START()
+AT_CHECK([ovs-ofctl del-flows br0])
+
+dnl Create p0 and ovs-p0(1)
+ADD_NAMESPACES(at_ns0)
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address e6:66:c1:11:11:11])
+NS_CHECK_EXEC([at_ns0], [arp -s 10.1.1.2 e6:66:c1:22:22:22])
+
+dnl Create p1(3) and ovs-p1(2), packets received from ovs-p1 will appear in p1
+AT_CHECK([ip link add p1 type veth peer name ovs-p1])
+on_exit 'ip link del ovs-p1'
+AT_CHECK([ip link set dev ovs-p1 up])
+AT_CHECK([ip link set dev p1 up])
+AT_CHECK([ovs-vsctl add-port br0 ovs-p1 -- set interface ovs-p1 ofport_request=2])
+dnl Use p1 to check the truncated packet
+AT_CHECK([ovs-vsctl add-port br0 p1 -- set interface p1 ofport_request=3])
+
+dnl Create p2(5) and ovs-p2(4)
+AT_CHECK([ip link add p2 type veth peer name ovs-p2])
+on_exit 'ip link del ovs-p2'
+AT_CHECK([ip link set dev ovs-p2 up])
+AT_CHECK([ip link set dev p2 up])
+AT_CHECK([ovs-vsctl add-port br0 ovs-p2 -- set interface ovs-p2 ofport_request=4])
+dnl Use p2 to check the truncated packet
+AT_CHECK([ovs-vsctl add-port br0 p2 -- set interface p2 ofport_request=5])
+
+dnl basic test
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_DATA([flows.txt], [dnl
+in_port=3 dl_dst=e6:66:c1:22:22:22 actions=drop
+in_port=5 dl_dst=e6:66:c1:22:22:22 actions=drop
+in_port=1 dl_dst=e6:66:c1:22:22:22 actions=output(port=2,max_len=100),output:4
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl use this file as payload file for ncat
+AT_CHECK([dd if=/dev/urandom of=payload200.bin bs=200 count=1 2> /dev/null])
+on_exit 'rm -f payload200.bin'
+NS_CHECK_EXEC([at_ns0], [nc -u 10.1.1.2 1234 < payload200.bin])
+
+dnl packet with truncated size
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=3" |  sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=100
+])
+dnl packet with original size
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=242
+])
+
+dnl more complicated output actions
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_DATA([flows.txt], [dnl
+in_port=3 dl_dst=e6:66:c1:22:22:22 actions=drop
+in_port=5 dl_dst=e6:66:c1:22:22:22 actions=drop
+in_port=1 dl_dst=e6:66:c1:22:22:22 actions=output(port=2,max_len=100),output:4,output(port=2,max_len=100),output(port=4,max_len=100),output:2,output(port=4,max_len=200),output(port=2,max_len=65535)
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+NS_CHECK_EXEC([at_ns0], [nc -u 10.1.1.2 1234 < payload200.bin])
+
+dnl 100 + 100 + 242 + min(65535,242) = 684
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=684
+])
+dnl 242 + 100 + min(242,200) = 542
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=542
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+dnl Create 2 bridges and 2 namespaces to test truncate over
+dnl GRE tunnel:
+dnl   br0: overlay bridge
+dnl   ns1: connect to br0, with IP:10.1.1.2
+dnl   br-underlay: with IP: 172.31.1.100
+dnl   ns0: connect to br-underlay, with IP: 10.1.1.1
+AT_SETUP([datapath - truncate and output to gre tunnel])
+OVS_CHECK_GRE()
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-underlay])
+ADD_NAMESPACES(at_ns0)
+ADD_NAMESPACES(at_ns1)
+AT_CHECK([ovs-ofctl add-flow br0 "actions=normal"])
+AT_CHECK([ovs-ofctl add-flow br-underlay "actions=normal"])
+
+dnl Set up underlay link from host into the namespace using veth pair.
+ADD_VETH(p0, at_ns0, br-underlay, "172.31.1.1/24")
+AT_CHECK([ip addr add dev br-underlay "172.31.1.100/24"])
+AT_CHECK([ip link set dev br-underlay up])
+
+dnl Set up tunnel endpoints on OVS outside the namespace and with a native
+dnl linux device inside the namespace.
+ADD_OVS_TUNNEL([gre], [br0], [at_gre0], [172.31.1.1], [10.1.1.100/24])
+ADD_NATIVE_TUNNEL([gretap], [ns_gre0], [at_ns0], [172.31.1.100], [10.1.1.1/24])
+AT_CHECK([ovs-vsctl -- set interface at_gre0 ofport_request=1])
+NS_CHECK_EXEC([at_ns0], [ip link set dev ns_gre0 address e6:66:c1:11:11:11])
+NS_CHECK_EXEC([at_ns0], [arp -s 10.1.1.2 e6:66:c1:22:22:22])
+
+dnl Set up (p1 and ovs-p1) at br0
+ADD_VETH(p1, at_ns1, br0, '10.1.1.2/24')
+AT_CHECK([ovs-vsctl -- set interface ovs-p1 ofport_request=2])
+NS_CHECK_EXEC([at_ns1], [ip link set dev p1 address e6:66:c1:22:22:22])
+NS_CHECK_EXEC([at_ns1], [arp -s 10.1.1.1 e6:66:c1:11:11:11])
+
+dnl Set up (p2 and ovs-p2) as loopback for verifying packet size
+AT_CHECK([ip link add p2 type veth peer name ovs-p2])
+on_exit 'ip link del ovs-p2'
+AT_CHECK([ip link set dev ovs-p2 up])
+AT_CHECK([ip link set dev p2 up])
+AT_CHECK([ovs-vsctl add-port br0 ovs-p2 -- set interface ovs-p2 ofport_request=3])
+AT_CHECK([ovs-vsctl add-port br0 p2 -- set interface p2 ofport_request=4])
+
+dnl use this file as payload file for ncat
+AT_CHECK([dd if=/dev/urandom of=payload200.bin bs=200 count=1 2> /dev/null])
+on_exit 'rm -f payload200.bin'
+
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_DATA([flows.txt], [dnl
+priority=99,in_port=1,actions=output(port=2,max_len=100),output(port=3,max_len=100)
+priority=99,in_port=2,udp,actions=output(port=1,max_len=100)
+priority=1,in_port=4,ip,actions=drop
+priority=1,actions=drop
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+on_exit 'ovs-ofctl dump-flows br0'
+AT_CHECK([ovs-ofctl del-flows br-underlay])
+AT_DATA([flows-underlay.txt], [dnl
+priority=99,dl_type=0x0800,nw_proto=47,in_port=1,actions=LOCAL
+priority=99,dl_type=0x0800,nw_proto=47,in_port=LOCAL,ip_dst=172.31.1.1/24,actions=1
+priority=1,actions=drop
+])
+
+AT_CHECK([ovs-ofctl add-flows br-underlay flows-underlay.txt])
+
+dnl check tunnel push path, from at_ns1 to at_ns0
+NS_CHECK_EXEC([at_ns1], [nc -u 10.1.1.1 1234 < payload200.bin])
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+
+dnl Before truncation = ETH(14) + IP(20) + UDP(8) + 200 = 242B
+dnl AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=2" | awk --field-separator=', '  '{print $5}'], [0], [dnl
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=2" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=242
+])
+dnl After truncation = outer ETH(14) + outer IP(20) + GRE(4) + 100 = 138B
+AT_CHECK([ovs-ofctl dump-flows br-underlay | grep "in_port=LOCAL" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [0], [dnl
+n_bytes=138
+])
+
+dnl check tunnel pop path, from at_ns0 to at_ns1
+NS_CHECK_EXEC([at_ns0], [nc -u 10.1.1.2 5678 < payload200.bin])
+dnl After truncation = 100 byte at loopback device p2(4)
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=4" | awk --field-separator=', '  '{print $5}'], [0], [dnl
+n_bytes=100
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([conntrack - controller])
 CHECK_CONNTRACK()
 OVS_TRAFFIC_VSWITCHD_START()