@@ -1040,10 +1040,16 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
for (a = attr, rem = len; rem > 0;
a = nla_next(a, &rem)) {
int err = 0;
+ int cutlen = OVS_CB(skb)->cutlen;
if (unlikely(prev_port != -1)) {
struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);
+ if (cutlen > 0) {
+ pskb_trim(out_skb, out_skb->len - cutlen);
+ OVS_CB(skb)->cutlen = 0;
+ }
+
if (out_skb)
do_output(dp, out_skb, prev_port, key);
@@ -1055,6 +1061,15 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
prev_port = nla_get_u32(a);
break;
+ case OVS_ACTION_ATTR_TRUNC: {
+ struct ovs_action_trunc *trunc = nla_data(a);
+ if (trunc->max_len < ETH_MIN_FRAME_LEN)
+ return -EINVAL;
+ OVS_CB(skb)->cutlen = skb->len > trunc->max_len ?
+ skb->len - trunc->max_len : 0;
+ break;
+ }
+
case OVS_ACTION_ATTR_USERSPACE:
output_userspace(dp, skb, key, a, attr, len);
break;
@@ -1125,8 +1140,14 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
}
}
- if (prev_port != -1)
+ if (prev_port != -1) {
+ uint32_t cutlen = OVS_CB(skb)->cutlen;
+ if (cutlen > 0) {
+ pskb_trim(skb, skb->len - cutlen);
+ OVS_CB(skb)->cutlen = 0;
+ }
do_output(dp, skb, prev_port, key);
+ }
else
consume_skb(skb);
@@ -439,6 +439,7 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
size_t len;
unsigned int hlen;
int err, dp_ifindex;
+ int cutlen = OVS_CB(skb)->cutlen;
dp_ifindex = get_dpifindex(dp);
if (!dp_ifindex)
@@ -475,6 +476,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
else
hlen = skb->len;
+ if (cutlen > 0)
+ hlen -= cutlen;
+
len = upcall_msg_size(upcall_info, hlen);
user_skb = genlmsg_new_unicast(len, &info, GFP_ATOMIC);
if (!user_skb) {
@@ -534,7 +538,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
}
nla->nla_len = nla_attr_size(skb->len);
- err = skb_zerocopy(user_skb, skb, skb->len, hlen);
+ err = skb_zerocopy(user_skb, skb,
+ (cutlen > 0) ? hlen : skb->len, hlen);
if (err)
goto out;
@@ -548,6 +553,7 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
out:
if (err)
skb_tx_error(skb);
+ OVS_CB(skb)->cutlen = 0;
kfree_skb(user_skb);
kfree_skb(nskb);
return err;
@@ -98,10 +98,12 @@ struct datapath {
* when a packet is received by OVS.
* @mru: The maximum received fragement size; 0 if the packet is not
* fragmented.
+ * @cutlen: The number of bytes from the packet end to be removed.
*/
struct ovs_skb_cb {
struct vport *input_vport;
u16 mru;
+ u16 cutlen;
};
#define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)
@@ -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,15 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
return -EINVAL;
break;
+ case OVS_ACTION_ATTR_TRUNC: {
+ const struct ovs_action_trunc *trunc = nla_data(a);
+ if (trunc->max_len < ETH_MIN_FRAME_LEN) {
+ pr_info("%s invalid max_len\n", __func__);
+ return -EINVAL;
+ }
+ break;
+ }
+
case OVS_ACTION_ATTR_HASH: {
const struct ovs_action_hash *act_hash = nla_data(a);
@@ -600,6 +600,11 @@ enum ovs_userspace_attr {
#define OVS_USERSPACE_ATTR_MAX (__OVS_USERSPACE_ATTR_MAX - 1)
+struct ovs_action_trunc {
+ uint32_t max_len; /* Max packet size in bytes. */
+};
+#define ETH_MIN_FRAME_LEN 60
+
/**
* struct ovs_action_push_mpls - %OVS_ACTION_ATTR_PUSH_MPLS action argument.
* @mpls_lse: MPLS label stack entry to push.
@@ -742,6 +747,7 @@ enum ovs_nat_attr {
* enum ovs_action_attr - Action types.
*
* @OVS_ACTION_ATTR_OUTPUT: Output packet to port.
+ * @OVS_ACTION_ATTR_TRUNC: Output packet to port with truncated packet size.
* @OVS_ACTION_ATTR_USERSPACE: Send packet to userspace according to nested
* %OVS_USERSPACE_ATTR_* attributes.
* @OVS_ACTION_ATTR_PUSH_VLAN: Push a new outermost 802.1Q header onto the
@@ -802,6 +808,7 @@ enum ovs_action_attr {
* The data must be zero for the unmasked
* bits. */
OVS_ACTION_ATTR_CT, /* Nested OVS_CT_ATTR_* . */
+ OVS_ACTION_ATTR_TRUNC, /* u16 struct ovs_action_trunc. */
#ifndef __KERNEL__
OVS_ACTION_ATTR_TUNNEL_PUSH, /* struct ovs_action_push_tnl*/
@@ -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)->cutlen = 0;
+
if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
u32 mark;
@@ -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
@@ -30,6 +30,7 @@ dp_packet_init__(struct dp_packet *b, size_t allocated, enum dp_packet_source so
dp_packet_reset_offsets(b);
pkt_metadata_init(&b->md, 0);
dp_packet_rss_invalidate(b);
+ dp_packet_reset_cutlen(b);
}
static void
@@ -60,6 +60,7 @@ struct dp_packet {
* or UINT16_MAX. */
uint16_t l4_ofs; /* Transport-level header offset,
or UINT16_MAX. */
+ uint16_t cutlen; /* length in bytes to cut out from the end. */
union {
struct pkt_metadata md;
uint64_t data[DP_PACKET_CONTEXT_SIZE / 8];
@@ -494,6 +495,31 @@ dp_packet_set_allocated(struct dp_packet *b, uint16_t s)
}
#endif
+static inline void
+dp_packet_reset_cutlen(struct dp_packet *b)
+{
+ b->cutlen = 0;
+}
+
+static inline uint32_t
+dp_packet_set_cutlen(struct dp_packet *b, uint16_t max_len)
+{
+ if (max_len < ETH_MIN_FRAME_LEN ||
+ max_len >= dp_packet_size(b)) {
+ b->cutlen = 0;
+ }
+ else {
+ b->cutlen = dp_packet_size(b) - max_len;
+ }
+ return b->cutlen;
+}
+
+static inline uint32_t
+dp_packet_get_cutlen(struct dp_packet *b)
+{
+ return b->cutlen;
+}
+
static inline void *
dp_packet_data(const struct dp_packet *b)
{
@@ -3767,6 +3767,7 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
case OVS_ACTION_ATTR_TUNNEL_PUSH:
if (*depth < MAX_RECIRC_DEPTH) {
struct dp_packet_batch tnl_pkt;
+ struct dp_packet **orig_packets = packets_->packets;
int err;
if (!may_steal) {
@@ -3774,6 +3775,19 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
packets_ = &tnl_pkt;
}
+ for (int i = 0; i < packets_->count; i++) {
+ /* if may_steal, then opacket == packet. */
+ struct dp_packet *orig_packet = orig_packets[i];
+ struct dp_packet *packet = packets_->packets[i];
+ uint32_t cutlen = dp_packet_get_cutlen(orig_packet);
+
+ if (cutlen > 0) {
+ dp_packet_set_size(packet,
+ dp_packet_size(packet) - cutlen);
+ dp_packet_reset_cutlen(orig_packet);
+ }
+ }
+
err = push_tnl_action(dp, a, packets_);
if (!err) {
(*depth)++;
@@ -3786,6 +3800,7 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
case OVS_ACTION_ATTR_TUNNEL_POP:
if (*depth < MAX_RECIRC_DEPTH) {
+ struct dp_packet **orig_packets = packets_->packets;
odp_port_t portno = u32_to_odp(nl_attr_get_u32(a));
p = dp_netdev_lookup_port(dp, portno);
@@ -3794,8 +3809,20 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
int i;
if (!may_steal) {
- dp_packet_batch_clone(&tnl_pkt, packets_);
- packets_ = &tnl_pkt;
+ dp_packet_batch_clone(&tnl_pkt, packets_);
+ packets_ = &tnl_pkt;
+ }
+
+ for (int i = 0; i < packets_->count; i++) {
+ /* if may_steal, then opacket == packet. */
+ struct dp_packet *orig_packet = orig_packets[i];
+ struct dp_packet *packet = packets_->packets[i];
+ uint32_t cutlen = dp_packet_get_cutlen(orig_packet);
+ if (cutlen > 0) {
+ dp_packet_set_size(packet,
+ dp_packet_size(packet) - cutlen);
+ dp_packet_reset_cutlen(orig_packet);
+ }
}
netdev_pop_header(p->netdev, packets_);
@@ -3880,6 +3907,7 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
case OVS_ACTION_ATTR_SAMPLE:
case OVS_ACTION_ATTR_HASH:
case OVS_ACTION_ATTR_UNSPEC:
+ case OVS_ACTION_ATTR_TRUNC:
case __OVS_ACTION_ATTR_MAX:
OVS_NOT_REACHED();
}
@@ -1093,12 +1093,14 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
struct dpif_execute_helper_aux *aux = aux_;
int type = nl_attr_type(action);
struct dp_packet *packet = packets_->packets[0];
+ struct dp_packet *trunc_packet = NULL, *orig_packet;
ovs_assert(packets_->count == 1);
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:
@@ -1108,6 +1110,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
uint64_t stub[256 / 8];
struct pkt_metadata *md = &packet->md;
bool dst_set;
+ uint32_t cutlen = dp_packet_get_cutlen(packet);
dst_set = flow_tnl_dst_is_set(&md->tunnel);
if (dst_set) {
@@ -1125,6 +1128,18 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
execute.actions_len = NLA_ALIGN(action->nla_len);
}
+ orig_packet = packet;
+
+ if (cutlen > 0) {
+ if (!may_steal) {
+ trunc_packet = dp_packet_clone(packet);
+ packet = trunc_packet;
+ }
+ /* Truncation applies to the clone packet or the original
+ * packet with may_steal == true. */
+ dp_packet_set_size(packet, dp_packet_size(orig_packet) - cutlen);
+ }
+
execute.packet = packet;
execute.needs_help = false;
execute.probe = false;
@@ -1135,6 +1150,13 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
if (dst_set) {
ofpbuf_uninit(&execute_actions);
}
+ /* Reset the truncation state so next output action is intact. */
+ if (cutlen > 0) {
+ dp_packet_reset_cutlen(orig_packet);
+ if (!may_steal) {
+ dp_packet_delete(trunc_packet);
+ }
+ }
break;
}
@@ -698,6 +698,11 @@ netdev_bsd_send(struct netdev *netdev_, int qid OVS_UNUSED,
for (i = 0; i < cnt; i++) {
const void *data = dp_packet_data(pkts[i]);
size_t size = dp_packet_size(pkts[i]);
+ uint32_t cutlen = dp_packet_get_cutlen(pkts[i]);
+
+ if (cutlen > 0) {
+ size -= cutlen;
+ }
while (!error) {
ssize_t retval;
@@ -1561,6 +1561,12 @@ netdev_dpdk_send__(struct netdev_dpdk *dev, int qid,
for (i = 0; i < cnt; i++) {
int size = dp_packet_size(pkts[i]);
+ uint32_t cutlen = dp_packet_get_cutlen(pkts[i]);
+
+ if (cutlen > 0) {
+ size -= cutlen;
+ dp_packet_set_size(pkts[i], size);
+ }
if (OVS_UNLIKELY(size > dev->max_packet_len)) {
if (next_tx_idx != i) {
@@ -954,6 +954,11 @@ netdev_dummy_send(struct netdev *netdev, int qid OVS_UNUSED,
for (i = 0; i < cnt; i++) {
const void *buffer = dp_packet_data(pkts[i]);
size_t size = dp_packet_size(pkts[i]);
+ uint32_t cutlen = dp_packet_get_cutlen(pkts[i]);
+
+ if (cutlen > 0) {
+ size -= cutlen;
+ }
if (size < ETH_HEADER_LEN) {
error = EMSGSIZE;
@@ -1169,6 +1169,11 @@ netdev_linux_send(struct netdev *netdev_, int qid OVS_UNUSED,
const void *data = dp_packet_data(pkts[i]);
size_t size = dp_packet_size(pkts[i]);
ssize_t retval;
+ uint32_t cutlen = dp_packet_get_cutlen(pkts[i]);
+
+ if (cutlen > 0) {
+ size -= cutlen;
+ }
if (!is_tap_netdev(netdev_)) {
/* Use our AF_PACKET socket to send to this device. */
@@ -691,7 +691,7 @@ netdev_set_multiq(struct netdev *netdev, unsigned int n_txq,
return error;
}
-/* Sends 'buffers' on 'netdev'. Returns 0 if successful (for every packet),
+/* Sends 'batch' on 'netdev'. Returns 0 if successful (for every packet),
* otherwise a positive errno value. Returns EAGAIN without blocking if
* at least one the packets cannot be queued immediately. Returns EMSGSIZE
* if a partial packet was transmitted or if a packet is too big or too small
@@ -727,6 +727,12 @@ netdev_send(struct netdev *netdev, int qid, struct dp_packet_batch *batch,
if (!error) {
COVERAGE_INC(netdev_sent);
}
+
+ if (!may_steal) {
+ for (int i = 0; i < batch->count; i++) {
+ dp_packet_reset_cutlen(batch->packets[i]);
+ }
+ }
return error;
}
@@ -503,6 +503,7 @@ requires_datapath_assistance(const struct nlattr *a)
case OVS_ACTION_ATTR_HASH:
case OVS_ACTION_ATTR_PUSH_MPLS:
case OVS_ACTION_ATTR_POP_MPLS:
+ case OVS_ACTION_ATTR_TRUNC:
return false;
case OVS_ACTION_ATTR_UNSPEC:
@@ -546,6 +547,15 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, 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_cutlen(packets[i], trunc->max_len);
+ }
+ break;
+ }
+
case OVS_ACTION_ATTR_HASH: {
const struct ovs_action_hash *hash_act = nl_attr_get(a);
@@ -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;
@@ -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;
@@ -5540,6 +5576,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 < ETH_MIN_FRAME_LEN) {
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ return 0;
+}
+
+static void
+encode_OUTPUT_TRUNC(const struct ofpact_output_trunc *output_trunc,
+ enum ofp_version ofp_version OVS_UNUSED,
+ struct ofpbuf *out)
+{
+ struct nx_action_output_trunc *natrc = put_NXAST_OUTPUT_TRUNC(out);
+
+ natrc->max_len = htons(output_trunc->max_len);
+ natrc->port = 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. */
@@ -5934,6 +6027,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
case OFPACT_NOTE:
case OFPACT_OUTPUT:
case OFPACT_OUTPUT_REG:
+ case OFPACT_OUTPUT_TRUNC:
case OFPACT_POP_MPLS:
case OFPACT_POP_QUEUE:
case OFPACT_PUSH_MPLS:
@@ -5962,6 +6056,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:
@@ -6185,6 +6280,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:
@@ -6613,6 +6709,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);
@@ -7290,6 +7390,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:
@@ -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:
@@ -2597,7 +2597,6 @@ compose_sample_action(struct xlate_ctx *ctx,
OVS_ACTION_ATTR_SAMPLE);
nl_msg_put_u32(ctx->odp_actions, OVS_SAMPLE_ATTR_PROBABILITY, probability);
-
size_t actions_offset = nl_msg_start_nested(ctx->odp_actions,
OVS_SAMPLE_ATTR_ACTIONS);
@@ -3949,6 +3948,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)
{
@@ -4233,6 +4269,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:
@@ -4742,6 +4779,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;
@@ -1204,6 +1204,56 @@ 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 = ETH_MIN_FRAME_LEN + 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) \
@@ -1255,6 +1305,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);
@@ -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;
};
@@ -5322,6 +5322,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
@@ -250,6 +250,173 @@ 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])
+
+dnl use this file as payload file for ncat
+AT_CHECK([dd if=/dev/urandom of=payload200.bin bs=200 count=1 2> /dev/null])
+on_exit 'rm -f payload200.bin'
+NS_CHECK_EXEC([at_ns0], [nc -u 10.1.1.2 1234 < payload200.bin])
+
+dnl packet with truncated size
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=100\).*/\1/p'], [0], [dnl
+n_bytes=100
+])
+dnl packet with original size
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=242\).*/\1/p'], [0], [dnl
+n_bytes=242
+])
+
+dnl more complicated output actions
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_DATA([flows.txt], [dnl
+in_port=3 dl_dst=e6:66:c1:22:22:22 actions=drop
+in_port=5 dl_dst=e6:66:c1:22:22:22 actions=drop
+in_port=1 dl_dst=e6:66:c1:22:22:22 actions=output(port=2,max_len=100),output:4,output(port=2,max_len=100),output(port=4,max_len=100),output:2,output(port=4,max_len=200),output(port=2,max_len=65535)
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+NS_CHECK_EXEC([at_ns0], [nc -u 10.1.1.2 1234 < payload200.bin])
+
+dnl 100 + 100 + 242 + min(65535,242) = 684
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=684\).*/\1/p'], [0], [dnl
+n_bytes=684
+])
+dnl 242 + 100 + min(242,200) = 542
+AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=542\).*/\1/p'], [0], [dnl
+n_bytes=542
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+dnl Create 2 bridges and 2 namespaces to test truncate over
+dnl GRE tunnel:
+dnl br0: overlay bridge
+dnl ns1: connect to br0, with IP:10.1.1.2
+dnl br-underlay: with IP: 172.31.1.100
+dnl ns0: connect to br-underlay, with IP: 10.1.1.1
+AT_SETUP([datapath - truncate and 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 br0 "actions=normal"])
+AT_CHECK([ovs-ofctl add-flow br-underlay "actions=normal"])
+
+dnl Set up underlay link from host into the namespace using veth pair.
+ADD_VETH(p0, at_ns0, br-underlay, "172.31.1.1/24")
+AT_CHECK([ip addr add dev br-underlay "172.31.1.100/24"])
+AT_CHECK([ip link set dev br-underlay up])
+
+dnl Set up tunnel endpoints on OVS outside the namespace and with a native
+dnl linux device inside the namespace.
+ADD_OVS_TUNNEL([gre], [br0], [at_gre0], [172.31.1.1], [10.1.1.100/24])
+ADD_NATIVE_TUNNEL([gretap], [ns_gre0], [at_ns0], [172.31.1.100], [10.1.1.1/24])
+AT_CHECK([ovs-vsctl -- set interface at_gre0 ofport_request=1])
+NS_CHECK_EXEC([at_ns0], [ip link set dev ns_gre0 address e6:66:c1:11:11:11])
+NS_CHECK_EXEC([at_ns0], [arp -s 10.1.1.2 e6:66:c1:22:22:22])
+
+dnl Set up (p1 and ovs-p1) at br0
+ADD_VETH(p1, at_ns1, br0, '10.1.1.2/24')
+AT_CHECK([ovs-vsctl -- set interface ovs-p1 ofport_request=2])
+NS_CHECK_EXEC([at_ns1], [ip link set dev p1 address e6:66:c1:22:22:22])
+NS_CHECK_EXEC([at_ns1], [arp -s 10.1.1.1 e6:66:c1:11:11:11])
+
+dnl Set up (p2 and ovs-p2) as loopback for verifying packet size
+AT_CHECK([ip link add p2 type veth peer name ovs-p2])
+on_exit 'ip link del ovs-p2'
+AT_CHECK([ip link set dev ovs-p2 up])
+AT_CHECK([ip link set dev p2 up])
+AT_CHECK([ovs-vsctl add-port br0 ovs-p2 -- set interface ovs-p2 ofport_request=3])
+AT_CHECK([ovs-vsctl add-port br0 p2 -- set interface p2 ofport_request=4])
+
+dnl use this file as payload file for ncat
+AT_CHECK([dd if=/dev/urandom of=payload200.bin bs=200 count=1 2> /dev/null])
+on_exit 'rm -f payload200.bin'
+
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_DATA([flows.txt], [dnl
+priority=99,in_port=1,actions=output(port=2,max_len=100),output(port=3,max_len=100)
+priority=99,in_port=2,udp,actions=output(port=1,max_len=100)
+priority=1,in_port=4,ip,actions=drop
+priority=1,actions=drop
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+on_exit 'ovs-ofctl dump-flows br0'
+AT_CHECK([ovs-ofctl del-flows br-underlay])
+AT_DATA([flows-underlay.txt], [dnl
+priority=99,dl_type=0x0800,nw_proto=47,in_port=1,actions=LOCAL
+priority=99,dl_type=0x0800,nw_proto=47,in_port=LOCAL,ip_dst=172.31.1.1/24,actions=1
+priority=1,actions=drop
+])
+
+AT_CHECK([ovs-ofctl add-flows br-underlay flows-underlay.txt])
+
+dnl check tunnel push path, from at_ns1 to at_ns0
+NS_CHECK_EXEC([at_ns1], [nc -u 10.1.1.1 1234 < payload200.bin])
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+
+dnl Before truncation = ETH(14) + IP(20) + UDP(8) + 200 = 242B
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=2" | awk --field-separator=', ' '{print $5}'], [0], [dnl
+n_bytes=242
+])
+dnl After truncation = outer ETH(14) + outer IP(20) + GRE(4) + 100 = 138B
+AT_CHECK([ovs-ofctl dump-flows br-underlay | grep "in_port=LOCAL" | awk --field-separator=', ' '{print $5}'], [0], [dnl
+n_bytes=138
+])
+
+dnl check tunnel pop path, from at_ns0 to at_ns1
+NS_CHECK_EXEC([at_ns0], [nc -u 10.1.1.2 5678 < payload200.bin])
+dnl After truncation = 100 byte at loopback device p2(4)
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=4" | awk --field-separator=', ' '{print $5}'], [0], [dnl
+n_bytes=100
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([conntrack - controller])
CHECK_CONNTRACK()
OVS_TRAFFIC_VSWITCHD_START()
The patch adds a new action to support packet truncation. The new action is formatted as 'output(port=n,max_len=m)', as output to port n, with packet size being MIN(original_size, m). One use case is to enable port mirroring to send smaller packets to the destination port so that only useful packet information is mirrored/copied, saving some performance overhead of copying entire packet payload. Example use case is below as well as shown in the testcases: - Output to port 1 with max_len 100 bytes. - The output packet size on port 1 will be MIN(original_packet_size, 100). # ovs-ofctl add-flow br0 'actions=output(port=1,max_len=100)' - The scope of max_len is limited to output action itself. The following packet size of output:1 and output:2 will be intact. # ovs-ofctl add-flow br0 \ 'actions=output(port=1,max_len=100),output:1,output:2' - The Datapath actions shows: # Datapath actions: trunc(100),1,1,2 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. Signed-off-by: William Tu <u9012063@gmail.com> --- v3->v4 v3: https://patchwork.ozlabs.org/patch/618573/ - Save the bytes to removed in datapath, instead of max_len - Fix issues in test cases - Add truncate support when upcall to userspace v2->v3 v2: https://patchwork.ozlabs.org/patch/605082/ - 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 | 23 ++- datapath/datapath.c | 8 +- datapath/datapath.h | 2 + datapath/flow_netlink.c | 10 ++ datapath/linux/compat/include/linux/openvswitch.h | 7 + datapath/vport.c | 2 + include/openvswitch/ofp-actions.h | 10 ++ lib/dp-packet.c | 1 + lib/dp-packet.h | 26 ++++ lib/dpif-netdev.c | 32 ++++- lib/dpif.c | 22 +++ lib/netdev-bsd.c | 5 + lib/netdev-dpdk.c | 6 + lib/netdev-dummy.c | 5 + lib/netdev-linux.c | 5 + lib/netdev.c | 8 +- lib/odp-execute.c | 10 ++ lib/odp-util.c | 8 ++ lib/ofp-actions.c | 101 +++++++++++++ ofproto/ofproto-dpif-sflow.c | 3 +- ofproto/ofproto-dpif-xlate.c | 44 +++++- ofproto/ofproto-dpif.c | 51 +++++++ ofproto/ofproto-dpif.h | 3 + tests/ofproto-dpif.at | 76 ++++++++++ tests/system-traffic.at | 167 ++++++++++++++++++++++ 25 files changed, 628 insertions(+), 7 deletions(-)