[ovs-dev] [PATCH] Extend the sFlow agent to report tunnel and MPLS structures

Neil McKee neil.mckee at inmon.com
Mon May 18 16:47:38 UTC 2015


Packets are still sampled at ingress only, so the egress tunnel and/or MPLS
structures are only included when there is just 1 output port.  The actions
are either provided by the datapath in the sample upcall or looked up in the
userspace cache.  The former is preferred because it is more reliable and does
not present any new demands or constraints on the userspace cache, however the
code falls back on the userspace lookup so that this solution can work with
existing kernel datapath modules. If the lookup fails it is not critical: the
compiled user-action-cookie is still available and provides the essential
output port and output VLAN forwarding information just as before.

The openvswitch actions can express almost any tunneling/mangling so the only
totally faithful representation would be to somehow encode the whole list of
flow actions in the sFlow output.  However the standard sFlow tunnel structures
can express most common real-world scenarios, so in parsing the actions we
look for those and skip the encoding if we see anything unusual. For example,
a single set(tunnel()) or tnl_push() is interpreted,  but if a second such
action is encountered then the egress tunnel reporting is suppressed.

The sFlow standard allows "best effort" encoding so that if a field is not
knowable or too onerous to look up then it can be left out. This is often
the case for the layer-4 source port or even the src ip address of a tunnel.
The assumption is that monitoring is enabled everywhere so a missing field
can typically be seen at ingress to the next switch in the path.

This patch also adds unit tests to check the sFlow encoding of set(tunnel()),
tnl_push() and push_mpls() actions.

Signed-off-by: Neil McKee <neil.mckee at inmon.com>
---
 datapath/actions.c            |  14 +-
 datapath/datapath.c           |  18 ++
 datapath/datapath.h           |   2 +
 lib/dpif-netlink.c            |   2 +
 lib/dpif.h                    |   1 +
 ofproto/ofproto-dpif-sflow.c  | 574 +++++++++++++++++++++++++++++++++++++++++-
 ofproto/ofproto-dpif-sflow.h  |  30 ++-
 ofproto/ofproto-dpif-upcall.c |  34 ++-
 tests/ofproto-dpif.at         | 264 ++++++++++++++++++-
 tests/test-sflow.c            |  32 +++
 10 files changed, 950 insertions(+), 21 deletions(-)

diff --git a/datapath/actions.c b/datapath/actions.c
index 98c4376..6438248 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -548,7 +548,8 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port)
 }
 
 static int output_userspace(struct datapath *dp, struct sk_buff *skb,
-			    struct sw_flow_key *key, const struct nlattr *attr)
+			    struct sw_flow_key *key, const struct nlattr *attr,
+			    const struct nlattr *actions, int actions_len)
 {
 	struct ovs_tunnel_info info;
 	struct dp_upcall_info upcall;
@@ -559,6 +560,8 @@ static int output_userspace(struct datapath *dp, struct sk_buff *skb,
 	upcall.userdata = NULL;
 	upcall.portid = 0;
 	upcall.egress_tun_info = NULL;
+	upcall.actions = actions;
+	upcall.actions_len = actions_len;
 
 	for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
 		 a = nla_next(a, &rem)) {
@@ -594,7 +597,8 @@ static int output_userspace(struct datapath *dp, struct sk_buff *skb,
 }
 
 static int sample(struct datapath *dp, struct sk_buff *skb,
-		  struct sw_flow_key *key, const struct nlattr *attr)
+		  struct sw_flow_key *key, const struct nlattr *attr,
+		  const struct nlattr *actions, int actions_len)
 {
 	const struct nlattr *acts_list = NULL;
 	const struct nlattr *a;
@@ -628,7 +632,7 @@ static int sample(struct datapath *dp, struct sk_buff *skb,
 	 */
 	if (likely(nla_type(a) == OVS_ACTION_ATTR_USERSPACE &&
 		   nla_is_last(a, rem)))
-		return output_userspace(dp, skb, key, a);
+		return output_userspace(dp, skb, key, a, actions, actions_len);
 
 	skb = skb_clone(skb, GFP_ATOMIC);
 	if (!skb)
@@ -787,7 +791,7 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 			break;
 
 		case OVS_ACTION_ATTR_USERSPACE:
-			output_userspace(dp, skb, key, a);
+			output_userspace(dp, skb, key, a, attr, len);
 			break;
 
 		case OVS_ACTION_ATTR_HASH:
@@ -826,7 +830,7 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
 			break;
 
 		case OVS_ACTION_ATTR_SAMPLE:
-			err = sample(dp, skb, key, a);
+			err = sample(dp, skb, key, a, attr, len);
 			break;
 		}
 
diff --git a/datapath/datapath.c b/datapath/datapath.c
index 3c97b86..f270832 100644
--- a/datapath/datapath.c
+++ b/datapath/datapath.c
@@ -401,6 +401,11 @@ static size_t upcall_msg_size(const struct dp_upcall_info *upcall_info,
 	if (upcall_info->egress_tun_info)
 		size += nla_total_size(ovs_tun_key_attr_size());
 
+	/* OVS_PACKET_ATTR_ACTIONS */
+	if(upcall_info->actions_len) {
+		size += nla_total_size(upcall_info->actions_len);
+	}
+
 	return size;
 }
 
@@ -486,6 +491,19 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
 		nla_nest_end(user_skb, nla);
 	}
 
+	if(upcall_info->actions_len) {
+		nla = nla_nest_start(user_skb, OVS_PACKET_ATTR_ACTIONS);
+		err = ovs_nla_put_actions(upcall_info->actions,
+					  upcall_info->actions_len,
+					  user_skb);
+		if(!err) {
+			nla_nest_end(user_skb, nla);
+		}
+		else {
+			nla_nest_cancel(user_skb, nla);
+		}
+	}
+
 	/* Only reserve room for attribute header, packet data is added
 	 * in skb_zerocopy()
 	 */
diff --git a/datapath/datapath.h b/datapath/datapath.h
index fdf35f0..526ddad 100644
--- a/datapath/datapath.h
+++ b/datapath/datapath.h
@@ -120,6 +120,8 @@ struct ovs_skb_cb {
 struct dp_upcall_info {
 	const struct ovs_tunnel_info *egress_tun_info;
 	const struct nlattr *userdata;
+	const struct nlattr *actions;
+	int actions_len;
 	u32 portid;
 	u8 cmd;
 };
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index 6838def..c36282c 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 06c6525..565d456 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/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index b146f5d..45da789 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,75 @@ 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 +660,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 +682,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 +808,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 +832,427 @@ 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 = tunnel->ip_src;
+    ipv4->dst_ip.addr = tunnel->ip_dst;
+    ipv4->src_port = tunnel->tp_src;
+    ipv4->dst_port = 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 */
+    struct ovs_action_push_tnl *data;
+    const struct eth_header *eth;
+    const struct ip_header *ip;
+    const void *l3;
+    uint64_t tun_id;
+
+    data = (struct ovs_action_push_tnl *) nl_attr_get(attr);
+    sflow_actions->out_port = data->out_port;
+
+    eth = (const struct eth_header *)data->header;
+
+    l3 = eth + 1;
+    ip = (const struct ip_header *)l3;
+
+    /* 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. */
+    memcpy(&sflow_actions->tunnel.ip_src, &ip->ip_src, 4);
+    memcpy(&sflow_actions->tunnel.ip_dst, &ip->ip_dst, 4);
+    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;
+	udp = (const struct udp_header *) (ip + 1);
+	sflow_actions->tunnel.tp_src = ntohs(udp->udp_src);
+	sflow_actions->tunnel.tp_dst = ntohs(udp->udp_dst);
+
+	if (data->tnl_type == OVS_VPORT_TYPE_VXLAN) {
+	    const struct vxlanhdr *vxh;
+	    vxh = (const struct vxlanhdr *) (udp + 1);
+	    tun_id = ntohl(get_16aligned_be32(&vxh->vx_vni)) >> 8;
+	    sflow_actions->tunnel.tun_id = htonll(tun_id);
+	} else {
+	    const struct genevehdr *gnh;
+	    gnh = (const struct genevehdr *) (udp + 1);
+	    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 struct gre_base_hdr *greh;
+        ovs_16aligned_be32 *options;
+        void *l4;
+
+        l4 = ((uint8_t *)l3  + sizeof(struct ip_header));
+        greh = (const struct gre_base_hdr *) l4;
+        options = (ovs_16aligned_be32 *)(greh + 1);
+        if (greh->flags & htons(GRE_CSUM)) {
+            options++;
+        }
+        if (greh->flags & htons(GRE_KEY)) {
+	    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 = 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 +1303,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 ff8b231..014e6cc 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 01bc382..0674c14 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(). */
@@ -690,6 +691,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;
@@ -930,6 +932,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;
 }
@@ -1117,11 +1120,38 @@ 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) {
+	            ovs_mutex_lock(&ukey->mutex);
+		    actions = ukey->actions->data;
+		    actions_len = ukey->actions->size;
+		    dpif_sflow_read_actions(flow, actions, actions_len,
+					    &sflow_actions);
+		    ovs_mutex_unlock(&ukey->mutex);
+		}
+	    }
             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/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 139dfdd..7031083 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -4978,7 +4978,7 @@ CHECK_SFLOW_SAMPLING_PACKET([127.0.0.1], [IPv4])
 CHECK_SFLOW_SAMPLING_PACKET([[[::1]]], [IPv6])
 
 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 --				\
@@ -5035,6 +5035,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 at 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, IP_VERSION_TYPE)
 #
 # Test that basic NetFlow reports flow statistics correctly:
diff --git a/tests/test-sflow.c b/tests/test-sflow.c
index 70a0372..6453579 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... */
                 }
 
-- 
1.9.3




More information about the dev mailing list