summaryrefslogtreecommitdiff
path: root/regress
diff options
context:
space:
mode:
authorAlexander Bluhm <bluhm@cvs.openbsd.org>2013-06-03 05:06:39 +0000
committerAlexander Bluhm <bluhm@cvs.openbsd.org>2013-06-03 05:06:39 +0000
commit2ff2ceee1c6b079f25d797b92b8012338950712d (patch)
treef8d32197b18decf805b1347e3675791fb2cbefa9 /regress
parent021e76db6fd34fa2ba4b21c5fce3d5341fe2563e (diff)
Add a regression test suite for the pf divert-to and divert-reply
feature. It requires two machines, the local host is running the regression test, the remote host gets pf divert rules installed. The diverted TCP connections are running between these hosts in both directions. The remote host is controlled via ssh.
Diffstat (limited to 'regress')
-rw-r--r--regress/sys/net/pf_divert/Client.pm117
-rw-r--r--regress/sys/net/pf_divert/Makefile89
-rw-r--r--regress/sys/net/pf_divert/Proc.pm160
-rw-r--r--regress/sys/net/pf_divert/Remote.pm85
-rw-r--r--regress/sys/net/pf_divert/Server.pm93
-rw-r--r--regress/sys/net/pf_divert/args-tcp-reply.pl11
-rw-r--r--regress/sys/net/pf_divert/args-tcp-to.pl8
-rw-r--r--regress/sys/net/pf_divert/funcs.pl61
-rw-r--r--regress/sys/net/pf_divert/remote.pl157
9 files changed, 781 insertions, 0 deletions
diff --git a/regress/sys/net/pf_divert/Client.pm b/regress/sys/net/pf_divert/Client.pm
new file mode 100644
index 00000000000..f9f4160e23e
--- /dev/null
+++ b/regress/sys/net/pf_divert/Client.pm
@@ -0,0 +1,117 @@
+# $OpenBSD: Client.pm,v 1.1.1.1 2013/06/03 05:06:38 bluhm Exp $
+
+# Copyright (c) 2010-2013 Alexander Bluhm <bluhm@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;
+
+package Client;
+use parent 'Proc';
+use Carp;
+use Socket qw(IPPROTO_TCP TCP_NODELAY);
+use Socket6;
+use IO::Socket;
+use IO::Socket::INET6;
+use constant SO_BINDANY => 0x1000;
+
+sub new {
+ my $class = shift;
+ my %args = @_;
+ $args{logfile} ||= "client.log";
+ $args{up} ||= "Connected";
+ $args{down} ||= $args{alarm} ? "Alarm" :
+ "Shutdown|Broken pipe|Connection reset by peer";
+ my $self = Proc::new($class, %args);
+ $self->{protocol} ||= "tcp";
+ $self->{connectdomain}
+ or croak "$class connect domain not given";
+ $self->{connectaddr}
+ or croak "$class connect addr not given";
+ $self->{connectport}
+ or croak "$class connect port not given";
+
+ my $cs;
+ if ($self->{bindany}) {
+ $cs = IO::Socket::INET6->new(
+ Proto => $self->{protocol},
+ Domain => $self->{connectdomain},
+ Blocking => ($self->{nonblocking} ? 0 : 1),
+ ) or die ref($self), " socket connect failed: $!";
+ $cs->setsockopt(SOL_SOCKET, SO_BINDANY, 1)
+ or die ref($self), " setsockopt SO_BINDANY failed: $!";
+ my @rres = getaddrinfo($self->{bindaddr}, $self->{bindport}||0,
+ $self->{connectdomain}, SOCK_STREAM, 0, AI_PASSIVE);
+ $cs->bind($rres[3])
+ or die ref($self), " bind failed: $!";
+ } elsif ($self->{bindaddr} || $self->{bindport}) {
+ $cs = IO::Socket::INET6->new(
+ Proto => $self->{protocol},
+ Domain => $self->{connectdomain},
+ Blocking => ($self->{nonblocking} ? 0 : 1),
+ LocalAddr => $self->{bindaddr},
+ LocalPort => $self->{bindport},
+ ) or die ref($self), " socket connect failed: $!";
+ }
+ if ($cs) {
+ $self->{bindaddr} = $cs->sockhost();
+ $self->{bindport} = $cs->sockport();
+ $self->{cs} = $cs;
+ }
+
+ return $self;
+}
+
+sub child {
+ my $self = shift;
+
+ my $cs = $self->{cs} || IO::Socket::INET6->new(
+ Proto => $self->{protocol},
+ Domain => $self->{connectdomain},
+ Blocking => ($self->{nonblocking} ? 0 : 1),
+ ) or die ref($self), " socket connect failed: $!";
+ if ($self->{oobinline}) {
+ setsockopt($cs, SOL_SOCKET, SO_OOBINLINE, pack('i', 1))
+ or die ref($self), " set oobinline connect failed: $!";
+ }
+ if ($self->{sndbuf}) {
+ setsockopt($cs, SOL_SOCKET, SO_SNDBUF,
+ pack('i', $self->{sndbuf}))
+ or die ref($self), " set sndbuf connect failed: $!";
+ }
+ if ($self->{rcvbuf}) {
+ setsockopt($cs, SOL_SOCKET, SO_RCVBUF,
+ pack('i', $self->{rcvbuf}))
+ or die ref($self), " set rcvbuf connect failed: $!";
+ }
+ if ($self->{protocol} eq "tcp") {
+ setsockopt($cs, IPPROTO_TCP, TCP_NODELAY, pack('i', 1))
+ or die ref($self), " set nodelay connect failed: $!";
+ }
+ my @rres = getaddrinfo($self->{connectaddr}, $self->{connectport},
+ $self->{connectdomain}, SOCK_STREAM);
+ $cs->connect($rres[3])
+ or die ref($self), " connect failed: $!";
+ print STDERR "connect sock: ",$cs->sockhost()," ",$cs->sockport(),"\n";
+ print STDERR "connect peer: ",$cs->peerhost()," ",$cs->peerport(),"\n";
+ $self->{bindaddr} = $cs->sockhost();
+ $self->{bindport} = $cs->sockport();
+
+ open(STDOUT, '>&', $cs)
+ or die ref($self), " dup STDOUT failed: $!";
+ open(STDIN, '<&', $cs)
+ or die ref($self), " dup STDIN failed: $!";
+}
+
+1;
diff --git a/regress/sys/net/pf_divert/Makefile b/regress/sys/net/pf_divert/Makefile
new file mode 100644
index 00000000000..63403d47b24
--- /dev/null
+++ b/regress/sys/net/pf_divert/Makefile
@@ -0,0 +1,89 @@
+# $OpenBSD: Makefile,v 1.1.1.1 2013/06/03 05:06:38 bluhm Exp $
+
+# The following ports must be installed for the regression tests:
+# p5-IO-Socket-INET6 object interface for AF_INET and AF_INET6 domain sockets
+# p5-Socket6 Perl defines relating to AF_INET6 sockets
+
+
+TARGETS ?= ${ARGS}
+.if empty (TARGETS)
+ARGS != cd ${.CURDIR} && ls args-*.pl
+.endif
+REGRESS_TARGETS = ${TARGETS:S/^/run-regress-/}
+CLEANFILES += *.log ktrace.out stamp-*
+
+# Fill out these variables as you have to test divert with the pf
+# kernel running on a remote machine. You have to specify a local
+# and remote ip address for the test connections. The fake ip address
+# will be routed via the remote address to test divert with non-existing
+# addresses. To control the remote machine you need a hostname for
+# ssh to log in. All the test files must be in the same directory
+# local and remote.
+# You must have an anchor "regress" for the divert rules in the pf.conf
+# of the remote machine. The kernel of the remote machine gets testet.
+
+LOCAL_ADDR ?= 10.188.50.10
+REMOTE_ADDR ?= 10.188.50.50
+FAKE_ADDR ?= 10.188.50.51
+LOCAL_ADDR6 ?= fdd7:e83e:66bc:211:fce1:baff:fed1:561f
+REMOTE_ADDR6 ?= fdd7:e83e:66bc:211:5054:ff:fe12:3450
+FAKE_ADDR6 ?= fdd7:e83e:66bc:211:5054:ff:fe12:3451
+REMOTE_SSH ?= q0
+
+.MAIN: all
+
+.if make(regress) || make(all)
+.BEGIN:
+.if empty (REMOTE_SSH)
+ @echo "remote test target host not set"; false
+.endif
+ @echo
+ ${SUDO} true
+ ssh -t q0 ${SUDO} true
+ -${SUDO} route -n delete -inet -host ${FAKE_ADDR} 2>/dev/null
+ ${SUDO} route -n add -inet -host ${FAKE_ADDR} ${REMOTE_ADDR}
+ -${SUDO} route -n delete -inet6 -host ${FAKE_ADDR6} 2>/dev/null
+ ${SUDO} route -n add -inet6 -host ${FAKE_ADDR6} ${REMOTE_ADDR6}
+.endif
+
+# 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 = -I.
+PERLPATH =
+.else
+PERLINC = -I${.CURDIR}
+PERLPATH = ${.CURDIR}/
+.endif
+
+# The arg tests take a perl hash with arguments controlling the test
+# parameters. The remote.pl test has local client or server and the
+# diverted process is running on the remote machine reachable with
+# ssh.
+
+.for a in ${ARGS}
+run-regress-$a: $a
+.if !empty (LOCAL_ADDR)
+ @echo
+ time SUDO=${SUDO} perl ${PERLINC} ${PERLPATH}remote.pl inet ${LOCAL_ADDR} ${FAKE_ADDR} ${REMOTE_SSH} ${PERLPATH}$a
+.endif
+.if !empty (LOCAL_ADDR6)
+ @echo
+ time SUDO=${SUDO} perl ${PERLINC} ${PERLPATH}remote.pl inet6 ${LOCAL_ADDR6} ${FAKE_ADDR6} ${REMOTE_SSH} ${PERLPATH}$a
+.endif
+.endfor
+
+# 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>
diff --git a/regress/sys/net/pf_divert/Proc.pm b/regress/sys/net/pf_divert/Proc.pm
new file mode 100644
index 00000000000..e2a4b20e5fd
--- /dev/null
+++ b/regress/sys/net/pf_divert/Proc.pm
@@ -0,0 +1,160 @@
+# $OpenBSD: Proc.pm,v 1.1.1.1 2013/06/03 05:06:38 bluhm Exp $
+
+# Copyright (c) 2010-2013 Alexander Bluhm <bluhm@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;
+
+package Proc;
+use Carp;
+use IO::File;
+use List::Util qw(first);
+use POSIX;
+use Time::HiRes qw(time alarm sleep);
+
+my %CHILDREN;
+
+BEGIN {
+ $SIG{TERM} = $SIG{INT} = sub {
+ my $sig = shift;
+ kill TERM => keys %CHILDREN;
+ $SIG{TERM} = $SIG{INT} = 'DEFAULT';
+ POSIX::raise($sig);
+ };
+}
+
+END {
+ kill TERM => keys %CHILDREN;
+ $SIG{TERM} = $SIG{INT} = 'DEFAULT';
+}
+
+sub new {
+ my $class = shift;
+ my $self = { @_ };
+ $self->{down} ||= $self->{alarm} ? "Alarm" : "Shutdown";
+ $self->{func} && ref($self->{func}) eq 'CODE'
+ or croak "$class func not given";
+ $self->{logfile}
+ or croak "$class log file not given";
+ open(my $fh, '>', $self->{logfile})
+ or die "$class log file $self->{logfile} create failed: $!";
+ $fh->autoflush;
+ $self->{log} = $fh;
+ return bless $self, $class;
+}
+
+sub run {
+ my $self = shift;
+
+ defined(my $pid = fork())
+ or die ref($self), " fork child failed";
+ if ($pid) {
+ $CHILDREN{$pid} = 1;
+ $self->{pid} = $pid;
+ return $self;
+ }
+ %CHILDREN = ();
+ $SIG{TERM} = $SIG{INT} = 'DEFAULT';
+ $SIG{__DIE__} = sub {
+ die @_ if $^S;
+ warn @_;
+ IO::Handle::flush(\*STDERR);
+ POSIX::_exit(255);
+ };
+ open(STDERR, '>&', $self->{log})
+ or die ref($self), " dup STDERR failed: $!";
+
+ $self->child();
+ print STDERR $self->{up}, "\n";
+ alarm($self->{alarm}) if $self->{alarm};
+ $self->{func}->($self);
+ print STDERR "Shutdown", "\n";
+ IO::Handle::flush(\*STDOUT);
+ IO::Handle::flush(\*STDERR);
+
+ POSIX::_exit(0);
+}
+
+sub wait {
+ my $self = shift;
+ my $flags = shift;
+
+ my $pid = $self->{pid}
+ or croak ref($self), " no child pid";
+ my $kid = waitpid($pid, $flags);
+ if ($kid > 0) {
+ my $status = $?;
+ my $code;
+ $code = "exit: ". WEXITSTATUS($?) if WIFEXITED($?);
+ $code = "signal: ". WTERMSIG($?) if WIFSIGNALED($?);
+ $code = "stop: ". WSTOPSIG($?) if WIFSTOPPED($?);
+ return wantarray ? ($kid, $status, $code) : $kid;
+ }
+ return $kid;
+}
+
+sub loggrep {
+ my $self = shift;
+ my($regex, $timeout) = @_;
+
+ my $end = time() + $timeout if $timeout;
+
+ do {
+ my($kid, $status, $code) = $self->wait(WNOHANG);
+ if ($self->{alarm} && $kid > 0 &&
+ WIFSIGNALED($status) && WTERMSIG($status) == 14 ) {
+ # child killed by SIGALRM as expected
+ print {$self->{log}} "Alarm", "\n";
+ } elsif ($kid > 0 && $status != 0) {
+ # child terminated with failure
+ die ref($self), " child status: $status $code";
+ }
+ open(my $fh, '<', $self->{logfile})
+ or die ref($self), " log file open failed: $!";
+ my $match = first { /$regex/ } <$fh>;
+ return $match if $match;
+ close($fh);
+ # pattern not found
+ if ($kid == 0) {
+ # child still running, wait for log data
+ sleep .1;
+ } else {
+ # child terminated, no new log data possible
+ return;
+ }
+ } while ($timeout and time() < $end);
+
+ return;
+}
+
+sub up {
+ my $self = shift;
+ my $timeout = shift || 10;
+ $self->loggrep(qr/$self->{up}/, $timeout)
+ or croak ref($self), " no $self->{up} in $self->{logfile} ".
+ "after $timeout seconds";
+ return $self;
+}
+
+sub down {
+ my $self = shift;
+ my $timeout = shift || 30;
+ $self->loggrep(qr/$self->{down}/, $timeout)
+ or croak ref($self), " no $self->{down} in $self->{logfile} ".
+ "after $timeout seconds";
+ return $self;
+}
+
+1;
diff --git a/regress/sys/net/pf_divert/Remote.pm b/regress/sys/net/pf_divert/Remote.pm
new file mode 100644
index 00000000000..c6cad676eb6
--- /dev/null
+++ b/regress/sys/net/pf_divert/Remote.pm
@@ -0,0 +1,85 @@
+# $OpenBSD: Remote.pm,v 1.1.1.1 2013/06/03 05:06:38 bluhm Exp $
+
+# Copyright (c) 2010-2013 Alexander Bluhm <bluhm@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;
+
+package Remote;
+use parent 'Proc';
+use Carp;
+use Cwd;
+use File::Basename;
+
+sub new {
+ my $class = shift;
+ my %args = @_;
+ $args{logfile} ||= "remote.log";
+ $args{up} ||= "Started";
+ $args{func} = sub { Carp::confess "$class func may not be called" };
+ $args{remotessh}
+ or croak "$class remote ssh host not given";
+ my $self = Proc::new($class, %args);
+ $self->{af}
+ or croak "$class address family not given";
+ $self->{bindaddr}
+ or croak "$class bind addr not given";
+ $self->{connectaddr}
+ or croak "$class connect addr not given";
+ defined $self->{connectport}
+ or croak "$class connect port not given";
+ return $self;
+}
+
+sub up {
+ my $self = Proc::up(shift, @_);
+ my $timeout = shift || 10;
+ if ($self->{connectport}) {
+ $self->loggrep(qr/^Connected$/, $timeout)
+ or croak ref($self), " no Connected in $self->{logfile} ".
+ "after $timeout seconds";
+ return $self;
+ }
+ my $lsock = $self->loggrep(qr/^listen sock: /, $timeout)
+ or croak ref($self), " no listen sock in $self->{logfile} ".
+ "after $timeout seconds";
+ my($addr, $port) = $lsock =~ /: (\S+) (\S+)$/
+ or croak ref($self), " no listen addr and port in $self->{logfile}";
+ $self->{listenaddr} = $addr;
+ $self->{listenport} = $port;
+ return $self;
+}
+
+sub child {
+ my $self = shift;
+
+ print STDERR $self->{up}, "\n";
+ my @opts = split(' ', $ENV{SSH_OPTIONS}) if $ENV{SSH_OPTIONS};
+ # if sudo is set, run the remote perl as root, otherwise pass SUDO
+ my @sudo = !$ENV{SUDO} ? () :
+ $self->{sudo} ? $ENV{SUDO} : "SUDO=$ENV{SUDO}";
+ my $dir = dirname($0);
+ $dir = getcwd() if ! $dir || $dir eq '.';
+ my @cmd = ('ssh', '-n', @opts, $self->{remotessh}, @sudo, 'perl',
+ '-I', $dir, "$dir/".basename($0), $self->{af},
+ $self->{bindaddr}, $self->{connectaddr}, $self->{connectport},
+ ($self->{testfile} ? "$dir/".basename($self->{testfile}) :
+ ()));
+ print STDERR "execute: @cmd\n";
+ exec @cmd;
+ die "Exec @cmd failed: $!";
+}
+
+1;
diff --git a/regress/sys/net/pf_divert/Server.pm b/regress/sys/net/pf_divert/Server.pm
new file mode 100644
index 00000000000..cecde9b267e
--- /dev/null
+++ b/regress/sys/net/pf_divert/Server.pm
@@ -0,0 +1,93 @@
+# $OpenBSD: Server.pm,v 1.1.1.1 2013/06/03 05:06:38 bluhm Exp $
+
+# Copyright (c) 2010-2013 Alexander Bluhm <bluhm@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;
+
+package Server;
+use parent 'Proc';
+use Carp;
+use Socket qw(IPPROTO_TCP TCP_NODELAY);
+use Socket6;
+use IO::Socket;
+use IO::Socket::INET6;
+
+sub new {
+ my $class = shift;
+ my %args = @_;
+ $args{logfile} ||= "server.log";
+ $args{up} ||= "Accepted";
+ my $self = Proc::new($class, %args);
+ $self->{protocol} ||= "tcp";
+ $self->{listendomain}
+ or croak "$class listen domain not given";
+ my $ls = IO::Socket::INET6->new(
+ Proto => $self->{protocol},
+ ReuseAddr => 1,
+ Domain => $self->{listendomain},
+ $self->{listenaddr} ? (LocalAddr => $self->{listenaddr}) : (),
+ $self->{listenport} ? (LocalPort => $self->{listenport}) : (),
+ ) or die ref($self), " socket failed: $!";
+ if ($self->{oobinline}) {
+ setsockopt($ls, SOL_SOCKET, SO_OOBINLINE, pack('i', 1))
+ or die ref($self), " set oobinline listen failed: $!";
+ }
+ if ($self->{sndbuf}) {
+ setsockopt($ls, SOL_SOCKET, SO_SNDBUF,
+ pack('i', $self->{sndbuf}))
+ or die ref($self), " set sndbuf listen failed: $!";
+ }
+ if ($self->{rcvbuf}) {
+ setsockopt($ls, SOL_SOCKET, SO_RCVBUF,
+ pack('i', $self->{rcvbuf}))
+ or die ref($self), " set rcvbuf listen failed: $!";
+ }
+ if ($self->{protocol} eq "tcp") {
+ setsockopt($ls, IPPROTO_TCP, TCP_NODELAY, pack('i', 1))
+ or die ref($self), " set nodelay listen failed: $!";
+ listen($ls, 1)
+ or die ref($self), " socket failed: $!";
+ }
+ my $log = $self->{log};
+ print $log "listen sock: ",$ls->sockhost()," ",$ls->sockport(),"\n";
+ $self->{listenaddr} = $ls->sockhost() unless $self->{listenaddr};
+ $self->{listenport} = $ls->sockport() unless $self->{listenport};
+ $self->{ls} = $ls;
+ return $self;
+}
+
+sub child {
+ my $self = shift;
+
+ my $as = $self->{ls};
+ if ($self->{protocol} eq "tcp") {
+ $as = $self->{ls}->accept()
+ or die ref($self), " socket accept failed: $!";
+ print STDERR "accept sock: ",$as->sockhost()," ",
+ $as->sockport(),"\n";
+ print STDERR "accept peer: ",$as->peerhost()," ",
+ $as->peerport(),"\n";
+ }
+ $as->blocking($self->{nonblocking} ? 0 : 1)
+ or die ref($self), " non-blocking accept failed: $!";
+
+ open(STDIN, '<&', $as)
+ or die ref($self), " dup STDIN failed: $!";
+ open(STDOUT, '>&', $as)
+ or die ref($self), " dup STDOUT failed: $!";
+}
+
+1;
diff --git a/regress/sys/net/pf_divert/args-tcp-reply.pl b/regress/sys/net/pf_divert/args-tcp-reply.pl
new file mode 100644
index 00000000000..93cf76a749f
--- /dev/null
+++ b/regress/sys/net/pf_divert/args-tcp-reply.pl
@@ -0,0 +1,11 @@
+# test divert-reply
+# swap client and server
+# server is local
+# client diverts packets with reply-to
+
+use strict;
+use warnings;
+
+our %args = (
+ divert => "reply",
+);
diff --git a/regress/sys/net/pf_divert/args-tcp-to.pl b/regress/sys/net/pf_divert/args-tcp-to.pl
new file mode 100644
index 00000000000..0296cd35799
--- /dev/null
+++ b/regress/sys/net/pf_divert/args-tcp-to.pl
@@ -0,0 +1,8 @@
+# test divert-to
+
+use strict;
+use warnings;
+
+our %args = (
+ divert => "to",
+);
diff --git a/regress/sys/net/pf_divert/funcs.pl b/regress/sys/net/pf_divert/funcs.pl
new file mode 100644
index 00000000000..2d63ea92bfd
--- /dev/null
+++ b/regress/sys/net/pf_divert/funcs.pl
@@ -0,0 +1,61 @@
+# $OpenBSD: funcs.pl,v 1.1.1.1 2013/06/03 05:06:38 bluhm Exp $
+
+# Copyright (c) 2010-2013 Alexander Bluhm <bluhm@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;
+
+########################################################################
+# Client and Server funcs
+########################################################################
+
+sub write_read_stream {
+ my $self = shift;
+
+ my $out = ref($self). "\n";
+ print $out;
+ IO::Handle::flush(\*STDOUT);
+ print STDERR ">>> $out";
+
+ my $in = <STDIN>;
+ print STDERR "<<< $in";
+}
+
+########################################################################
+# Script funcs
+########################################################################
+
+sub check_logs {
+ my ($c, $s, %args) = @_;
+
+ return if $args{nocheck};
+
+ check_inout($c, $s, %args);
+}
+
+sub check_inout {
+ my ($c, $s, %args) = @_;
+
+ if ($c && !$args{client}{nocheck}) {
+ $c->loggrep(qr/^>>> Client$/) or die "no client out";
+ $c->loggrep(qr/^<<< Server$/) or die "no client in";
+ }
+ if ($s && !$args{server}{nocheck}) {
+ $s->loggrep(qr/^>>> Server$/) or die "no server out";
+ $s->loggrep(qr/^<<< Client$/) or die "no server in";
+ }
+}
+
+1;
diff --git a/regress/sys/net/pf_divert/remote.pl b/regress/sys/net/pf_divert/remote.pl
new file mode 100644
index 00000000000..44723f512b6
--- /dev/null
+++ b/regress/sys/net/pf_divert/remote.pl
@@ -0,0 +1,157 @@
+#!/usr/bin/perl
+# $OpenBSD: remote.pl,v 1.1.1.1 2013/06/03 05:06:38 bluhm Exp $
+
+# Copyright (c) 2010-2013 Alexander Bluhm <bluhm@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;
+use File::Basename;
+use File::Copy;
+use Socket;
+use Socket6;
+
+use Client;
+use Server;
+use Remote;
+require 'funcs.pl';
+
+sub usage {
+ die <<"EOF";
+usage:
+ remote.pl af bindaddr connectaddr connectport [test-args.pl]
+ Only start remote relay.
+ remote.pl af localaddr fakeaddr remotessh [test-args.pl]
+ Run test with local client and server. Remote relay is
+ started automatically with ssh on remotessh.
+EOF
+}
+
+my $test;
+our %args;
+if (@ARGV and -f $ARGV[-1]) {
+ $test = pop;
+ do $test
+ or die "Do test file $test failed: ", $@ || $!;
+}
+my($af, $domain);
+if (@ARGV) {
+ $af = shift;
+ $domain =
+ $af eq "inet" ? AF_INET :
+ $af eq "inet6" ? AF_INET6 :
+ die "address family must be 'inet' or 'inet6\n";
+}
+my $mode =
+ @ARGV == 3 && $ARGV[0] !~ /^\d+$/ && $ARGV[2] =~ /^\d+$/ ? "divert" :
+ @ARGV == 3 && $ARGV[0] !~ /^\d+$/ && $ARGV[2] !~ /^\d+$/ ? "auto" :
+ usage();
+
+my($c, $l, $r, $s, $logfile);
+my $func = \&write_read_stream;
+my $divert = $args{divert} || "to";
+my $local = $divert eq "to" ? "client" : "server";
+my $remote = $divert eq "to" ? "server" : "client";
+if ($mode eq "divert") {
+ $local = $divert eq "to" ? "server" : "client";
+ $logfile = dirname($0)."/remote.log";
+}
+
+if ($local eq "server") {
+ $l = $s = Server->new(
+ func => $func,
+ %{$args{server}},
+ logfile => $logfile,
+ listendomain => $domain,
+ listenaddr => $mode ne "divert" ? $ARGV[0] :
+ $af eq "inet" ? "127.0.0.1" : "::1",
+ );
+}
+if ($mode eq "auto") {
+ $r = Remote->new(
+ %{$args{relay}},
+ af => $af,
+ logfile => "$remote.log",
+ testfile => $test,
+ remotessh => $ARGV[2],
+ bindaddr => $ARGV[1],
+ connectaddr => $ARGV[0],
+ connectport => $s ? $s->{listenport} : 0,
+ sudo => $ENV{SUDO},
+ );
+ $r->run->up;
+ $r->loggrep(qr/^Diverted$/, 10)
+ or die "no Diverted in $r->{logfile}";
+}
+if ($local eq "client") {
+ $l = $c = Client->new(
+ func => $func,
+ %{$args{client}},
+ logfile => $logfile,
+ connectdomain => $domain,
+ connectaddr => $ARGV[1],
+ connectport => $r ? $r->{listenport} : $ARGV[2],
+ bindany => $mode eq "divert",
+ bindaddr => $ARGV[0],
+ );
+}
+
+if ($mode eq "divert") {
+ open(my $log, '<', $l->{logfile})
+ or die "Remote log file open failed: $!";
+ $SIG{__DIE__} = sub {
+ die @_ if $^S;
+ copy($log, \*STDERR);
+ warn @_;
+ exit 255;
+ };
+ copy($log, \*STDERR);
+
+ my @sudo = $ENV{SUDO} ? $ENV{SUDO} : ();
+ my @cmd = (@sudo, qw(pfctl -a regress -f -));
+ open(my $pf, '|-', @cmd)
+ or die "Open pipe to pf '@cmd' failed: $!";
+ if ($local eq "server") {
+ print $pf "pass in log $af proto tcp ".
+ "from $ARGV[1] to $ARGV[0] port $s->{listenport} ".
+ "divert-to $s->{listenaddr} port $s->{listenport}\n";
+ } else {
+ print $pf "pass out log $af proto tcp ".
+ "from $c->{bindaddr} to $ARGV[1] port $ARGV[2] ".
+ "divert-reply\n";
+ }
+ close($pf) or die $! ?
+ "Close pipe to pf '@cmd' failed: $!" :
+ "pf '@cmd' failed: $?";
+ print STDERR "Diverted\n";
+
+ $l->run;
+ copy($log, \*STDERR);
+ $l->up;
+ copy($log, \*STDERR);
+ $l->down;
+ copy($log, \*STDERR);
+
+ exit;
+}
+
+$s->run if $s;
+$c->run->up if $c;
+$s->up if $s;
+
+$c->down if $c;
+$r->down if $r;
+$s->down if $s;
+
+check_logs($c, $s, %args);