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

One use case is to enable port mirroring to send smaller packets to the
destination port so that only useful packet information is mirrored/copied,
saving some performance overhead of copying entire packet payload.
The patch proposes adding a '(max_len=<N>)' after the output action.  An
example use case is below as well as shown in the tests/:

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

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

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

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

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

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to