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