tests: Add sFlow test.
authorNeil Mckee <neil.mckee@inmon.com>
Thu, 28 Mar 2013 06:02:21 +0000 (23:02 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 1 Apr 2013 20:08:31 +0000 (13:08 -0700)
This patch adds an sFlow test to the test suite.

I have only tested this on a Fedora 17 OS.

Signed-off-by: Neil Mckee <neil.mckee@inmon.com>
Signed-off-by: Ben Pfaff <blp@nicira.com>
lib/netdev-dummy.c
tests/atlocal.in
tests/automake.mk
tests/ofproto-dpif.at
tests/test-sflow.c [new file with mode: 0644]

index 234d7bc..bdb3ea1 100644 (file)
@@ -51,6 +51,7 @@ struct netdev_dev_dummy {
     unsigned int change_seq;
 
     struct list devs;           /* List of child "netdev_dummy"s. */
+    int ifindex;
 };
 
 struct netdev_dummy {
@@ -110,6 +111,7 @@ netdev_dummy_create(const struct netdev_class *class, const char *name,
     netdev_dev->mtu = 1500;
     netdev_dev->flags = 0;
     netdev_dev->change_seq = 1;
+    netdev_dev->ifindex = -EOPNOTSUPP;
     list_init(&netdev_dev->devs);
 
     shash_add(&dummy_netdev_devs, name, netdev_dev);
@@ -131,6 +133,27 @@ netdev_dummy_destroy(struct netdev_dev *netdev_dev_)
     free(netdev_dev);
 }
 
+static int
+netdev_dummy_get_config(struct netdev_dev *netdev_dev_, struct smap *args)
+{
+    struct netdev_dev_dummy *netdev_dev = netdev_dev_dummy_cast(netdev_dev_);
+
+    if (netdev_dev->ifindex >= 0) {
+        smap_add_format(args, "ifindex", "%d", netdev_dev->ifindex);
+    }
+    return 0;
+}
+
+static int
+netdev_dummy_set_config(struct netdev_dev *netdev_dev_,
+                        const struct smap *args)
+{
+    struct netdev_dev_dummy *netdev_dev = netdev_dev_dummy_cast(netdev_dev_);
+
+    netdev_dev->ifindex = smap_get_int(args, "ifindex", -EOPNOTSUPP);
+    return 0;
+}
+
 static int
 netdev_dummy_open(struct netdev_dev *netdev_dev_, struct netdev **netdevp)
 {
@@ -283,6 +306,15 @@ netdev_dummy_set_stats(struct netdev *netdev, const struct netdev_stats *stats)
     return 0;
 }
 
+static int
+netdev_dummy_get_ifindex(const struct netdev *netdev)
+{
+    struct netdev_dev_dummy *dev =
+        netdev_dev_dummy_cast(netdev_get_dev(netdev));
+
+    return dev->ifindex;
+}
+
 static int
 netdev_dummy_update_flags(struct netdev *netdev,
                           enum netdev_flags off, enum netdev_flags on,
@@ -337,8 +369,8 @@ static const struct netdev_class dummy_class = {
 
     netdev_dummy_create,
     netdev_dummy_destroy,
-    NULL,                       /* get_config */
-    NULL,                       /* set_config */
+    netdev_dummy_get_config,
+    netdev_dummy_set_config,
     NULL,                       /* get_tunnel_config */
 
     netdev_dummy_open,
@@ -356,7 +388,7 @@ static const struct netdev_class dummy_class = {
     netdev_dummy_get_etheraddr,
     netdev_dummy_get_mtu,
     netdev_dummy_set_mtu,
-    NULL,                       /* get_ifindex */
+    netdev_dummy_get_ifindex,
     NULL,                       /* get_carrier */
     NULL,                       /* get_carrier_resets */
     NULL,                       /* get_miimon */
index c736df4..3db626c 100644 (file)
@@ -1,6 +1,7 @@
 # -*- shell-script -*-
 HAVE_OPENSSL='@HAVE_OPENSSL@'
 HAVE_PYTHON='@HAVE_PYTHON@'
+EGREP='@EGREP@'
 PERL='@PERL@'
 
 if test x"$PYTHON" = x; then
index b11e0a2..275ff53 100644 (file)
@@ -232,6 +232,10 @@ noinst_PROGRAMS += tests/test-stp
 tests_test_stp_SOURCES = tests/test-stp.c
 tests_test_stp_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 
+noinst_PROGRAMS += tests/test-sflow
+tests_test_sflow_SOURCES = tests/test-sflow.c
+tests_test_sflow_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+
 noinst_PROGRAMS += tests/test-netflow
 tests_test_netflow_SOURCES = tests/test-netflow.c
 tests_test_netflow_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
index 96b166e..06ebf23 100644 (file)
@@ -1206,6 +1206,297 @@ AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | sed 's/[[0-9]]\{1,\}$/?/' | sort],
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+dnl Test that sFlow samples packets correctly.
+AT_SETUP([ofproto-dpif - sFlow packet sampling])
+AT_CHECK([perl $srcdir/choose-port.pl], [0], [stdout])
+SFLOW_PORT=`cat stdout`
+OVS_VSWITCHD_START([set Bridge br0 fail-mode=standalone])
+
+ovs-appctl time/stop
+
+ADD_OF_PORTS([br0], 1, 2)
+ovs-vsctl \
+   set Interface br0 options:ifindex=1002 -- \
+   set Interface p1 options:ifindex=1004 -- \
+   set Interface p2 options:ifindex=1003 -- \
+   set Bridge br0 sflow=@sf -- \
+   --id=@sf create sflow targets=\"127.0.0.1:$SFLOW_PORT\" \
+     header=128 sampling=1 polling=1
+ON_EXIT([kill `cat test-sflow.pid`])
+AT_CHECK([test-sflow --detach --no-chdir --pidfile $SFLOW_PORT:127.0.0.1 > sflow.log])
+AT_CAPTURE_FILE([sflow.log])
+
+dnl open with ARP packets to seed the bridge-learning.  The output
+dnl ifIndex numbers should be reported predictably after that.
+dnl Since we set sampling=1 we should see all of these packets
+dnl reported. Sorting the output by data-source and seqNo makes
+dnl it deterministic. Ensuring that we send at least two packets
+dnl into each port means we get to check the seq nos are
+dnl incrementing correctly.
+
+ovs-appctl netdev-dummy/receive p1 'in_port(2),eth(src=50:54:00:00:00:05,dst=FF:FF:FF:FF:FF:FF),eth_type(0x0806),arp(sip=192.168.0.2,tip=192.168.0.1,op=1,sha=50:54:00:00:00:05,tha=00:00:00:00:00:00)'
+ovs-appctl netdev-dummy/receive p2 'in_port(1),eth(src=50:54:00:00:00:07,dst=FF:FF:FF:FF:FF:FF),eth_type(0x0806),arp(sip=192.168.0.1,tip=192.168.0.2,op=1,sha=50:54:00:00:00:07,tha=00:00:00:00:00:00)'
+ovs-appctl netdev-dummy/receive p1 'in_port(2),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'
+ovs-appctl netdev-dummy/receive p2 'in_port(1),eth(src=50:54:00:00:00:07,dst=50:54:00:00:00:05),eth_type(0x0800),ipv4(src=192.168.0.2,dst=192.168.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=0,code=0)'
+ovs-appctl netdev-dummy/receive p2 'in_port(1),eth(src=50:54:00:00:00:07,dst=50:54:00:00:00:05),eth_type(0x86dd),ipv6(src=fe80::1,dst=fe80::2,label=0,proto=10,tclass=0x70,hlimit=128,frag=no)'
+
+dnl sleep long enough to get more than one counter sample
+dnl from each datasource so we can check sequence numbers
+for i in `seq 1 30`; do
+    ovs-appctl time/warp 100
+done
+OVS_VSWITCHD_STOP
+ovs-appctl -t test-sflow exit
+
+AT_CHECK([[sort sflow.log | $EGREP 'HEADER|ERROR' | sed 's/ /\
+       /g']], [0], [dnl
+HEADER
+       dgramSeqNo=1
+       ds=127.0.0.1>0:1003
+       fsSeqNo=1
+       in_vlan=0
+       in_priority=0
+       out_vlan=0
+       out_priority=0
+       meanSkip=1
+       samplePool=1
+       dropEvents=0
+       in_ifindex=1003
+       in_format=0
+       out_ifindex=2
+       out_format=2
+       hdr_prot=1
+       pkt_len=64
+       stripped=4
+       hdr_len=60
+       hdr=FF-FF-FF-FF-FF-FF-50-54-00-00-00-07-08-06-00-01-08-00-06-04-00-01-50-54-00-00-00-07-C0-A8-00-01-00-00-00-00-00-00-C0-A8-00-02-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>0:1003
+       fsSeqNo=2
+       in_vlan=0
+       in_priority=0
+       out_vlan=0
+       out_priority=0
+       meanSkip=1
+       samplePool=2
+       dropEvents=0
+       in_ifindex=1003
+       in_format=0
+       out_ifindex=1004
+       out_format=0
+       hdr_prot=1
+       pkt_len=64
+       stripped=4
+       hdr_len=60
+       hdr=50-54-00-00-00-05-50-54-00-00-00-07-08-00-45-00-00-1C-00-00-00-00-40-01-F9-8D-C0-A8-00-02-C0-A8-00-01-00-00-FF-FF-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>0:1003
+       fsSeqNo=3
+       in_vlan=0
+       in_priority=0
+       out_vlan=0
+       out_priority=0
+       meanSkip=1
+       samplePool=3
+       dropEvents=0
+       in_ifindex=1003
+       in_format=0
+       out_ifindex=1004
+       out_format=0
+       hdr_prot=1
+       pkt_len=64
+       stripped=4
+       hdr_len=60
+       hdr=50-54-00-00-00-05-50-54-00-00-00-07-86-DD-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-00-00-00-00
+HEADER
+       dgramSeqNo=1
+       ds=127.0.0.1>0:1004
+       fsSeqNo=1
+       in_vlan=0
+       in_priority=0
+       out_vlan=0
+       out_priority=0
+       meanSkip=1
+       samplePool=1
+       dropEvents=0
+       in_ifindex=1004
+       in_format=0
+       out_ifindex=2
+       out_format=2
+       hdr_prot=1
+       pkt_len=64
+       stripped=4
+       hdr_len=60
+       hdr=FF-FF-FF-FF-FF-FF-50-54-00-00-00-05-08-06-00-01-08-00-06-04-00-01-50-54-00-00-00-05-C0-A8-00-02-00-00-00-00-00-00-C0-A8-00-01-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>0:1004
+       fsSeqNo=2
+       in_vlan=0
+       in_priority=0
+       out_vlan=0
+       out_priority=0
+       meanSkip=1
+       samplePool=2
+       dropEvents=0
+       in_ifindex=1004
+       in_format=0
+       out_ifindex=1003
+       out_format=0
+       hdr_prot=1
+       pkt_len=64
+       stripped=4
+       hdr_len=60
+       hdr=50-54-00-00-00-07-50-54-00-00-00-05-08-00-45-00-00-1C-00-00-00-00-40-01-F9-8D-C0-A8-00-01-C0-A8-00-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
+])
+
+AT_CHECK([[sort sflow.log | $EGREP 'IFCOUNTERS|ERROR' | head -6 | sed 's/ /\
+       /g']], [0], [dnl
+IFCOUNTERS
+       dgramSeqNo=2
+       ds=127.0.0.1>0:1002
+       csSeqNo=1
+       ifindex=1002
+       type=6
+       ifspeed=100000000
+       direction=0
+       status=3
+       in_octets=0
+       in_unicasts=0
+       in_multicasts=0
+       in_broadcasts=4294967295
+       in_discards=0
+       in_errors=0
+       in_unknownprotos=4294967295
+       out_octets=120
+       out_unicasts=2
+       out_multicasts=4294967295
+       out_broadcasts=4294967295
+       out_discards=0
+       out_errors=0
+       promiscuous=0
+IFCOUNTERS
+       dgramSeqNo=2
+       ds=127.0.0.1>0:1003
+       csSeqNo=1
+       ifindex=1003
+       type=6
+       ifspeed=100000000
+       direction=0
+       status=0
+       in_octets=98
+       in_unicasts=3
+       in_multicasts=0
+       in_broadcasts=4294967295
+       in_discards=0
+       in_errors=0
+       in_unknownprotos=4294967295
+       out_octets=120
+       out_unicasts=2
+       out_multicasts=4294967295
+       out_broadcasts=4294967295
+       out_discards=0
+       out_errors=0
+       promiscuous=0
+IFCOUNTERS
+       dgramSeqNo=2
+       ds=127.0.0.1>0:1004
+       csSeqNo=1
+       ifindex=1004
+       type=6
+       ifspeed=100000000
+       direction=0
+       status=0
+       in_octets=84
+       in_unicasts=2
+       in_multicasts=0
+       in_broadcasts=4294967295
+       in_discards=0
+       in_errors=0
+       in_unknownprotos=4294967295
+       out_octets=180
+       out_unicasts=3
+       out_multicasts=4294967295
+       out_broadcasts=4294967295
+       out_discards=0
+       out_errors=0
+       promiscuous=0
+IFCOUNTERS
+       dgramSeqNo=3
+       ds=127.0.0.1>0:1002
+       csSeqNo=2
+       ifindex=1002
+       type=6
+       ifspeed=100000000
+       direction=0
+       status=3
+       in_octets=0
+       in_unicasts=0
+       in_multicasts=0
+       in_broadcasts=4294967295
+       in_discards=0
+       in_errors=0
+       in_unknownprotos=4294967295
+       out_octets=120
+       out_unicasts=2
+       out_multicasts=4294967295
+       out_broadcasts=4294967295
+       out_discards=0
+       out_errors=0
+       promiscuous=0
+IFCOUNTERS
+       dgramSeqNo=3
+       ds=127.0.0.1>0:1003
+       csSeqNo=2
+       ifindex=1003
+       type=6
+       ifspeed=100000000
+       direction=0
+       status=0
+       in_octets=98
+       in_unicasts=3
+       in_multicasts=0
+       in_broadcasts=4294967295
+       in_discards=0
+       in_errors=0
+       in_unknownprotos=4294967295
+       out_octets=120
+       out_unicasts=2
+       out_multicasts=4294967295
+       out_broadcasts=4294967295
+       out_discards=0
+       out_errors=0
+       promiscuous=0
+IFCOUNTERS
+       dgramSeqNo=3
+       ds=127.0.0.1>0:1004
+       csSeqNo=2
+       ifindex=1004
+       type=6
+       ifspeed=100000000
+       direction=0
+       status=0
+       in_octets=84
+       in_unicasts=2
+       in_multicasts=0
+       in_broadcasts=4294967295
+       in_discards=0
+       in_errors=0
+       in_unknownprotos=4294967295
+       out_octets=180
+       out_unicasts=3
+       out_multicasts=4294967295
+       out_broadcasts=4294967295
+       out_discards=0
+       out_errors=0
+       promiscuous=0
+])
+AT_CLEANUP
+
+
+
 dnl Test that basic NetFlow reports flow statistics correctly:
 dnl - The initial packet of a flow are correctly accounted.
 dnl - Later packets within a flow are correctly accounted.
diff --git a/tests/test-sflow.c b/tests/test-sflow.c
new file mode 100644 (file)
index 0000000..3eb93c5
--- /dev/null
@@ -0,0 +1,618 @@
+/*
+ * Copyright (c) 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2013 InMon Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <setjmp.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "dynamic-string.h"
+#include "netflow.h"
+#include "ofpbuf.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "unixctl.h"
+#include "util.h"
+#include "vlog.h"
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+static unixctl_cb_func test_sflow_exit;
+
+/* Datagram. */
+#define SFLOW_VERSION_5 5
+#define SFLOW_MIN_LEN 36
+#define SFLOW_MAX_AGENTIP_STRLEN 64
+
+/* Sample tag numbers. */
+#define SFLOW_FLOW_SAMPLE 1
+#define SFLOW_COUNTERS_SAMPLE 2
+#define SFLOW_FLOW_SAMPLE_EXPANDED 3
+#define SFLOW_COUNTERS_SAMPLE_EXPANDED 4
+
+/* Structure element tag numbers. */
+#define SFLOW_TAG_CTR_IFCOUNTERS 1
+#define SFLOW_TAG_PKT_HEADER 1
+#define SFLOW_TAG_PKT_SWITCH 1001
+
+struct sflow_addr {
+    enum {
+        SFLOW_ADDRTYPE_undefined = 0,
+        SFLOW_ADDRTYPE_IP4,
+        SFLOW_ADDRTYPE_IP6
+    } type;
+
+    union {
+        ovs_be32 ip4;
+        ovs_be32 ip6[4];
+    } a;
+};
+
+struct sflow_xdr {
+    /* Exceptions. */
+    jmp_buf env;
+    int errline;
+
+    /* Cursor. */
+    ovs_be32 *datap;
+    uint32_t i;
+    uint32_t quads;
+
+    /* Agent. */
+    struct sflow_addr agentAddr;
+    char agentIPStr[SFLOW_MAX_AGENTIP_STRLEN];
+    uint32_t subAgentId;
+    uint32_t uptime_mS;
+
+    /* Datasource. */
+    uint32_t dsClass;
+    uint32_t dsIndex;
+
+    /* Sequence numbers. */
+    uint32_t dgramSeqNo;
+    uint32_t fsSeqNo;
+    uint32_t csSeqNo;
+
+    /* Structure offsets. */
+    struct {
+        uint32_t HEADER;
+        uint32_t SWITCH;
+        uint32_t IFCOUNTERS;
+    } offset;
+
+    /* Flow sample fields. */
+    uint32_t meanSkipCount;
+    uint32_t samplePool;
+    uint32_t dropEvents;
+    uint32_t inputPortFormat;
+    uint32_t inputPort;
+    uint32_t outputPortFormat;
+    uint32_t outputPort;
+};
+
+#define SFLOWXDR_try(x) ((x->errline = setjmp(x->env)) == 0)
+#define SFLOWXDR_throw(x) longjmp(x->env, __LINE__)
+#define SFLOWXDR_assert(x, t) if (!(t)) SFLOWXDR_throw(x)
+
+static void
+sflowxdr_init(struct sflow_xdr *x, void *buf, size_t len)
+{
+    x->datap = buf;
+    x->quads = len >> 2;
+}
+
+static uint32_t
+sflowxdr_next(struct sflow_xdr *x)
+{
+    return ntohl(x->datap[x->i++]);
+}
+
+static ovs_be32
+sflowxdr_next_n(struct sflow_xdr *x)
+{
+    return x->datap[x->i++];
+}
+
+static bool
+sflowxdr_more(const struct sflow_xdr *x, uint32_t q)
+{
+    return q + x->i <= x->quads;
+}
+
+static void
+sflowxdr_skip(struct sflow_xdr *x, uint32_t q)
+{
+    x->i += q;
+}
+
+static uint32_t
+sflowxdr_mark(const struct sflow_xdr *x, uint32_t q)
+{
+    return x->i + q;
+}
+
+static bool
+sflowxdr_mark_ok(const struct sflow_xdr *x, uint32_t m)
+{
+    return m == x->i;
+}
+
+static void
+sflowxdr_mark_unique(struct sflow_xdr *x, uint32_t *pi)
+{
+    if (*pi) {
+        SFLOWXDR_throw(x);
+    }
+    *pi = x->i;
+}
+
+static void
+sflowxdr_setc(struct sflow_xdr *x, uint32_t j)
+{
+    x->i = j;
+}
+
+static const char *
+sflowxdr_str(const struct sflow_xdr *x)
+{
+    return (const char *) (x->datap + x->i);
+}
+
+static uint64_t
+sflowxdr_next_int64(struct sflow_xdr *x)
+{
+    uint64_t scratch;
+    scratch = sflowxdr_next(x);
+    scratch <<= 32;
+    scratch += sflowxdr_next(x);
+    return scratch;
+}
+
+static void
+process_counter_sample(struct sflow_xdr *x)
+{
+    if (x->offset.IFCOUNTERS) {
+        sflowxdr_setc(x, x->offset.IFCOUNTERS);
+        printf("IFCOUNTERS");
+        printf(" dgramSeqNo=%"PRIu32, x->dgramSeqNo);
+        printf(" ds=%s>%"PRIu32":%"PRIu32,
+               x->agentIPStr, x->dsClass, x->dsIndex);
+        printf(" csSeqNo=%"PRIu32, x->csSeqNo);
+        printf(" ifindex=%"PRIu32, sflowxdr_next(x));
+        printf(" type=%"PRIu32, sflowxdr_next(x));
+        printf(" ifspeed=%"PRIu64, sflowxdr_next_int64(x));
+        printf(" direction=%"PRIu32, sflowxdr_next(x));
+        printf(" status=%"PRIu32, sflowxdr_next(x));
+        printf(" in_octets=%"PRIu64, sflowxdr_next_int64(x));
+        printf(" in_unicasts=%"PRIu32, sflowxdr_next(x));
+        printf(" in_multicasts=%"PRIu32, sflowxdr_next(x));
+        printf(" in_broadcasts=%"PRIu32, sflowxdr_next(x));
+        printf(" in_discards=%"PRIu32, sflowxdr_next(x));
+        printf(" in_errors=%"PRIu32, sflowxdr_next(x));
+        printf(" in_unknownprotos=%"PRIu32, sflowxdr_next(x));
+        printf(" out_octets=%"PRIu64, sflowxdr_next_int64(x));
+        printf(" out_unicasts=%"PRIu32, sflowxdr_next(x));
+        printf(" out_multicasts=%"PRIu32, sflowxdr_next(x));
+        printf(" out_broadcasts=%"PRIu32, sflowxdr_next(x));
+        printf(" out_discards=%"PRIu32, sflowxdr_next(x));
+        printf(" out_errors=%"PRIu32, sflowxdr_next(x));
+        printf(" promiscuous=%"PRIu32, sflowxdr_next(x));
+        printf("\n");
+    }
+}
+
+static char
+bin_to_hex(int hexit)
+{
+    return "0123456789ABCDEF"[hexit];
+}
+
+static int
+print_hex(const char *a, int len, char *buf, int bufLen)
+{
+    unsigned char nextByte;
+    int b = 0;
+    int i;
+
+    for (i = 0; i < len; i++) {
+        if (b > bufLen - 10) {
+            break;
+        }
+        nextByte = a[i];
+        buf[b++] = bin_to_hex(nextByte >> 4);
+        buf[b++] = bin_to_hex(nextByte & 0x0f);
+        if (i < len - 1) {
+            buf[b++] = '-';
+        }
+    }
+    buf[b] = '\0';
+    return b;
+}
+
+#define SFLOW_HEX_SCRATCH 1024
+
+static void
+process_flow_sample(struct sflow_xdr *x)
+{
+    if (x->offset.HEADER) {
+        uint32_t headerLen;
+        char scratch[SFLOW_HEX_SCRATCH];
+
+        printf("HEADER");
+        printf(" dgramSeqNo=%"PRIu32, x->dgramSeqNo);
+        printf(" ds=%s>%"PRIu32":%"PRIu32,
+               x->agentIPStr, x->dsClass, x->dsIndex);
+        printf(" fsSeqNo=%"PRIu32, x->fsSeqNo);
+
+        if (x->offset.SWITCH) {
+            sflowxdr_setc(x, x->offset.SWITCH);
+            printf(" in_vlan=%"PRIu32, sflowxdr_next(x));
+            printf(" in_priority=%"PRIu32, sflowxdr_next(x));
+            printf(" out_vlan=%"PRIu32, sflowxdr_next(x));
+            printf(" out_priority=%"PRIu32, sflowxdr_next(x));
+        }
+
+        sflowxdr_setc(x, x->offset.HEADER);
+        printf(" meanSkip=%"PRIu32, x->meanSkipCount);
+        printf(" samplePool=%"PRIu32, x->samplePool);
+        printf(" dropEvents=%"PRIu32, x->dropEvents);
+        printf(" in_ifindex=%"PRIu32, x->inputPort);
+        printf(" in_format=%"PRIu32, x->inputPortFormat);
+        printf(" out_ifindex=%"PRIu32, x->outputPort);
+        printf(" out_format=%"PRIu32, x->outputPortFormat);
+        printf(" hdr_prot=%"PRIu32, sflowxdr_next(x));
+        printf(" pkt_len=%"PRIu32, sflowxdr_next(x));
+        printf(" stripped=%"PRIu32, sflowxdr_next(x));
+        headerLen = sflowxdr_next(x);
+        printf(" hdr_len=%"PRIu32, headerLen);
+        print_hex(sflowxdr_str(x), headerLen, scratch, SFLOW_HEX_SCRATCH);
+        printf(" hdr=%s", scratch);
+        printf("\n");
+    }
+}
+
+static void
+process_datagram(struct sflow_xdr *x)
+{
+    uint32_t samples, s;
+
+    SFLOWXDR_assert(x, (sflowxdr_next(x) == SFLOW_VERSION_5));
+
+    /* Read the sFlow header. */
+    x->agentAddr.type = sflowxdr_next(x);
+    switch (x->agentAddr.type) {
+    case SFLOW_ADDRTYPE_IP4:
+        x->agentAddr.a.ip4 = sflowxdr_next_n(x);
+        break;
+
+    case SFLOW_ADDRTYPE_IP6:
+        x->agentAddr.a.ip6[0] = sflowxdr_next_n(x);
+        x->agentAddr.a.ip6[1] = sflowxdr_next_n(x);
+        x->agentAddr.a.ip6[2] = sflowxdr_next_n(x);
+        x->agentAddr.a.ip6[3] = sflowxdr_next_n(x);
+        break;
+
+    case SFLOW_ADDRTYPE_undefined:
+    default:
+        SFLOWXDR_throw(x);
+        break;
+    }
+    x->subAgentId = sflowxdr_next(x);
+    x->dgramSeqNo = sflowxdr_next(x);
+    x->uptime_mS = sflowxdr_next(x);
+
+    /* Store the agent address as a string. */
+    if (x->agentAddr.type == SFLOW_ADDRTYPE_IP6) {
+        snprintf(x->agentIPStr, SFLOW_MAX_AGENTIP_STRLEN,
+                 "%04x:%04x:%04x:%04x",
+                 x->agentAddr.a.ip6[0],
+                 x->agentAddr.a.ip6[1],
+                 x->agentAddr.a.ip6[2],
+                 x->agentAddr.a.ip6[3]);
+    } else {
+        snprintf(x->agentIPStr, SFLOW_MAX_AGENTIP_STRLEN,
+                 IP_FMT, IP_ARGS(x->agentAddr.a.ip4));
+    }
+
+    /* Array of flow/counter samples. */
+    samples = sflowxdr_next(x);
+    for (s = 0; s < samples; s++) {
+        uint32_t sType = sflowxdr_next(x);
+        uint32_t sQuads = sflowxdr_next(x) >> 2;
+        uint32_t sMark = sflowxdr_mark(x, sQuads);
+        SFLOWXDR_assert(x, sflowxdr_more(x, sQuads));
+
+        switch (sType) {
+        case SFLOW_COUNTERS_SAMPLE_EXPANDED:
+        case SFLOW_COUNTERS_SAMPLE:
+        {
+            uint32_t csElements, e;
+            uint32_t ceTag, ceQuads, ceMark, csEnd;
+
+            x->csSeqNo = sflowxdr_next(x);
+            if (sType == SFLOW_COUNTERS_SAMPLE_EXPANDED) {
+                x->dsClass = sflowxdr_next(x);
+                x->dsIndex = sflowxdr_next(x);
+            } else {
+                uint32_t dsCombined = sflowxdr_next(x);
+                x->dsClass = dsCombined >> 24;
+                x->dsIndex = dsCombined & 0x00FFFFFF;
+            }
+
+            csElements = sflowxdr_next(x);
+            for (e = 0; e < csElements; e++) {
+                SFLOWXDR_assert(x, sflowxdr_more(x,2));
+                ceTag = sflowxdr_next(x);
+                ceQuads = sflowxdr_next(x) >> 2;
+                ceMark = sflowxdr_mark(x, ceQuads);
+                SFLOWXDR_assert(x, sflowxdr_more(x,ceQuads));
+                /* Only care about selected structures.  Just record their
+                 * offsets here. We'll read the fields out later. */
+                switch (ceTag) {
+                case SFLOW_TAG_CTR_IFCOUNTERS:
+                    sflowxdr_mark_unique(x, &x->offset.IFCOUNTERS);
+                    break;
+
+                    /* Add others here... */
+                }
+
+                sflowxdr_skip(x, ceQuads);
+                SFLOWXDR_assert(x, sflowxdr_mark_ok(x, ceMark));
+            }
+
+            csEnd = sflowxdr_mark(x, 0);
+            process_counter_sample(x);
+            /* Make sure we pick up the decoding where we left off. */
+            sflowxdr_setc(x, csEnd);
+
+            /* Clear the offsets for the next sample. */
+            memset(&x->offset, 0, sizeof x->offset);
+        }
+        break;
+
+        case SFLOW_FLOW_SAMPLE:
+        case SFLOW_FLOW_SAMPLE_EXPANDED:
+        {
+            uint32_t fsElements, e;
+            uint32_t feTag, feQuads, feMark, fsEnd;
+            x->fsSeqNo = sflowxdr_next(x);
+            if (sType == SFLOW_FLOW_SAMPLE_EXPANDED) {
+                x->dsClass = sflowxdr_next(x);
+                x->dsIndex = sflowxdr_next(x);
+            } else {
+                uint32_t dsCombined = sflowxdr_next(x);
+                x->dsClass = dsCombined >> 24;
+                x->dsIndex = dsCombined & 0x00FFFFFF;
+            }
+            x->meanSkipCount = sflowxdr_next(x);
+            x->samplePool = sflowxdr_next(x);
+            x->dropEvents = sflowxdr_next(x);
+            if (sType == SFLOW_FLOW_SAMPLE_EXPANDED) {
+                x->inputPortFormat = sflowxdr_next(x);
+                x->inputPort = sflowxdr_next(x);
+                x->outputPortFormat = sflowxdr_next(x);
+                x->outputPort = sflowxdr_next(x);
+            } else {
+                uint32_t inp, outp;
+
+                inp = sflowxdr_next(x);
+                outp = sflowxdr_next(x);
+                x->inputPortFormat = inp >> 30;
+                x->inputPort = inp & 0x3fffffff;
+                x->outputPortFormat = outp >> 30;
+                x->outputPort = outp & 0x3fffffff;
+            }
+            fsElements = sflowxdr_next(x);
+            for (e = 0; e < fsElements; e++) {
+                SFLOWXDR_assert(x, sflowxdr_more(x,2));
+                feTag = sflowxdr_next(x);
+                feQuads = sflowxdr_next(x) >> 2;
+                feMark = sflowxdr_mark(x, feQuads);
+                SFLOWXDR_assert(x, sflowxdr_more(x,feQuads));
+                /* Only care about selected structures.  Just record their
+                 * offsets here. We'll read the fields out below. */
+                switch (feTag) {
+                case SFLOW_TAG_PKT_HEADER:
+                    sflowxdr_mark_unique(x, &x->offset.HEADER);
+                    break;
+
+                case SFLOW_TAG_PKT_SWITCH:
+                    sflowxdr_mark_unique(x, &x->offset.SWITCH);
+                    break;
+
+                    /* Add others here... */
+                }
+
+                sflowxdr_skip(x, feQuads);
+                SFLOWXDR_assert(x, sflowxdr_mark_ok(x, feMark));
+            }
+
+            fsEnd = sflowxdr_mark(x, 0);
+            process_flow_sample(x);
+            /* Make sure we pick up the decoding where we left off. */
+            sflowxdr_setc(x, fsEnd);
+
+            /* Clear the offsets for the next counter/flow sample. */
+            memset(&x->offset, 0, sizeof x->offset);
+        }
+        break;
+
+        default:
+            /* Skip other sample types. */
+            sflowxdr_skip(x, sQuads);
+        }
+        SFLOWXDR_assert(x, sflowxdr_mark_ok(x, sMark));
+    }
+}
+
+static void
+print_sflow(struct ofpbuf *buf)
+{
+    char *dgram_buf;
+    int dgram_len = buf->size;
+    struct sflow_xdr xdrDatagram;
+    struct sflow_xdr *x = &xdrDatagram;
+
+    memset(x, 0, sizeof *x);
+    if (SFLOWXDR_try(x)) {
+        SFLOWXDR_assert(x, (dgram_buf = ofpbuf_try_pull(buf, buf->size)));
+        sflowxdr_init(x, dgram_buf, dgram_len);
+        SFLOWXDR_assert(x, dgram_len >= SFLOW_MIN_LEN);
+        process_datagram(x);
+    } else {
+        // CATCH
+        printf("\n>>>>> ERROR in " __FILE__ " at line %u\n", x->errline);
+    }
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct unixctl_server *server;
+    enum { MAX_RECV = 1500 };
+    const char *target;
+    struct ofpbuf buf;
+    bool exiting = false;
+    int error;
+    int sock;
+
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    parse_options(argc, argv);
+
+    if (argc - optind != 1) {
+        ovs_fatal(0, "exactly one non-option argument required "
+                  "(use --help for help)");
+    }
+    target = argv[optind];
+
+    sock = inet_open_passive(SOCK_DGRAM, target, 0, NULL, 0);
+    if (sock < 0) {
+        ovs_fatal(0, "%s: failed to open (%s)", argv[1], strerror(-sock));
+    }
+
+    daemon_save_fd(STDOUT_FILENO);
+    daemonize_start();
+
+    error = unixctl_server_create(NULL, &server);
+    if (error) {
+        ovs_fatal(error, "failed to create unixctl server");
+    }
+    unixctl_command_register("exit", "", 0, 0, test_sflow_exit, &exiting);
+
+    daemonize_complete();
+
+    ofpbuf_init(&buf, MAX_RECV);
+    for (;;) {
+        int retval;
+
+        unixctl_server_run(server);
+
+        ofpbuf_clear(&buf);
+        do {
+            retval = read(sock, buf.data, buf.allocated);
+        } while (retval < 0 && errno == EINTR);
+        if (retval > 0) {
+            ofpbuf_put_uninit(&buf, retval);
+            print_sflow(&buf);
+            fflush(stdout);
+        }
+
+        if (exiting) {
+            break;
+        }
+
+        poll_fd_wait(sock, POLLIN);
+        unixctl_server_wait(server);
+        poll_block();
+    }
+
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        DAEMON_OPTION_ENUMS
+    };
+    static struct option long_options[] = {
+        {"verbose", optional_argument, NULL, 'v'},
+        {"help", no_argument, NULL, 'h'},
+        DAEMON_LONG_OPTIONS,
+        {NULL, 0, NULL, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+            DAEMON_OPTION_HANDLERS
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: sflow collector test utility\n"
+           "usage: %s [OPTIONS] PORT[:IP]\n"
+           "where PORT is the UDP port to listen on and IP is optionally\n"
+           "the IP address to listen on.\n",
+           program_name, program_name);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n");
+    exit(EXIT_SUCCESS);
+}
+
+static void
+test_sflow_exit(struct unixctl_conn *conn,
+                int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
+                void *exiting_)
+{
+    bool *exiting = exiting_;
+    *exiting = true;
+    unixctl_command_reply(conn, NULL);
+}