From 79c5987128cc624def39bcd8f360555b23d5fe56 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 19 Jan 2009 11:00:50 -0800 Subject: [PATCH] Add unit test for flow extraction. This tests only flow_extract() in lib/flow.c. We should really test the flow extraction in the kernel module also. --- tests/automake.mk | 6 ++ tests/flowgen.pl | 224 ++++++++++++++++++++++++++++++++++++++++++++ tests/test-flows.c | 76 +++++++++++++++ tests/test-flows.sh | 9 ++ 4 files changed, 315 insertions(+) create mode 100755 tests/flowgen.pl create mode 100644 tests/test-flows.c create mode 100755 tests/test-flows.sh diff --git a/tests/automake.mk b/tests/automake.mk index ecfd58e4..a4e945a9 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -1,3 +1,9 @@ +TESTS += tests/test-flows.sh +noinst_PROGRAMS += tests/test-flows +tests_test_flows_SOURCES = tests/test-flows.c +tests_test_flows_LDADD = lib/libopenflow.a +dist_check_SCRIPTS = tests/test-flows.sh tests/flowgen.pl + TESTS += tests/test-hmap noinst_PROGRAMS += tests/test-hmap tests_test_hmap_SOURCES = tests/test-hmap.c diff --git a/tests/flowgen.pl b/tests/flowgen.pl new file mode 100755 index 00000000..eb17b2af --- /dev/null +++ b/tests/flowgen.pl @@ -0,0 +1,224 @@ +#! /usr/bin/perl + +use strict; +use warnings; + +open(FLOWS, ">&=3");# or die "failed to open fd 3 for writing: $!\n"; +open(PACKETS, ">&=4");# or die "failed to open fd 4 for writing: $!\n"; + +# Print pcap file header. +print PACKETS pack('NnnNNNN', + 0xa1b2c3d4, # magic number + 2, # major version + 4, # minor version + 0, # time zone offset + 0, # time stamp accuracy + 1518, # snaplen + 1); # Ethernet + +output(DL_HEADER => '802.2'); + +for my $dl_header qw(802.2+SNAP Ethernet) { + my %a = (DL_HEADER => $dl_header); + for my $dl_vlan qw(none zero nonzero) { + my %b = (%a, DL_VLAN => $dl_vlan); + + # Non-IP case. + output(%b, DL_TYPE => 'non-ip'); + + for my $ip_options qw(no yes) { + my %c = (%b, DL_TYPE => 'ip', IP_OPTIONS => $ip_options); + for my $ip_fragment qw(no first middle last) { + my %d = (%c, IP_FRAGMENT => $ip_fragment); + for my $tp_proto qw(TCP TCP+options UDP ICMP other) { + output(%d, TP_PROTO => $tp_proto); + } + } + } + } +} + +sub output { + my (%attrs) = @_; + + # Compose flow. + my (%flow); + $flow{DL_SRC} = "00:02:e3:0f:80:a4"; + $flow{DL_DST} = "00:1a:92:40:ac:05"; + $flow{NW_PROTO} = 0; + $flow{NW_SRC} = '0.0.0.0'; + $flow{NW_DST} = '0.0.0.0'; + $flow{TP_SRC} = 0; + $flow{TP_DST} = 0; + if (defined($attrs{DL_VLAN})) { + my (%vlan_map) = ('none' => 0xffff, + 'zero' => 0, + 'nonzero' => 0x0123); + $flow{DL_VLAN} = $vlan_map{$attrs{DL_VLAN}}; + } else { + $flow{DL_VLAN} = 0xffff; # OFP_VLAN_NONE + } + if ($attrs{DL_HEADER} eq '802.2') { + $flow{DL_TYPE} = 0x5ff; # OFP_DL_TYPE_NOT_ETH_TYPE + } elsif ($attrs{DL_TYPE} eq 'ip') { + $flow{DL_TYPE} = 0x0800; # ETH_TYPE_IP + $flow{NW_SRC} = '10.0.2.15'; + $flow{NW_DST} = '192.168.1.20'; + if ($attrs{TP_PROTO} eq 'other') { + $flow{NW_PROTO} = 42; + } elsif ($attrs{TP_PROTO} eq 'TCP' || + $attrs{TP_PROTO} eq 'TCP+options') { + $flow{NW_PROTO} = 6; # IP_TYPE_TCP + $flow{TP_SRC} = 6667; + $flow{TP_DST} = 9998; + } elsif ($attrs{TP_PROTO} eq 'UDP') { + $flow{NW_PROTO} = 17; # IP_TYPE_UDP + $flow{TP_SRC} = 1112; + $flow{TP_DST} = 2223; + } elsif ($attrs{TP_PROTO} eq 'ICMP') { + $flow{NW_PROTO} = 1; # IP_TYPE_ICMP + $flow{TP_SRC} = 8; # echo request + $flow{TP_DST} = 0; # code + } else { + die; + } + if ($attrs{IP_FRAGMENT} ne 'no') { + $flow{TP_SRC} = $flow{TP_DST} = 0; + } + } elsif ($attrs{DL_TYPE} eq 'non-ip') { + $flow{DL_TYPE} = 0x5678; + } else { + die; + } + + # Compose packet. + my $packet = ''; + $packet .= pack_ethaddr($flow{DL_DST}); + $packet .= pack_ethaddr($flow{DL_SRC}); + $packet .= pack('n', 0) if $attrs{DL_HEADER} =~ /^802.2/; + if ($attrs{DL_HEADER} eq '802.2') { + $packet .= pack('CCC', 0x42, 0x42, 0x03); # LLC for 802.1D STP. + } else { + if ($attrs{DL_HEADER} eq '802.2+SNAP') { + $packet .= pack('CCC', 0xaa, 0xaa, 0x03); # LLC for SNAP. + $packet .= pack('CCC', 0, 0, 0); # SNAP OUI. + } + if ($attrs{DL_VLAN} ne 'none') { + $packet .= pack('nn', 0x8100, $flow{DL_VLAN}); + } + $packet .= pack('n', $flow{DL_TYPE}); + if ($attrs{DL_TYPE} eq 'ip') { + my $ip = pack('CCnnnCCnNN', + (4 << 4) | 5, # version, hdrlen + 0, # type of service + 0, # total length (filled in later) + 65432, # id + 0, # frag offset + 64, # ttl + $flow{NW_PROTO}, # protocol + 0, # checksum + 0x0a00020f, # source + 0xc0a80114); # dest + if ($attrs{IP_OPTIONS} eq 'yes') { + substr($ip, 0, 1) = pack('C', (4 << 4) | 8); + $ip .= pack('CCnnnCCCx', + 130, # type + 11, # length + 0x6bc5, # top secret + 0xabcd, + 0x1234, + 1, + 2, + 3); + } + if ($attrs{IP_FRAGMENT} ne 'no') { + my (%frag_map) = ('first' => 0x2000, # more frags, ofs 0 + 'middle' => 0x2111, # more frags, ofs 0x888 + 'last' => 0x0222); # last frag, ofs 0x1110 + substr($ip, 6, 2) + = pack('n', $frag_map{$attrs{IP_FRAGMENT}}); + } + + if ($attrs{TP_PROTO} =~ '^TCP') { + my $tcp = pack('nnNNnnnn', + $flow{TP_SRC}, # source port + $flow{TP_DST}, # dest port + 87123455, # seqno + 712378912, # ackno + (5 << 12) | 0x02 | 0x10, # hdrlen, SYN, ACK + 5823, # window size + 18923, # checksum + 12893); # urgent pointer + if ($attrs{TP_PROTO} eq 'TCP+options') { + substr($tcp, 12, 2) = pack('n', (6 << 12) | 0x02 | 0x10); + $tcp .= pack('CCn', 2, 4, 1975); # MSS option + } + $tcp .= 'payload'; + $ip .= $tcp; + } elsif ($attrs{TP_PROTO} eq 'UDP') { + my $len = 15; + my $udp = pack('nnnn', $flow{TP_SRC}, $flow{TP_DST}, $len, 0); + $udp .= chr($len) while length($udp) < $len; + $ip .= $udp; + } elsif ($attrs{TP_PROTO} eq 'ICMP') { + $ip .= pack('CCnnn', + 8, # echo request + 0, # code + 0, # checksum + 736, # identifier + 931); # sequence number + } elsif ($attrs{TP_PROTO} eq 'other') { + $ip .= 'other header'; + } else { + die; + } + + substr($ip, 2, 2) = pack('n', length($ip)); + $packet .= $ip; + } + } + substr($packet, 12, 2) = pack('n', length($packet)) + if $attrs{DL_HEADER} =~ /^802.2/; + + print join(' ', map("$_=$attrs{$_}", keys(%attrs))), "\n"; + print join(' ', map("$_=$flow{$_}", keys(%flow))), "\n"; + print "\n"; + + print FLOWS pack('Nn', + 0, # wildcards + 0); # in_port + print FLOWS pack_ethaddr($flow{DL_SRC}); + print FLOWS pack_ethaddr($flow{DL_DST}); + print FLOWS pack('nnCxNNnn', + $flow{DL_VLAN}, + $flow{DL_TYPE}, + $flow{NW_PROTO}, + inet_aton($flow{NW_SRC}), + inet_aton($flow{NW_DST}), + $flow{TP_SRC}, + $flow{TP_DST}); + + print PACKETS pack('NNNN', + 0, # timestamp seconds + 0, # timestamp microseconds + length($packet), # bytes saved + length($packet)), # total length + $packet; +} + +sub pack_ethaddr { + local ($_) = @_; + my $xx = '([0-9a-fA-F][0-9a-fA-F])'; + my (@octets) = /$xx:$xx:$xx:$xx:$xx:$xx/; + @octets == 6 or die $_; + my ($out) = ''; + $out .= pack('C', hex($_)) foreach @octets; + return $out; +} + +sub inet_aton { + local ($_) = @_; + my ($a, $b, $c, $d) = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; + defined $d or die $_; + return ($a << 24) | ($b << 16) | ($c << 8) | $d; +} diff --git a/tests/test-flows.c b/tests/test-flows.c new file mode 100644 index 00000000..fb4026fb --- /dev/null +++ b/tests/test-flows.c @@ -0,0 +1,76 @@ +#include +#include "flow.h" +#include +#include +#include +#include "openflow/openflow.h" +#include "timeval.h" +#include "ofpbuf.h" +#include "ofp-print.h" +#include "pcap.h" +#include "util.h" +#include "vlog.h" + +#undef NDEBUG +#include + +int +main(int argc UNUSED, char *argv[]) +{ + struct ofp_match expected_match; + FILE *flows, *pcap; + int retval; + int n = 0, errors = 0; + + set_program_name(argv[0]); + time_init(); + vlog_init(); + + flows = stdin; + pcap = fdopen(3, "rb"); + if (!pcap) { + ofp_fatal(errno, "failed to open fd 3 for reading"); + } + + retval = pcap_read_header(pcap); + if (retval) { + ofp_fatal(retval > 0 ? retval : 0, "reading pcap header failed"); + } + + while (fread(&expected_match, sizeof expected_match, 1, flows)) { + struct ofpbuf *packet; + struct ofp_match extracted_match; + struct flow flow; + + n++; + + retval = pcap_read(pcap, &packet); + if (retval == EOF) { + ofp_fatal(0, "unexpected end of file reading pcap file"); + } else if (retval) { + ofp_fatal(retval, "error reading pcap file"); + } + + flow_extract(packet, 0, &flow); + flow_fill_match(&extracted_match, &flow, 0); + + if (memcmp(&expected_match, &extracted_match, sizeof expected_match)) { + char *exp_s = ofp_match_to_string(&expected_match, 2); + char *got_s = ofp_match_to_string(&extracted_match, 2); + errors++; + printf("mismatch on packet #%d (1-based).\n", n); + printf("Packet:\n"); + ofp_print_packet(stdout, packet->data, packet->size, packet->size); + printf("Expected flow:\n%s\n", exp_s); + printf("Actually extracted flow:\n%s\n", got_s); + printf("\n"); + free(exp_s); + free(got_s); + } + + ofpbuf_delete(packet); + } + printf("checked %d packets, %d errors\n", n, errors); + return errors != 0; +} + diff --git a/tests/test-flows.sh b/tests/test-flows.sh new file mode 100755 index 00000000..0d38ad78 --- /dev/null +++ b/tests/test-flows.sh @@ -0,0 +1,9 @@ +#! /bin/sh -e +srcdir=`cd $srcdir && pwd` +trap 'rm -f flows$$ pcap$$ out$$' 0 1 2 13 15 +cd tests +"$srcdir"/tests/flowgen.pl >/dev/null 3>flows$$ 4>pcap$$ +./test-flows out$$ || true +diff -u - out$$ <