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

William Tu u9012063 at gmail.com
Wed May 4 18:00:21 UTC 2016


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