diff mbox

[ovs-dev,RFC] ofp-actions: Extend the use of max_len in OUTPUT action.

Message ID 1459289805-84385-1-git-send-email-u9012063@gmail.com
State RFC
Headers show

Commit Message

William Tu March 29, 2016, 10:16 p.m. UTC
Before, OpenFlow specification defines 'max_len' in struct ofp_action_output
as the max number of bytes to send when port is OFPP_CONTROLLER.  A max_len
of zero means no bytes of the packet should be sent, and max_len of
OFPCML_NO_BUFFER means the complete packet is sent to the controller.
It is left undefined of max_len, when output port is not OFPP_CONTROLLER.
The patch extends the use of max_len when output is non-controller.

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.
The patch proposes adding a '(max_len=<N>)' after the output action.  An
example use case is below as well as shown in the tests/:

    - 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:1(max_len=100)'

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

Implementation/Limitaions:
    - Userspace and kernel datapath is supported, no Windows support.
    - The minimum value of max_len is 60 byte (minimum Ethernet frame size).
      This is defined in OVS_ACTION_OUTPUT_MIN.
    - Since 'max_len' is undefined in OpenFlow spec, the controller might
      accidentally place a garbage value in max_len and send to OVS.
    - For compatibility, if the kernel datapath is not supported, set
      max_len to zero.
    - OUTPUT_REG with max_len is not supported.
    - actions=1(max_len=100) is not supported, must specify as 'output:1'.
    - Only output:[0-9]*(max_len=<N>) is supported.  Output to IN_PORT,
      TABLE, NORMAL, FLOOD, ALL, and LOCAL are not supported.

Signed-off-by: William Tu <u9012063@gmail.com>
---
 datapath/actions.c                                | 19 +++++--
 datapath/datapath.h                               |  1 +
 datapath/flow_netlink.c                           | 10 ++--
 datapath/linux/compat/include/linux/openvswitch.h |  7 +++
 datapath/vport.c                                  |  6 +++
 lib/dp-packet.c                                   |  1 +
 lib/dp-packet.h                                   |  1 +
 lib/dpctl.c                                       | 19 ++++---
 lib/dpif-netdev.c                                 | 20 ++++++-
 lib/netdev.c                                      |  8 +++
 lib/netlink.h                                     |  1 +
 lib/odp-util.c                                    | 27 ++++++++--
 lib/ofp-actions.c                                 | 41 +++++++++++++++
 lib/ofp-actions.h                                 |  4 +-
 ofproto/ofproto-dpif-xlate.c                      | 33 +++++++-----
 ofproto/ofproto-dpif.c                            | 45 ++++++++++++++++
 ofproto/ofproto-dpif.h                            |  4 ++
 tests/ofp-print.at                                |  6 +--
 tests/ofproto-dpif.at                             | 53 +++++++++++++++++++
 tests/system-traffic.at                           | 63 +++++++++++++++++++++++
 20 files changed, 330 insertions(+), 39 deletions(-)

Comments

Pravin Shelar March 29, 2016, 11:52 p.m. UTC | #1
On Tue, Mar 29, 2016 at 3:16 PM, William Tu <u9012063@gmail.com> wrote:
> Before, OpenFlow specification defines 'max_len' in struct ofp_action_output
> as the max number of bytes to send when port is OFPP_CONTROLLER.  A max_len
> of zero means no bytes of the packet should be sent, and max_len of
> OFPCML_NO_BUFFER means the complete packet is sent to the controller.
> It is left undefined of max_len, when output port is not OFPP_CONTROLLER.
> The patch extends the use of max_len when output is non-controller.
>
> 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.
> The patch proposes adding a '(max_len=<N>)' after the output action.  An
> example use case is below as well as shown in the tests/:
>
>     - 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:1(max_len=100)'
>
>     - The scope of max_len is limited to output action itself.  The following
>       output:1 and output:2 will be the original packet size.
>     # ovs-ofctl add-flow br0 'actions=output:1(max_len=100),output:1,output:2'
>
> Implementation/Limitaions:
>     - Userspace and kernel datapath is supported, no Windows support.
>     - The minimum value of max_len is 60 byte (minimum Ethernet frame size).
>       This is defined in OVS_ACTION_OUTPUT_MIN.
>     - Since 'max_len' is undefined in OpenFlow spec, the controller might
>       accidentally place a garbage value in max_len and send to OVS.
>     - For compatibility, if the kernel datapath is not supported, set
>       max_len to zero.
>     - OUTPUT_REG with max_len is not supported.
>     - actions=1(max_len=100) is not supported, must specify as 'output:1'.
>     - Only output:[0-9]*(max_len=<N>) is supported.  Output to IN_PORT,
>       TABLE, NORMAL, FLOOD, ALL, and LOCAL are not supported.
>
> Signed-off-by: William Tu <u9012063@gmail.com>
> ---
>  datapath/actions.c                                | 19 +++++--
>  datapath/datapath.h                               |  1 +
>  datapath/flow_netlink.c                           | 10 ++--
>  datapath/linux/compat/include/linux/openvswitch.h |  7 +++
>  datapath/vport.c                                  |  6 +++
>  lib/dp-packet.c                                   |  1 +
>  lib/dp-packet.h                                   |  1 +
>  lib/dpctl.c                                       | 19 ++++---
>  lib/dpif-netdev.c                                 | 20 ++++++-
>  lib/netdev.c                                      |  8 +++
>  lib/netlink.h                                     |  1 +
>  lib/odp-util.c                                    | 27 ++++++++--
>  lib/ofp-actions.c                                 | 41 +++++++++++++++
>  lib/ofp-actions.h                                 |  4 +-
>  ofproto/ofproto-dpif-xlate.c                      | 33 +++++++-----
>  ofproto/ofproto-dpif.c                            | 45 ++++++++++++++++
>  ofproto/ofproto-dpif.h                            |  4 ++
>  tests/ofp-print.at                                |  6 +--
>  tests/ofproto-dpif.at                             | 53 +++++++++++++++++++
>  tests/system-traffic.at                           | 63 +++++++++++++++++++++++
>  20 files changed, 330 insertions(+), 39 deletions(-)
>
> diff --git a/datapath/actions.c b/datapath/actions.c
> index dcf8591..d64dadf 100644
> --- a/datapath/actions.c
> +++ b/datapath/actions.c
> @@ -738,10 +738,15 @@ err:
>  }
>
>  static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port,
> -                     struct sw_flow_key *key)
> +                    uint16_t max_len, struct sw_flow_key *key)
>  {
>         struct vport *vport = ovs_vport_rcu(dp, out_port);
>
> +       /* This is after skb_clone called from do_execute_actions,
> +          so max_len only applies to the current skb. */
> +       if (unlikely(max_len != 0))
> +               OVS_CB(skb)->max_len = max_len;
> +
>         if (likely(vport)) {
>                 u16 mru = OVS_CB(skb)->mru;
>
> @@ -1034,6 +1039,7 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
>          * is slightly obscure just to avoid that.
>          */
>         int prev_port = -1;
> +       uint16_t max_len = 0;
>         const struct nlattr *a;
>         int rem;
>
> @@ -1045,15 +1051,18 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
>                         struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);
>
>                         if (out_skb)
> -                               do_output(dp, out_skb, prev_port, key);
> +                               do_output(dp, out_skb, prev_port, max_len, key);
>
>                         prev_port = -1;
>                 }
>
>                 switch (nla_type(a)) {
> -               case OVS_ACTION_ATTR_OUTPUT:
> -                       prev_port = nla_get_u32(a);
> +               case OVS_ACTION_ATTR_OUTPUT: {
> +                       struct ovs_action_output *output = nla_data(a);
> +                       prev_port = output->port;
> +                       max_len = output->max_len;
>                         break;
> +               }

It is better to add separate action to truncate a packet rather than
extending output action. Separate action would allow greater reuse of
the action. For example we can use truncate action for sampling
truncated packets.
William Tu March 30, 2016, 5:24 p.m. UTC | #2
Hi Pravin,

Thanks for the feedback.
So another option is to add an new truncate action, and modify the size for
all the following output packets.  For example, the output:1 remains the
original size and the output:2 and output:3 will have size 100.
  # ovs-ofctl add-flow br0 'actions=output:1, truncate:100, output:2,
output:3'

Or we can implement truncate_output action so that the truncate only scope
at a specific output. For example:
  # ovs-ofctl add-flow br0 'actions=output:1, truncate_output:
(2,max_len=100), output:3'
So that only output:2 is re-sized to 100.

Which one do you think is more useful?

Regards,
William

On Tue, Mar 29, 2016 at 4:52 PM, pravin shelar <pshelar@ovn.org> wrote:

> On Tue, Mar 29, 2016 at 3:16 PM, William Tu <u9012063@gmail.com> wrote:
> > Before, OpenFlow specification defines 'max_len' in struct
> ofp_action_output
> > as the max number of bytes to send when port is OFPP_CONTROLLER.  A
> max_len
> > of zero means no bytes of the packet should be sent, and max_len of
> > OFPCML_NO_BUFFER means the complete packet is sent to the controller.
> > It is left undefined of max_len, when output port is not OFPP_CONTROLLER.
> > The patch extends the use of max_len when output is non-controller.
> >
> > 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.
> > The patch proposes adding a '(max_len=<N>)' after the output action.  An
> > example use case is below as well as shown in the tests/:
> >
> >     - 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:1(max_len=100)'
> >
> >     - The scope of max_len is limited to output action itself.  The
> following
> >       output:1 and output:2 will be the original packet size.
> >     # ovs-ofctl add-flow br0
> 'actions=output:1(max_len=100),output:1,output:2'
> >
> > Implementation/Limitaions:
> >     - Userspace and kernel datapath is supported, no Windows support.
> >     - The minimum value of max_len is 60 byte (minimum Ethernet frame
> size).
> >       This is defined in OVS_ACTION_OUTPUT_MIN.
> >     - Since 'max_len' is undefined in OpenFlow spec, the controller might
> >       accidentally place a garbage value in max_len and send to OVS.
> >     - For compatibility, if the kernel datapath is not supported, set
> >       max_len to zero.
> >     - OUTPUT_REG with max_len is not supported.
> >     - actions=1(max_len=100) is not supported, must specify as
> 'output:1'.
> >     - Only output:[0-9]*(max_len=<N>) is supported.  Output to IN_PORT,
> >       TABLE, NORMAL, FLOOD, ALL, and LOCAL are not supported.
> >
> > Signed-off-by: William Tu <u9012063@gmail.com>
> > ---
> >  datapath/actions.c                                | 19 +++++--
> >  datapath/datapath.h                               |  1 +
> >  datapath/flow_netlink.c                           | 10 ++--
> >  datapath/linux/compat/include/linux/openvswitch.h |  7 +++
> >  datapath/vport.c                                  |  6 +++
> >  lib/dp-packet.c                                   |  1 +
> >  lib/dp-packet.h                                   |  1 +
> >  lib/dpctl.c                                       | 19 ++++---
> >  lib/dpif-netdev.c                                 | 20 ++++++-
> >  lib/netdev.c                                      |  8 +++
> >  lib/netlink.h                                     |  1 +
> >  lib/odp-util.c                                    | 27 ++++++++--
> >  lib/ofp-actions.c                                 | 41 +++++++++++++++
> >  lib/ofp-actions.h                                 |  4 +-
> >  ofproto/ofproto-dpif-xlate.c                      | 33 +++++++-----
> >  ofproto/ofproto-dpif.c                            | 45 ++++++++++++++++
> >  ofproto/ofproto-dpif.h                            |  4 ++
> >  tests/ofp-print.at                                |  6 +--
> >  tests/ofproto-dpif.at                             | 53
> +++++++++++++++++++
> >  tests/system-traffic.at                           | 63
> +++++++++++++++++++++++
> >  20 files changed, 330 insertions(+), 39 deletions(-)
> >
> > diff --git a/datapath/actions.c b/datapath/actions.c
> > index dcf8591..d64dadf 100644
> > --- a/datapath/actions.c
> > +++ b/datapath/actions.c
> > @@ -738,10 +738,15 @@ err:
> >  }
> >
> >  static void do_output(struct datapath *dp, struct sk_buff *skb, int
> out_port,
> > -                     struct sw_flow_key *key)
> > +                    uint16_t max_len, struct sw_flow_key *key)
> >  {
> >         struct vport *vport = ovs_vport_rcu(dp, out_port);
> >
> > +       /* This is after skb_clone called from do_execute_actions,
> > +          so max_len only applies to the current skb. */
> > +       if (unlikely(max_len != 0))
> > +               OVS_CB(skb)->max_len = max_len;
> > +
> >         if (likely(vport)) {
> >                 u16 mru = OVS_CB(skb)->mru;
> >
> > @@ -1034,6 +1039,7 @@ static int do_execute_actions(struct datapath *dp,
> struct sk_buff *skb,
> >          * is slightly obscure just to avoid that.
> >          */
> >         int prev_port = -1;
> > +       uint16_t max_len = 0;
> >         const struct nlattr *a;
> >         int rem;
> >
> > @@ -1045,15 +1051,18 @@ static int do_execute_actions(struct datapath
> *dp, struct sk_buff *skb,
> >                         struct sk_buff *out_skb = skb_clone(skb,
> GFP_ATOMIC);
> >
> >                         if (out_skb)
> > -                               do_output(dp, out_skb, prev_port, key);
> > +                               do_output(dp, out_skb, prev_port,
> max_len, key);
> >
> >                         prev_port = -1;
> >                 }
> >
> >                 switch (nla_type(a)) {
> > -               case OVS_ACTION_ATTR_OUTPUT:
> > -                       prev_port = nla_get_u32(a);
> > +               case OVS_ACTION_ATTR_OUTPUT: {
> > +                       struct ovs_action_output *output = nla_data(a);
> > +                       prev_port = output->port;
> > +                       max_len = output->max_len;
> >                         break;
> > +               }
>
> It is better to add separate action to truncate a packet rather than
> extending output action. Separate action would allow greater reuse of
> the action. For example we can use truncate action for sampling
> truncated packets.
>
Pravin Shelar March 30, 2016, 7:31 p.m. UTC | #3
On Wed, Mar 30, 2016 at 10:24 AM, William Tu <u9012063@gmail.com> wrote:
> Hi Pravin,
>
> Thanks for the feedback.
> So another option is to add an new truncate action, and modify the size for
> all the following output packets.  For example, the output:1 remains the
> original size and the output:2 and output:3 will have size 100.
>   # ovs-ofctl add-flow br0 'actions=output:1, truncate:100, output:2,
> output:3'
>
> Or we can implement truncate_output action so that the truncate only scope
> at a specific output. For example:
>   # ovs-ofctl add-flow br0 'actions=output:1, truncate_output:
> (2,max_len=100), output:3'
> So that only output:2 is re-sized to 100.
>
> Which one do you think is more useful?
>
The comment was about datapath action. I am fine with ofctl interface
that you have.
As far as datapath truncate action is concerned I think we could limit
scope of the truncate action to next output or user-space action.
Ben Pfaff March 30, 2016, 11:35 p.m. UTC | #4
On Wed, Mar 30, 2016 at 12:31:27PM -0700, pravin shelar wrote:
> On Wed, Mar 30, 2016 at 10:24 AM, William Tu <u9012063@gmail.com> wrote:
> > Hi Pravin,
> >
> > Thanks for the feedback.
> > So another option is to add an new truncate action, and modify the size for
> > all the following output packets.  For example, the output:1 remains the
> > original size and the output:2 and output:3 will have size 100.
> >   # ovs-ofctl add-flow br0 'actions=output:1, truncate:100, output:2,
> > output:3'
> >
> > Or we can implement truncate_output action so that the truncate only scope
> > at a specific output. For example:
> >   # ovs-ofctl add-flow br0 'actions=output:1, truncate_output:
> > (2,max_len=100), output:3'
> > So that only output:2 is re-sized to 100.
> >
> > Which one do you think is more useful?
> >
> The comment was about datapath action. I am fine with ofctl interface
> that you have.

The formatting/parsing syntax diverges from that for the other newer
actions we've introduced; it's kind of weird to have parenthesized
arguments following another argument.

Therefore I'd prefer something more like:
        output(port=1, max_len=128)

Also, I don't think it's reasonable to reuse OFPAT_OUTPUT for this.  One
simply cannot rely on every existing controller to fill something
sensible in for the max_len field.  I suggest inventing a new OpenFlow
extension action.  (That doesn't mean you have to change the syntax used
in ovs-ofctl; we have plenty of examples where multiple OpenFlow actions
have a common syntax.)
diff mbox

Patch

diff --git a/datapath/actions.c b/datapath/actions.c
index dcf8591..d64dadf 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -738,10 +738,15 @@  err:
 }
 
 static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port,
-		      struct sw_flow_key *key)
+		     uint16_t max_len, struct sw_flow_key *key)
 {
 	struct vport *vport = ovs_vport_rcu(dp, out_port);
 
+	/* This is after skb_clone called from do_execute_actions,
+	   so max_len only applies to the current skb. */
+	if (unlikely(max_len != 0))
+		OVS_CB(skb)->max_len = max_len;
+
 	if (likely(vport)) {
 		u16 mru = OVS_CB(skb)->mru;
 
@@ -1034,6 +1039,7 @@  static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 	 * is slightly obscure just to avoid that.
 	 */
 	int prev_port = -1;
+	uint16_t max_len = 0;
 	const struct nlattr *a;
 	int rem;
 
@@ -1045,15 +1051,18 @@  static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 			struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);
 
 			if (out_skb)
-				do_output(dp, out_skb, prev_port, key);
+				do_output(dp, out_skb, prev_port, max_len, key);
 
 			prev_port = -1;
 		}
 
 		switch (nla_type(a)) {
-		case OVS_ACTION_ATTR_OUTPUT:
-			prev_port = nla_get_u32(a);
+		case OVS_ACTION_ATTR_OUTPUT: {
+			struct ovs_action_output *output = nla_data(a);
+			prev_port = output->port;
+			max_len = output->max_len;
 			break;
+		}
 
 		case OVS_ACTION_ATTR_USERSPACE:
 			output_userspace(dp, skb, key, a, attr, len);
@@ -1126,7 +1135,7 @@  static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 	}
 
 	if (prev_port != -1)
-		do_output(dp, skb, prev_port, key);
+		do_output(dp, skb, prev_port, max_len, key);
 	else
 		consume_skb(skb);
 
diff --git a/datapath/datapath.h b/datapath/datapath.h
index ceb3372..abac47e 100644
--- a/datapath/datapath.h
+++ b/datapath/datapath.h
@@ -102,6 +102,7 @@  struct datapath {
 struct ovs_skb_cb {
 	struct vport		*input_vport;
 	u16			mru;
+	u16			max_len;
 };
 #define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)
 
diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c
index 6ffcc53..f1f304f 100644
--- a/datapath/flow_netlink.c
+++ b/datapath/flow_netlink.c
@@ -2169,7 +2169,7 @@  static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
 	nla_for_each_nested(a, attr, rem) {
 		/* Expected argument lengths, (u32)-1 for variable length. */
 		static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = {
-			[OVS_ACTION_ATTR_OUTPUT] = sizeof(u32),
+			[OVS_ACTION_ATTR_OUTPUT] = sizeof(struct ovs_action_output),
 			[OVS_ACTION_ATTR_RECIRC] = sizeof(u32),
 			[OVS_ACTION_ATTR_USERSPACE] = (u32)-1,
 			[OVS_ACTION_ATTR_PUSH_MPLS] = sizeof(struct ovs_action_push_mpls),
@@ -2202,10 +2202,14 @@  static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
 				return err;
 			break;
 
-		case OVS_ACTION_ATTR_OUTPUT:
-			if (nla_get_u32(a) >= DP_MAX_PORTS)
+		case OVS_ACTION_ATTR_OUTPUT: {
+			const struct ovs_action_output *output = nla_data(a);
+			if (output->port >= DP_MAX_PORTS ||
+				(output->max_len != 0 &&
+				output->max_len < OVS_ACTION_OUTPUT_MIN))
 				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..9a98c4e 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -600,6 +600,13 @@  enum ovs_userspace_attr {
 
 #define OVS_USERSPACE_ATTR_MAX (__OVS_USERSPACE_ATTR_MAX - 1)
 
+struct ovs_action_output {
+	uint32_t port;
+	uint16_t max_len;
+};
+/* Minimum packet size max_len can have, 60 = ETH_MIN_FRAME_LEN. */
+#define OVS_ACTION_OUTPUT_MIN 60
+
 /**
  * struct ovs_action_push_mpls - %OVS_ACTION_ATTR_PUSH_MPLS action argument.
  * @mpls_lse: MPLS label stack entry to push.
diff --git a/datapath/vport.c b/datapath/vport.c
index 44b9dfb..96aada4 100644
--- a/datapath/vport.c
+++ b/datapath/vport.c
@@ -487,6 +487,8 @@  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)->max_len = 0;
+
 	if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
 		u32 mark;
 
@@ -615,6 +617,7 @@  static unsigned int packet_length(const struct sk_buff *skb)
 void ovs_vport_send(struct vport *vport, struct sk_buff *skb)
 {
 	int mtu = vport->dev->mtu;
+	u16 max_len = OVS_CB(skb)->max_len;
 
 	if (unlikely(packet_length(skb) > mtu && !skb_is_gso(skb))) {
 		net_warn_ratelimited("%s: dropped over-mtu packet: %d > %d\n",
@@ -624,6 +627,9 @@  void ovs_vport_send(struct vport *vport, struct sk_buff *skb)
 		goto drop;
 	}
 
+	if (unlikely(max_len != 0))
+		skb_trim(skb, max_len);
+
 	skb->dev = vport->dev;
 	vport->ops->send(skb);
 	return;
diff --git a/lib/dp-packet.c b/lib/dp-packet.c
index aec7fe7..d32fa85 100644
--- a/lib/dp-packet.c
+++ b/lib/dp-packet.c
@@ -29,6 +29,7 @@  dp_packet_init__(struct dp_packet *b, size_t allocated, enum dp_packet_source so
     b->source = source;
     dp_packet_reset_offsets(b);
     pkt_metadata_init(&b->md, 0);
+    b->max_len = 0;
 }
 
 static void
diff --git a/lib/dp-packet.h b/lib/dp-packet.h
index bf4e758..53bc5ce 100644
--- a/lib/dp-packet.h
+++ b/lib/dp-packet.h
@@ -58,6 +58,7 @@  struct dp_packet {
                                     * or UINT16_MAX. */
     uint16_t l4_ofs;               /* Transport-level header offset,
                                       or UINT16_MAX. */
+    uint16_t max_len;           /* packet's max_len, 0 means remain origingal size */
     struct pkt_metadata md;
 };
 
diff --git a/lib/dpctl.c b/lib/dpctl.c
index 854190f..6382b37 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -860,8 +860,10 @@  get_in_port_netdev_from_key(struct dpif *dpif, const struct ofpbuf *key)
         struct dpif_port dpif_port;
         odp_port_t port_no;
         int error;
+        const struct ovs_action_output *output =
+            nl_attr_get_unspec(in_port_nla, sizeof *output);
 
-        port_no = ODP_PORT_C(nl_attr_get_u32(in_port_nla));
+        port_no = ODP_PORT_C(output->port);
         error = dpif_port_query_by_number(dpif, port_no, &dpif_port);
         if (error) {
             goto out;
@@ -1391,9 +1393,13 @@  compare_output_actions(const void *a_, const void *b_)
 {
     const struct nlattr *a = a_;
     const struct nlattr *b = b_;
-    uint32_t a_port = nl_attr_get_u32(a);
-    uint32_t b_port = nl_attr_get_u32(b);
+    const struct ovs_action_output *a_output =
+                    nl_attr_get_unspec(a, sizeof *a_output);
+    const struct ovs_action_output *b_output =
+                    nl_attr_get_unspec(b, sizeof *b_output);
 
+    uint32_t a_port = a_output->port;
+    uint32_t b_port = b_output->port;
     return a_port < b_port ? -1 : a_port > b_port;
 }
 
@@ -1401,10 +1407,9 @@  static void
 sort_output_actions__(struct nlattr *first, struct nlattr *end)
 {
     size_t bytes = (uint8_t *) end - (uint8_t *) first;
-    size_t n = bytes / NL_A_U32_SIZE;
-
-    ovs_assert(bytes % NL_A_U32_SIZE == 0);
-    qsort(first, n, NL_A_U32_SIZE, compare_output_actions);
+    size_t n = bytes / NL_A_U48_SIZE;
+    ovs_assert(bytes % NL_A_U48_SIZE == 0);
+    qsort(first, n, NL_A_U48_SIZE, compare_output_actions);
 }
 
 static void
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 0f2385a..d8cc255 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -3744,8 +3744,23 @@  dp_execute_cb(void *aux_, struct dp_packet **packets, int cnt,
     int i;
 
     switch ((enum ovs_action_attr)type) {
-    case OVS_ACTION_ATTR_OUTPUT:
-        p = dp_netdev_lookup_port(dp, u32_to_odp(nl_attr_get_u32(a)));
+    case OVS_ACTION_ATTR_OUTPUT: {
+        struct dp_packet *trunc_pkts[cnt];
+        const struct ovs_action_output *output =
+                    nl_attr_get_unspec(a, sizeof *output);
+
+        p = dp_netdev_lookup_port(dp, output->port);
+
+        if (output->max_len >= OVS_ACTION_OUTPUT_MIN) {
+            if (!may_steal) {
+                dp_netdev_clone_pkt_batch(trunc_pkts, packets, cnt);
+                packets = trunc_pkts;
+            }
+            for (i = 0; i < cnt; i++) {
+                packets[i]->max_len = output->max_len;
+            }
+        }
+
         if (OVS_LIKELY(p)) {
             int tx_qid;
 
@@ -3755,6 +3770,7 @@  dp_execute_cb(void *aux_, struct dp_packet **packets, int cnt,
             return;
         }
         break;
+    }
 
     case OVS_ACTION_ATTR_TUNNEL_PUSH:
         if (*depth < MAX_RECIRC_DEPTH) {
diff --git a/lib/netdev.c b/lib/netdev.c
index 95fdbc7..0c0dfd2 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -758,6 +758,14 @@  netdev_send(struct netdev *netdev, int qid, struct dp_packet **buffers,
         return EOPNOTSUPP;
     }
 
+    for (int i = 0; i < cnt; i++) {
+        struct dp_packet *packet = buffers[i];
+        if (packet->max_len != 0 &&
+            packet->max_len < dp_packet_size(packet)) {
+            dp_packet_set_size(packet, packet->max_len);
+        }
+    }
+
     int error = netdev->netdev_class->send(netdev, qid, buffers, cnt,
                                            may_steal);
     if (!error) {
diff --git a/lib/netlink.h b/lib/netlink.h
index b931a41..2c334ca 100644
--- a/lib/netlink.h
+++ b/lib/netlink.h
@@ -111,6 +111,7 @@  struct nlmsghdr *nl_msg_next(struct ofpbuf *buffer, struct ofpbuf *msg);
 #define NL_A_U8_SIZE   NL_ATTR_SIZE(sizeof(uint8_t))
 #define NL_A_U16_SIZE  NL_ATTR_SIZE(sizeof(uint16_t))
 #define NL_A_U32_SIZE  NL_ATTR_SIZE(sizeof(uint32_t))
+#define NL_A_U48_SIZE  NL_ATTR_SIZE(sizeof(uint32_t) + sizeof(uint16_t))
 #define NL_A_U64_SIZE  NL_ATTR_SIZE(sizeof(uint64_t))
 #define NL_A_BE16_SIZE NL_ATTR_SIZE(sizeof(ovs_be16))
 #define NL_A_BE32_SIZE NL_ATTR_SIZE(sizeof(ovs_be32))
diff --git a/lib/odp-util.c b/lib/odp-util.c
index b53de4e..0fb9bfd 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -106,7 +106,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_OUTPUT: return sizeof(struct ovs_action_output);
     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;
@@ -772,9 +772,16 @@  format_odp_action(struct ds *ds, const struct nlattr *a)
     }
 
     switch (type) {
-    case OVS_ACTION_ATTR_OUTPUT:
-        ds_put_format(ds, "%"PRIu32, nl_attr_get_u32(a));
+    case OVS_ACTION_ATTR_OUTPUT: {
+        const struct ovs_action_output *output =
+                       nl_attr_get_unspec(a, sizeof *output);
+        ds_put_format(ds, "%"PRIu32, output->port);
+        if (output->max_len != 0 &&
+            output->max_len != UINT16_MAX) {
+            ds_put_format(ds, "(max_len=%"PRIu32")", output->max_len);
+        }
         break;
+    }
     case OVS_ACTION_ATTR_TUNNEL_POP:
         ds_put_format(ds, "tnl_pop(%"PRIu32")", nl_attr_get_u32(a));
         break;
@@ -1516,9 +1523,14 @@  parse_odp_action(const char *s, const struct simap *port_names,
     {
         uint32_t port;
         int n;
+        struct ovs_action_output *output;
 
         if (ovs_scan(s, "%"SCNi32"%n", &port, &n)) {
-            nl_msg_put_u32(actions, OVS_ACTION_ATTR_OUTPUT, port);
+            output = nl_msg_put_unspec_uninit(actions,
+                                              OVS_ACTION_ATTR_OUTPUT,
+                                              sizeof *output);
+            output->port = port;
+            output->max_len = 0;
             return n;
         }
     }
@@ -1526,10 +1538,15 @@  parse_odp_action(const char *s, const struct simap *port_names,
     if (port_names) {
         int len = strcspn(s, delimiters);
         struct simap_node *node;
+        struct ovs_action_output *output;
 
         node = simap_find_len(port_names, s, len);
         if (node) {
-            nl_msg_put_u32(actions, OVS_ACTION_ATTR_OUTPUT, node->data);
+            output = nl_msg_put_unspec_uninit(actions,
+                                              OVS_ACTION_ATTR_OUTPUT,
+                                              sizeof *output);
+            output->port = node->data;
+            output->max_len = 0;
             return len;
         }
     }
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 0438c62..ee99641 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -545,6 +545,44 @@  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, "max_len")) {
+        char *name, *value;
+        char *arg_, *arg_option, *arg_port;
+        uint16_t max_len, port_len, value_len;
+        struct ofpact_output *output;
+
+        arg_ = xstrdup(arg);
+        value_len = strspn(arg_, "0123456789 (\t");
+        arg_option = arg_ + value_len;
+        value_len = strcspn(arg_option, " )\t");
+        arg_option[value_len] = '\0';
+
+        while (ofputil_parse_key_value(&arg_option, &name, &value)) {
+            if (!strcmp(name, "max_len")) {
+                char *error = str_to_u16(value, "max_len", &max_len);
+                if (error) {
+                    return error;
+                }
+            }
+        }
+        arg_port = arg_;
+        port_len = strspn(arg_port, "0123456789");
+        arg_port[port_len] = '\0';
+
+        output = ofpact_put_OUTPUT(ofpacts);
+        if (!ofputil_port_from_string(arg_port, &output->port)) {
+            return xasprintf("%s: output to unknown port", arg);
+        }
+
+        /* Re-use max_len as max send packet length.  If max_len = 0,
+         * output to port with its original size.  If max_len =
+         * (0, UINT16_MAX], output to port with MIN(original_size, max_len).
+         */
+        output->max_len = max_len;
+        free(arg_);
+        return NULL;
+
     } else {
         struct ofpact_output *output;
 
@@ -563,6 +601,9 @@  format_OUTPUT(const struct ofpact_output *a, struct ds *s)
     if (ofp_to_u16(a->port) < ofp_to_u16(OFPP_MAX)) {
         ds_put_format(s, "%soutput:%s%"PRIu16,
                       colors.special, colors.end, a->port);
+        if (ofp_to_u16(a->max_len) != 0) {
+           ds_put_format(s, "(max_len=%"PRIu16")", a->max_len);
+        }
     } else {
         ofputil_format_port(a->port, s);
         if (a->port == OFPP_CONTROLLER) {
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 46818e3..fe6e256 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -247,7 +247,9 @@  struct ofpact_null {
 struct ofpact_output {
     struct ofpact ofpact;
     ofp_port_t port;            /* Output port. */
-    uint16_t max_len;           /* Max send len, for port OFPP_CONTROLLER. */
+    uint16_t max_len;           /* Max send len, for port OFPP_CONTROLLER.
+                                   For non-controller port, send packet with
+                                   MIN(max_len, original_packet_size). */
 };
 
 /* OFPACT_CONTROLLER.
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 19e690e..c497808 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -523,7 +523,7 @@  struct xlate_bond_recirc {
 };
 
 static void compose_output_action(struct xlate_ctx *, ofp_port_t ofp_port,
-                                  const struct xlate_bond_recirc *xr);
+                                  uint16_t max_len, const struct xlate_bond_recirc *xr);
 
 static struct xbridge *xbridge_lookup(struct xlate_cfg *,
                                       const struct ofproto_dpif *);
@@ -1910,7 +1910,7 @@  output_normal(struct xlate_ctx *ctx, const struct xbundle *out_xbundle,
     }
     *flow_tci = tci;
 
-    compose_output_action(ctx, xport->ofp_port, use_recirc ? &xr : NULL);
+    compose_output_action(ctx, xport->ofp_port, 0, use_recirc ? &xr : NULL);
     *flow_tci = old_tci;
 }
 
@@ -2932,6 +2932,7 @@  clear_conntrack(struct flow *flow)
 
 static void
 compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
+                        uint16_t max_len,
                         const struct xlate_bond_recirc *xr, bool check_stp)
 {
     const struct xport *xport = get_ofp_port(ctx->xbridge, ofp_port);
@@ -3175,12 +3176,18 @@  compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
                                         OVS_ACTION_ATTR_TUNNEL_POP,
                                         odp_tnl_port);
                 } else {
+                    struct ovs_action_output *output;
+
                     /* Tunnel push-pop action is not compatible with
                      * IPFIX action. */
                     compose_ipfix_action(ctx, out_port);
-                    nl_msg_put_odp_port(ctx->odp_actions,
-                                        OVS_ACTION_ATTR_OUTPUT,
-                                        out_port);
+
+                    output = nl_msg_put_unspec_uninit(ctx->odp_actions,
+                                                OVS_ACTION_ATTR_OUTPUT,
+                                                sizeof *output);
+                    output->port = out_port;
+                    output->max_len = ctx->xbridge->support.output_max_len ?
+                                      max_len : 0;
                }
            }
         }
@@ -3205,9 +3212,9 @@  compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
 
 static void
 compose_output_action(struct xlate_ctx *ctx, ofp_port_t ofp_port,
-                      const struct xlate_bond_recirc *xr)
+                      uint16_t max_len, const struct xlate_bond_recirc *xr)
 {
-    compose_output_action__(ctx, ofp_port, xr, true);
+    compose_output_action__(ctx, ofp_port, max_len, xr, true);
 }
 
 static void
@@ -3567,9 +3574,9 @@  flood_packets(struct xlate_ctx *ctx, bool all)
         }
 
         if (all) {
-            compose_output_action__(ctx, xport->ofp_port, NULL, false);
+            compose_output_action__(ctx, xport->ofp_port, 0, NULL, false);
         } else if (!(xport->config & OFPUTIL_PC_NO_FLOOD)) {
-            compose_output_action(ctx, xport->ofp_port, NULL);
+            compose_output_action(ctx, xport->ofp_port, 0, NULL);
         }
     }
 
@@ -3865,7 +3872,7 @@  xlate_output_action(struct xlate_ctx *ctx,
 
     switch (port) {
     case OFPP_IN_PORT:
-        compose_output_action(ctx, ctx->xin->flow.in_port.ofp_port, NULL);
+        compose_output_action(ctx, ctx->xin->flow.in_port.ofp_port, 0, NULL);
         break;
     case OFPP_TABLE:
         xlate_table_action(ctx, ctx->xin->flow.in_port.ofp_port,
@@ -3892,7 +3899,7 @@  xlate_output_action(struct xlate_ctx *ctx,
     case OFPP_LOCAL:
     default:
         if (port != ctx->xin->flow.in_port.ofp_port) {
-            compose_output_action(ctx, port, NULL);
+            compose_output_action(ctx, port, max_len, NULL);
         } else {
             xlate_report(ctx, "skipping output to input port");
         }
@@ -3951,7 +3958,7 @@  xlate_enqueue_action(struct xlate_ctx *ctx,
     /* Add datapath actions. */
     flow_priority = ctx->xin->flow.skb_priority;
     ctx->xin->flow.skb_priority = priority;
-    compose_output_action(ctx, ofp_port, NULL);
+    compose_output_action(ctx, ofp_port, 0, NULL);
     ctx->xin->flow.skb_priority = flow_priority;
 
     /* Update NetFlow output port. */
@@ -5341,7 +5348,7 @@  xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
             && xbridge->has_in_band
             && in_band_must_output_to_local_port(flow)
             && !actions_output_to_local_port(&ctx)) {
-            compose_output_action(&ctx, OFPP_LOCAL, NULL);
+            compose_output_action(&ctx, OFPP_LOCAL, 0, NULL);
         }
 
         if (user_cookie_offset) {
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 6182ec2..5058858 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1189,6 +1189,50 @@  check_max_mpls_depth(struct dpif_backer *backer)
     return n;
 }
 
+/* Tests whether 'backer''s datapath supports output to non-controller
+ * with max_len packet size in OVS_ACTION_ATTR_OUTPUT. */
+static bool
+check_output_max_len(struct dpif_backer *backer)
+{
+    struct eth_header *eth;
+    struct ofpbuf action;
+    struct dpif_execute execute;
+    struct dp_packet packet;
+    struct ovs_action_output *output;
+    int error;
+
+    /* Compose an action with output:1(max_len=OVS_ACTION_OUTPUT_MIN). */
+    ofpbuf_init(&action, 64);
+    output = nl_msg_put_unspec_uninit(&action,
+                                      OVS_ACTION_ATTR_OUTPUT, sizeof *output);
+    output->port = 1;
+    output->max_len = OVS_ACTION_OUTPUT_MIN;
+
+    /* 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);
+
+    execute.actions = action.data;
+    execute.actions_len = action.size;
+    execute.packet = &packet;
+    execute.needs_help = false;
+    execute.probe = true;
+    execute.mtu = 0;
+
+    error = dpif_execute(backer->dpif, &execute);
+
+    dp_packet_uninit(&packet);
+    ofpbuf_uninit(&action);
+
+    if (error) {
+        /* Output max_len is not supported. */
+        VLOG_INFO("%s: datapath does not support output max_len feature.",
+                  dpif_name(backer->dpif));
+    }
+    return !error;
+}
+
 /* Tests whether 'backer''s datapath supports masked data in
  * OVS_ACTION_ATTR_SET actions.  We need to disable some features on older
  * datapaths that don't support this feature. */
@@ -1292,6 +1336,7 @@  check_support(struct dpif_backer *backer)
     backer->support.masked_set_action = check_masked_set_action(backer);
     backer->support.ufid = check_ufid(backer);
     backer->support.tnl_push_pop = dpif_supports_tnl_push_pop(backer->dpif);
+    backer->support.output_max_len = check_output_max_len(backer);
 
     backer->support.odp.ct_state = check_ct_state(backer);
     backer->support.odp.ct_zone = check_ct_zone(backer);
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index 0064178..e9e718b 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -90,6 +90,10 @@  struct dpif_backer_support {
     /* True if the datapath supports OVS_FLOW_ATTR_UFID. */
     bool ufid;
 
+    /* True if the datapath supports OVS_ACTION_ATTR_OUTPUT with max_len
+     * for non-controller output. */
+    bool output_max_len;
+
     /* Each member represents support for related OVS_KEY_ATTR_* fields. */
     struct odp_support odp;
 };
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index 8e97434..4888163 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -1332,9 +1332,9 @@  ca da ad d6 0d 37 80 00 0a 02 08 00 80 00 10 01 \
 05 dc 00 00 00 00 00 00 \
 "], [0], [dnl
 OFPST_FLOW reply (OF1.2) (xid=0x2):
- cookie=0x0, duration=3.023s, table=0, n_packets=1, n_bytes=98, ip,metadata=0,in_port=2,dl_dst=ca:da:ad:d6:0d:37,nw_tos=0 actions=output:2
- cookie=0x0, duration=4.545s, table=0, n_packets=2, n_bytes=140, ip,metadata=0,in_port=2,dl_dst=52:54:00:c3:00:89,nw_tos=0 actions=output:2
- cookie=0x0, duration=4.548s, table=0, n_packets=1, n_bytes=42, ip,metadata=0,in_port=2,dl_dst=52:54:00:97:00:69,nw_tos=0 actions=output:2
+ cookie=0x0, duration=3.023s, table=0, n_packets=1, n_bytes=98, ip,metadata=0,in_port=2,dl_dst=ca:da:ad:d6:0d:37,nw_tos=0 actions=output:2(max_len=1500)
+ cookie=0x0, duration=4.545s, table=0, n_packets=2, n_bytes=140, ip,metadata=0,in_port=2,dl_dst=52:54:00:c3:00:89,nw_tos=0 actions=output:2(max_len=1500)
+ cookie=0x0, duration=4.548s, table=0, n_packets=1, n_bytes=42, ip,metadata=0,in_port=2,dl_dst=52:54:00:97:00:69,nw_tos=0 actions=output:2(max_len=1500)
 ])
 AT_CLEANUP
 
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index da29ac2..989c851 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -5309,6 +5309,59 @@  PORTNAME
 	portName=p2
 ])])
 
+
+AT_SETUP([ofproto-dpif - OUTPUT action with max_len])
+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])
+
+dnl output:2(max_len=32) shows here as truncated size
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=3,actions=drop'])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=5,actions=drop'])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=1,actions=output:2(max_len=64),output: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
+])
+dnl packet with truncated size
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=64\).*/\1/p'], [0], [dnl
+n_bytes=64
+])
+dnl dnl packet with original size
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=170\).*/\1/p'], [0], [dnl
+n_bytes=170
+])
+
+dnl More complicated case
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=3,actions=drop'])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=5,actions=drop'])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=1,actions=output:2(max_len=64),output:2(max_len=128),output:4(max_len=60),output:2,output:4'])
+
+dnl An 170 byte packet
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '000c29c8a0a4005056c0000808004500009cb4a6000040019003c0a8da01c0a8da640800cb5fa762000556f431ad0009388e08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f'])
+
+sleep 1
+dnl packet size: 64 + 128 + 170 = 362
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=362\).*/\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=230\).*/\1/p'], [0], [dnl
+n_bytes=230
+])
+
+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/system-traffic.at b/tests/system-traffic.at
index 28adbdc..93eac2a 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -49,6 +49,69 @@  NS_CHECK_EXEC([at_ns0], [ping -s 3200 -q -c 3 -i 0.3 -w 2 10.2.2.2 | FORMAT_PING
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([datapath - output action with max_len])
+OVS_TRAFFIC_VSWITCHD_START()
+
+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])
+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])
+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 p1 to check the truncated packet
+AT_CHECK([ovs-vsctl add-port br0 p2 -- set interface p2 ofport_request=5])
+
+dnl test1: basic
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=3 dl_dst=e6:66:c1:22:22:22 actions=drop'])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=5 dl_dst=e6:66:c1:22:22:22 actions=drop'])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=1 dl_dst=e6:66:c1:22:22:22 actions=output:2(max_len=100),output:4'])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 1 -s 1024 -w 1 10.1.1.2 | FORMAT_PING], [0], [dnl
+1 packets transmitted, 0 received, 100% packet loss, time 0ms
+])
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=3" |  sed -n 's/.*\(n\_bytes=100\).*/\1/p'], [0], [dnl
+n_bytes=100
+])
+dnl packet with original size
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=1066\).*/\1/p'], [0], [dnl
+n_bytes=1066
+])
+
+dnl test2: more complicated output actions
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=3 dl_dst=e6:66:c1:22:22:22 actions=drop'])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=5 dl_dst=e6:66:c1:22:22:22 actions=drop'])
+AT_CHECK([ovs-ofctl add-flow br0 'in_port=1 dl_dst=e6:66:c1:22:22:22 actions=output:2(max_len=100),output:4,output:2(max_len=100),output:4(max_len=100),output:2'])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 1 -s 1024 -w 1 10.1.1.2 | FORMAT_PING], [0], [dnl
+1 packets transmitted, 0 received, 100% packet loss, time 0ms
+])
+dnl 100 + 100 + 1066 = 1266
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=3" |  sed -n 's/.*\(n\_bytes=1266\).*/\1/p'], [0], [dnl
+n_bytes=1266
+])
+dnl 1066 + 100 = 1166
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=1166\).*/\1/p'], [0], [dnl
+n_bytes=1166
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([datapath - ping6 between two ports])
 OVS_TRAFFIC_VSWITCHD_START()