From: Ben Pfaff Date: Mon, 19 Dec 2011 22:46:16 +0000 (-0800) Subject: netflow: Add basic unit tests. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=17f7f7e02cfd44dc7755fbebcfb942c07f937858;p=openvswitch netflow: Add basic unit tests. These tests would have caught the flow statistics bug introduced by commit 501f8d1fd75 (ofproto-dpif: Batch interacting with the dpif on flow miss operations.) Signed-off-by: Ben Pfaff --- diff --git a/tests/automake.mk b/tests/automake.mk index 8649b804..9ae4c5cd 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -261,6 +261,10 @@ noinst_PROGRAMS += tests/test-stp tests_test_stp_SOURCES = tests/test-stp.c tests_test_stp_LDADD = lib/libopenvswitch.a +noinst_PROGRAMS += tests/test-netflow +tests_test_netflow_SOURCES = tests/test-netflow.c +tests_test_netflow_LDADD = lib/libopenvswitch.a + noinst_PROGRAMS += tests/test-unix-socket tests_test_unix_socket_SOURCES = tests/test-unix-socket.c tests_test_unix_socket_LDADD = lib/libopenvswitch.a diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index d733f9b5..55d1500d 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -758,3 +758,131 @@ AT_CHECK_UNQUOTED([ovs-appctl fdb/show br1 | sed 's/[[0-9]]$/?/'], [0], [dnl OVS_VSWITCHD_STOP 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. +dnl - Flow actions changing (in this case, due to MAC learning) +dnl cause a record to be sent. +AT_SETUP([ofproto-dpif - NetFlow flow expiration]) + +AT_SKIP_IF([test "x$RANDOM" = x]) +NETFLOW_PORT=`expr 32767 + \( $RANDOM % 32767 \)` + +OVS_VSWITCHD_START( + [set Bridge br0 fail-mode=standalone -- \ + add-port br0 p1 -- set Interface p1 type=dummy -- \ + add-port br0 p2 -- set Interface p2 type=dummy -- \ + set Bridge br0 netflow=@nf -- \ + --id=@nf create NetFlow targets=\"127.0.0.1:$NETFLOW_PORT\" \ + engine_id=1 engine_type=2 active_timeout=30 \ + add-id-to-interface=false], [<0> +]) + +AT_CHECK([test-netflow --detach --pidfile $NETFLOW_PORT:127.0.0.1 > netflow.log])AT_CAPTURE_FILE([netflow.log]) + +for delay in 1000 30000; do + 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 time/warp $delay +done + +OVS_VSWITCHD_STOP +ovs-appctl -t test-netflow exit + +AT_CHECK([[sed -e 's/, uptime [0-9]*// +s/, now [0-9.]*// +s/time \([0-9]*\)\.\.\.\1/time / +s/time [0-9]*\.\.\.[0-9]*/time / +' netflow.log]], [0], + [header: v5, seq 0, engine 2,1 +rec: 192.168.0.1 > 192.168.0.2, if 1 > 65535, 1 pkts, 60 bytes, ICMP 8:0, time + +header: v5, seq 1, engine 2,1 +rec: 192.168.0.2 > 192.168.0.1, if 2 > 1, 2 pkts, 120 bytes, ICMP 0:0, time +rec: 192.168.0.1 > 192.168.0.2, if 1 > 2, 1 pkts, 60 bytes, ICMP 8:0, time +]) +AT_CLEANUP + +dnl Test that basic NetFlow reports active expirations correctly. +AT_SETUP([ofproto-dpif - NetFlow active expiration]) + +AT_SKIP_IF([test "x$RANDOM" = x]) +NETFLOW_PORT=`expr 32767 + \( $RANDOM % 32767 \)` + +OVS_VSWITCHD_START( + [set Bridge br0 fail-mode=standalone -- \ + add-port br0 p1 -- set Interface p1 type=dummy -- \ + add-port br0 p2 -- set Interface p2 type=dummy -- \ + set Bridge br0 netflow=@nf -- \ + --id=@nf create NetFlow targets=\"127.0.0.1:$NETFLOW_PORT\" \ + engine_id=1 engine_type=2 active_timeout=10 \ + add-id-to-interface=false], [<0> +]) + +AT_CHECK([test-netflow --detach --pidfile $NETFLOW_PORT:127.0.0.1 > netflow.log])AT_CAPTURE_FILE([netflow.log]) + +n=1 +while test $n -le 60; do + n=`expr $n + 1` + + 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=6,tos=0,ttl=64,frag=no),tcp(src=1234,dst=80)' + 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=6,tos=0,ttl=64,frag=no),tcp(src=80,dst=1234)' + + ovs-appctl time/warp 1000 +done + +ovs-appctl time/warp 10000 + +OVS_VSWITCHD_STOP +ovs-appctl -t test-netflow exit + +# Count the number of reported packets: +# - From source to destination before MAC learning kicks in (just one). +# - From source to destination after that. +# - From destination to source. +n_learn=0 +n_in=0 +n_out=0 +n_other=0 +n_recs=0 +none=0 +while read line; do + pkts=`echo "$line" | sed 's/.*, \([[0-9]]*\) pkts,.*/\1/'` + case $pkts in + [[0-9]]*) ;; + *) continue ;; + esac + + case $line in + "rec: 192.168.0.1 > 192.168.0.2, if 1 > 65535, "*" pkts, "*" bytes, TCP 1234 > 80, time "*) + counter=n_learn + ;; + "rec: 192.168.0.1 > 192.168.0.2, if 1 > 2, "*" pkts, "*" bytes, TCP 1234 > 80, time "*) + counter=n_in + ;; + "rec: 192.168.0.2 > 192.168.0.1, if 2 > 1, "*" pkts, "*" bytes, TCP 80 > 1234, time "*) + counter=n_out + ;; + *) + counter=n_other + ;; + esac + eval $counter=\`expr \$$counter + \$pkts\` + n_recs=`expr $n_recs + 1` +done < netflow.log + +# There should be exactly 1 MAC learning packet, +# exactly 59 other packets in that direction, +# and exactly 60 packets in the other direction. +AT_CHECK([echo $n_learn $n_in $n_out $n_other], [0], [1 59 60 0 +]) + +# There should be 1 expiration for MAC learning, +# at least 5 active and a final expiration in one direction, +# and at least 5 active and a final expiration in the other direction. +echo $n_recs +AT_CHECK([test $n_recs -ge 13]) + +AT_CLEANUP diff --git a/tests/test-netflow.c b/tests/test-netflow.c new file mode 100644 index 00000000..5a88ada3 --- /dev/null +++ b/tests/test-netflow.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2011 Nicira Networks. + * + * 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 + +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "daemon.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_netflow_exit; + +static void +print_netflow(struct ofpbuf *buf) +{ + const struct netflow_v5_header *hdr; + int i; + + hdr = ofpbuf_try_pull(buf, sizeof *hdr); + if (!hdr) { + printf("truncated NetFlow packet header\n"); + return; + } + printf("header: v%"PRIu16", " + "uptime %"PRIu32", " + "now %"PRIu32".%09"PRIu32", " + "seq %"PRIu32", " + "engine %"PRIu8",%"PRIu8, + ntohs(hdr->version), + ntohl(hdr->sysuptime), + ntohl(hdr->unix_secs), ntohl(hdr->unix_nsecs), + ntohl(hdr->flow_seq), + hdr->engine_type, hdr->engine_id); + if (hdr->sampling_interval != htons(0)) { + printf(", interval %"PRIu16, ntohs(hdr->sampling_interval)); + } + putchar('\n'); + + for (i = 0; i < ntohs(hdr->count); i++) { + struct netflow_v5_record *rec; + + rec = ofpbuf_try_pull(buf, sizeof *rec); + if (!rec) { + printf("truncated NetFlow records\n"); + return; + } + + printf("rec: "IP_FMT" > "IP_FMT, + IP_ARGS(&rec->src_addr), IP_ARGS(&rec->dst_addr)); + + printf(", if %"PRIu16" > %"PRIu16, + ntohs(rec->input), ntohs(rec->output)); + + printf(", %"PRIu32" pkts, %"PRIu32" bytes", + ntohl(rec->packet_count), ntohl(rec->byte_count)); + + switch (rec->ip_proto) { + case IPPROTO_TCP: + printf(", TCP %"PRIu16" > %"PRIu16, + ntohs(rec->src_port), ntohs(rec->dst_port)); + if (rec->tcp_flags) { + putchar(' '); + if (rec->tcp_flags & TCP_SYN) { + putchar('S'); + } + if (rec->tcp_flags & TCP_FIN) { + putchar('F'); + } + if (rec->tcp_flags & TCP_PSH) { + putchar('P'); + } + if (rec->tcp_flags & TCP_RST) { + putchar('R'); + } + if (rec->tcp_flags & TCP_URG) { + putchar('U'); + } + if (rec->tcp_flags & TCP_ACK) { + putchar('.'); + } + if (rec->tcp_flags & 0x40) { + printf("[40]"); + } + if (rec->tcp_flags & 0x80) { + printf("[80]"); + } + } + break; + + case IPPROTO_UDP: + printf(", UDP %"PRIu16" > %"PRIu16, + ntohs(rec->src_port), ntohs(rec->dst_port)); + break; + + case IPPROTO_ICMP: + printf(", ICMP %"PRIu16":%"PRIu16, + ntohs(rec->dst_port) >> 8, + ntohs(rec->dst_port) & 0xff); + if (rec->src_port != htons(0)) { + printf(", src_port=%"PRIu16, ntohs(rec->src_port)); + } + break; + + default: + printf(", proto %"PRIu8, rec->ip_proto); + break; + } + + if (rec->ip_proto != IPPROTO_TCP && rec->tcp_flags != 0) { + printf(", flags %"PRIx8, rec->tcp_flags); + } + + if (rec->ip_proto != IPPROTO_TCP && + rec->ip_proto != IPPROTO_UDP && + rec->ip_proto != IPPROTO_ICMP) { + if (rec->src_port != htons(0)) { + printf(", src_port %"PRIu16, ntohs(rec->src_port)); + } + if (rec->dst_port != htons(0)) { + printf(", dst_port %"PRIu16, ntohs(rec->dst_port)); + } + } + + if (rec->ip_tos) { + printf(", TOS %"PRIx8, rec->ip_tos); + } + + printf(", time %"PRIu32"...%"PRIu32, + ntohl(rec->init_time), ntohl(rec->used_time)); + + if (rec->nexthop != htonl(0)) { + printf(", nexthop "IP_FMT, IP_ARGS(&rec->nexthop)); + } + if (rec->src_as != htons(0) || rec->dst_as != htons(0)) { + printf(", AS %"PRIu16" > %"PRIu16, + ntohs(rec->src_as), ntohs(rec->dst_as)); + } + if (rec->src_mask != 0 || rec->dst_mask != 0) { + printf(", mask %"PRIu8" > %"PRIu8, rec->src_mask, rec->dst_mask); + } + if (rec->pad1) { + printf(", pad1 %"PRIu8, rec->pad1); + } + if (rec->pad[0] || rec->pad[1]) { + printf(", pad %"PRIu8", %"PRIu8, rec->pad[0], rec->pad[1]); + } + putchar('\n'); + } + + if (buf->size) { + printf("%zu extra bytes after last record\n", buf->size); + } +} + +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; + int fd; + int n; + + 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); + if (sock < 0) { + ovs_fatal(0, "%s: failed to open (%s)", argv[1], strerror(-sock)); + } + + /* Daemonization will close stdout but we really want to keep it, so make a + * copy. */ + fd = dup(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_netflow_exit, &exiting); + + daemonize_complete(); + + /* Now get stdout back. */ + dup2(fd, STDOUT_FILENO); + + ofpbuf_init(&buf, MAX_RECV); + n = 0; + 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); + if (n++ > 0) { + putchar('\n'); + } + print_netflow(&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: netflow 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_netflow_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, 200, ""); +}