Message ID | 1466658174-53475-3-git-send-email-u9012063@gmail.com |
---|---|
State | Superseded |
Headers | show |
On Wed, Jun 22, 2016 at 10:02 PM, William Tu <u9012063@gmail.com> wrote: > 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 > > Tested-at: https://travis-ci.org/williamtu/ovs-travis/builds/139668398 > Signed-off-by: William Tu <u9012063@gmail.com> > --- > include/openvswitch/ofp-actions.h | 10 ++ > lib/dp-packet.c | 2 + > lib/dp-packet.h | 67 +++++++++++ > lib/dpif-netdev.c | 33 ++++- > lib/dpif.c | 25 +++- > lib/dpif.h | 1 + > lib/netdev-bsd.c | 3 + > lib/netdev-dpdk.c | 21 ++++ > lib/netdev-dummy.c | 2 + > lib/netdev-linux.c | 3 + > lib/netdev.c | 5 +- > lib/odp-execute.c | 12 ++ > lib/odp-util.c | 23 ++++ > lib/ofp-actions.c | 106 ++++++++++++++++ > ofproto/ofproto-dpif-sflow.c | 1 + > ofproto/ofproto-dpif-xlate.c | 57 +++++++++ > ofproto/ofproto-dpif.c | 79 ++++++++++++ > ofproto/ofproto-dpif.h | 3 + > tests/odp.at | 1 + > tests/ofp-actions.at | 3 + > tests/ofproto-dpif.at | 124 +++++++++++++++++++ > tests/ovs-ofctl.at | 8 ++ > tests/system-kmod-macros.at | 7 ++ > tests/system-traffic.at | 247 ++++++++++++++++++++++++++++++++++++++ > tests/system-userspace-macros.at | 7 ++ > 25 files changed, 846 insertions(+), 4 deletions(-) > > diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h > index 91c7ee5..0b8ccbb 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. */ > + uint32_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..8e7defc 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); > + dp_packet_reset_cutlen(b); > } > > static void > @@ -168,6 +169,7 @@ dp_packet_clone_with_headroom(const struct dp_packet *buffer, size_t headroom) > new_buffer->l3_ofs = buffer->l3_ofs; > new_buffer->l4_ofs = buffer->l4_ofs; > new_buffer->md = buffer->md; > + new_buffer->cutlen = buffer->cutlen; > #ifdef DPDK_NETDEV > new_buffer->mbuf.ol_flags = buffer->mbuf.ol_flags; > #else > diff --git a/lib/dp-packet.h b/lib/dp-packet.h > index 118c84d..f505fa5 100644 > --- a/lib/dp-packet.h > +++ b/lib/dp-packet.h > @@ -60,6 +60,7 @@ struct dp_packet { > * or UINT16_MAX. */ > uint16_t l4_ofs; /* Transport-level header offset, > or UINT16_MAX. */ > + uint32_t cutlen; /* length in bytes to cut from the end. */ > union { > struct pkt_metadata md; > uint64_t data[DP_PACKET_CONTEXT_SIZE / 8]; > @@ -494,6 +495,34 @@ 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, uint32_t max_len) > +{ > + if (max_len < ETH_HEADER_LEN) { > + max_len = ETH_HEADER_LEN; > + } > + > + if (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) > +{ > + /* Always in valid range if user uses dp_packet_set_cutlen. */ > + return b->cutlen; > +} > + > static inline void * > dp_packet_data(const struct dp_packet *b) > { > @@ -567,12 +596,14 @@ enum { NETDEV_MAX_BURST = 32 }; /* Maximum number packets in a batch. */ > > struct dp_packet_batch { > int count; > + bool trunc; /* true if the batch needs truncate. */ > struct dp_packet *packets[NETDEV_MAX_BURST]; > }; > > static inline void dp_packet_batch_init(struct dp_packet_batch *b) > { > b->count = 0; > + b->trunc = false; > } > > static inline void > @@ -585,12 +616,14 @@ dp_packet_batch_clone(struct dp_packet_batch *dst, > dst->packets[i] = dp_packet_clone(src->packets[i]); > } > dst->count = src->count; > + dst->trunc = src->trunc; > } > > static inline void > packet_batch_init_packet(struct dp_packet_batch *b, struct dp_packet *p) > { > b->count = 1; > + b->trunc = false; > b->packets[0] = p; > } > > @@ -606,6 +639,40 @@ dp_packet_delete_batch(struct dp_packet_batch *batch, bool may_steal) > } > } > > +static inline void > +dp_packet_batch_apply_cutlen(struct dp_packet_batch *pktb) > +{ > + int i; > + > + if (!pktb->trunc) > + return; > + > + for (i = 0; i < pktb->count; i++) { > + uint32_t cutlen = dp_packet_get_cutlen(pktb->packets[i]); > + > + if (cutlen > 0) { > + dp_packet_set_size(pktb->packets[i], > + dp_packet_size(pktb->packets[i]) - cutlen); > + dp_packet_reset_cutlen(pktb->packets[i]); > + } > + } > + pktb->trunc = false; > +} > + > +static inline void > +dp_packet_batch_reset_cutlen(struct dp_packet_batch *pktb) > +{ > + int i; > + > + if (!pktb->trunc) > + return; > + > + pktb->trunc = false; > + for (i = 0; i < pktb->count; i++) { > + dp_packet_reset_cutlen(pktb->packets[i]); > + } > +} > + > #ifdef __cplusplus > } > #endif > diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c > index 8d39d9e..70f320d 100644 > --- a/lib/dpif-netdev.c > +++ b/lib/dpif-netdev.c > @@ -4057,13 +4057,17 @@ 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_batch *orig_packets_ = packets_; > int err; > > if (!may_steal) { > dp_packet_batch_clone(&tnl_pkt, packets_); > packets_ = &tnl_pkt; > + dp_packet_batch_reset_cutlen(orig_packets_); > } > > + dp_packet_batch_apply_cutlen(packets_); > + > err = push_tnl_action(pmd, a, packets_); > if (!err) { > (*depth)++; > @@ -4076,6 +4080,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_batch *orig_packets_ = packets_; > odp_port_t portno = u32_to_odp(nl_attr_get_u32(a)); > > p = pmd_tx_port_cache_lookup(pmd, portno); > @@ -4084,10 +4089,13 @@ 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; > + dp_packet_batch_reset_cutlen(orig_packets_); > } > > + dp_packet_batch_apply_cutlen(packets_); > + > netdev_pop_header(p->netdev, packets_); > if (!packets_->count) { > return; > @@ -4107,22 +4115,42 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_, > > case OVS_ACTION_ATTR_USERSPACE: > if (!fat_rwlock_tryrdlock(&dp->upcall_rwlock)) { > + struct dp_packet_batch *orig_packets_ = packets_; > struct dp_packet **packets = packets_->packets; > const struct nlattr *userdata; > + struct dp_packet_batch usr_pkt; > struct ofpbuf actions; > struct flow flow; > ovs_u128 ufid; > + bool clone = false; > int i; > > userdata = nl_attr_find_nested(a, OVS_USERSPACE_ATTR_USERDATA); > ofpbuf_init(&actions, 0); > > + if (packets_->trunc) { > + if (!may_steal) { > + dp_packet_batch_clone(&usr_pkt, packets_); > + packets_ = &usr_pkt; > + packets = packets_->packets; > + clone = true; > + dp_packet_batch_reset_cutlen(orig_packets_); > + } > + > + dp_packet_batch_apply_cutlen(packets_); > + } > + > for (i = 0; i < packets_->count; i++) { > flow_extract(packets[i], &flow); > dpif_flow_hash(dp->dpif, &flow, sizeof flow, &ufid); > dp_execute_userspace_action(pmd, packets[i], may_steal, &flow, > &ufid, &actions, userdata); > } > + > + if (clone) { > + dp_packet_delete_batch(packets_, true); > + } > + > ofpbuf_uninit(&actions); > fat_rwlock_unlock(&dp->upcall_rwlock); > > @@ -4170,6 +4198,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(); > } > diff --git a/lib/dpif.c b/lib/dpif.c > index c4f24c7..5f1be41 100644 > --- a/lib/dpif.c > +++ b/lib/dpif.c > @@ -1092,6 +1092,7 @@ 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); > > @@ -1106,7 +1107,8 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, > struct ofpbuf execute_actions; > uint64_t stub[256 / 8]; > struct pkt_metadata *md = &packet->md; > - bool dst_set; > + bool dst_set, clone = false; > + uint32_t cutlen = dp_packet_get_cutlen(packet); > > dst_set = flow_tnl_dst_is_set(&md->tunnel); > if (dst_set) { > @@ -1124,6 +1126,22 @@ 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 && (type == OVS_ACTION_ATTR_OUTPUT || > + type == OVS_ACTION_ATTR_TUNNEL_PUSH || > + type == OVS_ACTION_ATTR_TUNNEL_POP || > + type == OVS_ACTION_ATTR_USERSPACE)) { > + if (!may_steal) { > + trunc_packet = dp_packet_clone(packet); > + packet = trunc_packet; > + clone = true; > + } > + > + dp_packet_set_size(packet, dp_packet_size(packet) - cutlen); > + dp_packet_reset_cutlen(orig_packet); > + } > + > execute.packet = packet; > execute.flow = aux->flow; > execute.needs_help = false; > @@ -1135,6 +1153,10 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, > if (dst_set) { > ofpbuf_uninit(&execute_actions); > } > + > + if (clone) { > + dp_packet_delete(trunc_packet); > + } > break; > } > > @@ -1146,6 +1168,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, > case OVS_ACTION_ATTR_SET: > case OVS_ACTION_ATTR_SET_MASKED: > case OVS_ACTION_ATTR_SAMPLE: > + case OVS_ACTION_ATTR_TRUNC: > case OVS_ACTION_ATTR_UNSPEC: > case __OVS_ACTION_ATTR_MAX: > OVS_NOT_REACHED(); > diff --git a/lib/dpif.h b/lib/dpif.h > index 6788301..981868c 100644 > --- a/lib/dpif.h > +++ b/lib/dpif.h > @@ -784,6 +784,7 @@ struct dpif_upcall { > size_t key_len; /* Length of 'key' in bytes. */ > ovs_u128 ufid; /* Unique flow identifier for 'key'. */ > struct nlattr *mru; /* Maximum receive unit. */ > + struct nlattr *cutlen; /* Number of bytes shrink from the end. */ > > /* DPIF_UC_ACTION only. */ > struct nlattr *userdata; /* Argument to OVS_ACTION_ATTR_USERSPACE. */ > diff --git a/lib/netdev-bsd.c b/lib/netdev-bsd.c > index 43fa982..2e92d97 100644 > --- a/lib/netdev-bsd.c > +++ b/lib/netdev-bsd.c > @@ -699,6 +699,9 @@ netdev_bsd_send(struct netdev *netdev_, int qid OVS_UNUSED, > const void *data = dp_packet_data(pkts[i]); > size_t size = dp_packet_size(pkts[i]); > > + /* Truncate the packet if it is configured. */ > + size -= dp_packet_get_cutlen(pkts[i]); > + > while (!error) { > ssize_t retval; > if (dev->tap_fd >= 0) { > diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c > index fc0c8d3..8063add 100644 > --- a/lib/netdev-dpdk.c > +++ b/lib/netdev-dpdk.c > @@ -1487,6 +1487,7 @@ dpdk_do_tx_copy(struct netdev *netdev, int qid, struct dp_packet **pkts, > > for (i = 0; i < cnt; i++) { > int size = dp_packet_size(pkts[i]); > + int cutlen = dp_packet_get_cutlen(pkts[i]); > > if (OVS_UNLIKELY(size > dev->max_packet_len)) { > VLOG_WARN_RL(&rl, "Too big size %d max_packet_len %d", > @@ -1503,6 +1504,12 @@ dpdk_do_tx_copy(struct netdev *netdev, int qid, struct dp_packet **pkts, > break; > } > > + if (OVS_UNLIKELY(cutlen > 0)) { > + /* Cut the size so only the truncated size is copied. */ > + size -= cutlen; > + dp_packet_reset_cutlen(pkts[i]); > + } > + As commented in last review, there is no need to check for cut length anywhere while setting the size. > /* We have to do a copy for now */ > memcpy(rte_pktmbuf_mtod(mbufs[newcnt], void *), dp_packet_data(pkts[i]), size); > > @@ -1550,6 +1557,14 @@ netdev_dpdk_vhost_send(struct netdev *netdev, int qid, struct dp_packet **pkts, > } > } > } else { > + int i; > + > + for (i = 0; i < cnt; i++) { > + int cutlen = dp_packet_get_cutlen(pkts[i]); > + > + dp_packet_set_size(pkts[i], dp_packet_size(pkts[i]) - cutlen); > + dp_packet_reset_cutlen(pkts[i]); > + } > __netdev_dpdk_vhost_send(netdev, qid, pkts, cnt, may_steal); > } > return 0; > @@ -1585,6 +1600,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 (OVS_UNLIKELY(cutlen > 0)) { > + size -= cutlen; > + dp_packet_set_size(pkts[i], size); > + } > > if (OVS_UNLIKELY(size > dev->max_packet_len)) { > if (next_tx_idx != i) { > diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c > index aa244b6..127b6ae 100644 > --- a/lib/netdev-dummy.c > +++ b/lib/netdev-dummy.c > @@ -1044,6 +1044,8 @@ 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]); > > + size -= dp_packet_get_cutlen(pkts[i]); > + > if (size < ETH_HEADER_LEN) { > error = EMSGSIZE; > break; > diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c > index 82813ba..d6e5a54 100644 > --- a/lib/netdev-linux.c > +++ b/lib/netdev-linux.c > @@ -1170,6 +1170,9 @@ netdev_linux_send(struct netdev *netdev_, int qid OVS_UNUSED, > size_t size = dp_packet_size(pkts[i]); > ssize_t retval; > > + /* Truncate the packet if it is configured. */ > + size -= dp_packet_get_cutlen(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 4be806d..6651173 100644 > --- a/lib/netdev.c > +++ b/lib/netdev.c > @@ -681,7 +681,7 @@ netdev_set_tx_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 > @@ -716,6 +716,9 @@ netdev_send(struct netdev *netdev, int qid, struct dp_packet_batch *batch, > may_steal); > if (!error) { > COVERAGE_INC(netdev_sent); > + if (!may_steal) { > + dp_packet_batch_reset_cutlen(batch); > + } > } > return error; > } > diff --git a/lib/odp-execute.c b/lib/odp-execute.c > index 4239624..5a43904 100644 > --- a/lib/odp-execute.c > +++ b/lib/odp-execute.c > @@ -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: > @@ -625,6 +626,17 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal, > } > break; > > + case OVS_ACTION_ATTR_TRUNC: { > + const struct ovs_action_trunc *trunc = > + nl_attr_get_unspec(a, sizeof *trunc); > + > + batch->trunc = true; > + for (i = 0; i < cnt; i++) { > + dp_packet_set_cutlen(packets[i], trunc->max_len); > + } > + break; > + } > + > case OVS_ACTION_ATTR_OUTPUT: > case OVS_ACTION_ATTR_TUNNEL_PUSH: > case OVS_ACTION_ATTR_TUNNEL_POP: > diff --git a/lib/odp-util.c b/lib/odp-util.c > index 48c05f5..d7b6a2d 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; > @@ -777,6 +778,14 @@ 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(%"PRIu32")", trunc->max_len); > + break; > + } > + break; > case OVS_ACTION_ATTR_TUNNEL_POP: > ds_put_format(ds, "tnl_pop(%"PRIu32")", nl_attr_get_u32(a)); > break; > @@ -1528,6 +1537,20 @@ parse_odp_action(const char *s, const struct simap *port_names, > } > } > > + { > + uint32_t max_len; > + int n; > + > + if (ovs_scan(s, "trunc(%"SCNi32")%n", &max_len, &n)) { > + struct ovs_action_trunc *trunc; > + > + trunc = nl_msg_put_unspec_uninit(actions, > + OVS_ACTION_ATTR_TRUNC, sizeof *trunc); > + trunc->max_len = max_len; > + return n; > + } > + } > + > if (port_names) { > int len = strcspn(s, delimiters); > struct simap_node *node; > diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c > index ea55896..997cc15 100644 > --- a/lib/ofp-actions.c > +++ b/lib/ofp-actions.c > @@ -301,6 +301,9 @@ enum ofp_raw_action_type { > /* NX1.0+(36): struct nx_action_nat, ... */ > NXAST_RAW_NAT, > > + /* NX1.0+(39): struct nx_action_output_trunc. */ > + NXAST_RAW_OUTPUT_TRUNC, > + > /* ## ------------------ ## */ > /* ## Debugging actions. ## */ > /* ## ------------------ ## */ > @@ -381,6 +384,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: > @@ -538,6 +542,40 @@ 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")) { > + if (!ofputil_port_from_string(value, &output_trunc->port)) { > + return xasprintf("output to unknown truncate port: %s", > + value); > + } > + if (ofp_to_u16(output_trunc->port) > ofp_to_u16(OFPP_MAX)) { > + if (output_trunc->port != OFPP_LOCAL && > + output_trunc->port != OFPP_IN_PORT) > + return xasprintf("output to unsupported truncate port: %s", > + value); > + } > + } else if (!strcmp(key, "max_len")) { > + char *err; > + > + err = str_to_u32(value, &output_trunc->max_len); > + if (err) { > + return err; > + } > + } 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) > { > @@ -547,6 +585,11 @@ 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; > > @@ -5603,6 +5646,61 @@ 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 port; /* Output port */ > + ovs_be32 max_len; /* Truncate packet to size bytes */ > +}; > +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 = ntohl(natrc->max_len); > + output_trunc->port = u16_to_ofp(ntohs(natrc->port)); > + > + if (output_trunc->max_len < ETH_HEADER_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 = htonl(output_trunc->max_len); > + natrc->port = htons(ofp_to_u16(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) > +{ > + ds_put_format(s, "%soutput%s(port=%"PRIu16",max_len=%"PRIu32")", > + colors.special, colors.end, a->port, a->max_len); > +} > + > > /* Meter instruction. */ > > @@ -5997,6 +6095,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: > @@ -6025,6 +6124,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: > @@ -6249,6 +6349,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: > @@ -6677,6 +6778,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); > > @@ -7354,6 +7459,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..5d26b7c 100644 > --- a/ofproto/ofproto-dpif-sflow.c > +++ b/ofproto/ofproto-dpif-sflow.c > @@ -1140,6 +1140,7 @@ dpif_sflow_read_actions(const struct flow *flow, > } > break; > > + case OVS_ACTION_ATTR_TRUNC: > case OVS_ACTION_ATTR_USERSPACE: > case OVS_ACTION_ATTR_RECIRC: > case OVS_ACTION_ATTR_HASH: > diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c > index f8c4a93..d46a52c 100644 > --- a/ofproto/ofproto-dpif-xlate.c > +++ b/ofproto/ofproto-dpif-xlate.c > @@ -3995,6 +3995,56 @@ xlate_output_reg_action(struct xlate_ctx *ctx, > } > > static void > +xlate_output_trunc_action(struct xlate_ctx *ctx, > + ofp_port_t port, uint32_t max_len) > +{ > + bool support_trunc = ctx->xbridge->support.trunc; > + struct ovs_action_trunc *trunc; > + char name[OFP_MAX_PORT_NAME_LEN]; > + > + switch (port) { > + case OFPP_TABLE: > + case OFPP_NORMAL: > + case OFPP_FLOOD: > + case OFPP_ALL: > + case OFPP_CONTROLLER: > + case OFPP_NONE: > + ofputil_port_to_string(port, name, sizeof name); > + xlate_report(ctx, "output_trunc does not support port: %s", name); > + break; > + case OFPP_LOCAL: > + case OFPP_IN_PORT: > + default: > + if (port != ctx->xin->flow.in_port.ofp_port) { > + const struct xport *xport = get_ofp_port(ctx->xbridge, port); > + > + if (xport == NULL || xport->odp_port == ODPP_NONE) { > + /* Since truncate happens at its following output action, if > + * the output port is a patch port, the behavior is somehow > + * unpredicable. For simpilicity, disallow this case. */ > + ofputil_port_to_string(port, name, sizeof name); > + XLATE_REPORT_ERROR(ctx, "bridge %s: " > + "output_trunc does not support port: %s", > + ctx->xbridge->name, name); > + break; > + } > + > + 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) > { > @@ -4333,6 +4383,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: > @@ -4584,6 +4635,7 @@ recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx) > > /* Output actions do not require recirculation. */ > case OFPACT_OUTPUT: > + case OFPACT_OUTPUT_TRUNC: > case OFPACT_ENQUEUE: > case OFPACT_OUTPUT_REG: > /* Set actions that don't touch L3+ fields do not require recirculation. */ > @@ -4933,6 +4985,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 100e8d5..0cd1c25 100644 > --- a/ofproto/ofproto-dpif.c > +++ b/ofproto/ofproto-dpif.c > @@ -1212,6 +1212,63 @@ 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; > + struct flow flow; > + 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_HEADER_LEN + 1; > + nl_msg_put_odp_port(&actions, OVS_ACTION_ATTR_OUTPUT, u32_to_odp(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); > + > + flow_extract(&packet, &flow); > + > + /* 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.flow = &flow; > + 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) { > + VLOG_INFO("%s: Datapath does not support truncate action", > + dpif_name(backer->dpif)); > + } else { > + VLOG_INFO("%s: Datapath supports truncate action", > + dpif_name(backer->dpif)); > + } > + > + return !error; > +} > + > #define CHECK_FEATURE__(NAME, SUPPORT, FIELD, VALUE) \ > static bool \ > check_##NAME(struct dpif_backer *backer) \ > @@ -1263,6 +1320,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); > > @@ -5343,6 +5401,24 @@ disable_tnl_push_pop(struct unixctl_conn *conn OVS_UNUSED, int argc OVS_UNUSED, > } > > static void > +disable_datapath_truncate(struct unixctl_conn *conn OVS_UNUSED, > + int argc OVS_UNUSED, > + const char *argv[] OVS_UNUSED, > + void *aux OVS_UNUSED) > +{ > + const struct shash_node **backers; > + int i; > + > + backers = shash_sort(&all_dpif_backers); > + for (i = 0; i < shash_count(&all_dpif_backers); i++) { > + struct dpif_backer *backer = backers[i]->data; > + backer->support.trunc = false; > + } > + free(backers); > + unixctl_command_reply(conn, "Datapath truncate action diabled"); > +} > + > +static void > ofproto_unixctl_init(void) > { > static bool registered; > @@ -5376,6 +5452,9 @@ ofproto_unixctl_init(void) > > unixctl_command_register("ofproto/tnl-push-pop", "[on]|[off]", 1, 1, > disable_tnl_push_pop, NULL); > + > + unixctl_command_register("dpif/disable-truncate", "", 0, 0, > + disable_datapath_truncate, NULL); > } > > /* Returns true if 'table' is the table used for internal rules, > diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h > index 9e03b01..4034475 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/odp.at b/tests/odp.at > index 7b94c92..e630855 100644 > --- a/tests/odp.at > +++ b/tests/odp.at > @@ -329,6 +329,7 @@ ct(commit,nat(src=fe80::20c:29ff:fe88:a18b,random)) > ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b,random)) > ct(commit,nat(src=[[fe80::20c:29ff:fe88:1]]-[[fe80::20c:29ff:fe88:a18b]]:255-4096,random)) > ct(commit,helper=ftp,nat(src=10.1.1.240-10.1.1.255)) > +trunc(100) > ]) > AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], > [`cat actions.txt` > diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at > index 50f74e9..ca4d1ba 100644 > --- a/tests/ofp-actions.at > +++ b/tests/ofp-actions.at > @@ -241,6 +241,9 @@ fe800000 00000000 020c 29ff fe88 0001 dnl > fe800000 00000000 020c 29ff fe88 a18b dnl > 00ff1000 00000000 > > +# actions=output(port=1,max_len=100) > +ffff 0010 00002320 0027 0001 00000064 > + > # bad OpenFlow10 actions: NXBRC_MUST_BE_ZERO > ffff 0018 00002320 0025 0000 0005 0000 1122334455 000005 > > diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at > index 9b94f3d..b0c6e0a 100644 > --- a/tests/ofproto-dpif.at > +++ b/tests/ofproto-dpif.at > @@ -5322,6 +5322,130 @@ PORTNAME > portName=p2 > ])]) > > +AT_SETUP([ofproto-dpif - basic 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 > +]) > + > +AT_CHECK([ovs-appctl revalidator/purge], [0]) > +dnl packet with truncated size > +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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']) > + > +AT_CHECK([ovs-appctl revalidator/purge], [0]) > +dnl packet size: 64 + 128 + 170 = 362 > +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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: output to unsupported truncate port: ALL > +]) > + > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > +AT_SETUP([ofproto-dpif - truncate and output to patch port]) > +OVS_VSWITCHD_START([add-br br1 \ > +-- set bridge br1 datapath-type=dummy fail-mode=secure \ > +-- add-port br1 pbr1 -- set int pbr1 type=patch options:peer=pbr0 ofport_request=1 \ > +-- add-port br0 pbr0 -- set int pbr0 type=patch options:peer=pbr1]) > + > +add_of_ports br0 2 > + > +AT_CHECK([ovs-ofctl add-flow br0 actions='output(port=1,max_len=100),output:2']) > +AT_CHECK([ovs-ofctl add-flow br1 actions=NORMAL]) > + > +AT_CHECK([ovs-appctl ofproto/trace br0 in_port=LOCAL,dl_src=10:20:30:40:50:60], > +[0], [stdout]) > +AT_CHECK([tail -1 stdout], [0], [Datapath actions: 2 > +]) > +dnl the output(port=1,max_len=100) fails the translation, only output:2 in datapath > +AT_CHECK([grep "output_trunc does not support port: [[0-9]]*" stdout], [0], [stdout]) > + > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > +AT_SETUP([ofproto-dpif - truncate and output to gre tunnel]) > +OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=gre \ > + options:remote_ip=1.1.1.1 options:local_ip=2.2.2.2 \ > + options:key=5 ofport_request=1\ > + -- add-port br0 p2 -- set Interface p2 type=dummy \ > + ofport_request=2]) > +AT_DATA([flows.txt], [dnl > +actions=output(max_len=100, port=1) > +]) > +OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP > +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) > + > +AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl > + br0 65534/100: (dummy) > + p1 1/1: (gre: key=5, local_ip=2.2.2.2, remote_ip=1.1.1.1) > + p2 2/2: (dummy) > +]) > + > +dnl Basic > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),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(100),set(tunnel(tun_id=0x5,src=2.2.2.2,dst=1.1.1.1,ttl=64,flags(df|key))),1 > +]) > + > +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/ovs-ofctl.at b/tests/ovs-ofctl.at > index 613d9ce..71c4aab 100644 > --- a/tests/ovs-ofctl.at > +++ b/tests/ovs-ofctl.at > @@ -388,6 +388,10 @@ ip,actions=ct(commit,exec(load(0x1->NXM_NX_CT_LABEL[]))) > ip,actions=ct(commit,exec(load(0x1234567890ABCDEF->NXM_NX_CT_LABEL[32..95]))) > ip,actions=ct(commit,exec(set_field(0x1->ct_label))) > ip,ct_state=+trk,ct_label=0x1234567890abcdef12345678,actions=ct(commit) > +actions=output(max_len=100,port=123) > +actions=output(port=100,max_len=123) > +actions=output(port=LOCAL,max_len=123) > +actions=output(port=IN_PORT,max_len=123) > ]]) > > AT_CHECK([ovs-ofctl parse-flows flows.txt > @@ -431,6 +435,10 @@ NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[ > NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1234567890abcdef->NXM_NX_CT_LABEL[32..95])) > NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[0..63],load:0->NXM_NX_CT_LABEL[64..127])) > NXT_FLOW_MOD: ADD table:255 ct_state=+trk,ct_label=0x1234567890abcdef12345678,ip actions=ct(commit) > +NXT_FLOW_MOD: ADD table:255 actions=output(port=123,max_len=100) > +NXT_FLOW_MOD: ADD table:255 actions=output(port=100,max_len=123) > +NXT_FLOW_MOD: ADD table:255 actions=output(port=65534,max_len=123) > +NXT_FLOW_MOD: ADD table:255 actions=output(port=65528,max_len=123) > ]]) > AT_CLEANUP > > diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at > index cee0510..a3e4dd7 100644 > --- a/tests/system-kmod-macros.at > +++ b/tests/system-kmod-macros.at > @@ -66,3 +66,10 @@ m4_define([CHECK_CONNTRACK], > on_exit 'ovstest test-netlink-conntrack flush' > ] > ) > + > +# CHECK_KERNEL_DP, CHECK_USER_DP > +# > +# Ignore the CHECK_USER_DP and execute the CHECK_KERNEL_DP > +# > +m4_define([CHECK_KERNEL_DP], [$1]) > +m4_define([CHECK_USER_DP], []) > diff --git a/tests/system-traffic.at b/tests/system-traffic.at > index 5ce3955..606bc9b 100644 > --- a/tests/system-traffic.at > +++ b/tests/system-traffic.at > @@ -250,6 +250,253 @@ 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 - basic 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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\1/p'], [0], [dnl > +n_bytes=542 > +]) > + > +dnl SLOW_ACTION: disable kernel datapath truncate support > +dnl Repeat the test above, but exercise the SLOW_ACTION code path > +AT_CHECK([ovs-appctl dpif/disable-truncate], [0], > +[Datapath truncate action diabled > +]) > + > +dnl SLOW_ACTION test1: check datapatch actions > +AT_CHECK([ovs-ofctl del-flows br0]) > +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) > + > +CHECK_KERNEL_DP( > +AT_CHECK([ovs-appctl ofproto/trace system 'in_port(2),eth(src=e6:66:c1:11:11:11,dst=e6:66:c1:22:22:22),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 -3 stdout], [0], > +[Datapath actions: trunc(100),3,5,trunc(100),3,trunc(100),5,3,trunc(200),5,trunc(65535),3 > +This flow is handled by the userspace slow path because it: > + - Uses action(s) not supported by datapath. > +]) > +) > + > +dnl SLOW_ACTION test2: check actual packet truncate > +AT_CHECK([ovs-ofctl del-flows br0]) > +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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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 output 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]) > + > +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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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 > +]) > + > +dnl SLOW_ACTION: disable datapath truncate support > +dnl Repeat the test above, but exercise the SLOW_ACTION code path > +AT_CHECK([ovs-appctl dpif/disable-truncate], [0], > +[Datapath truncate action diabled > +]) > + > +dnl SLOW_ACTION test1: check datapatch actions > +AT_CHECK([ovs-ofctl del-flows br0]) > +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) > + > +CHECK_KERNEL_DP( > +AT_CHECK([ovs-appctl ofproto/trace system 'in_port(5),eth(src=e6:66:c1:11:11:11,dst=e6:66:c1:22:22:22),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=17,tos=4,ttl=128,frag=no),udp(src=8,dst=9)'], [0], [stdout]) > +AT_CHECK([tail -3 stdout], [0], > +[Datapath actions: trunc(100),set(tunnel(dst=172.31.1.1,ttl=64,flags(df))),4 > +This flow is handled by the userspace slow path because it: > + - Uses action(s) not supported by datapath. > +]) > +) > + > +dnl SLOW_ACTION test2: check actual packet truncate > +AT_CHECK([ovs-ofctl del-flows br0]) > +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) > +AT_CHECK([ovs-ofctl del-flows br-underlay]) > +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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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() > diff --git a/tests/system-userspace-macros.at b/tests/system-userspace-macros.at > index c09a4aa..dc4bd0e 100644 > --- a/tests/system-userspace-macros.at > +++ b/tests/system-userspace-macros.at > @@ -66,3 +66,10 @@ m4_define([CONFIGURE_VETH_OFFLOADS], > m4_define([CHECK_CONNTRACK], > [AT_SKIP_IF(true)] > ) > + > +# CHECK_KERNEL_DP, CHECK_USER_DP > +# > +# Ignore the CHECK_KERNEL_DP and execute the CHECK_USER_DP > +# > +m4_define([CHECK_KERNEL_DP], []) > +m4_define([CHECK_USER_DP], [$1]) > -- Test looks good. packet truncate support documentation is missing in man page. NEWS also needs to be updated.
Thanks, I will update and resubmit. On Thu, Jun 23, 2016 at 3:28 PM, pravin shelar <pshelar@ovn.org> wrote: > On Wed, Jun 22, 2016 at 10:02 PM, William Tu <u9012063@gmail.com> wrote: >> 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 >> >> Tested-at: https://travis-ci.org/williamtu/ovs-travis/builds/139668398 >> Signed-off-by: William Tu <u9012063@gmail.com> >> --- >> include/openvswitch/ofp-actions.h | 10 ++ >> lib/dp-packet.c | 2 + >> lib/dp-packet.h | 67 +++++++++++ >> lib/dpif-netdev.c | 33 ++++- >> lib/dpif.c | 25 +++- >> lib/dpif.h | 1 + >> lib/netdev-bsd.c | 3 + >> lib/netdev-dpdk.c | 21 ++++ >> lib/netdev-dummy.c | 2 + >> lib/netdev-linux.c | 3 + >> lib/netdev.c | 5 +- >> lib/odp-execute.c | 12 ++ >> lib/odp-util.c | 23 ++++ >> lib/ofp-actions.c | 106 ++++++++++++++++ >> ofproto/ofproto-dpif-sflow.c | 1 + >> ofproto/ofproto-dpif-xlate.c | 57 +++++++++ >> ofproto/ofproto-dpif.c | 79 ++++++++++++ >> ofproto/ofproto-dpif.h | 3 + >> tests/odp.at | 1 + >> tests/ofp-actions.at | 3 + >> tests/ofproto-dpif.at | 124 +++++++++++++++++++ >> tests/ovs-ofctl.at | 8 ++ >> tests/system-kmod-macros.at | 7 ++ >> tests/system-traffic.at | 247 ++++++++++++++++++++++++++++++++++++++ >> tests/system-userspace-macros.at | 7 ++ >> 25 files changed, 846 insertions(+), 4 deletions(-) >> >> diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h >> index 91c7ee5..0b8ccbb 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. */ >> + uint32_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..8e7defc 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); >> + dp_packet_reset_cutlen(b); >> } >> >> static void >> @@ -168,6 +169,7 @@ dp_packet_clone_with_headroom(const struct dp_packet *buffer, size_t headroom) >> new_buffer->l3_ofs = buffer->l3_ofs; >> new_buffer->l4_ofs = buffer->l4_ofs; >> new_buffer->md = buffer->md; >> + new_buffer->cutlen = buffer->cutlen; >> #ifdef DPDK_NETDEV >> new_buffer->mbuf.ol_flags = buffer->mbuf.ol_flags; >> #else >> diff --git a/lib/dp-packet.h b/lib/dp-packet.h >> index 118c84d..f505fa5 100644 >> --- a/lib/dp-packet.h >> +++ b/lib/dp-packet.h >> @@ -60,6 +60,7 @@ struct dp_packet { >> * or UINT16_MAX. */ >> uint16_t l4_ofs; /* Transport-level header offset, >> or UINT16_MAX. */ >> + uint32_t cutlen; /* length in bytes to cut from the end. */ >> union { >> struct pkt_metadata md; >> uint64_t data[DP_PACKET_CONTEXT_SIZE / 8]; >> @@ -494,6 +495,34 @@ 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, uint32_t max_len) >> +{ >> + if (max_len < ETH_HEADER_LEN) { >> + max_len = ETH_HEADER_LEN; >> + } >> + >> + if (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) >> +{ >> + /* Always in valid range if user uses dp_packet_set_cutlen. */ >> + return b->cutlen; >> +} >> + >> static inline void * >> dp_packet_data(const struct dp_packet *b) >> { >> @@ -567,12 +596,14 @@ enum { NETDEV_MAX_BURST = 32 }; /* Maximum number packets in a batch. */ >> >> struct dp_packet_batch { >> int count; >> + bool trunc; /* true if the batch needs truncate. */ >> struct dp_packet *packets[NETDEV_MAX_BURST]; >> }; >> >> static inline void dp_packet_batch_init(struct dp_packet_batch *b) >> { >> b->count = 0; >> + b->trunc = false; >> } >> >> static inline void >> @@ -585,12 +616,14 @@ dp_packet_batch_clone(struct dp_packet_batch *dst, >> dst->packets[i] = dp_packet_clone(src->packets[i]); >> } >> dst->count = src->count; >> + dst->trunc = src->trunc; >> } >> >> static inline void >> packet_batch_init_packet(struct dp_packet_batch *b, struct dp_packet *p) >> { >> b->count = 1; >> + b->trunc = false; >> b->packets[0] = p; >> } >> >> @@ -606,6 +639,40 @@ dp_packet_delete_batch(struct dp_packet_batch *batch, bool may_steal) >> } >> } >> >> +static inline void >> +dp_packet_batch_apply_cutlen(struct dp_packet_batch *pktb) >> +{ >> + int i; >> + >> + if (!pktb->trunc) >> + return; >> + >> + for (i = 0; i < pktb->count; i++) { >> + uint32_t cutlen = dp_packet_get_cutlen(pktb->packets[i]); >> + >> + if (cutlen > 0) { >> + dp_packet_set_size(pktb->packets[i], >> + dp_packet_size(pktb->packets[i]) - cutlen); >> + dp_packet_reset_cutlen(pktb->packets[i]); >> + } >> + } >> + pktb->trunc = false; >> +} >> + >> +static inline void >> +dp_packet_batch_reset_cutlen(struct dp_packet_batch *pktb) >> +{ >> + int i; >> + >> + if (!pktb->trunc) >> + return; >> + >> + pktb->trunc = false; >> + for (i = 0; i < pktb->count; i++) { >> + dp_packet_reset_cutlen(pktb->packets[i]); >> + } >> +} >> + >> #ifdef __cplusplus >> } >> #endif >> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c >> index 8d39d9e..70f320d 100644 >> --- a/lib/dpif-netdev.c >> +++ b/lib/dpif-netdev.c >> @@ -4057,13 +4057,17 @@ 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_batch *orig_packets_ = packets_; >> int err; >> >> if (!may_steal) { >> dp_packet_batch_clone(&tnl_pkt, packets_); >> packets_ = &tnl_pkt; >> + dp_packet_batch_reset_cutlen(orig_packets_); >> } >> >> + dp_packet_batch_apply_cutlen(packets_); >> + >> err = push_tnl_action(pmd, a, packets_); >> if (!err) { >> (*depth)++; >> @@ -4076,6 +4080,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_batch *orig_packets_ = packets_; >> odp_port_t portno = u32_to_odp(nl_attr_get_u32(a)); >> >> p = pmd_tx_port_cache_lookup(pmd, portno); >> @@ -4084,10 +4089,13 @@ 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; >> + dp_packet_batch_reset_cutlen(orig_packets_); >> } >> >> + dp_packet_batch_apply_cutlen(packets_); >> + >> netdev_pop_header(p->netdev, packets_); >> if (!packets_->count) { >> return; >> @@ -4107,22 +4115,42 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_, >> >> case OVS_ACTION_ATTR_USERSPACE: >> if (!fat_rwlock_tryrdlock(&dp->upcall_rwlock)) { >> + struct dp_packet_batch *orig_packets_ = packets_; >> struct dp_packet **packets = packets_->packets; >> const struct nlattr *userdata; >> + struct dp_packet_batch usr_pkt; >> struct ofpbuf actions; >> struct flow flow; >> ovs_u128 ufid; >> + bool clone = false; >> int i; >> >> userdata = nl_attr_find_nested(a, OVS_USERSPACE_ATTR_USERDATA); >> ofpbuf_init(&actions, 0); >> >> + if (packets_->trunc) { >> + if (!may_steal) { >> + dp_packet_batch_clone(&usr_pkt, packets_); >> + packets_ = &usr_pkt; >> + packets = packets_->packets; >> + clone = true; >> + dp_packet_batch_reset_cutlen(orig_packets_); >> + } >> + >> + dp_packet_batch_apply_cutlen(packets_); >> + } >> + >> for (i = 0; i < packets_->count; i++) { >> flow_extract(packets[i], &flow); >> dpif_flow_hash(dp->dpif, &flow, sizeof flow, &ufid); >> dp_execute_userspace_action(pmd, packets[i], may_steal, &flow, >> &ufid, &actions, userdata); >> } >> + >> + if (clone) { >> + dp_packet_delete_batch(packets_, true); >> + } >> + >> ofpbuf_uninit(&actions); >> fat_rwlock_unlock(&dp->upcall_rwlock); >> >> @@ -4170,6 +4198,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(); >> } >> diff --git a/lib/dpif.c b/lib/dpif.c >> index c4f24c7..5f1be41 100644 >> --- a/lib/dpif.c >> +++ b/lib/dpif.c >> @@ -1092,6 +1092,7 @@ 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); >> >> @@ -1106,7 +1107,8 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, >> struct ofpbuf execute_actions; >> uint64_t stub[256 / 8]; >> struct pkt_metadata *md = &packet->md; >> - bool dst_set; >> + bool dst_set, clone = false; >> + uint32_t cutlen = dp_packet_get_cutlen(packet); >> >> dst_set = flow_tnl_dst_is_set(&md->tunnel); >> if (dst_set) { >> @@ -1124,6 +1126,22 @@ 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 && (type == OVS_ACTION_ATTR_OUTPUT || >> + type == OVS_ACTION_ATTR_TUNNEL_PUSH || >> + type == OVS_ACTION_ATTR_TUNNEL_POP || >> + type == OVS_ACTION_ATTR_USERSPACE)) { >> + if (!may_steal) { >> + trunc_packet = dp_packet_clone(packet); >> + packet = trunc_packet; >> + clone = true; >> + } >> + >> + dp_packet_set_size(packet, dp_packet_size(packet) - cutlen); >> + dp_packet_reset_cutlen(orig_packet); >> + } >> + >> execute.packet = packet; >> execute.flow = aux->flow; >> execute.needs_help = false; >> @@ -1135,6 +1153,10 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, >> if (dst_set) { >> ofpbuf_uninit(&execute_actions); >> } >> + >> + if (clone) { >> + dp_packet_delete(trunc_packet); >> + } >> break; >> } >> >> @@ -1146,6 +1168,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, >> case OVS_ACTION_ATTR_SET: >> case OVS_ACTION_ATTR_SET_MASKED: >> case OVS_ACTION_ATTR_SAMPLE: >> + case OVS_ACTION_ATTR_TRUNC: >> case OVS_ACTION_ATTR_UNSPEC: >> case __OVS_ACTION_ATTR_MAX: >> OVS_NOT_REACHED(); >> diff --git a/lib/dpif.h b/lib/dpif.h >> index 6788301..981868c 100644 >> --- a/lib/dpif.h >> +++ b/lib/dpif.h >> @@ -784,6 +784,7 @@ struct dpif_upcall { >> size_t key_len; /* Length of 'key' in bytes. */ >> ovs_u128 ufid; /* Unique flow identifier for 'key'. */ >> struct nlattr *mru; /* Maximum receive unit. */ >> + struct nlattr *cutlen; /* Number of bytes shrink from the end. */ >> >> /* DPIF_UC_ACTION only. */ >> struct nlattr *userdata; /* Argument to OVS_ACTION_ATTR_USERSPACE. */ >> diff --git a/lib/netdev-bsd.c b/lib/netdev-bsd.c >> index 43fa982..2e92d97 100644 >> --- a/lib/netdev-bsd.c >> +++ b/lib/netdev-bsd.c >> @@ -699,6 +699,9 @@ netdev_bsd_send(struct netdev *netdev_, int qid OVS_UNUSED, >> const void *data = dp_packet_data(pkts[i]); >> size_t size = dp_packet_size(pkts[i]); >> >> + /* Truncate the packet if it is configured. */ >> + size -= dp_packet_get_cutlen(pkts[i]); >> + >> while (!error) { >> ssize_t retval; >> if (dev->tap_fd >= 0) { >> diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c >> index fc0c8d3..8063add 100644 >> --- a/lib/netdev-dpdk.c >> +++ b/lib/netdev-dpdk.c >> @@ -1487,6 +1487,7 @@ dpdk_do_tx_copy(struct netdev *netdev, int qid, struct dp_packet **pkts, >> >> for (i = 0; i < cnt; i++) { >> int size = dp_packet_size(pkts[i]); >> + int cutlen = dp_packet_get_cutlen(pkts[i]); >> >> if (OVS_UNLIKELY(size > dev->max_packet_len)) { >> VLOG_WARN_RL(&rl, "Too big size %d max_packet_len %d", >> @@ -1503,6 +1504,12 @@ dpdk_do_tx_copy(struct netdev *netdev, int qid, struct dp_packet **pkts, >> break; >> } >> >> + if (OVS_UNLIKELY(cutlen > 0)) { >> + /* Cut the size so only the truncated size is copied. */ >> + size -= cutlen; >> + dp_packet_reset_cutlen(pkts[i]); >> + } >> + > As commented in last review, there is no need to check for cut length > anywhere while setting the size. > >> /* We have to do a copy for now */ >> memcpy(rte_pktmbuf_mtod(mbufs[newcnt], void *), dp_packet_data(pkts[i]), size); >> >> @@ -1550,6 +1557,14 @@ netdev_dpdk_vhost_send(struct netdev *netdev, int qid, struct dp_packet **pkts, >> } >> } >> } else { >> + int i; >> + >> + for (i = 0; i < cnt; i++) { >> + int cutlen = dp_packet_get_cutlen(pkts[i]); >> + >> + dp_packet_set_size(pkts[i], dp_packet_size(pkts[i]) - cutlen); >> + dp_packet_reset_cutlen(pkts[i]); >> + } >> __netdev_dpdk_vhost_send(netdev, qid, pkts, cnt, may_steal); >> } >> return 0; >> @@ -1585,6 +1600,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 (OVS_UNLIKELY(cutlen > 0)) { >> + size -= cutlen; >> + dp_packet_set_size(pkts[i], size); >> + } >> >> if (OVS_UNLIKELY(size > dev->max_packet_len)) { >> if (next_tx_idx != i) { >> diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c >> index aa244b6..127b6ae 100644 >> --- a/lib/netdev-dummy.c >> +++ b/lib/netdev-dummy.c >> @@ -1044,6 +1044,8 @@ 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]); >> >> + size -= dp_packet_get_cutlen(pkts[i]); >> + >> if (size < ETH_HEADER_LEN) { >> error = EMSGSIZE; >> break; >> diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c >> index 82813ba..d6e5a54 100644 >> --- a/lib/netdev-linux.c >> +++ b/lib/netdev-linux.c >> @@ -1170,6 +1170,9 @@ netdev_linux_send(struct netdev *netdev_, int qid OVS_UNUSED, >> size_t size = dp_packet_size(pkts[i]); >> ssize_t retval; >> >> + /* Truncate the packet if it is configured. */ >> + size -= dp_packet_get_cutlen(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 4be806d..6651173 100644 >> --- a/lib/netdev.c >> +++ b/lib/netdev.c >> @@ -681,7 +681,7 @@ netdev_set_tx_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 >> @@ -716,6 +716,9 @@ netdev_send(struct netdev *netdev, int qid, struct dp_packet_batch *batch, >> may_steal); >> if (!error) { >> COVERAGE_INC(netdev_sent); >> + if (!may_steal) { >> + dp_packet_batch_reset_cutlen(batch); >> + } >> } >> return error; >> } >> diff --git a/lib/odp-execute.c b/lib/odp-execute.c >> index 4239624..5a43904 100644 >> --- a/lib/odp-execute.c >> +++ b/lib/odp-execute.c >> @@ -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: >> @@ -625,6 +626,17 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal, >> } >> break; >> >> + case OVS_ACTION_ATTR_TRUNC: { >> + const struct ovs_action_trunc *trunc = >> + nl_attr_get_unspec(a, sizeof *trunc); >> + >> + batch->trunc = true; >> + for (i = 0; i < cnt; i++) { >> + dp_packet_set_cutlen(packets[i], trunc->max_len); >> + } >> + break; >> + } >> + >> case OVS_ACTION_ATTR_OUTPUT: >> case OVS_ACTION_ATTR_TUNNEL_PUSH: >> case OVS_ACTION_ATTR_TUNNEL_POP: >> diff --git a/lib/odp-util.c b/lib/odp-util.c >> index 48c05f5..d7b6a2d 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; >> @@ -777,6 +778,14 @@ 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(%"PRIu32")", trunc->max_len); >> + break; >> + } >> + break; >> case OVS_ACTION_ATTR_TUNNEL_POP: >> ds_put_format(ds, "tnl_pop(%"PRIu32")", nl_attr_get_u32(a)); >> break; >> @@ -1528,6 +1537,20 @@ parse_odp_action(const char *s, const struct simap *port_names, >> } >> } >> >> + { >> + uint32_t max_len; >> + int n; >> + >> + if (ovs_scan(s, "trunc(%"SCNi32")%n", &max_len, &n)) { >> + struct ovs_action_trunc *trunc; >> + >> + trunc = nl_msg_put_unspec_uninit(actions, >> + OVS_ACTION_ATTR_TRUNC, sizeof *trunc); >> + trunc->max_len = max_len; >> + return n; >> + } >> + } >> + >> if (port_names) { >> int len = strcspn(s, delimiters); >> struct simap_node *node; >> diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c >> index ea55896..997cc15 100644 >> --- a/lib/ofp-actions.c >> +++ b/lib/ofp-actions.c >> @@ -301,6 +301,9 @@ enum ofp_raw_action_type { >> /* NX1.0+(36): struct nx_action_nat, ... */ >> NXAST_RAW_NAT, >> >> + /* NX1.0+(39): struct nx_action_output_trunc. */ >> + NXAST_RAW_OUTPUT_TRUNC, >> + >> /* ## ------------------ ## */ >> /* ## Debugging actions. ## */ >> /* ## ------------------ ## */ >> @@ -381,6 +384,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: >> @@ -538,6 +542,40 @@ 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")) { >> + if (!ofputil_port_from_string(value, &output_trunc->port)) { >> + return xasprintf("output to unknown truncate port: %s", >> + value); >> + } >> + if (ofp_to_u16(output_trunc->port) > ofp_to_u16(OFPP_MAX)) { >> + if (output_trunc->port != OFPP_LOCAL && >> + output_trunc->port != OFPP_IN_PORT) >> + return xasprintf("output to unsupported truncate port: %s", >> + value); >> + } >> + } else if (!strcmp(key, "max_len")) { >> + char *err; >> + >> + err = str_to_u32(value, &output_trunc->max_len); >> + if (err) { >> + return err; >> + } >> + } 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) >> { >> @@ -547,6 +585,11 @@ 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; >> >> @@ -5603,6 +5646,61 @@ 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 port; /* Output port */ >> + ovs_be32 max_len; /* Truncate packet to size bytes */ >> +}; >> +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 = ntohl(natrc->max_len); >> + output_trunc->port = u16_to_ofp(ntohs(natrc->port)); >> + >> + if (output_trunc->max_len < ETH_HEADER_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 = htonl(output_trunc->max_len); >> + natrc->port = htons(ofp_to_u16(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) >> +{ >> + ds_put_format(s, "%soutput%s(port=%"PRIu16",max_len=%"PRIu32")", >> + colors.special, colors.end, a->port, a->max_len); >> +} >> + >> >> /* Meter instruction. */ >> >> @@ -5997,6 +6095,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: >> @@ -6025,6 +6124,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: >> @@ -6249,6 +6349,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: >> @@ -6677,6 +6778,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); >> >> @@ -7354,6 +7459,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..5d26b7c 100644 >> --- a/ofproto/ofproto-dpif-sflow.c >> +++ b/ofproto/ofproto-dpif-sflow.c >> @@ -1140,6 +1140,7 @@ dpif_sflow_read_actions(const struct flow *flow, >> } >> break; >> >> + case OVS_ACTION_ATTR_TRUNC: >> case OVS_ACTION_ATTR_USERSPACE: >> case OVS_ACTION_ATTR_RECIRC: >> case OVS_ACTION_ATTR_HASH: >> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c >> index f8c4a93..d46a52c 100644 >> --- a/ofproto/ofproto-dpif-xlate.c >> +++ b/ofproto/ofproto-dpif-xlate.c >> @@ -3995,6 +3995,56 @@ xlate_output_reg_action(struct xlate_ctx *ctx, >> } >> >> static void >> +xlate_output_trunc_action(struct xlate_ctx *ctx, >> + ofp_port_t port, uint32_t max_len) >> +{ >> + bool support_trunc = ctx->xbridge->support.trunc; >> + struct ovs_action_trunc *trunc; >> + char name[OFP_MAX_PORT_NAME_LEN]; >> + >> + switch (port) { >> + case OFPP_TABLE: >> + case OFPP_NORMAL: >> + case OFPP_FLOOD: >> + case OFPP_ALL: >> + case OFPP_CONTROLLER: >> + case OFPP_NONE: >> + ofputil_port_to_string(port, name, sizeof name); >> + xlate_report(ctx, "output_trunc does not support port: %s", name); >> + break; >> + case OFPP_LOCAL: >> + case OFPP_IN_PORT: >> + default: >> + if (port != ctx->xin->flow.in_port.ofp_port) { >> + const struct xport *xport = get_ofp_port(ctx->xbridge, port); >> + >> + if (xport == NULL || xport->odp_port == ODPP_NONE) { >> + /* Since truncate happens at its following output action, if >> + * the output port is a patch port, the behavior is somehow >> + * unpredicable. For simpilicity, disallow this case. */ >> + ofputil_port_to_string(port, name, sizeof name); >> + XLATE_REPORT_ERROR(ctx, "bridge %s: " >> + "output_trunc does not support port: %s", >> + ctx->xbridge->name, name); >> + break; >> + } >> + >> + 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) >> { >> @@ -4333,6 +4383,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: >> @@ -4584,6 +4635,7 @@ recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx) >> >> /* Output actions do not require recirculation. */ >> case OFPACT_OUTPUT: >> + case OFPACT_OUTPUT_TRUNC: >> case OFPACT_ENQUEUE: >> case OFPACT_OUTPUT_REG: >> /* Set actions that don't touch L3+ fields do not require recirculation. */ >> @@ -4933,6 +4985,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 100e8d5..0cd1c25 100644 >> --- a/ofproto/ofproto-dpif.c >> +++ b/ofproto/ofproto-dpif.c >> @@ -1212,6 +1212,63 @@ 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; >> + struct flow flow; >> + 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_HEADER_LEN + 1; >> + nl_msg_put_odp_port(&actions, OVS_ACTION_ATTR_OUTPUT, u32_to_odp(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); >> + >> + flow_extract(&packet, &flow); >> + >> + /* 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.flow = &flow; >> + 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) { >> + VLOG_INFO("%s: Datapath does not support truncate action", >> + dpif_name(backer->dpif)); >> + } else { >> + VLOG_INFO("%s: Datapath supports truncate action", >> + dpif_name(backer->dpif)); >> + } >> + >> + return !error; >> +} >> + >> #define CHECK_FEATURE__(NAME, SUPPORT, FIELD, VALUE) \ >> static bool \ >> check_##NAME(struct dpif_backer *backer) \ >> @@ -1263,6 +1320,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); >> >> @@ -5343,6 +5401,24 @@ disable_tnl_push_pop(struct unixctl_conn *conn OVS_UNUSED, int argc OVS_UNUSED, >> } >> >> static void >> +disable_datapath_truncate(struct unixctl_conn *conn OVS_UNUSED, >> + int argc OVS_UNUSED, >> + const char *argv[] OVS_UNUSED, >> + void *aux OVS_UNUSED) >> +{ >> + const struct shash_node **backers; >> + int i; >> + >> + backers = shash_sort(&all_dpif_backers); >> + for (i = 0; i < shash_count(&all_dpif_backers); i++) { >> + struct dpif_backer *backer = backers[i]->data; >> + backer->support.trunc = false; >> + } >> + free(backers); >> + unixctl_command_reply(conn, "Datapath truncate action diabled"); >> +} >> + >> +static void >> ofproto_unixctl_init(void) >> { >> static bool registered; >> @@ -5376,6 +5452,9 @@ ofproto_unixctl_init(void) >> >> unixctl_command_register("ofproto/tnl-push-pop", "[on]|[off]", 1, 1, >> disable_tnl_push_pop, NULL); >> + >> + unixctl_command_register("dpif/disable-truncate", "", 0, 0, >> + disable_datapath_truncate, NULL); >> } >> >> /* Returns true if 'table' is the table used for internal rules, >> diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h >> index 9e03b01..4034475 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/odp.at b/tests/odp.at >> index 7b94c92..e630855 100644 >> --- a/tests/odp.at >> +++ b/tests/odp.at >> @@ -329,6 +329,7 @@ ct(commit,nat(src=fe80::20c:29ff:fe88:a18b,random)) >> ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b,random)) >> ct(commit,nat(src=[[fe80::20c:29ff:fe88:1]]-[[fe80::20c:29ff:fe88:a18b]]:255-4096,random)) >> ct(commit,helper=ftp,nat(src=10.1.1.240-10.1.1.255)) >> +trunc(100) >> ]) >> AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], >> [`cat actions.txt` >> diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at >> index 50f74e9..ca4d1ba 100644 >> --- a/tests/ofp-actions.at >> +++ b/tests/ofp-actions.at >> @@ -241,6 +241,9 @@ fe800000 00000000 020c 29ff fe88 0001 dnl >> fe800000 00000000 020c 29ff fe88 a18b dnl >> 00ff1000 00000000 >> >> +# actions=output(port=1,max_len=100) >> +ffff 0010 00002320 0027 0001 00000064 >> + >> # bad OpenFlow10 actions: NXBRC_MUST_BE_ZERO >> ffff 0018 00002320 0025 0000 0005 0000 1122334455 000005 >> >> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at >> index 9b94f3d..b0c6e0a 100644 >> --- a/tests/ofproto-dpif.at >> +++ b/tests/ofproto-dpif.at >> @@ -5322,6 +5322,130 @@ PORTNAME >> portName=p2 >> ])]) >> >> +AT_SETUP([ofproto-dpif - basic 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 >> +]) >> + >> +AT_CHECK([ovs-appctl revalidator/purge], [0]) >> +dnl packet with truncated size >> +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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']) >> + >> +AT_CHECK([ovs-appctl revalidator/purge], [0]) >> +dnl packet size: 64 + 128 + 170 = 362 >> +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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: output to unsupported truncate port: ALL >> +]) >> + >> +OVS_VSWITCHD_STOP >> +AT_CLEANUP >> + >> +AT_SETUP([ofproto-dpif - truncate and output to patch port]) >> +OVS_VSWITCHD_START([add-br br1 \ >> +-- set bridge br1 datapath-type=dummy fail-mode=secure \ >> +-- add-port br1 pbr1 -- set int pbr1 type=patch options:peer=pbr0 ofport_request=1 \ >> +-- add-port br0 pbr0 -- set int pbr0 type=patch options:peer=pbr1]) >> + >> +add_of_ports br0 2 >> + >> +AT_CHECK([ovs-ofctl add-flow br0 actions='output(port=1,max_len=100),output:2']) >> +AT_CHECK([ovs-ofctl add-flow br1 actions=NORMAL]) >> + >> +AT_CHECK([ovs-appctl ofproto/trace br0 in_port=LOCAL,dl_src=10:20:30:40:50:60], >> +[0], [stdout]) >> +AT_CHECK([tail -1 stdout], [0], [Datapath actions: 2 >> +]) >> +dnl the output(port=1,max_len=100) fails the translation, only output:2 in datapath >> +AT_CHECK([grep "output_trunc does not support port: [[0-9]]*" stdout], [0], [stdout]) >> + >> +OVS_VSWITCHD_STOP >> +AT_CLEANUP >> + >> +AT_SETUP([ofproto-dpif - truncate and output to gre tunnel]) >> +OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=gre \ >> + options:remote_ip=1.1.1.1 options:local_ip=2.2.2.2 \ >> + options:key=5 ofport_request=1\ >> + -- add-port br0 p2 -- set Interface p2 type=dummy \ >> + ofport_request=2]) >> +AT_DATA([flows.txt], [dnl >> +actions=output(max_len=100, port=1) >> +]) >> +OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP >> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) >> + >> +AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl >> + br0 65534/100: (dummy) >> + p1 1/1: (gre: key=5, local_ip=2.2.2.2, remote_ip=1.1.1.1) >> + p2 2/2: (dummy) >> +]) >> + >> +dnl Basic >> +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),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(100),set(tunnel(tun_id=0x5,src=2.2.2.2,dst=1.1.1.1,ttl=64,flags(df|key))),1 >> +]) >> + >> +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/ovs-ofctl.at b/tests/ovs-ofctl.at >> index 613d9ce..71c4aab 100644 >> --- a/tests/ovs-ofctl.at >> +++ b/tests/ovs-ofctl.at >> @@ -388,6 +388,10 @@ ip,actions=ct(commit,exec(load(0x1->NXM_NX_CT_LABEL[]))) >> ip,actions=ct(commit,exec(load(0x1234567890ABCDEF->NXM_NX_CT_LABEL[32..95]))) >> ip,actions=ct(commit,exec(set_field(0x1->ct_label))) >> ip,ct_state=+trk,ct_label=0x1234567890abcdef12345678,actions=ct(commit) >> +actions=output(max_len=100,port=123) >> +actions=output(port=100,max_len=123) >> +actions=output(port=LOCAL,max_len=123) >> +actions=output(port=IN_PORT,max_len=123) >> ]]) >> >> AT_CHECK([ovs-ofctl parse-flows flows.txt >> @@ -431,6 +435,10 @@ NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[ >> NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1234567890abcdef->NXM_NX_CT_LABEL[32..95])) >> NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[0..63],load:0->NXM_NX_CT_LABEL[64..127])) >> NXT_FLOW_MOD: ADD table:255 ct_state=+trk,ct_label=0x1234567890abcdef12345678,ip actions=ct(commit) >> +NXT_FLOW_MOD: ADD table:255 actions=output(port=123,max_len=100) >> +NXT_FLOW_MOD: ADD table:255 actions=output(port=100,max_len=123) >> +NXT_FLOW_MOD: ADD table:255 actions=output(port=65534,max_len=123) >> +NXT_FLOW_MOD: ADD table:255 actions=output(port=65528,max_len=123) >> ]]) >> AT_CLEANUP >> >> diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at >> index cee0510..a3e4dd7 100644 >> --- a/tests/system-kmod-macros.at >> +++ b/tests/system-kmod-macros.at >> @@ -66,3 +66,10 @@ m4_define([CHECK_CONNTRACK], >> on_exit 'ovstest test-netlink-conntrack flush' >> ] >> ) >> + >> +# CHECK_KERNEL_DP, CHECK_USER_DP >> +# >> +# Ignore the CHECK_USER_DP and execute the CHECK_KERNEL_DP >> +# >> +m4_define([CHECK_KERNEL_DP], [$1]) >> +m4_define([CHECK_USER_DP], []) >> diff --git a/tests/system-traffic.at b/tests/system-traffic.at >> index 5ce3955..606bc9b 100644 >> --- a/tests/system-traffic.at >> +++ b/tests/system-traffic.at >> @@ -250,6 +250,253 @@ 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 - basic 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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\1/p'], [0], [dnl >> +n_bytes=542 >> +]) >> + >> +dnl SLOW_ACTION: disable kernel datapath truncate support >> +dnl Repeat the test above, but exercise the SLOW_ACTION code path >> +AT_CHECK([ovs-appctl dpif/disable-truncate], [0], >> +[Datapath truncate action diabled >> +]) >> + >> +dnl SLOW_ACTION test1: check datapatch actions >> +AT_CHECK([ovs-ofctl del-flows br0]) >> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) >> + >> +CHECK_KERNEL_DP( >> +AT_CHECK([ovs-appctl ofproto/trace system 'in_port(2),eth(src=e6:66:c1:11:11:11,dst=e6:66:c1:22:22:22),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 -3 stdout], [0], >> +[Datapath actions: trunc(100),3,5,trunc(100),3,trunc(100),5,3,trunc(200),5,trunc(65535),3 >> +This flow is handled by the userspace slow path because it: >> + - Uses action(s) not supported by datapath. >> +]) >> +) >> + >> +dnl SLOW_ACTION test2: check actual packet truncate >> +AT_CHECK([ovs-ofctl del-flows br0]) >> +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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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 output 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]) >> + >> +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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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 >> +]) >> + >> +dnl SLOW_ACTION: disable datapath truncate support >> +dnl Repeat the test above, but exercise the SLOW_ACTION code path >> +AT_CHECK([ovs-appctl dpif/disable-truncate], [0], >> +[Datapath truncate action diabled >> +]) >> + >> +dnl SLOW_ACTION test1: check datapatch actions >> +AT_CHECK([ovs-ofctl del-flows br0]) >> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) >> + >> +CHECK_KERNEL_DP( >> +AT_CHECK([ovs-appctl ofproto/trace system 'in_port(5),eth(src=e6:66:c1:11:11:11,dst=e6:66:c1:22:22:22),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=17,tos=4,ttl=128,frag=no),udp(src=8,dst=9)'], [0], [stdout]) >> +AT_CHECK([tail -3 stdout], [0], >> +[Datapath actions: trunc(100),set(tunnel(dst=172.31.1.1,ttl=64,flags(df))),4 >> +This flow is handled by the userspace slow path because it: >> + - Uses action(s) not supported by datapath. >> +]) >> +) >> + >> +dnl SLOW_ACTION test2: check actual packet truncate >> +AT_CHECK([ovs-ofctl del-flows br0]) >> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) >> +AT_CHECK([ovs-ofctl del-flows br-underlay]) >> +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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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() >> diff --git a/tests/system-userspace-macros.at b/tests/system-userspace-macros.at >> index c09a4aa..dc4bd0e 100644 >> --- a/tests/system-userspace-macros.at >> +++ b/tests/system-userspace-macros.at >> @@ -66,3 +66,10 @@ m4_define([CONFIGURE_VETH_OFFLOADS], >> m4_define([CHECK_CONNTRACK], >> [AT_SKIP_IF(true)] >> ) >> + >> +# CHECK_KERNEL_DP, CHECK_USER_DP >> +# >> +# Ignore the CHECK_KERNEL_DP and execute the CHECK_USER_DP >> +# >> +m4_define([CHECK_KERNEL_DP], []) >> +m4_define([CHECK_USER_DP], [$1]) >> -- > Test looks good. > > packet truncate support documentation is missing in man page. NEWS > also needs to be updated.
diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h index 91c7ee5..0b8ccbb 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. */ + uint32_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..8e7defc 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); + dp_packet_reset_cutlen(b); } static void @@ -168,6 +169,7 @@ dp_packet_clone_with_headroom(const struct dp_packet *buffer, size_t headroom) new_buffer->l3_ofs = buffer->l3_ofs; new_buffer->l4_ofs = buffer->l4_ofs; new_buffer->md = buffer->md; + new_buffer->cutlen = buffer->cutlen; #ifdef DPDK_NETDEV new_buffer->mbuf.ol_flags = buffer->mbuf.ol_flags; #else diff --git a/lib/dp-packet.h b/lib/dp-packet.h index 118c84d..f505fa5 100644 --- a/lib/dp-packet.h +++ b/lib/dp-packet.h @@ -60,6 +60,7 @@ struct dp_packet { * or UINT16_MAX. */ uint16_t l4_ofs; /* Transport-level header offset, or UINT16_MAX. */ + uint32_t cutlen; /* length in bytes to cut from the end. */ union { struct pkt_metadata md; uint64_t data[DP_PACKET_CONTEXT_SIZE / 8]; @@ -494,6 +495,34 @@ 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, uint32_t max_len) +{ + if (max_len < ETH_HEADER_LEN) { + max_len = ETH_HEADER_LEN; + } + + if (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) +{ + /* Always in valid range if user uses dp_packet_set_cutlen. */ + return b->cutlen; +} + static inline void * dp_packet_data(const struct dp_packet *b) { @@ -567,12 +596,14 @@ enum { NETDEV_MAX_BURST = 32 }; /* Maximum number packets in a batch. */ struct dp_packet_batch { int count; + bool trunc; /* true if the batch needs truncate. */ struct dp_packet *packets[NETDEV_MAX_BURST]; }; static inline void dp_packet_batch_init(struct dp_packet_batch *b) { b->count = 0; + b->trunc = false; } static inline void @@ -585,12 +616,14 @@ dp_packet_batch_clone(struct dp_packet_batch *dst, dst->packets[i] = dp_packet_clone(src->packets[i]); } dst->count = src->count; + dst->trunc = src->trunc; } static inline void packet_batch_init_packet(struct dp_packet_batch *b, struct dp_packet *p) { b->count = 1; + b->trunc = false; b->packets[0] = p; } @@ -606,6 +639,40 @@ dp_packet_delete_batch(struct dp_packet_batch *batch, bool may_steal) } } +static inline void +dp_packet_batch_apply_cutlen(struct dp_packet_batch *pktb) +{ + int i; + + if (!pktb->trunc) + return; + + for (i = 0; i < pktb->count; i++) { + uint32_t cutlen = dp_packet_get_cutlen(pktb->packets[i]); + + if (cutlen > 0) { + dp_packet_set_size(pktb->packets[i], + dp_packet_size(pktb->packets[i]) - cutlen); + dp_packet_reset_cutlen(pktb->packets[i]); + } + } + pktb->trunc = false; +} + +static inline void +dp_packet_batch_reset_cutlen(struct dp_packet_batch *pktb) +{ + int i; + + if (!pktb->trunc) + return; + + pktb->trunc = false; + for (i = 0; i < pktb->count; i++) { + dp_packet_reset_cutlen(pktb->packets[i]); + } +} + #ifdef __cplusplus } #endif diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index 8d39d9e..70f320d 100644 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@ -4057,13 +4057,17 @@ 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_batch *orig_packets_ = packets_; int err; if (!may_steal) { dp_packet_batch_clone(&tnl_pkt, packets_); packets_ = &tnl_pkt; + dp_packet_batch_reset_cutlen(orig_packets_); } + dp_packet_batch_apply_cutlen(packets_); + err = push_tnl_action(pmd, a, packets_); if (!err) { (*depth)++; @@ -4076,6 +4080,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_batch *orig_packets_ = packets_; odp_port_t portno = u32_to_odp(nl_attr_get_u32(a)); p = pmd_tx_port_cache_lookup(pmd, portno); @@ -4084,10 +4089,13 @@ 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; + dp_packet_batch_reset_cutlen(orig_packets_); } + dp_packet_batch_apply_cutlen(packets_); + netdev_pop_header(p->netdev, packets_); if (!packets_->count) { return; @@ -4107,22 +4115,42 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_, case OVS_ACTION_ATTR_USERSPACE: if (!fat_rwlock_tryrdlock(&dp->upcall_rwlock)) { + struct dp_packet_batch *orig_packets_ = packets_; struct dp_packet **packets = packets_->packets; const struct nlattr *userdata; + struct dp_packet_batch usr_pkt; struct ofpbuf actions; struct flow flow; ovs_u128 ufid; + bool clone = false; int i; userdata = nl_attr_find_nested(a, OVS_USERSPACE_ATTR_USERDATA); ofpbuf_init(&actions, 0); + if (packets_->trunc) { + if (!may_steal) { + dp_packet_batch_clone(&usr_pkt, packets_); + packets_ = &usr_pkt; + packets = packets_->packets; + clone = true; + dp_packet_batch_reset_cutlen(orig_packets_); + } + + dp_packet_batch_apply_cutlen(packets_); + } + for (i = 0; i < packets_->count; i++) { flow_extract(packets[i], &flow); dpif_flow_hash(dp->dpif, &flow, sizeof flow, &ufid); dp_execute_userspace_action(pmd, packets[i], may_steal, &flow, &ufid, &actions, userdata); } + + if (clone) { + dp_packet_delete_batch(packets_, true); + } + ofpbuf_uninit(&actions); fat_rwlock_unlock(&dp->upcall_rwlock); @@ -4170,6 +4198,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(); } diff --git a/lib/dpif.c b/lib/dpif.c index c4f24c7..5f1be41 100644 --- a/lib/dpif.c +++ b/lib/dpif.c @@ -1092,6 +1092,7 @@ 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); @@ -1106,7 +1107,8 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, struct ofpbuf execute_actions; uint64_t stub[256 / 8]; struct pkt_metadata *md = &packet->md; - bool dst_set; + bool dst_set, clone = false; + uint32_t cutlen = dp_packet_get_cutlen(packet); dst_set = flow_tnl_dst_is_set(&md->tunnel); if (dst_set) { @@ -1124,6 +1126,22 @@ 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 && (type == OVS_ACTION_ATTR_OUTPUT || + type == OVS_ACTION_ATTR_TUNNEL_PUSH || + type == OVS_ACTION_ATTR_TUNNEL_POP || + type == OVS_ACTION_ATTR_USERSPACE)) { + if (!may_steal) { + trunc_packet = dp_packet_clone(packet); + packet = trunc_packet; + clone = true; + } + + dp_packet_set_size(packet, dp_packet_size(packet) - cutlen); + dp_packet_reset_cutlen(orig_packet); + } + execute.packet = packet; execute.flow = aux->flow; execute.needs_help = false; @@ -1135,6 +1153,10 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, if (dst_set) { ofpbuf_uninit(&execute_actions); } + + if (clone) { + dp_packet_delete(trunc_packet); + } break; } @@ -1146,6 +1168,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, case OVS_ACTION_ATTR_SET: case OVS_ACTION_ATTR_SET_MASKED: case OVS_ACTION_ATTR_SAMPLE: + case OVS_ACTION_ATTR_TRUNC: case OVS_ACTION_ATTR_UNSPEC: case __OVS_ACTION_ATTR_MAX: OVS_NOT_REACHED(); diff --git a/lib/dpif.h b/lib/dpif.h index 6788301..981868c 100644 --- a/lib/dpif.h +++ b/lib/dpif.h @@ -784,6 +784,7 @@ struct dpif_upcall { size_t key_len; /* Length of 'key' in bytes. */ ovs_u128 ufid; /* Unique flow identifier for 'key'. */ struct nlattr *mru; /* Maximum receive unit. */ + struct nlattr *cutlen; /* Number of bytes shrink from the end. */ /* DPIF_UC_ACTION only. */ struct nlattr *userdata; /* Argument to OVS_ACTION_ATTR_USERSPACE. */ diff --git a/lib/netdev-bsd.c b/lib/netdev-bsd.c index 43fa982..2e92d97 100644 --- a/lib/netdev-bsd.c +++ b/lib/netdev-bsd.c @@ -699,6 +699,9 @@ netdev_bsd_send(struct netdev *netdev_, int qid OVS_UNUSED, const void *data = dp_packet_data(pkts[i]); size_t size = dp_packet_size(pkts[i]); + /* Truncate the packet if it is configured. */ + size -= dp_packet_get_cutlen(pkts[i]); + while (!error) { ssize_t retval; if (dev->tap_fd >= 0) { diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c index fc0c8d3..8063add 100644 --- a/lib/netdev-dpdk.c +++ b/lib/netdev-dpdk.c @@ -1487,6 +1487,7 @@ dpdk_do_tx_copy(struct netdev *netdev, int qid, struct dp_packet **pkts, for (i = 0; i < cnt; i++) { int size = dp_packet_size(pkts[i]); + int cutlen = dp_packet_get_cutlen(pkts[i]); if (OVS_UNLIKELY(size > dev->max_packet_len)) { VLOG_WARN_RL(&rl, "Too big size %d max_packet_len %d", @@ -1503,6 +1504,12 @@ dpdk_do_tx_copy(struct netdev *netdev, int qid, struct dp_packet **pkts, break; } + if (OVS_UNLIKELY(cutlen > 0)) { + /* Cut the size so only the truncated size is copied. */ + size -= cutlen; + dp_packet_reset_cutlen(pkts[i]); + } + /* We have to do a copy for now */ memcpy(rte_pktmbuf_mtod(mbufs[newcnt], void *), dp_packet_data(pkts[i]), size); @@ -1550,6 +1557,14 @@ netdev_dpdk_vhost_send(struct netdev *netdev, int qid, struct dp_packet **pkts, } } } else { + int i; + + for (i = 0; i < cnt; i++) { + int cutlen = dp_packet_get_cutlen(pkts[i]); + + dp_packet_set_size(pkts[i], dp_packet_size(pkts[i]) - cutlen); + dp_packet_reset_cutlen(pkts[i]); + } __netdev_dpdk_vhost_send(netdev, qid, pkts, cnt, may_steal); } return 0; @@ -1585,6 +1600,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 (OVS_UNLIKELY(cutlen > 0)) { + size -= cutlen; + dp_packet_set_size(pkts[i], size); + } if (OVS_UNLIKELY(size > dev->max_packet_len)) { if (next_tx_idx != i) { diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c index aa244b6..127b6ae 100644 --- a/lib/netdev-dummy.c +++ b/lib/netdev-dummy.c @@ -1044,6 +1044,8 @@ 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]); + size -= dp_packet_get_cutlen(pkts[i]); + if (size < ETH_HEADER_LEN) { error = EMSGSIZE; break; diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c index 82813ba..d6e5a54 100644 --- a/lib/netdev-linux.c +++ b/lib/netdev-linux.c @@ -1170,6 +1170,9 @@ netdev_linux_send(struct netdev *netdev_, int qid OVS_UNUSED, size_t size = dp_packet_size(pkts[i]); ssize_t retval; + /* Truncate the packet if it is configured. */ + size -= dp_packet_get_cutlen(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 4be806d..6651173 100644 --- a/lib/netdev.c +++ b/lib/netdev.c @@ -681,7 +681,7 @@ netdev_set_tx_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 @@ -716,6 +716,9 @@ netdev_send(struct netdev *netdev, int qid, struct dp_packet_batch *batch, may_steal); if (!error) { COVERAGE_INC(netdev_sent); + if (!may_steal) { + dp_packet_batch_reset_cutlen(batch); + } } return error; } diff --git a/lib/odp-execute.c b/lib/odp-execute.c index 4239624..5a43904 100644 --- a/lib/odp-execute.c +++ b/lib/odp-execute.c @@ -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: @@ -625,6 +626,17 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal, } break; + case OVS_ACTION_ATTR_TRUNC: { + const struct ovs_action_trunc *trunc = + nl_attr_get_unspec(a, sizeof *trunc); + + batch->trunc = true; + for (i = 0; i < cnt; i++) { + dp_packet_set_cutlen(packets[i], trunc->max_len); + } + break; + } + case OVS_ACTION_ATTR_OUTPUT: case OVS_ACTION_ATTR_TUNNEL_PUSH: case OVS_ACTION_ATTR_TUNNEL_POP: diff --git a/lib/odp-util.c b/lib/odp-util.c index 48c05f5..d7b6a2d 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; @@ -777,6 +778,14 @@ 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(%"PRIu32")", trunc->max_len); + break; + } + break; case OVS_ACTION_ATTR_TUNNEL_POP: ds_put_format(ds, "tnl_pop(%"PRIu32")", nl_attr_get_u32(a)); break; @@ -1528,6 +1537,20 @@ parse_odp_action(const char *s, const struct simap *port_names, } } + { + uint32_t max_len; + int n; + + if (ovs_scan(s, "trunc(%"SCNi32")%n", &max_len, &n)) { + struct ovs_action_trunc *trunc; + + trunc = nl_msg_put_unspec_uninit(actions, + OVS_ACTION_ATTR_TRUNC, sizeof *trunc); + trunc->max_len = max_len; + return n; + } + } + if (port_names) { int len = strcspn(s, delimiters); struct simap_node *node; diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index ea55896..997cc15 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -301,6 +301,9 @@ enum ofp_raw_action_type { /* NX1.0+(36): struct nx_action_nat, ... */ NXAST_RAW_NAT, + /* NX1.0+(39): struct nx_action_output_trunc. */ + NXAST_RAW_OUTPUT_TRUNC, + /* ## ------------------ ## */ /* ## Debugging actions. ## */ /* ## ------------------ ## */ @@ -381,6 +384,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: @@ -538,6 +542,40 @@ 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")) { + if (!ofputil_port_from_string(value, &output_trunc->port)) { + return xasprintf("output to unknown truncate port: %s", + value); + } + if (ofp_to_u16(output_trunc->port) > ofp_to_u16(OFPP_MAX)) { + if (output_trunc->port != OFPP_LOCAL && + output_trunc->port != OFPP_IN_PORT) + return xasprintf("output to unsupported truncate port: %s", + value); + } + } else if (!strcmp(key, "max_len")) { + char *err; + + err = str_to_u32(value, &output_trunc->max_len); + if (err) { + return err; + } + } 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) { @@ -547,6 +585,11 @@ 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; @@ -5603,6 +5646,61 @@ 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 port; /* Output port */ + ovs_be32 max_len; /* Truncate packet to size bytes */ +}; +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 = ntohl(natrc->max_len); + output_trunc->port = u16_to_ofp(ntohs(natrc->port)); + + if (output_trunc->max_len < ETH_HEADER_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 = htonl(output_trunc->max_len); + natrc->port = htons(ofp_to_u16(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) +{ + ds_put_format(s, "%soutput%s(port=%"PRIu16",max_len=%"PRIu32")", + colors.special, colors.end, a->port, a->max_len); +} + /* Meter instruction. */ @@ -5997,6 +6095,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: @@ -6025,6 +6124,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: @@ -6249,6 +6349,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: @@ -6677,6 +6778,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); @@ -7354,6 +7459,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..5d26b7c 100644 --- a/ofproto/ofproto-dpif-sflow.c +++ b/ofproto/ofproto-dpif-sflow.c @@ -1140,6 +1140,7 @@ dpif_sflow_read_actions(const struct flow *flow, } break; + case OVS_ACTION_ATTR_TRUNC: case OVS_ACTION_ATTR_USERSPACE: case OVS_ACTION_ATTR_RECIRC: case OVS_ACTION_ATTR_HASH: diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index f8c4a93..d46a52c 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -3995,6 +3995,56 @@ xlate_output_reg_action(struct xlate_ctx *ctx, } static void +xlate_output_trunc_action(struct xlate_ctx *ctx, + ofp_port_t port, uint32_t max_len) +{ + bool support_trunc = ctx->xbridge->support.trunc; + struct ovs_action_trunc *trunc; + char name[OFP_MAX_PORT_NAME_LEN]; + + switch (port) { + case OFPP_TABLE: + case OFPP_NORMAL: + case OFPP_FLOOD: + case OFPP_ALL: + case OFPP_CONTROLLER: + case OFPP_NONE: + ofputil_port_to_string(port, name, sizeof name); + xlate_report(ctx, "output_trunc does not support port: %s", name); + break; + case OFPP_LOCAL: + case OFPP_IN_PORT: + default: + if (port != ctx->xin->flow.in_port.ofp_port) { + const struct xport *xport = get_ofp_port(ctx->xbridge, port); + + if (xport == NULL || xport->odp_port == ODPP_NONE) { + /* Since truncate happens at its following output action, if + * the output port is a patch port, the behavior is somehow + * unpredicable. For simpilicity, disallow this case. */ + ofputil_port_to_string(port, name, sizeof name); + XLATE_REPORT_ERROR(ctx, "bridge %s: " + "output_trunc does not support port: %s", + ctx->xbridge->name, name); + break; + } + + 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) { @@ -4333,6 +4383,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: @@ -4584,6 +4635,7 @@ recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx) /* Output actions do not require recirculation. */ case OFPACT_OUTPUT: + case OFPACT_OUTPUT_TRUNC: case OFPACT_ENQUEUE: case OFPACT_OUTPUT_REG: /* Set actions that don't touch L3+ fields do not require recirculation. */ @@ -4933,6 +4985,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 100e8d5..0cd1c25 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -1212,6 +1212,63 @@ 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; + struct flow flow; + 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_HEADER_LEN + 1; + nl_msg_put_odp_port(&actions, OVS_ACTION_ATTR_OUTPUT, u32_to_odp(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); + + flow_extract(&packet, &flow); + + /* 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.flow = &flow; + 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) { + VLOG_INFO("%s: Datapath does not support truncate action", + dpif_name(backer->dpif)); + } else { + VLOG_INFO("%s: Datapath supports truncate action", + dpif_name(backer->dpif)); + } + + return !error; +} + #define CHECK_FEATURE__(NAME, SUPPORT, FIELD, VALUE) \ static bool \ check_##NAME(struct dpif_backer *backer) \ @@ -1263,6 +1320,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); @@ -5343,6 +5401,24 @@ disable_tnl_push_pop(struct unixctl_conn *conn OVS_UNUSED, int argc OVS_UNUSED, } static void +disable_datapath_truncate(struct unixctl_conn *conn OVS_UNUSED, + int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, + void *aux OVS_UNUSED) +{ + const struct shash_node **backers; + int i; + + backers = shash_sort(&all_dpif_backers); + for (i = 0; i < shash_count(&all_dpif_backers); i++) { + struct dpif_backer *backer = backers[i]->data; + backer->support.trunc = false; + } + free(backers); + unixctl_command_reply(conn, "Datapath truncate action diabled"); +} + +static void ofproto_unixctl_init(void) { static bool registered; @@ -5376,6 +5452,9 @@ ofproto_unixctl_init(void) unixctl_command_register("ofproto/tnl-push-pop", "[on]|[off]", 1, 1, disable_tnl_push_pop, NULL); + + unixctl_command_register("dpif/disable-truncate", "", 0, 0, + disable_datapath_truncate, NULL); } /* Returns true if 'table' is the table used for internal rules, diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h index 9e03b01..4034475 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/odp.at b/tests/odp.at index 7b94c92..e630855 100644 --- a/tests/odp.at +++ b/tests/odp.at @@ -329,6 +329,7 @@ ct(commit,nat(src=fe80::20c:29ff:fe88:a18b,random)) ct(commit,nat(src=fe80::20c:29ff:fe88:1-fe80::20c:29ff:fe88:a18b,random)) ct(commit,nat(src=[[fe80::20c:29ff:fe88:1]]-[[fe80::20c:29ff:fe88:a18b]]:255-4096,random)) ct(commit,helper=ftp,nat(src=10.1.1.240-10.1.1.255)) +trunc(100) ]) AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], [`cat actions.txt` diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at index 50f74e9..ca4d1ba 100644 --- a/tests/ofp-actions.at +++ b/tests/ofp-actions.at @@ -241,6 +241,9 @@ fe800000 00000000 020c 29ff fe88 0001 dnl fe800000 00000000 020c 29ff fe88 a18b dnl 00ff1000 00000000 +# actions=output(port=1,max_len=100) +ffff 0010 00002320 0027 0001 00000064 + # bad OpenFlow10 actions: NXBRC_MUST_BE_ZERO ffff 0018 00002320 0025 0000 0005 0000 1122334455 000005 diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index 9b94f3d..b0c6e0a 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -5322,6 +5322,130 @@ PORTNAME portName=p2 ])]) +AT_SETUP([ofproto-dpif - basic 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 +]) + +AT_CHECK([ovs-appctl revalidator/purge], [0]) +dnl packet with truncated size +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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']) + +AT_CHECK([ovs-appctl revalidator/purge], [0]) +dnl packet size: 64 + 128 + 170 = 362 +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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: output to unsupported truncate port: ALL +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + +AT_SETUP([ofproto-dpif - truncate and output to patch port]) +OVS_VSWITCHD_START([add-br br1 \ +-- set bridge br1 datapath-type=dummy fail-mode=secure \ +-- add-port br1 pbr1 -- set int pbr1 type=patch options:peer=pbr0 ofport_request=1 \ +-- add-port br0 pbr0 -- set int pbr0 type=patch options:peer=pbr1]) + +add_of_ports br0 2 + +AT_CHECK([ovs-ofctl add-flow br0 actions='output(port=1,max_len=100),output:2']) +AT_CHECK([ovs-ofctl add-flow br1 actions=NORMAL]) + +AT_CHECK([ovs-appctl ofproto/trace br0 in_port=LOCAL,dl_src=10:20:30:40:50:60], +[0], [stdout]) +AT_CHECK([tail -1 stdout], [0], [Datapath actions: 2 +]) +dnl the output(port=1,max_len=100) fails the translation, only output:2 in datapath +AT_CHECK([grep "output_trunc does not support port: [[0-9]]*" stdout], [0], [stdout]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + +AT_SETUP([ofproto-dpif - truncate and output to gre tunnel]) +OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=gre \ + options:remote_ip=1.1.1.1 options:local_ip=2.2.2.2 \ + options:key=5 ofport_request=1\ + -- add-port br0 p2 -- set Interface p2 type=dummy \ + ofport_request=2]) +AT_DATA([flows.txt], [dnl +actions=output(max_len=100, port=1) +]) +OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl + br0 65534/100: (dummy) + p1 1/1: (gre: key=5, local_ip=2.2.2.2, remote_ip=1.1.1.1) + p2 2/2: (dummy) +]) + +dnl Basic +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),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(100),set(tunnel(tun_id=0x5,src=2.2.2.2,dst=1.1.1.1,ttl=64,flags(df|key))),1 +]) + +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/ovs-ofctl.at b/tests/ovs-ofctl.at index 613d9ce..71c4aab 100644 --- a/tests/ovs-ofctl.at +++ b/tests/ovs-ofctl.at @@ -388,6 +388,10 @@ ip,actions=ct(commit,exec(load(0x1->NXM_NX_CT_LABEL[]))) ip,actions=ct(commit,exec(load(0x1234567890ABCDEF->NXM_NX_CT_LABEL[32..95]))) ip,actions=ct(commit,exec(set_field(0x1->ct_label))) ip,ct_state=+trk,ct_label=0x1234567890abcdef12345678,actions=ct(commit) +actions=output(max_len=100,port=123) +actions=output(port=100,max_len=123) +actions=output(port=LOCAL,max_len=123) +actions=output(port=IN_PORT,max_len=123) ]]) AT_CHECK([ovs-ofctl parse-flows flows.txt @@ -431,6 +435,10 @@ NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[ NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1234567890abcdef->NXM_NX_CT_LABEL[32..95])) NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[0..63],load:0->NXM_NX_CT_LABEL[64..127])) NXT_FLOW_MOD: ADD table:255 ct_state=+trk,ct_label=0x1234567890abcdef12345678,ip actions=ct(commit) +NXT_FLOW_MOD: ADD table:255 actions=output(port=123,max_len=100) +NXT_FLOW_MOD: ADD table:255 actions=output(port=100,max_len=123) +NXT_FLOW_MOD: ADD table:255 actions=output(port=65534,max_len=123) +NXT_FLOW_MOD: ADD table:255 actions=output(port=65528,max_len=123) ]]) AT_CLEANUP diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at index cee0510..a3e4dd7 100644 --- a/tests/system-kmod-macros.at +++ b/tests/system-kmod-macros.at @@ -66,3 +66,10 @@ m4_define([CHECK_CONNTRACK], on_exit 'ovstest test-netlink-conntrack flush' ] ) + +# CHECK_KERNEL_DP, CHECK_USER_DP +# +# Ignore the CHECK_USER_DP and execute the CHECK_KERNEL_DP +# +m4_define([CHECK_KERNEL_DP], [$1]) +m4_define([CHECK_USER_DP], []) diff --git a/tests/system-traffic.at b/tests/system-traffic.at index 5ce3955..606bc9b 100644 --- a/tests/system-traffic.at +++ b/tests/system-traffic.at @@ -250,6 +250,253 @@ 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 - basic 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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\1/p'], [0], [dnl +n_bytes=542 +]) + +dnl SLOW_ACTION: disable kernel datapath truncate support +dnl Repeat the test above, but exercise the SLOW_ACTION code path +AT_CHECK([ovs-appctl dpif/disable-truncate], [0], +[Datapath truncate action diabled +]) + +dnl SLOW_ACTION test1: check datapatch actions +AT_CHECK([ovs-ofctl del-flows br0]) +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +CHECK_KERNEL_DP( +AT_CHECK([ovs-appctl ofproto/trace system 'in_port(2),eth(src=e6:66:c1:11:11:11,dst=e6:66:c1:22:22:22),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 -3 stdout], [0], +[Datapath actions: trunc(100),3,5,trunc(100),3,trunc(100),5,3,trunc(200),5,trunc(65535),3 +This flow is handled by the userspace slow path because it: + - Uses action(s) not supported by datapath. +]) +) + +dnl SLOW_ACTION test2: check actual packet truncate +AT_CHECK([ovs-ofctl del-flows br0]) +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=[[0-9]]*\).*/\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=[[0-9]]*\).*/\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 output 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]) + +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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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 +]) + +dnl SLOW_ACTION: disable datapath truncate support +dnl Repeat the test above, but exercise the SLOW_ACTION code path +AT_CHECK([ovs-appctl dpif/disable-truncate], [0], +[Datapath truncate action diabled +]) + +dnl SLOW_ACTION test1: check datapatch actions +AT_CHECK([ovs-ofctl del-flows br0]) +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +CHECK_KERNEL_DP( +AT_CHECK([ovs-appctl ofproto/trace system 'in_port(5),eth(src=e6:66:c1:11:11:11,dst=e6:66:c1:22:22:22),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=17,tos=4,ttl=128,frag=no),udp(src=8,dst=9)'], [0], [stdout]) +AT_CHECK([tail -3 stdout], [0], +[Datapath actions: trunc(100),set(tunnel(dst=172.31.1.1,ttl=64,flags(df))),4 +This flow is handled by the userspace slow path because it: + - Uses action(s) not supported by datapath. +]) +) + +dnl SLOW_ACTION test2: check actual packet truncate +AT_CHECK([ovs-ofctl del-flows br0]) +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) +AT_CHECK([ovs-ofctl del-flows br-underlay]) +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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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" | sed -n 's/.*\(n\_bytes=[[0-9]]*\).*/\1/p'], [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() diff --git a/tests/system-userspace-macros.at b/tests/system-userspace-macros.at index c09a4aa..dc4bd0e 100644 --- a/tests/system-userspace-macros.at +++ b/tests/system-userspace-macros.at @@ -66,3 +66,10 @@ m4_define([CONFIGURE_VETH_OFFLOADS], m4_define([CHECK_CONNTRACK], [AT_SKIP_IF(true)] ) + +# CHECK_KERNEL_DP, CHECK_USER_DP +# +# Ignore the CHECK_KERNEL_DP and execute the CHECK_USER_DP +# +m4_define([CHECK_KERNEL_DP], []) +m4_define([CHECK_USER_DP], [$1])
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 Tested-at: https://travis-ci.org/williamtu/ovs-travis/builds/139668398 Signed-off-by: William Tu <u9012063@gmail.com> --- include/openvswitch/ofp-actions.h | 10 ++ lib/dp-packet.c | 2 + lib/dp-packet.h | 67 +++++++++++ lib/dpif-netdev.c | 33 ++++- lib/dpif.c | 25 +++- lib/dpif.h | 1 + lib/netdev-bsd.c | 3 + lib/netdev-dpdk.c | 21 ++++ lib/netdev-dummy.c | 2 + lib/netdev-linux.c | 3 + lib/netdev.c | 5 +- lib/odp-execute.c | 12 ++ lib/odp-util.c | 23 ++++ lib/ofp-actions.c | 106 ++++++++++++++++ ofproto/ofproto-dpif-sflow.c | 1 + ofproto/ofproto-dpif-xlate.c | 57 +++++++++ ofproto/ofproto-dpif.c | 79 ++++++++++++ ofproto/ofproto-dpif.h | 3 + tests/odp.at | 1 + tests/ofp-actions.at | 3 + tests/ofproto-dpif.at | 124 +++++++++++++++++++ tests/ovs-ofctl.at | 8 ++ tests/system-kmod-macros.at | 7 ++ tests/system-traffic.at | 247 ++++++++++++++++++++++++++++++++++++++ tests/system-userspace-macros.at | 7 ++ 25 files changed, 846 insertions(+), 4 deletions(-)