--- /dev/null
+# $OpenBSD: Makefile,v 1.1 2016/07/19 17:04:19 reyk Exp $
+
+# The following ports must be installed for the regression tests:
+# p5-Net-Pcap Perl interface for libpcap
+# p5-NetPacket Perl interface for packet encoding/decoding
+# p5-Crypt-Random To fill payloads with weak random data
+#
+# Check wether all required perl packages are installed. If some
+# are missing print a warning and skip the tests, but do not fail.
+
+PERL_REQUIRE != perl -Mstrict -Mwarnings -e ' \
+ eval { require NetPacket::Ethernet } or print $@; \
+ eval { require Net::Pcap } or print $@; \
+ eval { require Crypt::Random } or print $@; \
+'
+.if ! empty (PERL_REQUIRE)
+regress:
+ @echo "${PERL_REQUIRE}"
+ @echo install these perl packages for additional tests
+.endif
+
+# Automatically generate regress targets from test cases in directory.
+
+ARGS != cd ${.CURDIR} && ls args-*.pm
+TARGETS ?= ${ARGS}
+REGRESS_TARGETS = ${TARGETS:S/^/run-regress-/}
+CLEANFILES += *.log ktrace.out stamp-*
+CLEANFILES += *.h *.ph
+
+SRC_PATH = ${.CURDIR}/../../../usr.sbin/switchd
+SYS_PATH = ${.CURDIR}/../../../sys
+OFP_HEADERS = ofp.h ofp10.h
+OFP_PERLHEADERS = ${OFP_HEADERS:S/.h/.ph/}
+
+# Set variables so that make runs with and without obj directory.
+# Only do that if necessary to keep visible output short.
+
+.if ${.CURDIR} == ${.OBJDIR}
+PERLINC =
+PERLPATH =
+.else
+PERLINC = -I${.CURDIR}
+PERLPATH = ${.CURDIR}/
+.endif
+
+# The arg tests take a perl hash with arguments controlling the
+# test parameters. Generally they consist of switch, switchd.
+
+.for a in ${ARGS}
+run-regress-$a: $a
+ @echo '\n======== $@ ========'
+ time SUDO=${SUDO} KTRACE=${KTRACE} SWITCHD=${SWITCHD} perl ${PERLINC} ${PERLPATH}run.pl ${PERLPATH}$a
+.endfor
+
+${OFP_HEADERS}:
+ @-mkdir -p ${.OBJDIR}/net
+ # XXX headers can be in two different locations
+ @-test ${SRC_PATH}/$@ && cp ${SRC_PATH}/$@ ${.OBJDIR}
+ @-test ${SYS_PATH}/$@ && cp ${SYS_PATH}/$@ ${.OBJDIR}/net
+
+.SUFFIXES: .h .ph
+.h.ph:
+ @h2ph -d ${.OBJDIR} $<
+
+${REGRESS_TARGETS:M*}: ${OFP_PERLHEADERS}
+
+# make perl syntax check for all args files
+
+.PHONY: syntax
+
+syntax: stamp-syntax
+
+stamp-syntax: ${ARGS}
+.for a in ${ARGS}
+ @perl -c ${PERLPATH}$a
+.endfor
+ @date >$@
+
+.include <bsd.regress.mk>
--- /dev/null
+#
+# NetPacket::OFP - Decode and encode OpenFlow packets.
+#
+
+package NetPacket::OFP;
+
+#
+# Copyright (c) 2016 Reyk Floeter <reyk@openbsd.org>.
+#
+# This package is free software and is provided "as is" without express
+# or implied warranty. It may be used, redistributed and/or modified
+# under the terms of the Perl Artistic License (see
+# http://www.perl.com/perl/misc/Artistic.html)
+#
+
+use strict;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+use NetPacket;
+
+my $myclass;
+BEGIN {
+ $myclass = __PACKAGE__;
+ $VERSION = "0.01";
+}
+sub Version () { "$myclass v$VERSION" }
+
+BEGIN {
+ @ISA = qw(Exporter NetPacket);
+
+ @EXPORT = qw();
+
+ @EXPORT_OK = qw(ofp_strip);
+
+ %EXPORT_TAGS = (
+ ALL => [@EXPORT, @EXPORT_OK],
+ strip => [qw(ofp_strip)],
+ );
+}
+
+#
+# Decode the packet
+#
+
+sub decode {
+ my $class = shift;
+ my($pkt, $parent, @rest) = @_;
+ my $self = {};
+
+ # Class fields
+ $self->{_parent} = $parent;
+ $self->{_frame} = $pkt;
+
+ $self->{version} = 1;
+ $self->{type} = 0;
+ $self->{length} = 0;
+ $self->{xid} = 0;
+ $self->{data} = '';
+
+ # Decode OpenFlow packet
+ if (defined($pkt)) {
+ ($self->{version}, $self->{type}, $self->{length},
+ $self->{xid}, $self->{data}) = unpack("CCnNa*", $pkt);
+ }
+
+ # Return a blessed object
+ bless($self, $class);
+
+ return ($self);
+}
+
+#
+# Strip header from packet and return the data contained in it
+#
+
+undef &udp_strip;
+*ofp_strip = \&strip;
+
+sub strip {
+ my ($pkt, @rest) = @_;
+ my $ofp_obj = decode($pkt);
+ return ($ofp_obj->data);
+}
+
+#
+# Encode a packet
+#
+
+sub encode {
+ my $class = shift;
+ my $self = shift;
+ my $pkt = '';
+
+ $self->{length} = 8;
+
+ $pkt = pack("CCnN", $self->{version}, $self->{type},
+ $self->{length}, $self->{xid});
+
+ if ($self->{version} == 1) {
+ # PACKET_IN
+ if ($self->{type} == 10) {
+ $self->{length} += length($self->{data});
+ $pkt = pack("CCnNNnnCCa*",
+ $self->{version}, $self->{type},
+ $self->{length}, $self->{xid}, $self->{buffer_id},
+ $self->{length} - 8,
+ $self->{port}, 0, 0, $self->{data});
+ }
+ }
+
+ return ($pkt);
+}
+
+#
+# Module initialisation
+#
+
+1;
+
+# autoloaded methods go after the END token (&& pod) below
+
+__END__
+
+=head1 NAME
+
+C<NetPacket::OFP> - Assemble and disassemble OpenFlow packets.
+
+=head1 SYNOPSIS
+
+ use NetPacket::OFP;
+
+ $ofp_obj = NetPacket::OFP->decode($raw_pkt);
+ $ofp_pkt = NetPacket::OFP->encode($ofp_obj);
+ $ofp_data = NetPacket::OFP::strip($raw_pkt);
+
+=head1 DESCRIPTION
+
+C<NetPacket::OFP> provides a set of routines for assembling and
+disassembling packets using OpenFlow.
+
+=head2 Methods
+
+=over
+
+=item C<NetPacket::OFP-E<gt>decode([RAW PACKET])>
+
+Decode the raw packet data given and return an object containing
+instance data. This method will quite happily decode garbage input.
+It is the responsibility of the programmer to ensure valid packet data
+is passed to this method.
+
+=item C<NetPacket::OFP-E<gt>encode($ofp_obj)>
+
+Return a OFP packet encoded with the instance data specified.
+
+=back
+
+=head2 Functions
+
+=over
+
+=item C<NetPacket::OFP::strip([RAW PACKET])>
+
+Return the encapsulated data (or payload) contained in the OpenFlow
+packet. This data is suitable to be used as input for other
+C<NetPacket::*> modules.
+
+This function is equivalent to creating an object using the
+C<decode()> constructor and returning the C<data> field of that
+object.
+
+=back
+
+=head2 Instance data
+
+The instance data for the C<NetPacket::OFP> object consists of
+the following fields.
+
+=over
+
+=item version
+
+The OpenFlow version.
+
+=item type
+
+The message type.
+
+=item length
+
+The total message length.
+
+=item xid
+
+The transaction Id.
+
+=item data
+
+The encapsulated data (payload) for this packet.
+
+=back
+
+=head2 Exports
+
+=over
+
+=item default
+
+none
+
+=item exportable
+
+ofp_strip
+
+=item tags
+
+The following tags group together related exportable items.
+
+=over
+
+=item C<:strip>
+
+Import the strip function C<ofp_strip>.
+
+=item C<:ALL>
+
+All the above exportable items.
+
+=back
+
+=back
+
+=head1 COPYRIGHT
+
+ Copyright (c) 2016 Reyk Floeter <reyk@openbsd.org>
+
+ This package is free software and is provided "as is" without express
+ or implied warranty. It may be used, redistributed and/or modified
+ under the terms of the Perl Artistic License (see
+ http://www.perl.com/perl/misc/Artistic.html)
+
+=head1 AUTHOR
+
+Reyk Floeter E<lt>reyk@openbsd.orgE<gt>
+
+=cut
+
+# any real autoloaded methods go after this line
--- /dev/null
+# $OpenBSD: args-packet-jumbo.pm,v 1.1 2016/07/19 17:04:19 reyk Exp $
+
+# Copyright (c) 2016 Reyk Floeter <reyk@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package args_packet_jumbo;
+
+use strict;
+use warnings;
+use base qw(Exporter);
+our @EXPORT = qw(init next);
+
+my $topology = {
+ buffers => {},
+ hosts => {
+ "a00000000001" => {
+ "port" => 1
+ },
+ "a00000000002" => {
+ "port" => 2
+ },
+ "a00000000003" => {
+ "port" => 3
+ }
+ },
+ packets => [
+ {
+ "src_mac" => "a00000000001",
+ "dest_mac" => "a00000000002",
+ "src_ip" => "10.0.0.1",
+ "src_port" => 12345,
+ "dest_ip" => "10.0.0.2",
+ "dest_port" => 80,
+ "length" => 1000,
+ "count" => 3,
+ "ofp_response" => main::OFP_T_PACKET_OUT()
+ },
+ {
+ "src_mac" => "a00000000002",
+ "dest_mac" => "a00000000001",
+ "src_ip" => "10.0.0.2",
+ "src_port" => 80,
+ "dest_ip" => "10.0.0.1",
+ "dest_port" => 12345,
+ "length" => 1000,
+ "count" => 3,
+ "ofp_response" => main::OFP_T_FLOW_MOD()
+
+ },
+ {
+ "src_mac" => "a00000000001",
+ "dest_mac" => "ffffffffffff",
+ "src_ip" => "10.0.0.1",
+ "dest_ip" => "10.255.255.255",
+ "length" => 5000,
+ "count" => 3,
+ "ofp_response" => main::OFP_T_PACKET_OUT()
+ }
+ ]
+};
+
+sub init {
+ my $class = shift;
+ my $sock = shift;
+ my $self = { "count" => 0,
+ "sock" => $sock, "version" => main::OFP_V_1_0() };
+
+ bless($self, $class);
+ main::ofp_hello($self);
+
+ for (my $i = 0; $i < @{$topology->{packets}}; $i++) {
+ my $packet = $topology->{packets}[$i];
+ my $src = $topology->{hosts}->{$packet->{src_mac}};
+
+ $self->{port} = $src->{port} if ($src);
+
+ for (my $j = 0; $j < $packet->{count}; $j++) {
+ my $ofp;
+ $self->{count}++;
+ $ofp = main::packet_send($self, $packet);
+
+ if (defined($packet->{ofp_response})) {
+ if ($ofp->{type} != $packet->{ofp_response}) {
+ main::fatal($class,
+ "invalid ofp response type ".
+ $ofp->{type});
+ }
+ }
+ }
+ }
+
+ return ($self);
+}
+
+sub next {
+ # Not used
+}
+
+1;
--- /dev/null
+#!/usr/bin/perl -w
+# $OpenBSD: run.pl,v 1.1 2016/07/19 17:04:19 reyk Exp $
+
+# Copyright (c) 2016 Reyk Floeter <reyk@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use strict;
+use warnings;
+
+BEGIN {
+ require OFP;
+ require 'ofp.ph';
+ require 'ofp10.ph';
+ require IO::Socket::INET;
+}
+
+use File::Basename;
+use Net::Pcap;
+use NetPacket::Ethernet;
+use NetPacket::IP;
+use NetPacket::UDP;
+use Data::Dumper;
+use Crypt::Random;
+
+sub fatal {
+ my $class = shift;
+ my $err = shift;
+ print STDERR "*** ".$class.": ".$err."\n";
+ die($err);
+}
+
+sub ofp_debug {
+ my $dir = shift;
+ my $ofp = shift;
+
+ printf("OFP ".$dir." version %d type %d length %d xid %d\n",
+ $ofp->{version},
+ $ofp->{type},
+ $ofp->{length},
+ $ofp->{xid});
+
+}
+
+sub ofp_hello {
+ my $class;
+ my $self = shift;
+ my $hello = NetPacket::OFP->decode() or fatal($class, "new packet");
+ my $resp = ();
+ my $pkt;
+ my $resppkt;
+
+ $hello->{version} = $self->{version};
+ $hello->{type} = OFP_T_HELLO();
+ $hello->{xid} = $self->{xid}++;
+
+ $pkt = NetPacket::OFP->encode($hello);
+
+ ofp_debug(">", $hello);
+
+ # XXX timeout
+ $self->{sock}->send($pkt);
+ $self->{sock}->recv($resppkt, 1024);
+
+ $resp = NetPacket::OFP->decode($resppkt) or
+ fatal($class, "recv'ed packet");
+
+ ofp_debug("<", $resp);
+
+ return ($resp);
+}
+
+sub ofp_packet_in {
+ my $class;
+ my $self = shift;
+ my $data = shift;
+ my $pktin = NetPacket::OFP->decode() or fatal($class, "new packet");
+ my $resp = ();
+ my $pkt;
+ my $resppkt;
+
+ $pktin->{version} = $self->{version};
+ $pktin->{type} = OFP_T_PACKET_IN();
+ $pktin->{xid} = $self->{xid}++;
+ $pktin->{data} = $data;
+ $pktin->{length} += length($data);
+ $pktin->{buffer_id} = $self->{count} || 1;
+ $pktin->{port} = $self->{port} || OFP_PORT_NORMAL();
+
+ $pkt = NetPacket::OFP->encode($pktin);
+
+ ofp_debug(">", $pktin);
+
+ # XXX timeout
+ $self->{sock}->send($pkt);
+ $self->{sock}->recv($resppkt, 1024);
+
+ $resp = NetPacket::OFP->decode($resppkt) or
+ fatal($class, "recv'ed packet");
+
+ ofp_debug("<", $resp);
+
+ return ($resp);
+}
+
+sub packet_send {
+ my $class;
+ my $self = shift;
+ my $packet = shift;
+ my $eth;
+ my $ip;
+ my $udp;
+ my $data;
+ my $pkt;
+ my $src;
+
+ # Payload
+ $data = Crypt::Random::makerandom_octet(Length => $packet->{length});
+
+ # IP header
+ $ip = NetPacket::IP->decode();
+ $ip->{src_ip} = $packet->{src_ip} || "127.0.0.1";
+ $ip->{dest_ip} = $packet->{dest_ip} || "127.0.0.1";
+ $ip->{ver} = NetPacket::IP::IP_VERSION_IPv4;
+ $ip->{hlen} = 5;
+ $ip->{tos} = 0;
+ $ip->{id} = Crypt::Random::makerandom(Size => 16);
+ $ip->{ttl} = 0x5a;
+ $ip->{flags} = 0; #XXX NetPacket::IP::IP_FLAG_DONTFRAG;
+ $ip->{foffset} = 0;
+ $ip->{proto} = NetPacket::IP::IP_PROTO_UDP;
+ $ip->{options} = '';
+
+ # UDP header
+ $udp = NetPacket::UDP->decode();
+ $udp->{src_port} = $packet->{src_port} || 9000;
+ $udp->{dest_port} = $packet->{dest_port} || 9000;
+ $udp->{data} = $data;
+
+ $ip->{data} = $udp->encode($ip);
+ $pkt = $ip->encode() or fatal($class, "ip");
+
+ # Create Ethernet header
+ $self->{data} = pack('H12H12na*' ,
+ $packet->{dest_mac},
+ $packet->{src_mac},
+ NetPacket::Ethernet::ETH_TYPE_IP,
+ $pkt);
+
+ return (main::ofp_packet_in($self, $self->{data}));
+
+}
+
+sub packet_decode {
+ my $pkt = shift;
+ my $hdr = shift;
+ my $eh = NetPacket::Ethernet->decode($pkt);
+
+ printf("%s %s %04x %d",
+ join(':', unpack '(A2)*', $eh->{src_mac}),
+ join(':', unpack '(A2)*', $eh->{dest_mac}),
+ $eh->{type}, length($pkt));
+ if (length($pkt) < $hdr->{len}) {
+ printf("/%d", $hdr->{len})
+ }
+ printf("\n");
+
+ return ($eh);
+}
+
+sub process {
+ my $sock = shift;
+ my $path = shift;
+ my $pcap_t;
+ my $err;
+ my $pkt;
+ my %hdr;
+ my ($filename, $dirs, $suffix) = fileparse($path, ".pm");
+ (my $func = $filename) =~ s/-/_/g;
+ my $state;
+
+ print "- $filename\n";
+
+ require $path or fatal("main", $path);
+
+ eval {
+ $state = $func->init($sock);
+ };
+
+ return if not $state->{pcap};
+
+ $pcap_t = Net::Pcap::open_offline($dirs."".$state->{pcap}, \$err)
+ or fatal("main", $err);
+
+ while ($pkt = Net::Pcap::next($pcap_t, \%hdr)) {
+
+ $state->{data} = $pkt;
+ $state->{eh} = packet_decode($pkt, \%hdr);
+
+ eval {
+ $func->next($state);
+ };
+ }
+
+ Net::Pcap::close($pcap_t);
+}
+
+if (@ARGV < 1) {
+ print "\nUsage: run.pl test.pl\n";
+ exit;
+}
+
+# Flush after every write
+$| = 1;
+
+my $test = $ARGV[0];
+my @test_files = ();
+for (@ARGV) {
+ push(@test_files, glob($_));
+}
+
+# Open connection to the controller
+my $sock = new IO::Socket::INET(
+ PeerHost => '127.0.0.1',
+ PeerPort => '6633',
+ Proto => 'tcp',
+) or fatal("main", "ERROR in Socket Creation : $!\n");
+
+# Run all requested tests
+for my $test_file (@test_files) {
+ process($sock, $test_file);
+}
+
+$sock->close();
+
+1;