diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | include/sparse/netinet/in.h | 1 | ||||
-rw-r--r-- | lib/dpif-netlink.c | 2 | ||||
-rw-r--r-- | lib/dpif.h | 1 | ||||
-rw-r--r-- | lib/odp-util.c | 27 | ||||
-rw-r--r-- | lib/odp-util.h | 1 | ||||
-rw-r--r-- | ofproto/ofproto-dpif-sflow.c | 562 | ||||
-rw-r--r-- | ofproto/ofproto-dpif-sflow.h | 30 | ||||
-rw-r--r-- | ofproto/ofproto-dpif-upcall.c | 34 | ||||
-rw-r--r-- | ofproto/ofproto-dpif-xlate.c | 16 | ||||
-rw-r--r-- | tests/odp.at | 6 | ||||
-rw-r--r-- | tests/ofproto-dpif.at | 264 | ||||
-rw-r--r-- | tests/test-sflow.c | 32 |
13 files changed, 948 insertions, 29 deletions
@@ -13,6 +13,7 @@ Post-v2.4.0 Geneve tunnels. - Support Multicast Listener Discovery (MLDv1 and MLDv2). - Add 'symmetric_l3l4' and 'symmetric_l3l4+udp' hash functions. + - sFlow agent now reports tunnel and MPLS structures. v2.4.0 - xx xxx xxxx diff --git a/include/sparse/netinet/in.h b/include/sparse/netinet/in.h index f3ce8b9ca..f66f2056b 100644 --- a/include/sparse/netinet/in.h +++ b/include/sparse/netinet/in.h @@ -67,6 +67,7 @@ struct sockaddr_in6 { #define IPPROTO_ROUTING 43 #define IPPROTO_FRAGMENT 44 #define IPPROTO_GRE 47 +#define IPPROTO_ESP 50 #define IPPROTO_AH 51 #define IPPROTO_ICMPV6 58 #define IPPROTO_NONE 59 diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c index 3650682ff..8884a9f29 100644 --- a/lib/dpif-netlink.c +++ b/lib/dpif-netlink.c @@ -1969,6 +1969,7 @@ parse_odp_packet(const struct dpif_netlink *dpif, struct ofpbuf *buf, /* OVS_PACKET_CMD_ACTION only. */ [OVS_PACKET_ATTR_USERDATA] = { .type = NL_A_UNSPEC, .optional = true }, [OVS_PACKET_ATTR_EGRESS_TUN_KEY] = { .type = NL_A_NESTED, .optional = true }, + [OVS_PACKET_ATTR_ACTIONS] = { .type = NL_A_NESTED, .optional = true }, }; struct ovs_header *ovs_header; @@ -2005,6 +2006,7 @@ parse_odp_packet(const struct dpif_netlink *dpif, struct ofpbuf *buf, dpif_flow_hash(&dpif->dpif, upcall->key, upcall->key_len, &upcall->ufid); upcall->userdata = a[OVS_PACKET_ATTR_USERDATA]; upcall->out_tun_key = a[OVS_PACKET_ATTR_EGRESS_TUN_KEY]; + upcall->actions = a[OVS_PACKET_ATTR_ACTIONS]; /* Allow overwriting the netlink attribute header without reallocating. */ dp_packet_use_stub(&upcall->packet, diff --git a/lib/dpif.h b/lib/dpif.h index ba5d59763..ea9caf8bb 100644 --- a/lib/dpif.h +++ b/lib/dpif.h @@ -784,6 +784,7 @@ struct dpif_upcall { /* DPIF_UC_ACTION only. */ struct nlattr *userdata; /* Argument to OVS_ACTION_ATTR_USERSPACE. */ struct nlattr *out_tun_key; /* Output tunnel key. */ + struct nlattr *actions; /* Argument to OVS_ACTION_ATTR_USERSPACE. */ }; /* A callback to process an upcall, currently implemented only by dpif-netdev. diff --git a/lib/odp-util.c b/lib/odp-util.c index 0e82b12e8..eec0bfb7e 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc. + * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -251,6 +251,8 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr) .optional = true }, [OVS_USERSPACE_ATTR_EGRESS_TUN_PORT] = { .type = NL_A_U32, .optional = true }, + [OVS_USERSPACE_ATTR_ACTIONS] = { .type = NL_A_UNSPEC, + .optional = true }, }; struct nlattr *a[ARRAY_SIZE(ovs_userspace_policy)]; const struct nlattr *userdata_attr; @@ -322,6 +324,10 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr) } } + if (a[OVS_USERSPACE_ATTR_ACTIONS]) { + ds_put_cstr(ds, ",actions"); + } + tunnel_out_port_attr = a[OVS_USERSPACE_ATTR_EGRESS_TUN_PORT]; if (tunnel_out_port_attr) { ds_put_format(ds, ",tunnel_out_port=%"PRIu32, @@ -666,6 +672,7 @@ parse_odp_userspace_action(const char *s, struct ofpbuf *actions) int n = -1; void *user_data = NULL; size_t user_data_size = 0; + bool include_actions = false; if (!ovs_scan(s, "userspace(pid=%"SCNi32"%n", &pid, &n)) { return -EINVAL; @@ -754,12 +761,22 @@ parse_odp_userspace_action(const char *s, struct ofpbuf *actions) { int n1 = -1; + if (ovs_scan(&s[n], ",actions%n", &n1)) { + n += n1; + include_actions = true; + } + } + + { + int n1 = -1; if (ovs_scan(&s[n], ",tunnel_out_port=%"SCNi32")%n", &tunnel_out_port, &n1)) { - odp_put_userspace_action(pid, user_data, user_data_size, tunnel_out_port, actions); + odp_put_userspace_action(pid, user_data, user_data_size, + tunnel_out_port, include_actions, actions); return n + n1; } else if (s[n] == ')') { - odp_put_userspace_action(pid, user_data, user_data_size, ODPP_NONE, actions); + odp_put_userspace_action(pid, user_data, user_data_size, + ODPP_NONE, include_actions, actions); return n + 1; } } @@ -4251,6 +4268,7 @@ size_t odp_put_userspace_action(uint32_t pid, const void *userdata, size_t userdata_size, odp_port_t tunnel_out_port, + bool include_actions, struct ofpbuf *odp_actions) { size_t userdata_ofs; @@ -4281,6 +4299,9 @@ odp_put_userspace_action(uint32_t pid, nl_msg_put_odp_port(odp_actions, OVS_USERSPACE_ATTR_EGRESS_TUN_PORT, tunnel_out_port); } + if (include_actions) { + nl_msg_put_flag(odp_actions, OVS_USERSPACE_ATTR_ACTIONS); + } nl_msg_end_nested(odp_actions, offset); return userdata_ofs; diff --git a/lib/odp-util.h b/lib/odp-util.h index 989bb81d3..1eaa06b04 100644 --- a/lib/odp-util.h +++ b/lib/odp-util.h @@ -288,6 +288,7 @@ BUILD_ASSERT_DECL(sizeof(union user_action_cookie) == 16); size_t odp_put_userspace_action(uint32_t pid, const void *userdata, size_t userdata_size, odp_port_t tunnel_out_port, + bool include_actions, struct ofpbuf *odp_actions); void odp_put_tunnel_action(const struct flow_tnl *tunnel, struct ofpbuf *odp_actions); diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c index b146f5d78..e54d3fbd3 100644 --- a/ofproto/ofproto-dpif-sflow.c +++ b/ofproto/ofproto-dpif-sflow.c @@ -40,6 +40,7 @@ #include "timeval.h" #include "openvswitch/vlog.h" #include "lib/odp-util.h" +#include "lib/unaligned.h" #include "ofproto-provider.h" #include "lacp.h" @@ -52,11 +53,26 @@ static struct ovs_mutex mutex; #define SFLOW_GC_SUBID_UNCLAIMED (uint32_t)-1 static uint32_t sflow_global_counters_subid = SFLOW_GC_SUBID_UNCLAIMED; +/* + * The enum dpif_sflow_tunnel_type is to declare the types supported + */ +enum dpif_sflow_tunnel_type { + DPIF_SFLOW_TUNNEL_UNKNOWN = 0, + DPIF_SFLOW_TUNNEL_VXLAN, + DPIF_SFLOW_TUNNEL_GRE, + DPIF_SFLOW_TUNNEL_GRE64, + DPIF_SFLOW_TUNNEL_LISP, + DPIF_SFLOW_TUNNEL_IPSEC_GRE, + DPIF_SFLOW_TUNNEL_IPSEC_GRE64, + DPIF_SFLOW_TUNNEL_GENEVE +}; + struct dpif_sflow_port { struct hmap_node hmap_node; /* In struct dpif_sflow's "ports" hmap. */ SFLDataSource_instance dsi; /* sFlow library's notion of port number. */ struct ofport *ofport; /* To retrive port stats. */ odp_port_t odp_port; + enum dpif_sflow_tunnel_type tunnel_type; }; struct dpif_sflow { @@ -568,20 +584,76 @@ dpif_sflow_add_poller(struct dpif_sflow *ds, struct dpif_sflow_port *dsp) sfl_poller_set_bridgePort(poller, odp_to_u32(dsp->odp_port)); } +static enum dpif_sflow_tunnel_type +dpif_sflow_tunnel_type(struct ofport *ofport) { + const char *type = netdev_get_type(ofport->netdev); + if (type) { + if (strcmp(type, "gre") == 0) { + return DPIF_SFLOW_TUNNEL_GRE; + } else if (strcmp(type, "gre64") == 0) { + return DPIF_SFLOW_TUNNEL_GRE64; + } else if (strcmp(type, "ipsec_gre") == 0) { + return DPIF_SFLOW_TUNNEL_IPSEC_GRE; + } else if (strcmp(type, "ipsec_gre64") == 0) { + return DPIF_SFLOW_TUNNEL_IPSEC_GRE64; + } else if (strcmp(type, "vxlan") == 0) { + return DPIF_SFLOW_TUNNEL_VXLAN; + } else if (strcmp(type, "lisp") == 0) { + return DPIF_SFLOW_TUNNEL_LISP; + } else if (strcmp(type, "geneve") == 0) { + return DPIF_SFLOW_TUNNEL_GENEVE; + } + } + return DPIF_SFLOW_TUNNEL_UNKNOWN; +} + +static uint8_t +dpif_sflow_tunnel_proto(enum dpif_sflow_tunnel_type tunnel_type) +{ + /* Default to 0 (IPPROTO_IP), meaning "unknown". */ + uint8_t ipproto = 0; + switch(tunnel_type) { + + case DPIF_SFLOW_TUNNEL_GRE: + case DPIF_SFLOW_TUNNEL_GRE64: + ipproto = IPPROTO_GRE; + break; + + case DPIF_SFLOW_TUNNEL_IPSEC_GRE: + case DPIF_SFLOW_TUNNEL_IPSEC_GRE64: + ipproto = IPPROTO_ESP; + break; + + case DPIF_SFLOW_TUNNEL_VXLAN: + case DPIF_SFLOW_TUNNEL_LISP: + case DPIF_SFLOW_TUNNEL_GENEVE: + ipproto = IPPROTO_UDP; + + case DPIF_SFLOW_TUNNEL_UNKNOWN: + break; + } + return ipproto; +} + void dpif_sflow_add_port(struct dpif_sflow *ds, struct ofport *ofport, odp_port_t odp_port) OVS_EXCLUDED(mutex) { struct dpif_sflow_port *dsp; int ifindex; + enum dpif_sflow_tunnel_type tunnel_type; ovs_mutex_lock(&mutex); dpif_sflow_del_port(ds, odp_port); + tunnel_type = dpif_sflow_tunnel_type(ofport); ifindex = netdev_get_ifindex(ofport->netdev); - if (ifindex <= 0) { - /* Not an ifindex port, so do not add a cross-reference to it here */ + if (ifindex <= 0 + && tunnel_type == DPIF_SFLOW_TUNNEL_UNKNOWN) { + /* Not an ifindex port, and not a tunnel port either + * so do not add a cross-reference to it here. + */ goto out; } @@ -589,12 +661,18 @@ dpif_sflow_add_port(struct dpif_sflow *ds, struct ofport *ofport, dsp = xmalloc(sizeof *dsp); dsp->ofport = ofport; dsp->odp_port = odp_port; - SFL_DS_SET(dsp->dsi, SFL_DSCLASS_IFINDEX, ifindex, 0); + dsp->tunnel_type = tunnel_type; hmap_insert(&ds->ports, &dsp->hmap_node, hash_odp_port(odp_port)); - /* Add poller. */ - if (ds->sflow_agent) { - dpif_sflow_add_poller(ds, dsp); + if (ifindex > 0) { + /* Add poller for ports that have ifindex. */ + SFL_DS_SET(dsp->dsi, SFL_DSCLASS_IFINDEX, ifindex, 0); + if (ds->sflow_agent) { + dpif_sflow_add_poller(ds, dsp); + } + } else { + /* Record "ifindex unknown" for the others */ + SFL_DS_SET(dsp->dsi, SFL_DSCLASS_IFINDEX, 0, 0); } out: @@ -605,9 +683,10 @@ static void dpif_sflow_del_port__(struct dpif_sflow *ds, struct dpif_sflow_port *dsp) OVS_REQUIRES(mutex) { - if (ds->sflow_agent) { - sfl_agent_removePoller(ds->sflow_agent, &dsp->dsi); - sfl_agent_removeSampler(ds->sflow_agent, &dsp->dsi); + if (ds->sflow_agent + && SFL_DS_INDEX(dsp->dsi)) { + sfl_agent_removePoller(ds->sflow_agent, &dsp->dsi); + sfl_agent_removeSampler(ds->sflow_agent, &dsp->dsi); } hmap_remove(&ds->ports, &dsp->hmap_node); free(dsp); @@ -730,7 +809,9 @@ dpif_sflow_set_options(struct dpif_sflow *ds, /* Add pollers for the currently known ifindex-ports */ HMAP_FOR_EACH (dsp, hmap_node, &ds->ports) { - dpif_sflow_add_poller(ds, dsp); + if (SFL_DS_INDEX(dsp->dsi)) { + dpif_sflow_add_poller(ds, dsp); + } } @@ -752,18 +833,414 @@ dpif_sflow_odp_port_to_ifindex(const struct dpif_sflow *ds, return ret; } +static void +dpif_sflow_tunnel_v4(uint8_t tunnel_ipproto, + const struct flow_tnl *tunnel, + SFLSampled_ipv4 *ipv4) + +{ + ipv4->protocol = tunnel_ipproto; + ipv4->tos = tunnel->ip_tos; + ipv4->src_ip.addr = (OVS_FORCE uint32_t) tunnel->ip_src; + ipv4->dst_ip.addr = (OVS_FORCE uint32_t) tunnel->ip_dst; + ipv4->src_port = (OVS_FORCE uint16_t) tunnel->tp_src; + ipv4->dst_port = (OVS_FORCE uint16_t) tunnel->tp_dst; +} + +static void +dpif_sflow_push_mpls_lse(struct dpif_sflow_actions *sflow_actions, + ovs_be32 lse) +{ + if (sflow_actions->mpls_stack_depth >= FLOW_MAX_MPLS_LABELS) { + sflow_actions->mpls_err = true; + return; + } + + /* Record the new lse in host-byte-order. */ + /* BOS flag will be fixed later when we send stack to sFlow library. */ + sflow_actions->mpls_lse[sflow_actions->mpls_stack_depth++] = ntohl(lse); +} + +static void +dpif_sflow_pop_mpls_lse(struct dpif_sflow_actions *sflow_actions) +{ + if (sflow_actions->mpls_stack_depth == 0) { + sflow_actions->mpls_err = true; + return; + } + sflow_actions->mpls_stack_depth--; +} + +static void +dpif_sflow_set_mpls(struct dpif_sflow_actions *sflow_actions, + const struct ovs_key_mpls *mpls_key, int n) +{ + int ii; + if (n > FLOW_MAX_MPLS_LABELS) { + sflow_actions->mpls_err = true; + return; + } + + for (ii = 0; ii < n; ii++) { + /* Reverse stack order, and use host-byte-order for each lse. */ + sflow_actions->mpls_lse[n - ii - 1] = ntohl(mpls_key[ii].mpls_lse); + } + sflow_actions->mpls_stack_depth = n; +} + +static void +sflow_read_tnl_push_action(const struct nlattr *attr, + struct dpif_sflow_actions *sflow_actions) +{ + /* Modeled on lib/odp-util.c: format_odp_tnl_push_header */ + const struct ovs_action_push_tnl *data = nl_attr_get(attr); + const struct eth_header *eth = (const struct eth_header *) data->header; + const struct ip_header *ip + = ALIGNED_CAST(const struct ip_header *, eth + 1); + + sflow_actions->out_port = u32_to_odp(data->out_port); + + /* Ethernet. */ + /* TODO: SFlow does not currently define a MAC-in-MAC + * encapsulation structure. We could use an extension + * structure to report this. + */ + + /* IPv4 */ + /* Cannot assume alignment so just use memcpy. */ + sflow_actions->tunnel.ip_src = get_16aligned_be32(&ip->ip_src); + sflow_actions->tunnel.ip_dst = get_16aligned_be32(&ip->ip_dst); + sflow_actions->tunnel.ip_tos = ip->ip_tos; + sflow_actions->tunnel.ip_ttl = ip->ip_ttl; + /* The tnl_push action can supply the ip_protocol too. */ + sflow_actions->tunnel_ipproto = ip->ip_proto; + + /* Layer 4 */ + if (data->tnl_type == OVS_VPORT_TYPE_VXLAN + || data->tnl_type == OVS_VPORT_TYPE_GENEVE) { + const struct udp_header *udp = (const struct udp_header *) (ip + 1); + sflow_actions->tunnel.tp_src = udp->udp_src; + sflow_actions->tunnel.tp_dst = udp->udp_dst; + + if (data->tnl_type == OVS_VPORT_TYPE_VXLAN) { + const struct vxlanhdr *vxh = (const struct vxlanhdr *) (udp + 1); + uint64_t tun_id = ntohl(get_16aligned_be32(&vxh->vx_vni)) >> 8; + sflow_actions->tunnel.tun_id = htonll(tun_id); + } else { + const struct genevehdr *gnh = (const struct genevehdr *) (udp + 1); + uint64_t tun_id = ntohl(get_16aligned_be32(&gnh->vni)) >> 8; + sflow_actions->tunnel.tun_id = htonll(tun_id); + } + } else if (data->tnl_type == OVS_VPORT_TYPE_GRE) { + const void *l4 = ip + 1; + const struct gre_base_hdr *greh = (const struct gre_base_hdr *) l4; + ovs_16aligned_be32 *options = (ovs_16aligned_be32 *)(greh + 1); + if (greh->flags & htons(GRE_CSUM)) { + options++; + } + if (greh->flags & htons(GRE_KEY)) { + uint64_t tun_id = ntohl(get_16aligned_be32(options)); + sflow_actions->tunnel.tun_id = htonll(tun_id); + } + } +} + +static void +sflow_read_set_action(const struct nlattr *attr, + struct dpif_sflow_actions *sflow_actions) +{ + enum ovs_key_attr type = nl_attr_type(attr); + switch (type) { + case OVS_KEY_ATTR_ENCAP: + if (++sflow_actions->encap_depth > 1) { + /* Do not handle multi-encap for now. */ + sflow_actions->tunnel_err = true; + } else { + dpif_sflow_read_actions(NULL, + nl_attr_get(attr), nl_attr_get_size(attr), + sflow_actions); + } + break; + case OVS_KEY_ATTR_PRIORITY: + case OVS_KEY_ATTR_SKB_MARK: + case OVS_KEY_ATTR_DP_HASH: + case OVS_KEY_ATTR_RECIRC_ID: + break; + + case OVS_KEY_ATTR_TUNNEL: { + if (++sflow_actions->encap_depth > 1) { + /* Do not handle multi-encap for now. */ + sflow_actions->tunnel_err = true; + } else { + if (odp_tun_key_from_attr(attr, &sflow_actions->tunnel) + == ODP_FIT_ERROR) { + /* Tunnel parsing error. */ + sflow_actions->tunnel_err = true; + } + } + break; + } + + case OVS_KEY_ATTR_IN_PORT: + case OVS_KEY_ATTR_ETHERNET: + case OVS_KEY_ATTR_VLAN: + break; + + case OVS_KEY_ATTR_MPLS: { + const struct ovs_key_mpls *mpls_key = nl_attr_get(attr); + size_t size = nl_attr_get_size(attr); + dpif_sflow_set_mpls(sflow_actions, mpls_key, size / sizeof *mpls_key); + break; + } + + case OVS_KEY_ATTR_ETHERTYPE: + case OVS_KEY_ATTR_IPV4: + if (sflow_actions->encap_depth == 1) { + const struct ovs_key_ipv4 *key = nl_attr_get(attr); + if (key->ipv4_src) { + sflow_actions->tunnel.ip_src = key->ipv4_src; + } + if (key->ipv4_dst) { + sflow_actions->tunnel.ip_dst = key->ipv4_dst; + } + if (key->ipv4_proto) { + sflow_actions->tunnel_ipproto = key->ipv4_proto; + } + if (key->ipv4_tos) { + sflow_actions->tunnel.ip_tos = key->ipv4_tos; + } + if (key->ipv4_ttl) { + sflow_actions->tunnel.ip_tos = key->ipv4_ttl; + } + } + break; + + case OVS_KEY_ATTR_IPV6: + /* TODO: parse IPv6 encap. */ + break; + + /* These have the same structure and format. */ + case OVS_KEY_ATTR_TCP: + case OVS_KEY_ATTR_UDP: + case OVS_KEY_ATTR_SCTP: + if (sflow_actions->encap_depth == 1) { + const struct ovs_key_tcp *key = nl_attr_get(attr); + if (key->tcp_src) { + sflow_actions->tunnel.tp_src = key->tcp_src; + } + if (key->tcp_dst) { + sflow_actions->tunnel.tp_dst = key->tcp_dst; + } + } + break; + + case OVS_KEY_ATTR_TCP_FLAGS: + case OVS_KEY_ATTR_ICMP: + case OVS_KEY_ATTR_ICMPV6: + case OVS_KEY_ATTR_ARP: + case OVS_KEY_ATTR_ND: + case OVS_KEY_ATTR_UNSPEC: + case __OVS_KEY_ATTR_MAX: + default: + break; + } +} + +static void +dpif_sflow_capture_input_mpls(const struct flow *flow, + struct dpif_sflow_actions *sflow_actions) +{ + if (eth_type_mpls(flow->dl_type)) { + int depth = 0; + int ii; + ovs_be32 lse; + /* Calculate depth by detecting BOS. */ + for (ii = 0; ii < FLOW_MAX_MPLS_LABELS; ii++) { + lse = flow->mpls_lse[ii]; + depth++; + if (lse & htonl(MPLS_BOS_MASK)) { + break; + } + } + /* Capture stack, reversing stack order, and + * using host-byte-order for each lse. BOS flag + * is ignored for now. It is set later when + * the output stack is encoded. + */ + for (ii = 0; ii < depth; ii++) { + lse = flow->mpls_lse[ii]; + sflow_actions->mpls_lse[depth - ii - 1] = ntohl(lse); + } + sflow_actions->mpls_stack_depth = depth; + } +} + +void +dpif_sflow_read_actions(const struct flow *flow, + const struct nlattr *actions, size_t actions_len, + struct dpif_sflow_actions *sflow_actions) +{ + const struct nlattr *a; + unsigned int left; + + if (actions_len == 0) { + /* Packet dropped.*/ + return; + } + + if (flow != NULL) { + /* Make sure the MPLS output stack + * is seeded with the input stack. + */ + dpif_sflow_capture_input_mpls(flow, sflow_actions); + + /* XXX when 802.1AD(QinQ) is supported then + * we can do the same with VLAN stacks here + */ + } + + NL_ATTR_FOR_EACH (a, left, actions, actions_len) { + enum ovs_action_attr type = nl_attr_type(a); + switch (type) { + case OVS_ACTION_ATTR_OUTPUT: + /* Capture the output port in case we need it + * to get the output tunnel type. + */ + sflow_actions->out_port = u32_to_odp(nl_attr_get_u32(a)); + break; + + case OVS_ACTION_ATTR_TUNNEL_POP: + /* XXX: Do not handle this for now. It's not clear + * if we should start with encap_depth == 1 when we + * see an input tunnel, or if we should assume + * that the input tunnel was always "popped" if it + * was presented to us decoded in flow->tunnel? + * + * If we do handle this it might look like this, + * as we clear the captured tunnel info and decrement + * the encap_depth: + * + * memset(&sflow_actions->tunnel, 0, sizeof struct flow_tnl); + * sflow_actions->tunnel_ipproto = 0; + * --sflow_actions->encap_depth; + * + * but for now just disable the tunnel annotation: + */ + sflow_actions->tunnel_err = true; + break; + + case OVS_ACTION_ATTR_TUNNEL_PUSH: + /* XXX: This actions appears to come with it's own + * OUTPUT action, so should it be regarded as having + * an implicit "pop" following it too? Put another + * way, would two tnl_push() actions in succession + * result in a packet with two layers of encap? + */ + if (++sflow_actions->encap_depth > 1) { + /* Do not handle multi-encap for now. */ + sflow_actions->tunnel_err = true; + } else { + sflow_read_tnl_push_action(a, sflow_actions); + } + break; + + case OVS_ACTION_ATTR_USERSPACE: + case OVS_ACTION_ATTR_RECIRC: + case OVS_ACTION_ATTR_HASH: + break; + + case OVS_ACTION_ATTR_SET_MASKED: + /* TODO: apply mask. XXX: Are we likely to see this? */ + break; + + case OVS_ACTION_ATTR_SET: + sflow_read_set_action(nl_attr_get(a), sflow_actions); + break; + + case OVS_ACTION_ATTR_PUSH_VLAN: + case OVS_ACTION_ATTR_POP_VLAN: + /* TODO: 802.1AD(QinQ) is not supported by OVS (yet), so do not + * construct a VLAN-stack. The sFlow user-action cookie already + * captures the egress VLAN ID so there is nothing more to do here. + */ + break; + + case OVS_ACTION_ATTR_PUSH_MPLS: { + const struct ovs_action_push_mpls *mpls = nl_attr_get(a); + if (mpls) { + dpif_sflow_push_mpls_lse(sflow_actions, mpls->mpls_lse); + } + break; + } + case OVS_ACTION_ATTR_POP_MPLS: { + dpif_sflow_pop_mpls_lse(sflow_actions); + break; + } + case OVS_ACTION_ATTR_SAMPLE: + case OVS_ACTION_ATTR_UNSPEC: + case __OVS_ACTION_ATTR_MAX: + default: + break; + } + } +} + +static void +dpif_sflow_encode_mpls_stack(SFLLabelStack *stack, + uint32_t *mpls_lse_buf, + const struct dpif_sflow_actions *sflow_actions) +{ + /* Put the MPLS stack back into "packet header" order, + * and make sure the BOS flag is set correctly on the last + * one. Each lse is still in host-byte-order. + */ + int ii; + uint32_t lse; + stack->depth = sflow_actions->mpls_stack_depth; + stack->stack = mpls_lse_buf; + for (ii = 0; ii < stack->depth; ii++) { + lse = sflow_actions->mpls_lse[stack->depth - ii - 1]; + stack->stack[ii] = (lse & ~MPLS_BOS_MASK); + } + stack->stack[stack->depth - 1] |= MPLS_BOS_MASK; +} + +/* Extract the output port count from the user action cookie. + * See http://sflow.org/sflow_version_5.txt "Input/Output port information" + */ +static uint32_t +dpif_sflow_cookie_num_outputs(const union user_action_cookie *cookie) +{ + uint32_t format = cookie->sflow.output & 0xC0000000; + uint32_t port_n = cookie->sflow.output & 0x3FFFFFFF; + if (format == 0) { + return port_n ? 1 : 0; + } + else if (format == 0x80000000) { + return port_n; + } + return 0; +} + void dpif_sflow_received(struct dpif_sflow *ds, const struct dp_packet *packet, - const struct flow *flow, odp_port_t odp_in_port, - const union user_action_cookie *cookie) + const struct flow *flow, odp_port_t odp_in_port, + const union user_action_cookie *cookie, + const struct dpif_sflow_actions *sflow_actions) OVS_EXCLUDED(mutex) { SFL_FLOW_SAMPLE_TYPE fs; SFLFlow_sample_element hdrElem; SFLSampled_header *header; SFLFlow_sample_element switchElem; + uint8_t tnlInProto, tnlOutProto; + SFLFlow_sample_element tnlInElem, tnlOutElem; + SFLFlow_sample_element vniInElem, vniOutElem; + SFLFlow_sample_element mplsElem; + uint32_t mpls_lse_buf[FLOW_MAX_MPLS_LABELS]; SFLSampler *sampler; struct dpif_sflow_port *in_dsp; + struct dpif_sflow_port *out_dsp; ovs_be16 vlan_tci; ovs_mutex_lock(&mutex); @@ -814,6 +1291,67 @@ dpif_sflow_received(struct dpif_sflow *ds, const struct dp_packet *packet, fs.output = cookie->sflow.output; + /* Input tunnel. */ + if (flow->tunnel.ip_dst) { + memset(&tnlInElem, 0, sizeof(tnlInElem)); + tnlInElem.tag = SFLFLOW_EX_IPV4_TUNNEL_INGRESS; + tnlInProto = dpif_sflow_tunnel_proto(in_dsp->tunnel_type); + dpif_sflow_tunnel_v4(tnlInProto, + &flow->tunnel, + &tnlInElem.flowType.ipv4); + SFLADD_ELEMENT(&fs, &tnlInElem); + if (flow->tunnel.tun_id) { + memset(&vniInElem, 0, sizeof(vniInElem)); + vniInElem.tag = SFLFLOW_EX_VNI_INGRESS; + vniInElem.flowType.tunnel_vni.vni + = ntohll(flow->tunnel.tun_id); + SFLADD_ELEMENT(&fs, &vniInElem); + } + } + + /* Output tunnel. */ + if (sflow_actions + && sflow_actions->encap_depth == 1 + && !sflow_actions->tunnel_err + && dpif_sflow_cookie_num_outputs(cookie) == 1) { + tnlOutProto = sflow_actions->tunnel_ipproto; + if (tnlOutProto == 0) { + /* Try to infer the ip-protocol from the output port. */ + if (sflow_actions->out_port != ODPP_NONE) { + out_dsp = dpif_sflow_find_port(ds, sflow_actions->out_port); + if (out_dsp) { + tnlOutProto = dpif_sflow_tunnel_proto(out_dsp->tunnel_type); + } + } + } + memset(&tnlOutElem, 0, sizeof(tnlOutElem)); + tnlOutElem.tag = SFLFLOW_EX_IPV4_TUNNEL_EGRESS; + dpif_sflow_tunnel_v4(tnlOutProto, + &sflow_actions->tunnel, + &tnlOutElem.flowType.ipv4); + SFLADD_ELEMENT(&fs, &tnlOutElem); + if (sflow_actions->tunnel.tun_id) { + memset(&vniOutElem, 0, sizeof(vniOutElem)); + vniOutElem.tag = SFLFLOW_EX_VNI_EGRESS; + vniOutElem.flowType.tunnel_vni.vni + = ntohll(sflow_actions->tunnel.tun_id); + SFLADD_ELEMENT(&fs, &vniOutElem); + } + } + + /* MPLS output label stack. */ + if (sflow_actions + && sflow_actions->mpls_stack_depth > 0 + && !sflow_actions->mpls_err + && dpif_sflow_cookie_num_outputs(cookie) == 1) { + memset(&mplsElem, 0, sizeof(mplsElem)); + mplsElem.tag = SFLFLOW_EX_MPLS; + dpif_sflow_encode_mpls_stack(&mplsElem.flowType.mpls.out_stack, + mpls_lse_buf, + sflow_actions); + SFLADD_ELEMENT(&fs, &mplsElem); + } + /* Submit the flow sample to be encoded into the next datagram. */ SFLADD_ELEMENT(&fs, &hdrElem); SFLADD_ELEMENT(&fs, &switchElem); diff --git a/ofproto/ofproto-dpif-sflow.h b/ofproto/ofproto-dpif-sflow.h index ff8b23158..014e6cce3 100644 --- a/ofproto/ofproto-dpif-sflow.h +++ b/ofproto/ofproto-dpif-sflow.h @@ -28,6 +28,29 @@ struct flow; struct ofproto_sflow_options; struct ofport; +/* When we have the actions for a sampled packet that + * will go to just one output, then this structure is + * populated by parsing them. Only fields relevant to + * the sFlow export are extracted. + */ +struct dpif_sflow_actions { + odp_port_t out_port; /* ODP output port. */ + + uint32_t encap_depth; /* Count layers of tunnel-encap. */ + struct flow_tnl tunnel; /* Egress tunnel push/set. */ + uint8_t tunnel_ipproto; /* Tunnel push action can set ipproto. */ + bool tunnel_err; /* Tunnel actions parse failure. */ + + /* Using host-byte order for the mpls stack here + to match the expectations of the sFlow library. Also + the ordering is reversed, so that the entry at offset 0 + is the bottom of the stack. + */ + uint32_t mpls_lse[FLOW_MAX_MPLS_LABELS]; /* Out stack in host byte order. */ + uint32_t mpls_stack_depth; /* Out stack depth. */ + bool mpls_err; /* MPLS actions parse failure. */ +}; + struct dpif_sflow *dpif_sflow_create(void); struct dpif_sflow *dpif_sflow_ref(const struct dpif_sflow *); void dpif_sflow_unref(struct dpif_sflow *); @@ -46,9 +69,14 @@ void dpif_sflow_del_port(struct dpif_sflow *, odp_port_t odp_port); void dpif_sflow_run(struct dpif_sflow *); void dpif_sflow_wait(struct dpif_sflow *); +void dpif_sflow_read_actions(const struct flow *, + const struct nlattr *actions, size_t actions_len, + struct dpif_sflow_actions *); + void dpif_sflow_received(struct dpif_sflow *, const struct dp_packet *, const struct flow *, odp_port_t odp_port, - const union user_action_cookie *); + const union user_action_cookie *, + const struct dpif_sflow_actions *); int dpif_sflow_odp_port_to_ifindex(const struct dpif_sflow *, odp_port_t odp_port); diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c index 06a7a285d..b65ead6ea 100644 --- a/ofproto/ofproto-dpif-upcall.c +++ b/ofproto/ofproto-dpif-upcall.c @@ -166,6 +166,7 @@ struct upcall { enum dpif_upcall_type type; /* Datapath type of the upcall. */ const struct nlattr *userdata; /* Userdata for DPIF_UC_ACTION Upcalls. */ + const struct nlattr *actions; /* Flow actions in DPIF_UC_ACTION Upcalls. */ bool xout_initialized; /* True if 'xout' must be uninitialized. */ struct xlate_out xout; /* Result of xlate_actions(). */ @@ -694,6 +695,7 @@ recv_upcalls(struct handler *handler) upcall->ufid = &dupcall->ufid; upcall->out_tun_key = dupcall->out_tun_key; + upcall->actions = dupcall->actions; if (vsp_adjust_flow(upcall->ofproto, flow, &dupcall->packet)) { upcall->vsp_adjusted = true; @@ -893,8 +895,8 @@ compose_slow_path(struct udpif *udpif, struct xlate_out *xout, ? ODPP_NONE : odp_in_port; pid = dpif_port_get_pid(udpif->dpif, port, flow_hash_5tuple(flow, 0)); - odp_put_userspace_action(pid, &cookie, sizeof cookie.slow_path, ODPP_NONE, - buf); + odp_put_userspace_action(pid, &cookie, sizeof cookie.slow_path, + ODPP_NONE, false, buf); } /* If there is no error, the upcall must be destroyed with upcall_uninit() @@ -934,6 +936,7 @@ upcall_receive(struct upcall *upcall, const struct dpif_backer *backer, upcall->key_len = 0; upcall->out_tun_key = NULL; + upcall->actions = NULL; return 0; } @@ -1121,11 +1124,34 @@ process_upcall(struct udpif *udpif, struct upcall *upcall, case SFLOW_UPCALL: if (upcall->sflow) { union user_action_cookie cookie; - + const struct nlattr *actions; + int actions_len = 0; + struct dpif_sflow_actions sflow_actions; + memset(&sflow_actions, 0, sizeof sflow_actions); memset(&cookie, 0, sizeof cookie); memcpy(&cookie, nl_attr_get(userdata), sizeof cookie.sflow); + if (upcall->actions) { + /* Actions were passed up from datapath. */ + actions = nl_attr_get(upcall->actions); + actions_len = nl_attr_get_size(upcall->actions); + if (actions && actions_len) { + dpif_sflow_read_actions(flow, actions, actions_len, + &sflow_actions); + } + } + if (actions_len == 0) { + /* Lookup actions in userspace cache. */ + struct udpif_key *ukey = ukey_lookup(udpif, upcall->ufid); + if (ukey) { + actions = ukey->actions->data; + actions_len = ukey->actions->size; + dpif_sflow_read_actions(flow, actions, actions_len, + &sflow_actions); + } + } dpif_sflow_received(upcall->sflow, packet, flow, - flow->in_port.odp_port, &cookie); + flow->in_port.odp_port, &cookie, + actions_len > 0 ? &sflow_actions : NULL); } break; diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 803c268f2..52395a770 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -2440,7 +2440,8 @@ compose_sample_action(const struct xbridge *xbridge, const uint32_t probability, const union user_action_cookie *cookie, const size_t cookie_size, - const odp_port_t tunnel_out_port) + const odp_port_t tunnel_out_port, + bool include_actions) { size_t sample_offset, actions_offset; odp_port_t odp_port; @@ -2457,7 +2458,9 @@ compose_sample_action(const struct xbridge *xbridge, pid = dpif_port_get_pid(xbridge->dpif, odp_port, flow_hash_5tuple(flow, 0)); cookie_offset = odp_put_userspace_action(pid, cookie, cookie_size, - tunnel_out_port, odp_actions); + tunnel_out_port, + include_actions, + odp_actions); nl_msg_end_nested(odp_actions, actions_offset); nl_msg_end_nested(odp_actions, sample_offset); @@ -2515,7 +2518,8 @@ compose_sflow_action(const struct xbridge *xbridge, odp_port == ODPP_NONE ? 0 : 1, &cookie); return compose_sample_action(xbridge, odp_actions, flow, probability, - &cookie, sizeof cookie.sflow, ODPP_NONE); + &cookie, sizeof cookie.sflow, ODPP_NONE, + true); } static void @@ -2578,7 +2582,8 @@ compose_ipfix_action(const struct xbridge *xbridge, compose_ipfix_cookie(&cookie, output_odp_port); compose_sample_action(xbridge, odp_actions, flow, probability, - &cookie, sizeof cookie.ipfix, tunnel_out_port); + &cookie, sizeof cookie.ipfix, tunnel_out_port, + false); } /* SAMPLE action for sFlow must be first action in any given list of @@ -3969,7 +3974,8 @@ xlate_sample_action(struct xlate_ctx *ctx, os->obs_domain_id, os->obs_point_id, &cookie); compose_sample_action(ctx->xbridge, ctx->xout->odp_actions, &ctx->xin->flow, probability, &cookie, - sizeof cookie.flow_sample, ODPP_NONE); + sizeof cookie.flow_sample, ODPP_NONE, + false); } static bool diff --git a/tests/odp.at b/tests/odp.at index a50a9cd07..61edde337 100644 --- a/tests/odp.at +++ b/tests/odp.at @@ -234,13 +234,13 @@ AT_DATA([actions.txt], [dnl 1,2,3 userspace(pid=555666777) userspace(pid=555666777,tunnel_out_port=10) -userspace(pid=6633,sFlow(vid=9,pcp=7,output=10)) -userspace(pid=6633,sFlow(vid=9,pcp=7,output=10),tunnel_out_port=10) +userspace(pid=6633,sFlow(vid=9,pcp=7,output=10),actions) +userspace(pid=6633,sFlow(vid=9,pcp=7,output=10),actions,tunnel_out_port=10) userspace(pid=9765,slow_path(0)) userspace(pid=9765,slow_path(0),tunnel_out_port=10) userspace(pid=9765,slow_path(cfm)) userspace(pid=9765,slow_path(cfm),tunnel_out_port=10) -userspace(pid=1234567,userdata(0102030405060708090a0b0c0d0e0f)) +userspace(pid=1234567,userdata(0102030405060708090a0b0c0d0e0f),actions) userspace(pid=1234567,userdata(0102030405060708090a0b0c0d0e0f),tunnel_out_port=10) userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456)) userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456),tunnel_out_port=10) diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index f9d92e019..5b063d2b5 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -5030,7 +5030,7 @@ CHECK_SFLOW_SAMPLING_PACKET([[[::1]]]) AT_CLEANUP dnl Test sFlow LAG structures -AT_SETUP([ofproto-dpif - sFlow LACP structures]) +AT_SETUP([ofproto-dpif - sFlow packet sampling - LACP structures]) AT_SKIP_IF([test "$IS_WIN32" = "yes"]) OVS_VSWITCHD_START([dnl add-bond br0 bond p1 p2 -- \ @@ -5087,6 +5087,268 @@ LACPCOUNTERS AT_CLEANUP +AT_SETUP([ofproto-dpif - sFlow packet sampling - tunnel set]) +AT_XFAIL_IF([test "$IS_WIN32" = "yes"]) +OVS_VSWITCHD_START([set Bridge br0 fail-mode=standalone]) + +dnl set up sFlow logging +dnl ON_EXIT([kill `cat test-sflow.pid`]) +AT_CHECK([ovstest test-sflow --log-file --detach --no-chdir --pidfile 0:127.0.0.1 > sflow.log], [0], [], [ignore]) +AT_CAPTURE_FILE([sflow.log]) +SFLOW_PORT=`parse_listening_port < test-sflow.log` +ovs-appctl time/stop + +OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP +AT_CHECK([ovs-vsctl add-port br0 gre0 -- set Interface gre0 type=gre \ + options:remote_ip=1.1.1.1 options:key=456 ofport_request=3]) +AT_CHECK([ovs-vsctl add-port br0 p1 -- set Interface p1 type=dummy ofport_request=4]) + +AT_CHECK([ovs-ofctl add-flow br0 action=3]) + +dnl enable sflow +ovs-vsctl \ + set Bridge br0 sflow=@sf -- \ + --id=@sf create sflow targets=\"127.0.0.1:$SFLOW_PORT\" \ + header=128 sampling=1 polling=0 + +dnl introduce a packet that will be flooded to the tunnel +AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(4),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.10.10.2,dst=10.10.10.1,proto=1,tos=1,ttl=128,frag=no),icmp(type=8,code=0)']) + +dnl sleep long enough to get the sFlow datagram flushed out (may be delayed for up to 1 second) +for i in `seq 1 30`; do + ovs-appctl time/warp 100 +done + +ovs-appctl -t test-sflow exit + +AT_CHECK_UNQUOTED([[sort sflow.log | $EGREP 'HEADER|ERROR' | sed 's/ /\ + /g']], [0], [dnl +HEADER + dgramSeqNo=1 + ds=127.0.0.1>2:1000 + fsSeqNo=1 + tunnel4_out_length=0 + tunnel4_out_protocol=47 + tunnel4_out_src=0.0.0.0 + tunnel4_out_dst=1.1.1.1 + tunnel4_out_src_port=0 + tunnel4_out_dst_port=0 + tunnel4_out_tcp_flags=0 + tunnel4_out_tos=1 + tunnel_out_vni=456 + in_vlan=0 + in_priority=0 + out_vlan=0 + out_priority=0 + meanSkip=1 + samplePool=1 + dropEvents=0 + in_ifindex=0 + in_format=0 + out_ifindex=1 + out_format=2 + hdr_prot=1 + pkt_len=64 + stripped=4 + hdr_len=60 + hdr=50-54-00-00-00-0A-50-54-00-00-00-09-08-00-45-01-00-1C-00-00-00-00-80-01-12-CA-0A-0A-0A-02-0A-0A-0A-01-08-00-F7-FF-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + +AT_SETUP([ofproto-dpif - sFlow packet sampling - tunnel push]) +AT_XFAIL_IF([test "$IS_WIN32" = "yes"]) + +OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 options:ifindex=1010]) + +dnl set up sFlow logging +dnl ON_EXIT([kill `cat test-sflow.pid`]) +AT_CHECK([ovstest test-sflow --log-file --detach --no-chdir --pidfile 0:127.0.0.1 > sflow.log], [0], [], [ignore]) +AT_CAPTURE_FILE([sflow.log]) +SFLOW_PORT=`parse_listening_port < test-sflow.log` +ovs-appctl time/stop + +AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg]) +AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0]) +AT_CHECK([ovs-vsctl -- add-port int-br t1 -- set Interface t1 type=gre \ + options:remote_ip=1.1.2.92 options:key=456 ofport_request=4\ + -- add-port int-br vm1 -- set Interface vm1 type=dummy \ + options:ifindex=2011 ofport_request=5 + ], [0]) + +AT_CHECK([ovs-appctl dpif/show], [0], [dnl +dummy@ovs-dummy: hit:0 missed:0 + br0: + br0 65534/100: (dummy) + p0 1/1: (dummy: ifindex=1010) + int-br: + int-br 65534/2: (dummy) + t1 4/4: (gre: key=456, remote_ip=1.1.2.92) + vm1 5/3: (dummy: ifindex=2011) +]) + +dnl set up route to 1.1.2.92 via br0 and action=normal +AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK +]) +AT_CHECK([ovs-appctl ovs/route/add 192.168.0.0/16 br0], [0], [OK +]) +AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK +]) +AT_CHECK([ovs-ofctl add-flow br0 action=normal]) + +dnl Prime ARP Cache for 1.1.2.92 +AT_CHECK([ovs-appctl netdev-dummy/receive br0 'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=1,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)']) + +dnl configure sflow on int-br only +ovs-vsctl \ + set Bridge int-br sflow=@sf -- \ + --id=@sf create sflow targets=\"127.0.0.1:$SFLOW_PORT\" \ + header=128 sampling=1 polling=0 + +dnl add rule for int-br to force packet onto tunnel. There is no ifindex +dnl for this port so the sFlow output will just report that it went to +dnl 1 output (out_format=2, out_ifindex=1) +AT_CHECK([ovs-ofctl add-flow int-br "actions=4"]) + +AT_CHECK([ovs-appctl netdev-dummy/receive vm1 'in_port(3),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=192.168.1.1,dst=192.168.2.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)']) + +dnl sleep long enough to get the sFlow datagram flushed out (may be delayed for up to 1 second) +for i in `seq 1 30`; do + ovs-appctl time/warp 100 +done + +ovs-appctl -t test-sflow exit + +AT_CHECK_UNQUOTED([[sort sflow.log | $EGREP 'HEADER|ERROR' | sed 's/ /\ + /g']], [0], [dnl +HEADER + dgramSeqNo=1 + ds=127.0.0.1>2:1000 + fsSeqNo=1 + tunnel4_out_length=0 + tunnel4_out_protocol=47 + tunnel4_out_src=1.1.2.88 + tunnel4_out_dst=1.1.2.92 + tunnel4_out_src_port=0 + tunnel4_out_dst_port=0 + tunnel4_out_tcp_flags=0 + tunnel4_out_tos=0 + tunnel_out_vni=456 + in_vlan=0 + in_priority=0 + out_vlan=0 + out_priority=0 + meanSkip=1 + samplePool=1 + dropEvents=0 + in_ifindex=2011 + in_format=0 + out_ifindex=1 + out_format=2 + hdr_prot=1 + pkt_len=64 + stripped=4 + hdr_len=60 + hdr=50-54-00-00-00-0A-50-54-00-00-00-05-08-00-45-00-00-1C-00-00-00-00-80-01-B6-8D-C0-A8-01-01-C0-A8-02-02-08-00-F7-FF-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + +AT_SETUP([ofproto-dpif - sFlow packet sampling - MPLS]) +OVS_VSWITCHD_START +AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg]) +ADD_OF_PORTS([br0], [1], [2]) +AT_DATA([flows.txt], [dnl +table=0 dl_src=50:54:00:00:00:09 actions=push_mpls:0x8847,set_mpls_label:789,set_mpls_tc:4,set_mpls_ttl:32,2 +table=0 dl_src=50:54:00:00:00:0b actions=pop_mpls:0x0800,2 +]) +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +dnl set up sFlow logging +dnl ON_EXIT([kill `cat test-sflow.pid`]) +AT_CHECK([ovstest test-sflow --log-file --detach --no-chdir --pidfile 0:127.0.0.1 > sflow.log], [0], [], [ignore]) +AT_CAPTURE_FILE([sflow.log]) +SFLOW_PORT=`parse_listening_port < test-sflow.log` +ovs-appctl time/stop + +dnl configure sflow +ovs-vsctl \ + set Bridge br0 sflow=@sf -- \ + --id=@sf create sflow targets=\"127.0.0.1:$SFLOW_PORT\" \ + header=128 sampling=1 polling=0 + +AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x8847),mpls(label=11,tc=3,ttl=64,bos=1)']) +AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)']) + +dnl sleep long enough to get the sFlow datagram flushed out (may be delayed for up to 1 second) +for i in `seq 1 30`; do + ovs-appctl time/warp 100 +done + +ovs-appctl -t test-sflow exit + +AT_CHECK_UNQUOTED([[sort sflow.log | $EGREP 'HEADER|ERROR' | sed 's/ /\ + /g']], [0], [dnl +HEADER + dgramSeqNo=1 + ds=127.0.0.1>2:1000 + fsSeqNo=1 + mpls_label_0=789 + mpls_tc_0=4 + mpls_ttl_0=32 + mpls_bos_0=0 + mpls_label_1=11 + mpls_tc_1=3 + mpls_ttl_1=64 + mpls_bos_1=1 + in_vlan=0 + in_priority=0 + out_vlan=0 + out_priority=0 + meanSkip=1 + samplePool=1 + dropEvents=0 + in_ifindex=0 + in_format=0 + out_ifindex=1 + out_format=2 + hdr_prot=1 + pkt_len=64 + stripped=4 + hdr_len=60 + hdr=50-54-00-00-00-0A-50-54-00-00-00-09-88-47-00-00-B7-40-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 +HEADER + dgramSeqNo=1 + ds=127.0.0.1>2:1000 + fsSeqNo=2 + mpls_label_0=789 + mpls_tc_0=4 + mpls_ttl_0=32 + mpls_bos_0=1 + in_vlan=0 + in_priority=0 + out_vlan=0 + out_priority=0 + meanSkip=1 + samplePool=2 + dropEvents=0 + in_ifindex=0 + in_format=0 + out_ifindex=1 + out_format=2 + hdr_prot=1 + pkt_len=64 + stripped=4 + hdr_len=60 + hdr=50-54-00-00-00-0A-50-54-00-00-00-09-08-00-45-00-00-14-00-00-00-00-00-00-BA-EB-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + + # CHECK_NETFLOW_EXPIRATION(LOOPBACK_ADDR) # # Test that basic NetFlow reports flow statistics correctly: diff --git a/tests/test-sflow.c b/tests/test-sflow.c index 70a037256..3f6ffb231 100644 --- a/tests/test-sflow.c +++ b/tests/test-sflow.c @@ -63,6 +63,7 @@ static unixctl_cb_func test_sflow_exit; #define SFLOW_TAG_PKT_TUNNEL4_IN 1024 #define SFLOW_TAG_PKT_TUNNEL_VNI_OUT 1029 #define SFLOW_TAG_PKT_TUNNEL_VNI_IN 1030 +#define SFLOW_TAG_PKT_MPLS 1006 /* string sizes */ #define SFL_MAX_PORTNAME_LEN 255 @@ -113,6 +114,7 @@ struct sflow_xdr { uint32_t TUNNEL4_IN; uint32_t TUNNEL_VNI_OUT; uint32_t TUNNEL_VNI_IN; + uint32_t MPLS; uint32_t IFCOUNTERS; uint32_t LACPCOUNTERS; uint32_t OPENFLOWPORT; @@ -379,6 +381,32 @@ process_flow_sample(struct sflow_xdr *x) printf( " tunnel_out_vni=%"PRIu32, sflowxdr_next(x)); } + if (x->offset.MPLS) { + uint32_t addr_type, stack_depth, ii; + ovs_be32 mpls_lse; + sflowxdr_setc(x, x->offset.MPLS); + /* OVS only sets the out_stack. The rest will be blank. */ + /* skip next hop address */ + addr_type = sflowxdr_next(x); + sflowxdr_skip(x, addr_type == SFLOW_ADDRTYPE_IP6 ? 4 : 1); + /* skip in_stack */ + stack_depth = sflowxdr_next(x); + sflowxdr_skip(x, stack_depth); + /* print out_stack */ + stack_depth = sflowxdr_next(x); + for(ii = 0; ii < stack_depth; ii++) { + mpls_lse=sflowxdr_next_n(x); + printf(" mpls_label_%"PRIu32"=%"PRIu32, + ii, mpls_lse_to_label(mpls_lse)); + printf(" mpls_tc_%"PRIu32"=%"PRIu32, + ii, mpls_lse_to_tc(mpls_lse)); + printf(" mpls_ttl_%"PRIu32"=%"PRIu32, + ii, mpls_lse_to_ttl(mpls_lse)); + printf(" mpls_bos_%"PRIu32"=%"PRIu32, + ii, mpls_lse_to_bos(mpls_lse)); + } + } + if (x->offset.SWITCH) { sflowxdr_setc(x, x->offset.SWITCH); printf(" in_vlan=%"PRIu32, sflowxdr_next(x)); @@ -578,6 +606,10 @@ process_datagram(struct sflow_xdr *x) sflowxdr_mark_unique(x, &x->offset.TUNNEL_VNI_IN); break; + case SFLOW_TAG_PKT_MPLS: + sflowxdr_mark_unique(x, &x->offset.MPLS); + break; + /* Add others here... */ } |