Add unit test for flow extraction.
authorBen Pfaff <blp@nicira.com>
Mon, 19 Jan 2009 19:00:50 +0000 (11:00 -0800)
committerBen Pfaff <blp@nicira.com>
Fri, 23 Jan 2009 22:05:43 +0000 (14:05 -0800)
This tests only flow_extract() in lib/flow.c.  We should really test
the flow extraction in the kernel module also.

tests/automake.mk
tests/flowgen.pl [new file with mode: 0755]
tests/test-flows.c [new file with mode: 0644]
tests/test-flows.sh [new file with mode: 0755]

index ecfd58e4493bc629afa6f3641fa5b55a60025051..a4e945a9f4b19db6ce9fa79c79c34c2e8da3b1c1 100644 (file)
@@ -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 (executable)
index 0000000..eb17b2a
--- /dev/null
@@ -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 (file)
index 0000000..fb4026f
--- /dev/null
@@ -0,0 +1,76 @@
+#include <config.h>
+#include "flow.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#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 <assert.h>
+
+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 (executable)
index 0000000..0d38ad7
--- /dev/null
@@ -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 <flows$$ 3<pcap$$ >out$$ || true
+diff -u - out$$ <<EOF
+checked 247 packets, 0 errors
+EOF