[ovs-dev] [PATCHv3 RFC] ofp-actions: Add a new action to truncate a packet.

William Tu u9012063 at gmail.com
Wed May 18 22:25:19 UTC 2016


Hi,

The RFC has been posted for a while and I started to rethink about the
design decision of this packet truncation patch.
Below listed a couple of different view points gathered from different
people and any comments are welcomed.

1. Deferral Truncate (current patch)
------------------------------------------------
This patch adds a new "truncate" datapath action, which only saves the
max_len at skb CB. The actual truncation happens when the packet is
outputting to a port.

One potential issue is that other OF actions might happen in-between the
truncate and the final output action.  Ex: if recirculate happens
in-between and upcalls to userspace, then we lose the max_len because the
skb CB is dropped. As a result, it is safer to make sure no other actions
between truncation and output, so we expose at ovs-ofctl "output(max_len=N,
port=M)" translating to two actions followed by each other as "truncate(N),
output:M" in datapath.

One limitation is that we can only truncate before outputting.  Users can't
place truncate at any point of interest in the pipeline. Of course, we can
expose "truncate" action to ovs-ofctl so user can do s.t like
    actions = "truncate(100), push_vlan, output:2, output:3, ..."

We still do deferral truncate, however, it suffers from two issues:
a. Semantically this is confusing because truncate will only happen at the
next coming output:2. (output:3 still have the original size)
b. Incorrect truncated packet len: Assuming the input packet is 500B, users
are interested in its first 100B. In this example, due to push_vlan and
deferral truncation, user gets 100 - 4 (vlan_header) = 96B.  This can be
solved in (4).


2. Immediate truncation
--------------------------------
On the other hand, we can expose OF truncate actions and truncate the
packet in-place. Then truncate happens right away and actions/pipelines
afterward see truncated packet, ex:
    actions=truncate(100), output:2, resubmit(,5), output:3
in this case, all actions afterward and table 5 see truncated packet.

Potential issues:
- it makes controller difficult to maintain the flows since once the packet
is truncated, the csum is incorrect and packet becomes malformed.
- truncate to arbitrary length might break or partially break the header
field, making packet matching in other table incorrect or undefined.


3. Parenthesis Truncate
--------------------------------
Instead, we could apply truncation immediately but within a limited scope.
Ex:
  truncate(max_len=100, actions="output:3, resubmit(,10), output:4"),
output:5
so only output:3, table 10, and output:4 see truncated packet, output:5 has
the original.

This then looks similar as sample actions with 100% sample rate. Currently
we use sample with IPFIX or sFlow, the actions looks like:
  sample(sample=9.7%,actions(1,2,3,push_vlan(vid=1,pcp=2))), output:5
the actions() inside sample supports any existing actions and has no side
effect. If we add a new truncate config into sample, ex:
  sample(sample=100%, truncate=100, actions(1,2,3,push_vlan(vid=1,pcp=2))),
output:5
then output:1,2,3 see the truncated packets and output:5 has the original.


4. Add a new action to save current packet len
--------------------------------------------------------------
To solve the incorrect packet len in (1b), we can introduce another action
called "SAVE_LEN" (we can think of better name later). Users still use
"output(max_len=100, output:3)" but with SAVE_LEN in the middle of pipeline
to calibrate the packet length. For example, a 500B packet hits the flow
action
    actions = "SAVE_LEN, push_vlan, output:2, output(max_len=100, port=3),
..."

Action SAVE_LEN saves the current packet len 500 in context, and later on,
when seeing "output(max_len=100, port=3)", it gets the current packet size
504B, knowing that this pipeline adds 4B more, so it translates to datapath
as truncate(104) instead of truncate(100). If the next coming packet with
1500B hit the datapath flow, it will also truncate to 104B.


Regards,
William


On Wed, May 4, 2016 at 11:00 AM, William Tu <u9012063 at gmail.com> wrote:

> The patch proposes adding 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
>
> Implementation/Limitaions:
>     - The patch adds a new OpenFlow extension action OFPACT_OUTPUT_TRUNC,
> and
>       a new datapath action, OVS_ACTION_ATTR_TRUNC. An OFPACT_OUTPUT_TRUNC
>       will translate into a datapath truncate action, followed by a
> datapath
>       output action.
>     - The OVS_ACTION_ATTR_TRUNC action only marks the packet with a
> truncate
>       size (max_len).  Actual truncation happens when the packet is about
> to
>       be transmitted.
>     - Only "output(port=[0-9]*,max_len=<N>)" is supported.  Output to any
>       OFPUTIL_NAMED_PORTS are not supported.
>     - Compatibility: If the openvswitch kernel module does not support
>       OVS_ACTION_ATTR_TRUNC, it falls back to userspace slow path, do the
>       packet truncation and send to the output port.
>
> Performance:
>     - For kernel datapath, since a packet is always cloned when seeing an
>       output action, we trim the cloned packet in ovs_vport_send() so
>       different vports are agnostic to truncate action.  No performance
>       overhead is introduced.
>     - For userspace datapath, if !may_steal, truncate on the current packet
>       affects the rest of actions.  To avoid, two options are considered:
>         1) at netdev_send(), clone the packet, truncate it, and pass down
>            to specific nedev provider's send. Or,
>         2) at each netdev provider's send, truncate the packet.  This
> avoids
>            extra copy of the packet, but more intrusive to every netdev
>            providers.  This patch implements (2) at netdev-linux and
>            netdev-dummy.
>
> TODO:
>     - Truncate support for mirror/ipfix/slfow configuration will be in
>       separate patch.
>
> Signed-off-by: William Tu <u9012063 at gmail.com>
>
> ---
> v2: https://patchwork.ozlabs.org/patch/605082/
> v2->v3
> - Separate the truncate action and output action in datapath.
> - Add truncate support for tunnel push and pop.
> - Fix clang error.
> - Use pskb_trim instead of skb_trim.
> - Fallback to userspace truncate action when the
>   openvswitch kernel module does not support.
> - Disallow truncate port to be any OFPUTIL_NAMED_PORTS.
> - Add more testcases.
> ---
>  datapath/actions.c                                |  17 ++-
>  datapath/datapath.h                               |   1 +
>  datapath/flow_netlink.c                           |   8 ++
>  datapath/linux/compat/include/linux/openvswitch.h |   8 ++
>  datapath/vport.c                                  |   6 +
>  include/openvswitch/ofp-actions.h                 |  10 ++
>  lib/dp-packet.c                                   |   1 +
>  lib/dp-packet.h                                   |  33 +++++
>  lib/dpif-netdev.c                                 |  23 +++-
>  lib/dpif.c                                        |  21 +++
>  lib/netdev-dummy.c                                |   4 +
>  lib/netdev-linux.c                                |   4 +
>  lib/netdev.c                                      |   6 +
>  lib/odp-execute.c                                 |  10 ++
>  lib/odp-util.c                                    |   8 ++
>  lib/ofp-actions.c                                 | 101 +++++++++++++++
>  ofproto/ofproto-dpif-sflow.c                      |   3 +-
>  ofproto/ofproto-dpif-xlate.c                      |  43 ++++++
>  ofproto/ofproto-dpif.c                            |  52 ++++++++
>  ofproto/ofproto-dpif.h                            |   3 +
>  tests/ofproto-dpif.at                             |  76 +++++++++++
>  tests/system-traffic.at                           | 151
> ++++++++++++++++++++++
>  22 files changed, 583 insertions(+), 6 deletions(-)
>
> diff --git a/datapath/actions.c b/datapath/actions.c
> index dcf8591..92db16f 100644
> --- a/datapath/actions.c
> +++ b/datapath/actions.c
> @@ -738,10 +738,13 @@ 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);
>
> +       if (unlikely(max_len != 0))
> +               OVS_CB(skb)->max_len = max_len;
> +
>         if (likely(vport)) {
>                 u16 mru = OVS_CB(skb)->mru;
>
> @@ -1034,6 +1037,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,9 +1049,10 @@ 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;
> +                       max_len = 0;
>                 }
>
>                 switch (nla_type(a)) {
> @@ -1055,6 +1060,12 @@ 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);
> +                       max_len = trunc->max_len;
> +                       break;
> +        }
> +
>                 case OVS_ACTION_ATTR_USERSPACE:
>                         output_userspace(dp, skb, key, a, attr, len);
>                         break;
> @@ -1126,7 +1137,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..178d81d 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; /* 0 means original packet size.
> */
>  };
>  #define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)
>
> diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c
> index 6ffcc53..2a2f17a 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,13 @@ 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 < 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..13b3799 100644
> --- a/datapath/linux/compat/include/linux/openvswitch.h
> +++ b/datapath/linux/compat/include/linux/openvswitch.h
> @@ -600,6 +600,12 @@ enum ovs_userspace_attr {
>
>  #define OVS_USERSPACE_ATTR_MAX (__OVS_USERSPACE_ATTR_MAX - 1)
>
> +struct ovs_action_trunc {
> +       uint16_t max_len; /* 0 means original packet size. */
> +};
> +/* 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.
> @@ -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
> @@ -807,6 +814,7 @@ enum ovs_action_attr {
>         OVS_ACTION_ATTR_TUNNEL_PUSH,   /* struct ovs_action_push_tnl*/
>         OVS_ACTION_ATTR_TUNNEL_POP,    /* u32 port number. */
>  #endif
> +       OVS_ACTION_ATTR_TRUNC,        /* u16 max_len */
>         __OVS_ACTION_ATTR_MAX,        /* Nothing past this will be accepted
>                                        * from userspace. */
>
> diff --git a/datapath/vport.c b/datapath/vport.c
> index 44b9dfb..72a80cc 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 >= OVS_ACTION_OUTPUT_MIN))
> +               pskb_trim(skb, max_len);
> +
>         skb->dev = vport->dev;
>         vport->ops->send(skb);
>         return;
> diff --git a/include/openvswitch/ofp-actions.h
> b/include/openvswitch/ofp-actions.h
> index 0608ac1..28b517f 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..9772769 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);
> +    b->max_len = 0;
>  }
>
>  static void
> diff --git a/lib/dp-packet.h b/lib/dp-packet.h
> index 000b09d..921fa19 100644
> --- a/lib/dp-packet.h
> +++ b/lib/dp-packet.h
> @@ -58,6 +58,8 @@ struct dp_packet {
>                                      * or UINT16_MAX. */
>      uint16_t l4_ofs;               /* Transport-level header offset,
>                                        or UINT16_MAX. */
> +    uint16_t max_len;              /* Max packet sent size.
> +                                      0 means sent with original size. */
>      struct pkt_metadata md;
>  };
>
> @@ -489,6 +491,37 @@ dp_packet_set_allocated(struct dp_packet *b, uint16_t
> s)
>  }
>  #endif
>
> +static inline bool
> +dp_packet_valid_trunc(struct dp_packet *b)
> +{
> +    return b->max_len != 0;
> +}
> +
> +static inline void
> +dp_packet_reset_trunc(struct dp_packet *b)
> +{
> +    b->max_len = 0;
> +}
> +
> +static inline uint32_t
> +dp_packet_set_trunc(struct dp_packet *b, uint16_t max_len)
> +{
> +    if (max_len < OVS_ACTION_OUTPUT_MIN ||
> +            max_len >= dp_packet_size(b)) {
> +        dp_packet_reset_trunc(b);
> +        return dp_packet_size(b);
> +    }
> +
> +    b->max_len = max_len;
> +    return max_len;
> +}
> +
> +static inline uint32_t
> +dp_packet_get_trunc(struct dp_packet *b)
> +{
> +    return b->max_len;
> +}
> +
>  static inline void *
>  dp_packet_data(const struct dp_packet *b)
>  {
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 1e8a37c..3a56b02 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -3742,6 +3742,7 @@ dp_execute_cb(void *aux_, struct dp_packet
> **packets, int cnt,
>      case OVS_ACTION_ATTR_TUNNEL_PUSH:
>          if (*depth < MAX_RECIRC_DEPTH) {
>              struct dp_packet *tnl_pkt[NETDEV_MAX_BURST];
> +            struct dp_packet **orig_packets = packets;
>              int err;
>
>              if (!may_steal) {
> @@ -3749,6 +3750,14 @@ dp_execute_cb(void *aux_, struct dp_packet
> **packets, int cnt,
>                  packets = tnl_pkt;
>              }
>
> +            for (int i = 0; i < cnt; i++) {
> +                if (dp_packet_valid_trunc(orig_packets[i])) {
> +                        dp_packet_set_size(packets[i],
> +
>  dp_packet_get_trunc(orig_packets[i]));
> +                    dp_packet_reset_trunc(orig_packets[i]);
> +                }
> +            }
> +
>              err = push_tnl_action(dp, a, packets, cnt);
>              if (!err) {
>                  (*depth)++;
> @@ -3763,6 +3772,7 @@ dp_execute_cb(void *aux_, struct dp_packet
> **packets, int cnt,
>
>      case OVS_ACTION_ATTR_TUNNEL_POP:
>          if (*depth < MAX_RECIRC_DEPTH) {
> +            struct dp_packet **orig_packets = packets;
>              odp_port_t portno = u32_to_odp(nl_attr_get_u32(a));
>
>              p = dp_netdev_lookup_port(dp, portno);
> @@ -3771,8 +3781,16 @@ dp_execute_cb(void *aux_, struct dp_packet
> **packets, int cnt,
>                  int err;
>
>                  if (!may_steal) {
> -                   dp_netdev_clone_pkt_batch(tnl_pkt, packets, cnt);
> -                   packets = tnl_pkt;
> +                    dp_netdev_clone_pkt_batch(tnl_pkt, packets, cnt);
> +                    packets = tnl_pkt;
> +                }
> +
> +                for (int i = 0; i < cnt; i++) {
> +                    if (dp_packet_valid_trunc(orig_packets[i])) {
> +                            dp_packet_set_size(packets[i],
> +
>  dp_packet_get_trunc(orig_packets[i]));
> +                        dp_packet_reset_trunc(orig_packets[i]);
> +                    }
>                  }
>
>                  err = netdev_pop_header(p->netdev, packets, cnt);
> @@ -3866,6 +3884,7 @@ dp_execute_cb(void *aux_, struct dp_packet
> **packets, int cnt,
>      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.c b/lib/dpif.c
> index 2c64d9e..f75277a 100644
> --- a/lib/dpif.c
> +++ b/lib/dpif.c
> @@ -1093,12 +1093,14 @@ dpif_execute_helper_cb(void *aux_, struct
> dp_packet **packets, int cnt,
>      struct dpif_execute_helper_aux *aux = aux_;
>      int type = nl_attr_type(action);
>      struct dp_packet *packet = *packets;
> +    struct dp_packet *trunc_packet = NULL, *orig_packet;
>
>      ovs_assert(cnt == 1);
>
>      switch ((enum ovs_action_attr)type) {
>      case OVS_ACTION_ATTR_CT:
>      case OVS_ACTION_ATTR_OUTPUT:
> +    case OVS_ACTION_ATTR_TRUNC:
>      case OVS_ACTION_ATTR_TUNNEL_PUSH:
>      case OVS_ACTION_ATTR_TUNNEL_POP:
>      case OVS_ACTION_ATTR_USERSPACE:
> @@ -1125,6 +1127,18 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet
> **packets, int cnt,
>              execute.actions_len = NLA_ALIGN(action->nla_len);
>          }
>
> +        orig_packet = packet;
> +
> +        if (dp_packet_valid_trunc(packet)) {
> +            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_get_trunc(orig_packet));
> +        }
> +
>          execute.packet = packet;
>          execute.needs_help = false;
>          execute.probe = false;
> @@ -1135,6 +1149,13 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet
> **packets, int cnt,
>          if (dst_set) {
>              ofpbuf_uninit(&execute_actions);
>          }
> +        /* Reset the truncation state so next output action is intact. */
> +        if (dp_packet_valid_trunc(orig_packet)) {
> +            dp_packet_reset_trunc(orig_packet);
> +            if (!may_steal) {
> +                dp_packet_delete(trunc_packet);
> +            }
> +        }
>          break;
>      }
>
> diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
> index 5acb4e1..eb3dc25 100644
> --- a/lib/netdev-dummy.c
> +++ b/lib/netdev-dummy.c
> @@ -955,6 +955,10 @@ netdev_dummy_send(struct netdev *netdev, int qid
> OVS_UNUSED,
>          const void *buffer = dp_packet_data(pkts[i]);
>          size_t size = dp_packet_size(pkts[i]);
>
> +        if (dp_packet_valid_trunc(pkts[i])) {
> +            size = dp_packet_get_trunc(pkts[i]);
> +        }
> +
>          if (size < ETH_HEADER_LEN) {
>              error = EMSGSIZE;
>              break;
> diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
> index a2b6b8a..1be318b 100644
> --- a/lib/netdev-linux.c
> +++ b/lib/netdev-linux.c
> @@ -1170,6 +1170,10 @@ netdev_linux_send(struct netdev *netdev_, int qid
> OVS_UNUSED,
>          size_t size = dp_packet_size(pkts[i]);
>          ssize_t retval;
>
> +        if (dp_packet_valid_trunc(pkts[i])) {
> +            size = dp_packet_get_trunc(pkts[i]);
> +        }
> +
>          if (!is_tap_netdev(netdev_)) {
>              /* Use our AF_PACKET socket to send to this device. */
>              struct sockaddr_ll sll;
> diff --git a/lib/netdev.c b/lib/netdev.c
> index 3e50694..ff0375b 100644
> --- a/lib/netdev.c
> +++ b/lib/netdev.c
> @@ -763,6 +763,12 @@ netdev_send(struct netdev *netdev, int qid, struct
> dp_packet **buffers,
>      if (!error) {
>          COVERAGE_INC(netdev_sent);
>      }
> +
> +    if (!may_steal) {
> +        for (int i = 0; i < cnt; i++) {
> +            dp_packet_reset_trunc(buffers[i]);
> +        }
> +    }
>      return error;
>  }
>
> diff --git a/lib/odp-execute.c b/lib/odp-execute.c
> index 7efd9ec..4183290 100644
> --- a/lib/odp-execute.c
> +++ b/lib/odp-execute.c
> @@ -501,6 +501,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:
> @@ -542,6 +543,15 @@ odp_execute_actions(void *dp, struct dp_packet
> **packets, int cnt, bool steal,
>          }
>
>          switch ((enum ovs_action_attr) type) {
> +        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_trunc(packets[i], trunc->max_len);
> +            }
> +            break;
> +        }
> +
>          case OVS_ACTION_ATTR_HASH: {
>              const struct ovs_action_hash *hash_act = nl_attr_get(a);
>
> diff --git a/lib/odp-util.c b/lib/odp-util.c
> index 10fb6c2..cfae9cf 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,13 @@ 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;
> diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
> index 39b6fbc..2344d41 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,34 @@ 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")) {
> +            return str_to_u16(value, key, &output_trunc->max_len);
> +        } 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 +577,10 @@ 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;
>
> @@ -5512,6 +5548,63 @@ 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_be32 port;              /* Output port */
> +};
> +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 = ntohl(natrc->port);
> +
> +    if (output_trunc->max_len < OVS_ACTION_OUTPUT_MIN) {
> +        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 = htonl(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. */
>
> @@ -5906,6 +5999,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:
> @@ -5934,6 +6028,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:
> @@ -6157,6 +6252,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:
> @@ -6585,6 +6681,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);
>
> @@ -7262,6 +7362,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..7b2d4e0 100644
> --- a/ofproto/ofproto-dpif-sflow.c
> +++ b/ofproto/ofproto-dpif-sflow.c
> @@ -1140,7 +1140,8 @@ dpif_sflow_read_actions(const struct flow *flow,
>             }
>             break;
>
> -       case OVS_ACTION_ATTR_USERSPACE:
> +    case OVS_ACTION_ATTR_TRUNC:
> +    case OVS_ACTION_ATTR_USERSPACE:
>         case OVS_ACTION_ATTR_RECIRC:
>         case OVS_ACTION_ATTR_HASH:
>          case OVS_ACTION_ATTR_CT:
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index 7a201bd..d28d872 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -3925,6 +3925,43 @@ 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) {
> +            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)
>  {
> @@ -4209,6 +4246,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:
> @@ -4718,6 +4756,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 285e377..15aa5aa 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -1237,6 +1237,57 @@ 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;
> +    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 = OVS_ACTION_OUTPUT_MIN + 1;
> +    nl_msg_put_odp_port(&actions, OVS_ACTION_ATTR_OUTPUT, 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);
> +
> +    /* 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.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)
>   \
> @@ -1288,6 +1339,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 91bf463..feec333 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/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 9ac2e2a..5db1c7e 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -5312,6 +5312,82 @@ PORTNAME
>         portName=p2
>  ])])
>
> +AT_SETUP([ofproto-dpif - output(port=N,max_len=M) 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
> +])
> +
> +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 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_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'])
> +
> +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
> +])
> +
> +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 - 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 669226d..7bb5ec0 100644
> --- a/tests/system-traffic.at
> +++ b/tests/system-traffic.at
> @@ -228,6 +228,157 @@ 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 - output(port=N,max_len=M) 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])
> +
> +NS_CHECK_EXEC([at_ns0], [ping -q -c 1 -s 1024 -w 0 10.1.1.2 |
> FORMAT_PING], [0], [dnl
> +1 packets transmitted, 0 received, 100% packet loss, time 0ms
> +])
> +dnl packet with truncated size
> +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 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
> +])
> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
> +
> +NS_CHECK_EXEC([at_ns0], [ping -q -c 1 -s 1024 -w 0 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
> +
> +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 forward 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 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,actions=output(port=1,max_len=100)
> +priority=99,in_port=4,udp,actions=drop
> +priority=1,in_port=4,actions=drop
> +])
> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
> +
> +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,actions=1
> +priority=1,actions=NORMAL
> +])
> +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])
> +dnl Before truncation = ETH(14) + IP(20) + UDP(8) + 200 = 242B
> +AT_CHECK([ovs-ofctl dump-flows br0 | grep "n_bytes=242" > /dev/null])
> +dnl After truncation = ETH(14) + GRE(4) + IP(20) + 100 = 138B
> +AT_CHECK([ovs-ofctl dump-flows br-underlay | grep "n_bytes=138" >
> /dev/null])
> +
> +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-ofctl dump-flows br0 | grep "n_bytes=100" > /dev/null])
> +
> +OVS_TRAFFIC_VSWITCHD_STOP
> +AT_CLEANUP
> +
>  AT_SETUP([conntrack - controller])
>  CHECK_CONNTRACK()
>  OVS_TRAFFIC_VSWITCHD_START()
> --
> 2.5.0
>
>



More information about the dev mailing list