summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--include/sparse/netinet/in.h1
-rw-r--r--lib/dpif-netlink.c2
-rw-r--r--lib/dpif.h1
-rw-r--r--lib/odp-util.c27
-rw-r--r--lib/odp-util.h1
-rw-r--r--ofproto/ofproto-dpif-sflow.c562
-rw-r--r--ofproto/ofproto-dpif-sflow.h30
-rw-r--r--ofproto/ofproto-dpif-upcall.c34
-rw-r--r--ofproto/ofproto-dpif-xlate.c16
-rw-r--r--tests/odp.at6
-rw-r--r--tests/ofproto-dpif.at264
-rw-r--r--tests/test-sflow.c32
13 files changed, 948 insertions, 29 deletions
diff --git a/NEWS b/NEWS
index f118c97e3..c2f2a5d69 100644
--- a/NEWS
+++ b/NEWS
@@ -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... */
}