commit caed4502eeca392fd0b05d89d49209b1b751cdfd from: Rafael Sadowski date: Sat May 30 15:55:51 2026 UTC Add regress from base commit - b400ca1d900bb955d47442147287a7f0112ed698 commit + caed4502eeca392fd0b05d89d49209b1b751cdfd blob - /dev/null blob + 7aaa0401e2f36632a49315053e1bf9fdc219fad8 (mode 644) --- /dev/null +++ regress/Client.pm @@ -0,0 +1,110 @@ +# $OpenBSD: Client.pm,v 1.15 2024/10/28 19:57:02 tb Exp $ + +# Copyright (c) 2010-2021 Alexander Bluhm +# +# 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(:DEFAULT IPPROTO_TCP TCP_NODELAY); +use Socket6; +use IO::Socket::IP; +use IO::Socket::SSL; + +sub new { + my $class = shift; + my %args = @_; + $args{logfile} ||= "client.log"; + $args{up} ||= "Connected"; + $args{timefile} //= "time.log"; + my $self = Proc::new($class, %args); + $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"; + return $self; +} + +sub child { + my $self = shift; + + # in case we redo the connect, shutdown the old one + shutdown(\*STDOUT, SHUT_WR); + delete $self->{cs}; + + $SSL_ERROR = ""; + my $iosocket = $self->{ssl} ? "IO::Socket::SSL" : "IO::Socket::IP"; + my $cs = $iosocket->new( + Proto => "tcp", + Domain => $self->{connectdomain}, + # IO::Socket::IP calls the domain family + Family => $self->{connectdomain}, + PeerAddr => $self->{connectaddr}, + PeerPort => $self->{connectport}, + SSL_verify_mode => SSL_VERIFY_NONE, + SSL_use_cert => $self->{offertlscert} ? 1 : 0, + SSL_cert_file => $self->{offertlscert} ? + "client.crt" : "", + SSL_key_file => $self->{offertlscert} ? + "client.key" : "", + ) or die ref($self), " $iosocket socket connect failed: $!,$SSL_ERROR"; + if ($self->{sndbuf}) { + setsockopt($cs, SOL_SOCKET, SO_SNDBUF, + pack('i', $self->{sndbuf})) + or die ref($self), " set SO_SNDBUF failed: $!"; + } + if ($self->{rcvbuf}) { + setsockopt($cs, SOL_SOCKET, SO_RCVBUF, + pack('i', $self->{rcvbuf})) + or die ref($self), " set SO_SNDBUF failed: $!"; + } + if ($self->{sndtimeo}) { + setsockopt($cs, SOL_SOCKET, SO_SNDTIMEO, + pack('l!l!', $self->{sndtimeo}, 0)) + or die ref($self), " set SO_SNDTIMEO failed: $!"; + } + if ($self->{rcvtimeo}) { + setsockopt($cs, SOL_SOCKET, SO_RCVTIMEO, + pack('l!l!', $self->{rcvtimeo}, 0)) + or die ref($self), " set SO_RCVTIMEO failed: $!"; + } + setsockopt($cs, IPPROTO_TCP, TCP_NODELAY, pack('i', 1)) + or die ref($self), " set TCP_NODELAY failed: $!"; + + print STDERR "connect sock: ",$cs->sockhost()," ",$cs->sockport(),"\n"; + print STDERR "connect peer: ",$cs->peerhost()," ",$cs->peerport(),"\n"; + if ($self->{ssl}) { + print STDERR "ssl version: ",$cs->get_sslversion(),"\n"; + print STDERR "ssl cipher: ",$cs->get_cipher(),"\n"; + print STDERR "ssl peer certificate:\n", + $cs->dump_peer_certificate(); + + if ($self->{offertlscert}) { + print STDERR "ssl client certificate:\n"; + print STDERR "Subject Name: ", + "${\$cs->sock_certificate('subject')}\n"; + print STDERR "Issuer Name: ", + "${\$cs->sock_certificate('issuer')}\n"; + } + } + + *STDIN = *STDOUT = $self->{cs} = $cs; +} + +1; blob - /dev/null blob + 78d7a52f6fbc00f7a9c4ef1e02adbeeaf874dd72 (mode 644) --- /dev/null +++ regress/LICENSE @@ -0,0 +1,14 @@ +# Copyright (c) 2010-2021 Alexander Bluhm +# Copyright (c) 2014 Reyk Floeter +# +# 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. blob - /dev/null blob + a199e9ab731c5e3f621d2a52a0993894c4ba453f (mode 644) --- /dev/null +++ regress/Makefile @@ -0,0 +1,156 @@ +# $OpenBSD: Makefile,v 1.23 2024/12/27 10:53:46 bluhm Exp $ + +# The following ports must be installed for the regression tests: +# p5-Socket6 Perl defines relating to AF_INET6 sockets +# p5-IO-Socket-SSL perl interface to SSL sockets +# +# 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 Socket6 } or print $@; \ + eval { require IO::Socket::SSL } or print $@; \ +' +.if ! empty (PERL_REQUIRE) +regress: + @echo "${PERL_REQUIRE}" + @echo 'run "pkg_add p5-Socket6 p5-IO-Socket-SSL"' + @echo SKIPPED +.endif + +REGRESS_SETUP_ONCE += setup +setup: +.if empty (REMOTE_SSH) + ${SUDO} true +.else + ssh -t ${REMOTE_SSH} ${SUDO} true +.endif + +# Fill out these variables if you want to test relayd with +# the relayd process running on a remote machine. You have to specify +# a local and remote ip address for the tcp connections. 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. + +LOCAL_ADDR ?= +REMOTE_ADDR ?= +REMOTE_SSH ?= + +# Automatically generate regress targets from test cases in directory. +# EC tests are handled separately to avoid overwriting the RSA cert. + +ARGS_EC != cd ${.CURDIR} && ls args-*-ec.pl +ARGS != cd ${.CURDIR} && ls args-*.pl | grep -v -- -ec\.pl +CLEANFILES += *.log relayd.conf ktrace.out stamp-* +CLEANFILES += *.pem *.req *.crt *.key *.srl + +# 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. Generally they consist of client, relayd, server. + +.for a in ${ARGS} +REGRESS_TARGETS += run-$a +run-$a: $a +.if empty (REMOTE_SSH) + time SUDO="${SUDO}" KTRACE=${KTRACE} RELAYD=${RELAYD} perl ${PERLINC} ${PERLPATH}relayd.pl copy ${PERLPATH}$a + time SUDO="${SUDO}" KTRACE=${KTRACE} RELAYD=${RELAYD} perl ${PERLINC} ${PERLPATH}relayd.pl splice ${PERLPATH}$a +.else + time SUDO="${SUDO}" KTRACE=${KTRACE} RELAYD=${RELAYD} perl ${PERLINC} ${PERLPATH}remote.pl copy ${LOCAL_ADDR} ${REMOTE_ADDR} ${REMOTE_SSH} ${PERLPATH}$a + time SUDO="${SUDO}" KTRACE=${KTRACE} RELAYD=${RELAYD} perl ${PERLINC} ${PERLPATH}remote.pl splice ${LOCAL_ADDR} ${REMOTE_ADDR} ${REMOTE_SSH} ${PERLPATH}$a +.endif +.endfor + +# EC tests +.for a in ${ARGS_EC} +REGRESS_TARGETS += run-$a +run-$a: $a server.crt client.crt 127.0.0.1-ec.crt +.if empty (REMOTE_SSH) + ${SUDO} cp 127.0.0.1-ec.crt /etc/ssl/127.0.0.1.crt + ${SUDO} cp 127.0.0.1-ec.key /etc/ssl/private/127.0.0.1.key + time SUDO="${SUDO}" KTRACE=${KTRACE} RELAYD=${RELAYD} perl ${PERLINC} ${PERLPATH}relayd.pl copy ${PERLPATH}$a + time SUDO="${SUDO}" KTRACE=${KTRACE} RELAYD=${RELAYD} perl ${PERLINC} ${PERLPATH}relayd.pl splice ${PERLPATH}$a +.else + scp ${REMOTE_ADDR}-ec.crt root@${REMOTE_SSH}:/etc/ssl/${REMOTE_ADDR}.crt + scp ${REMOTE_ADDR}-ec.key root@${REMOTE_SSH}:/etc/ssl/private/${REMOTE_ADDR}.key + time SUDO="${SUDO}" KTRACE=${KTRACE} RELAYD=${RELAYD} perl ${PERLINC} ${PERLPATH}remote.pl copy ${LOCAL_ADDR} ${REMOTE_ADDR} ${REMOTE_SSH} ${PERLPATH}$a + time SUDO="${SUDO}" KTRACE=${KTRACE} RELAYD=${RELAYD} perl ${PERLINC} ${PERLPATH}remote.pl splice ${LOCAL_ADDR} ${REMOTE_ADDR} ${REMOTE_SSH} ${PERLPATH}$a +.endif +.endfor + +# create certificates for TLS + +.for ip in ${REMOTE_ADDR} 127.0.0.1 +${ip}.crt: ca.crt client-ca.crt + openssl req -batch -new \ + -subj /L=OpenBSD/O=relayd-regress/OU=relayd/CN=${ip}/ \ + -nodes -newkey rsa -keyout ${ip}.key -x509 \ + -out $@ +.if empty (REMOTE_SSH) + ${SUDO} cp 127.0.0.1.crt /etc/ssl/ + ${SUDO} cp 127.0.0.1.key /etc/ssl/private/ +.else + scp ${REMOTE_ADDR}.crt root@${REMOTE_SSH}:/etc/ssl/ + scp ${REMOTE_ADDR}.key root@${REMOTE_SSH}:/etc/ssl/private/ + scp ca.crt ca.key ${REMOTE_SSH}: + scp client-ca.crt client-ca.key ${REMOTE_SSH}: +.endif + +${ip}-ec.crt: + openssl ecparam -name secp384r1 -genkey -noout \ + -out ${ip}-ec.key + openssl req -batch -new -x509 \ + -subj /L=OpenBSD/O=relayd-regress/OU=relayd/CN=${ip}/ \ + -key ${ip}-ec.key \ + -out $@ +.endfor + +ca.crt client-ca.crt: + openssl req -batch -new \ + -subj /L=OpenBSD/O=relayd-regress/OU=${@:R}/CN=root/ \ + -nodes -newkey rsa -keyout ${@:R}.key -x509 \ + -out $@ + +server.req client.req: + openssl req -batch -new \ + -subj /L=OpenBSD/O=relayd-regress/OU=${@:R}/CN=localhost/ \ + -nodes -newkey rsa -keyout ${@:R}.key \ + -out $@ + +server.crt: ca.crt server.req + openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt \ + -req -in server.req -out server.crt + +client.crt: client-ca.crt client.req + openssl x509 -CAcreateserial -CAkey client-ca.key -CA client-ca.crt \ + -req -in client.req -out client.crt + +${REGRESS_TARGETS:M*ssl*} ${REGRESS_TARGETS:M*https*}: server.crt client.crt +.if empty (REMOTE_SSH) +${REGRESS_TARGETS:M*ssl*} ${REGRESS_TARGETS:M*https*}: 127.0.0.1.crt +.else +${REGRESS_TARGETS:M*ssl*} ${REGRESS_TARGETS:M*https*}: ${REMOTE_ADDR}.crt +.endif + +# make perl syntax check for all args files + +.PHONY: syntax + +syntax: stamp-syntax + +stamp-syntax: ${ARGS} ${ARGS_EC} +.for a in ${ARGS} ${ARGS_EC} + @perl -c ${PERLPATH}$a +.endfor + @date >$@ + +.include blob - /dev/null blob + 7ca27a57a6d3f9b505f8b72907b4f3b144578565 (mode 644) --- /dev/null +++ regress/Proc.pm @@ -0,0 +1,205 @@ +# $OpenBSD: Proc.pm,v 1.13 2021/10/12 05:42:39 anton Exp $ + +# Copyright (c) 2010-2016 Alexander Bluhm +# +# 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 Errno; +use File::Basename; +use IO::File; +use POSIX; +use Time::HiRes qw(time alarm sleep); + +my %CHILDREN; + +sub kill_children { + my @pids = @_ ? @_ : keys %CHILDREN + or return; + my @perms; + foreach my $pid (@pids) { + if (kill(TERM => $pid) != 1 and $!{EPERM}) { + push @perms, $pid; + } + } + if (my @sudo = split(' ', $ENV{SUDO}) and @perms) { + local $?; # do not modify during END block + my @cmd = (@sudo, '/bin/kill', '-TERM', @perms); + system(@cmd); + } + delete @CHILDREN{@pids}; +} + +BEGIN { + $SIG{TERM} = $SIG{INT} = sub { + my $sig = shift; + kill_children(); + $SIG{TERM} = $SIG{INT} = 'DEFAULT'; + POSIX::raise($sig); + }; +} + +END { + kill_children(); + $SIG{TERM} = $SIG{INT} = 'DEFAULT'; +} + +sub new { + my $class = shift; + my $self = { @_ }; + $self->{down} ||= "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; + $self->{ppid} = $$; + return bless $self, $class; +} + +sub run { + my $self = shift; + + pipe(my $reader, my $writer) + or die ref($self), " pipe to child failed: $!"; + defined(my $pid = fork()) + or die ref($self), " fork child failed: $!"; + if ($pid) { + $CHILDREN{$pid} = 1; + $self->{pid} = $pid; + close($reader); + $self->{pipe} = $writer; + 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: $!"; + close($writer); + open(STDIN, '<&', $reader) + or die ref($self), " dup STDIN failed: $!"; + close($reader); + + do { + $self->child(); + print STDERR $self->{up}, "\n"; + $self->{begin} = time(); + $self->{func}->($self); + } while ($self->{redo}); + $self->{end} = time(); + print STDERR "Shutdown", "\n"; + if ($self->{timefile}) { + open(my $fh, '>>', $self->{timefile}) + or die ref($self), " open $self->{timefile} failed: $!"; + printf $fh "time='%s' duration='%.10g' ". + "forward='%s' test='%s'\n", + scalar(localtime(time())), $self->{end} - $self->{begin}, + $self->{forward}, basename($self->{testfile}); + } + + IO::Handle::flush(\*STDOUT); + IO::Handle::flush(\*STDERR); + POSIX::_exit(0); +} + +sub wait { + my $self = shift; + my $flags = shift; + + # if we a not the parent process, assume the child is still running + return 0 unless $self->{ppid} == $$; + + 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($?); + delete $CHILDREN{$pid} if WIFEXITED($?) || WIFSIGNALED($?); + return wantarray ? ($kid, $status, $code) : $kid; + } + return $kid; +} + +sub loggrep { + my $self = shift; + my($regex, $timeout) = @_; + + my $end; + $end = time() + $timeout if $timeout; + + do { + my($kid, $status, $code) = $self->wait(WNOHANG); + if ($kid > 0 && $status != 0 && !$self->{dryrun}) { + # 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 = grep { /$regex/ } <$fh>; + return wantarray ? @match : $match[0] 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; +} + +sub kill_child { + my $self = shift; + kill_children($self->{pid}); + return $self; +} + +1; blob - /dev/null blob + 0af36294e788e90a67daec4d06968b7a2c87ca92 (mode 644) --- /dev/null +++ regress/README @@ -0,0 +1,25 @@ +Run relayd regressions tests. The framework runs a client, and a +server, and a relayd. Currently the tcp and http forwarding code +path is covered. Each test creates a special relayd.conf and starts +those three processes. All processes write log files that are +checked for certain messages. The test arguments are kept in the +args-*.pl files. To find socket splicing bugs, each test is run +in both copy and splice mode. + +SUDO=doas +As relayd needs root privileges either run the tests as root or set +this variable and run make as a regular user. Only the code that +requires it, is run as root. + +KTRACE=ktrace +Set this variable if you want a ktrace output from relayd. Note that +ktrace is invoked after sudo as sudo would disable it. + +RELAYD=/usr/src/usr.sbin/relayd/obj/relayd +Start an alternative relayd program that is not in the path. + +LOCAL_ADDR, REMOTE_ADDR, REMOTE_SSH +Set these to run the relayd on a remote machine. As the client and +server run locally, network timing may influence the test results. + +Changes here have to be discussed with bluhm@, reyk@ or benno@. blob - /dev/null blob + 2a6aa926d1654d8e33cfc6112895d10e26aaabb5 (mode 644) --- /dev/null +++ regress/Relayd.pm @@ -0,0 +1,129 @@ +# $OpenBSD: Relayd.pm,v 1.20 2024/10/28 19:57:02 tb Exp $ + +# Copyright (c) 2010-2015 Alexander Bluhm +# +# 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 Relayd; +use parent 'Proc'; +use Carp; +use Cwd; +use Sys::Hostname; +use File::Basename; + +sub new { + my $class = shift; + my %args = @_; + $args{logfile} ||= "relayd.log"; + $args{up} ||= $args{dryrun} || "relay_launch: "; + $args{down} ||= $args{dryrun} ? "relayd.conf:" : "parent terminating"; + $args{func} = sub { Carp::confess "$class func may not be called" }; + $args{conffile} ||= "relayd.conf"; + $args{forward} + or croak "$class forward not given"; + my $self = Proc::new($class, %args); + ref($self->{protocol}) eq 'ARRAY' + or $self->{protocol} = [ split("\n", $self->{protocol} || "") ]; + ref($self->{relay}) eq 'ARRAY' + or $self->{relay} = [ split("\n", $self->{relay} || "") ]; + $self->{listenaddr} + or croak "$class listen addr not given"; + $self->{listenport} + or croak "$class listen port not given"; + $self->{connectaddr} + or croak "$class connect addr not given"; + $self->{connectport} + or croak "$class connect port not given"; + + my $test = basename($self->{testfile} || ""); + # tls does not allow a too long session id, so truncate it + substr($test, 25, length($test) - 25, "") if length($test) > 25; + open(my $fh, '>', $self->{conffile}) + or die ref($self), " conf file $self->{conffile} create failed: $!"; + print $fh "log state changes\n"; + print $fh "log host checks\n"; + print $fh "log connection\n"; + print $fh "prefork 1\n"; # only crashes of first child are observed + print $fh "table { $self->{connectaddr} }\n" + if defined($self->{table}); + + # substitute variables in config file + my $curdir = dirname($0) || "."; + my $objdir = getcwd(); + my $hostname = hostname(); + (my $host = $hostname) =~ s/\..*//; + my $connectport = $self->{connectport}; + my $connectaddr = $self->{connectaddr}; + my $listenaddr = $self->{listenaddr}; + my $listenport = $self->{listenport}; + + my @protocol = @{$self->{protocol}}; + my $proto = shift @protocol; + $proto = defined($proto) ? "$proto " : ""; + unshift @protocol, + $self->{forward} eq "splice" ? "tcp splice" : + $self->{forward} eq "copy" ? "tcp no splice" : + die ref($self), " invalid forward $self->{forward}" + unless grep { /splice/ } @protocol; + push @protocol, "tcp nodelay"; + print $fh "${proto}protocol proto-$test {"; + if ($self->{inspectssl}) { + $self->{listenssl} = $self->{forwardssl} = 1; + print $fh "\n\ttls ca cert ca.crt"; + print $fh "\n\ttls ca key ca.key password ''"; + } + if ($self->{verifyclient}) { + print $fh "\n\ttls client ca client-ca.crt"; + } + # substitute variables in config file + foreach (@protocol) { + s/(\$[a-z]+)/$1/eeg; + } + print $fh map { "\n\t$_" } @protocol; + print $fh "\n}\n"; + + my @relay = @{$self->{relay}}; + print $fh "relay relay-$test {"; + print $fh "\n\tprotocol proto-$test" + unless grep { /^protocol / } @relay; + my $tls = $self->{listenssl} ? " tls" : ""; + print $fh "\n\tlisten on $self->{listenaddr} ". + "port $self->{listenport}$tls" unless grep { /^listen / } @relay; + my $withtls = $self->{forwardssl} ? " with tls" : ""; + print $fh "\n\tforward$withtls to $self->{connectaddr} ". + "port $self->{connectport}" unless grep { /^forward / } @relay; + # substitute variables in config file + foreach (@relay) { + s/(\$[a-z]+)/$1/eeg; + } + print $fh map { "\n\t$_" } @relay; + print $fh "\n}\n"; + + return $self; +} + +sub child { + my $self = shift; + my @sudo = $ENV{SUDO} ? split(' ', $ENV{SUDO}) : (); + my @ktrace = $ENV{KTRACE} ? ($ENV{KTRACE}, "-i") : (); + my $relayd = $ENV{RELAYD} ? $ENV{RELAYD} : "relayd"; + my @cmd = (@sudo, @ktrace, $relayd, "-dvv", "-f", $self->{conffile}); + print STDERR "execute: @cmd\n"; + exec @cmd; + die ref($self), " exec '@cmd' failed: $!"; +} + +1; blob - /dev/null blob + 552bb82c7bf8c4576ba36e85ae17128fbebd2460 (mode 644) --- /dev/null +++ regress/Remote.pm @@ -0,0 +1,109 @@ +# $OpenBSD: Remote.pm,v 1.7 2016/05/03 19:13:04 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# +# 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; + +my %PIPES; + +sub close_pipes { + my @pipes = @_ ? @_ : keys %PIPES + or return; + foreach (@pipes) { + # file descriptor cannot be a hash key, so use hash value + my $fh = $PIPES{$_}; + # also print new line as close is delayed by forked processes + print $fh "close\n"; + close($fh); + } + sleep 1; # give other end a chance to finish process + delete @PIPES{@pipes}; +} + +END { + close_pipes(); +} + +sub new { + my $class = shift; + my %args = @_; + $args{logfile} ||= "remote.log"; + $args{up} ||= "listen sock: "; + $args{down} ||= $args{dryrun} ? "relayd.conf" : "parent terminating"; + $args{func} = sub { Carp::confess "$class func may not be called" }; + $args{remotessh} + or croak "$class remote ssh host not given"; + $args{forward} + or croak "$class forward not given"; + my $self = Proc::new($class, %args); + $self->{listenaddr} + or croak "$class listen addr not given"; + $self->{connectaddr} + or croak "$class connect addr not given"; + $self->{connectport} + or croak "$class connect port not given"; + return $self; +} + +sub run { + my $self = Proc::run(shift, @_); + $PIPES{$self->{pipe}} = $self->{pipe}; + return $self; +} + +sub up { + my $self = Proc::up(shift, @_); + my $lsock = $self->loggrep(qr/^listen sock: /) + or croak ref($self), " no 'listen sock: ' in $self->{logfile}"; + 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; + + my @opts = $ENV{SSH_OPTIONS} ? split(' ', $ENV{SSH_OPTIONS}) : (); + my @sudo = $ENV{SUDO} ? "SUDO=$ENV{SUDO}" : (); + my @ktrace = $ENV{KTRACE} ? "KTRACE=$ENV{KTRACE}" : (); + my @relayd = $ENV{RELAYD} ? "RELAYD=$ENV{RELAYD}" : (); + my $dir = dirname($0); + $dir = getcwd() if ! $dir || $dir eq "."; + my @cmd = ("ssh", @opts, $self->{remotessh}, + @sudo, @ktrace, @relayd, "perl", + "-I", $dir, "$dir/".basename($0), $self->{forward}, + $self->{listenaddr}, $self->{connectaddr}, $self->{connectport}, + ($self->{testfile} ? "$dir/".basename($self->{testfile}) : ())); + print STDERR "execute: @cmd\n"; + exec @cmd; + die ref($self), " exec '@cmd' failed: $!"; +} + +sub close_child { + my $self = shift; + close_pipes(delete $self->{pipe}); + return $self; +} + +1; blob - /dev/null blob + 8f1d4f3ce88ae30d869c020ecb717db8309475d0 (mode 644) --- /dev/null +++ regress/Server.pm @@ -0,0 +1,108 @@ +# $OpenBSD: Server.pm,v 1.15 2021/12/22 11:50:28 bluhm Exp $ + +# Copyright (c) 2010-2021 Alexander Bluhm +# +# 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 Config; +use Socket qw(:DEFAULT IPPROTO_TCP TCP_NODELAY); +use Socket6; +use IO::Socket::IP; +use IO::Socket::SSL; + +sub new { + my $class = shift; + my %args = @_; + $args{logfile} ||= "server.log"; + $args{up} ||= "Accepted"; + my $self = Proc::new($class, %args); + $self->{listendomain} + or croak "$class listen domain not given"; + $SSL_ERROR = ""; + my $iosocket = $self->{ssl} ? "IO::Socket::SSL" : "IO::Socket::IP"; + my $ls = $iosocket->new( + Proto => "tcp", + ReuseAddr => 1, + Domain => $self->{listendomain}, + # IO::Socket::IP calls the domain family + Family => $self->{listendomain}, + $self->{listenaddr} ? (LocalAddr => $self->{listenaddr}) : (), + $self->{listenport} ? (LocalPort => $self->{listenport}) : (), + SSL_server => 1, + SSL_key_file => "server.key", + SSL_cert_file => "server.crt", + SSL_verify_mode => SSL_VERIFY_NONE, + ) or die ref($self), " $iosocket socket failed: $!,$SSL_ERROR"; + if ($self->{sndbuf}) { + setsockopt($ls, SOL_SOCKET, SO_SNDBUF, + pack('i', $self->{sndbuf})) + or die ref($self), " set SO_SNDBUF failed: $!"; + } + if ($self->{rcvbuf}) { + setsockopt($ls, SOL_SOCKET, SO_RCVBUF, + pack('i', $self->{rcvbuf})) + or die ref($self), " set SO_RCVBUF failed: $!"; + } + my $packstr = $Config{longsize} == 8 ? 'ql!' : + $Config{byteorder} == 1234 ? 'lxxxxl!xxxx' : 'xxxxll!'; + if ($self->{sndtimeo}) { + setsockopt($ls, SOL_SOCKET, SO_SNDTIMEO, + pack($packstr, $self->{sndtimeo}, 0)) + or die ref($self), " set SO_SNDTIMEO failed: $!"; + } + if ($self->{rcvtimeo}) { + setsockopt($ls, SOL_SOCKET, SO_RCVTIMEO, + pack($packstr, $self->{rcvtimeo}, 0)) + or die ref($self), " set SO_RCVTIMEO failed: $!"; + } + setsockopt($ls, IPPROTO_TCP, TCP_NODELAY, pack('i', 1)) + or die ref($self), " set TCP_NODELAY failed: $!"; + listen($ls, 1) + or die ref($self), " socket listen 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; + + # in case we redo the accept, shutdown the old one + shutdown(\*STDOUT, SHUT_WR); + delete $self->{as}; + + my $as = $self->{ls}->accept() + or die ref($self)," ",ref($self->{ls}), + " socket accept failed: $!,$SSL_ERROR"; + print STDERR "accept sock: ",$as->sockhost()," ",$as->sockport(),"\n"; + print STDERR "accept peer: ",$as->peerhost()," ",$as->peerport(),"\n"; + if ($self->{ssl}) { + print STDERR "ssl version: ",$as->get_sslversion(),"\n"; + print STDERR "ssl cipher: ",$as->get_cipher(),"\n"; + print STDERR "ssl peer certificate:\n", + $as->dump_peer_certificate(); + } + + *STDIN = *STDOUT = $self->{as} = $as; +} + +1; blob - /dev/null blob + 28e53a9212642ffef6336db5db6dc037a490f692 (mode 644) --- /dev/null +++ regress/args-default.pl @@ -0,0 +1,11 @@ +# test default values + +use strict; +use warnings; + +our %args = ( + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 23b7c3faf73c7a8c60de5fc7fecadf667411eaf7 (mode 644) --- /dev/null +++ regress/args-dryrun.pl @@ -0,0 +1,20 @@ +# test broken config + +use strict; +use warnings; + +our %args = ( + client => { + noclient => 1, + }, + relayd => { + protocol => [ "foo" ], + dryrun => qr/invalid protocol type: foo/, + }, + server => { + noserver => 1, + }, + nocheck => 1, +); + +1; blob - /dev/null blob + f5acbd1ecf9bb88305ff06ea808c775e1672c2b9 (mode 644) --- /dev/null +++ regress/args-http-append-header.pl @@ -0,0 +1,40 @@ +# test appending headers, both directions + +use strict; +use warnings; + +my %header_client = ( + "X-Header-Client" => "ABC", +); +my %header_server = ( + "X-Header-Server" => "XYZ", +); +our %args = ( + client => { + func => \&http_client, + header => \%header_client, + loggrep => { + "X-Header-Server: XYZ" => 1, + "X-Header-Server: xyz" => 1, + }, + }, + relayd => { + protocol => [ "http", + 'match request header append "X-Header-Client" value "abc"', + 'match response header append "X-Header-Server" value "xyz"', + 'match request header log "X-Header*"', + 'match response header log "X-Header*"', + ], + loggrep => { qr/ (?:done|last write \(done\)), \[X-Header-Client: ABC\]\ GET \{X-Header-Server: XYZ\};/ => 1 }, + }, + server => { + func => \&http_server, + header => \%header_server, + loggrep => { + "X-Header-Client: ABC" => 1, + "X-Header-Client: abc" => 1, + }, + }, +); + +1; blob - /dev/null blob + db13a06ae3d9dd11d4aeb05fffb40e4ff81061df (mode 644) --- /dev/null +++ regress/args-http-append.pl @@ -0,0 +1,27 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + loggrep => { + 'X-Server-Append: \d+\.\d+\.\d+\.\d+:\d+$' => 1, + 'Set-Cookie: a=b\;' => 1, + }, + }, + relayd => { + protocol => [ "http", + 'match request header append X-Client-Append value \ + "$REMOTE_ADDR:$REMOTE_PORT"', + 'match response header append X-Server-Append value \ + "$SERVER_ADDR:$SERVER_PORT" \ + cookie set "a" value "b"', + ], + }, + server => { + func => \&http_server, + loggrep => { 'X-Client-Append: \d+\.\d+\.\d+\.\d+:\d+$' => 1 }, + }, +); + +1; blob - /dev/null blob + 0339d897a232a01d84cd0fcc7c71c74b9b33408d (mode 644) --- /dev/null +++ regress/args-http-callback.pl @@ -0,0 +1,54 @@ +# test http connection over http relay invoking the callback. +# The client uses a bad method in the second request. +# Check that the relay handles the input after the error correctly. + +use strict; +use warnings; + +my @lengths = (4, 3); +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +PUT /4 HTTP/1.1 +Host: foo.bar +Content-Length: 4 + +123 +XXX +PUT /3 HTTP/1.1 +Host: foo.bar +Content-Length: 3 + +12 +EOF + print STDERR "LEN: 4\n"; + print STDERR "LEN: 3\n"; + # relayd does not forward the first request if the second one + # is invalid. So do not expect any response. + #http_response($self, "without len"); + }, + http_vers => ["1.1"], + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/, malformed, PUT/ => 1, + }, + }, + server => { + func => \&http_server, + # The server does not get any connection. + noserver => 1, + nocheck => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + c6808c9815954754f3c96d07a5e2378155322b8b (mode 644) --- /dev/null +++ regress/args-http-change-cookie.pl @@ -0,0 +1,27 @@ +use strict; +use warnings; + +my $name = "Set-Cookie"; +my %header = ("$name" => [ "test=a;", "test=b;" ]); +our %args = ( + client => { + func => \&http_client, + loggrep => { + qr/$name: test=c/ => 1, + } + }, + relayd => { + protocol => [ "http", + 'match response header set "'.$name.'" value "test=c"', + ], + }, + server => { + func => \&http_server, + header => \%header, + loggrep => { + qr/$name: test=a/ => 1 + }, + }, +); + +1; blob - /dev/null blob + c4070b2a76b4c2827151c35216199f85f9274428 (mode 644) --- /dev/null +++ regress/args-http-change-path.pl @@ -0,0 +1,27 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + loggrep => { + qr/GET \/251 HTTP\/1\.0/ => 1, + }, + }, + relayd => { + protocol => [ "http", + 'match request path set "*" value "/foopath" \ + url log "*"', + ], + loggrep => { qr/ (?:done|last write \(done\)), \[foo.bar\/foopath\]/ => 1 }, + }, + server => { + func => \&http_server, + loggrep => { + qr/GET \/foopath HTTP\/1\.0/ => 1, + }, + }, + len => 8, +); + +1; blob - /dev/null blob + fb38379b5ad120e0be40024c444e856f82fbfb80 (mode 644) --- /dev/null +++ regress/args-http-change.pl @@ -0,0 +1,29 @@ +use strict; +use warnings; + +my %header = ("X-Test-Header" => "XOriginalValue"); +our %args = ( + client => { + func => \&http_client, + loggrep => { + qr/X-Test-Header: XChangedValue/ => 1, + qr/Host: foo.bar/ => 1, + } + }, + relayd => { + protocol => [ "http", + 'match request header set "Host" value "foobar.changed"', + 'match response header set "X-Test-Header" value "XChangedValue"', + ], + }, + server => { + func => \&http_server, + header => \%header, + loggrep => { + qr/X-Test-Header: XOriginalValue/ => 1, + qr/Host: foobar.changed/ => 1, + }, + }, +); + +1; blob - /dev/null blob + 3cb248a73bfc9984dd20bf75e6a23790a698bb7c (mode 644) --- /dev/null +++ regress/args-http-chunked-callback.pl @@ -0,0 +1,58 @@ +# test chunked http connection over http relay invoking the callback +# The client writes a bad chunk length in the second chunk. +# Check that the relay handles the input after the error correctly. + +use strict; +use warnings; + +my @lengths = ([4, 3]); +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +PUT /4/3 HTTP/1.1 +Host: foo.bar +Transfer-Encoding: chunked + +EOF + ${$self->{server}}->up; + print <<'EOF'; +4 +123 + +XXX +3 +12 + +0 + +EOF + print STDERR "LEN: 4\n"; + print STDERR "LEN: 3\n"; + # relayd does not forward the first chunk if the second one + # is invalid. So do not expect any response. + #http_response($self, "without len"); + }, + http_vers => ["1.1"], + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/, invalid chunk size, PUT/ => 1, + }, + }, + server => { + down => "Server missing chunk size", + func => sub { errignore(@_); http_server(@_); }, + nocheck => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + ffe0751ab46700c9656c94b423558ce0c7150b07 (mode 644) --- /dev/null +++ regress/args-http-chunked-invalid.pl @@ -0,0 +1,52 @@ +# Test parsing of invalid chunk length values +# We force multiple connections since relayd will abort the connection +# when it encounters a bogus chunk size. +# + +use strict; +use warnings; + +my @lengths = (7, 6, 5, 4, 3, 2); +my @chunks = ("0x4", "+3", "-0", "foo", "dead beef", "Ff0"); +our %args = ( + client => { + func => sub { + my $self = shift; + my $chunk = shift(@chunks); + $self->{redo} = int(@chunks); + print <<"EOF"; +PUT /4/3 HTTP/1.1 +Host: foo.bar +Transfer-Encoding: chunked + +$chunk + +EOF + foreach (@lengths) { + print STDERR "LEN: $_\n"; + } + # relayd does not forward the first chunk if the second one + # is invalid. So do not expect any response. + #http_response($self, "without len"); + }, + http_vers => ["1.1"], + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/, invalid chunk size, PUT/ => 5, + }, + }, + server => { + func => \&http_server, + nocheck => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + 9a7e36dc84ac8142ec8e9518f3dcfd5ff749634b (mode 644) --- /dev/null +++ regress/args-http-chunked-put.pl @@ -0,0 +1,38 @@ +# test chunked http request over http relay + +use strict; +use warnings; + +my @lengths = ([ 251, 10000, 10 ], 1, [2, 3]); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + http_vers => ["1.1"], + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log Transfer-Encoding", + "match response header log bar", + ], + loggrep => { + qr/\[Transfer-Encoding: chunked\]/ => 1, + qr/\[\(null\)\]/ => 0, + }, + }, + server => { + func => \&http_server, + }, + lengths => \@lengths, + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "fccd8d69acceb0cc35f2fd4e2f6938d3", + "c47658d102d5b989e0da09ce403f7463", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + ], +); + +1; blob - /dev/null blob + 5b0abfe3f806351cb3bfcb67250b7ca9c12cdb34 (mode 644) --- /dev/null +++ regress/args-http-chunked.pl @@ -0,0 +1,37 @@ +# test chunked http 1.1 connection over http relay + +use strict; +use warnings; + +my @lengths = ([ 251, 10000, 10 ], 1, [2, 3]); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + http_vers => ["1.1"], + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log Transfer-Encoding", + ], + loggrep => { + "{Transfer-Encoding: chunked}" => 1, + qr/\[\(null\)\]/ => 0, + }, + }, + server => { + func => \&http_server, + }, + lengths => \@lengths, + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "fccd8d69acceb0cc35f2fd4e2f6938d3", + "c47658d102d5b989e0da09ce403f7463", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + ], +); + +1; blob - /dev/null blob + 01dfa221debdccc204a60a0b80de363345ece275 (mode 644) --- /dev/null +++ regress/args-http-contentlength-get.pl @@ -0,0 +1,50 @@ +# Test to verify that relayd strips Content-Length and body +# from GET requests. + +use strict; +use warnings; + +my $payload_len = 64; +our %args = ( + client => { + func => sub { + my $self = shift; + my @request_stream = split("\n", <<"EOF", -1); +GET http://foo.bar/$payload_len HTTP/1.1 +Content-Length: $payload_len + +foo=bar + +EOF + pop @request_stream; + print map { "$_\r\n" } @request_stream; + print STDERR map { ">>> $_\n" } @request_stream; + $self->{method} = 'GET'; + http_response($self, $payload_len); + }, + loggrep => { + qr/Content-Length: $payload_len/ => 2, + qr/foo=bar/ => 1, + }, + http_vers => ["1.1"], + nocheck => 1, + }, + relayd => { + protocol => [ "http", + "match request path log \"*\"", + ], + loggrep => { + qr/, done, \[http:\/\/foo.bar\/$payload_len\] GET/ => 1, + }, + }, + server => { + func => \&http_server, + loggrep => { + qr/Content-Length: $payload_len/ => 1, + qr/foo=bar/ => 0, + }, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + 5b7ad2d9e3bcf2283cb16eee11670e9e23479557 (mode 644) --- /dev/null +++ regress/args-http-contentlength-invalid.pl @@ -0,0 +1,40 @@ +# Test that relayd aborts the connection if Content-Length is invalid +# We test "+0" because it is accepted by strtol(), sscanf(), etc +# but is not legal according to the RFC. + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + my $self = shift; + print <<"EOF"; +PUT /1 HTTP/1.1 +Host: www.foo.com +Content-Length: +0 + +EOF + # no http_response($self, 1); + }, + http_vers => ["1.1"], + nocheck => 1, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log Host", + ], + loggrep => { + qr/, invalid$/ => 1, + qr/\[Host: www.foo.com\] PUT/ => 0, + }, + }, + server => { + func => \&http_server, + nocheck => 1, + noserver => 1, + } +); + +1; blob - /dev/null blob + 2362af457c50e3692e20df3d21e0e45ae553a26a (mode 644) --- /dev/null +++ regress/args-http-contentlength.pl @@ -0,0 +1,37 @@ +# test persistent http 1.1 connection and grep for content length + +use strict; +use warnings; + +my @lengths = (1, 2, 0, 3, 4); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log Content-Length", + ], + loggrep => [ map { "Content-Length: $_" } @lengths ], + }, + server => { + func => \&http_server, + }, + lengths => \@lengths, + md5 => [ + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "d41d8cd98f00b204e9800998ecf8427e", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "d41d8cd98f00b204e9800998ecf8427e", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + ], +); + +1; blob - /dev/null blob + 710ed854c45f2074818f17b6473477d8fea9520c (mode 644) --- /dev/null +++ regress/args-http-expect.pl @@ -0,0 +1,28 @@ +use strict; +use warnings; + +my @lengths = (21); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + path => "query?foo=bar&ok=yes", + }, + relayd => { + protocol => [ "http", + 'block request', + 'block request query log "ok"', + 'pass query log "foo" value "bar"', + ], + loggrep => { + qr/\[foo: bar\]/ => 2, + qr/\[ok: yes\]/ => 0, + }, + }, + server => { + func => \&http_server, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + 340853ea25ea466f3151363c47334872145cac17 (mode 644) --- /dev/null +++ regress/args-http-filter-block.pl @@ -0,0 +1,25 @@ +# test http block + +use strict; +use warnings; + +my @lengths = (1, 2, 0, 3); +our %args = ( + client => { + func => \&http_client, + loggrep => qr/Client missing http 3 response/, + lengths => \@lengths, + }, + relayd => { + protocol => [ "http", + 'block request path "/3"', + ], + loggrep => qr/Forbidden/, + }, + server => { + func => \&http_server, + }, + lengths => [1, 2, 0], +); + +1; blob - /dev/null blob + d72f99ddb4d2df7655843940fc08997269aa563c (mode 644) --- /dev/null +++ regress/args-http-filter-contentlength.pl @@ -0,0 +1,27 @@ +# test http connection with request filter and explicit content length 0 + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + header => { + 'Content-Length' => 0, + }, + len => 1, + }, + relayd => { + protocol => [ "http", + 'block request path "/2"', + ], + loggrep => qr/done/, + }, + server => { + func => \&http_server, + }, + len => 1, + md5 => "68b329da9893e34099c7d8ad5cb9c940", +); + +1; blob - /dev/null blob + 3ac271660bbb491a41734c386a9a8f06f8e5f51e (mode 644) --- /dev/null +++ regress/args-http-filter-cookie.pl @@ -0,0 +1,32 @@ +# test http block cookies + +use strict; +use warnings; + +my @lengths = (1, 2, 3, 4); +my @cookies = ("med=thx; domain=.foo.bar; path=/; expires=Mon, 27-Oct-2014 04:11:56 GMT;", "", "", ""); +our %args = ( + client => { + func => \&http_client, + loggrep => { + qr/Client missing http 1 response/ => 2, + qr/Set-Cookie: a\=b\;/ => 6, + }, + cookies => \@cookies, + lengths => \@lengths, + }, + relayd => { + protocol => [ "http", + 'block request cookie log "med" value "thx"', + 'match response cookie append "a" value "b" tag "cookie"', + 'pass tagged "cookie"', + ], + loggrep => qr/Forbidden, \[Cookie: med=thx.*/, + }, + server => { + func => \&http_server, + }, + lengths => [2, 3, 4], +); + +1; blob - /dev/null blob + e646178c149c75349a0beaa989d62bd840ba995a (mode 644) --- /dev/null +++ regress/args-http-filter-method.pl @@ -0,0 +1,27 @@ +# test method filtering + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + method => 'HEAD', + loggrep => qr/HTTP\/1\.0 403 Forbidden/, + httpnok => 1, + nocheck => 1, + }, + relayd => { + protocol => [ "http", + 'return error', + 'block request method "HEAD" path log', + ], + loggrep => qr/403 Forbidden/, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + 3ef85eb455dca29c9873c31af24c93224cc7ad54 (mode 644) --- /dev/null +++ regress/args-http-filter-null-host.pl @@ -0,0 +1,33 @@ +# test http request with null Host header + +use strict; +use warnings; + + +my %header_client = ( + "Host" => "", +); + +our %args = ( + client => { + func => \&http_client, + header => \%header_client, + httpnok => 1, + nocheck => 1, + }, + relayd => { + protocol => [ "http", + 'pass', + 'block url "Host"', + 'return error', + ], +# loggrep => qr/Forbidden, \[Cookie: med=thx.*/, + }, + server => { + func => \&http_server, + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + fe03666de897826d9b4dfd8c72c687a3761d380e (mode 644) --- /dev/null +++ regress/args-http-filter-persistent.pl @@ -0,0 +1,41 @@ +# test persistent http connection with request filter + +use strict; +use warnings; + +my @lengths = (251, 16384, 0, 1, 2, 3, 4, 5); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + loggrep => qr/Client missing http 2 response/, + }, + relayd => { + protocol => [ "http", + 'block request path "/2"', + ], + loggrep => qr/Forbidden/, + }, + server => { + func => \&http_server, + }, + lengths => [251, 16384, 0, 1, 3, 4, 5], + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + ], +); + +1; blob - /dev/null blob + 691beb7d27f4d1e453db004672e093456dcd8c98 (mode 644) --- /dev/null +++ regress/args-http-filter-put-contentlength.pl @@ -0,0 +1,28 @@ +# test http put with request filter and request contentlength + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + header => { + 'Content-Length' => 1, + }, + len => 1, + method => 'PUT', + }, + relayd => { + protocol => [ "http", + 'match request path "/2"', + ], + loggrep => qr/done/, + }, + server => { + func => \&http_server, + }, + len => 1, + md5 => "68b329da9893e34099c7d8ad5cb9c940", +); + +1; blob - /dev/null blob + e1e57543b392ba470c20c5cef8fb51b92d3a9262 (mode 644) --- /dev/null +++ regress/args-http-filter-put.pl @@ -0,0 +1,25 @@ +# test http put with request filter + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 1, + method => 'PUT', + }, + relayd => { + protocol => [ "http", + 'block request path "/2"', + ], + loggrep => qr/done/, + }, + server => { + func => \&http_server, + }, + len => 1, + md5 => "68b329da9893e34099c7d8ad5cb9c940", +); + +1; blob - /dev/null blob + 64551ea3d9bf08293b091a9ee13f3491fc906f1f (mode 644) --- /dev/null +++ regress/args-http-filter-url-digest.pl @@ -0,0 +1,32 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + path => "a/b/c/d/e/f/gindex.html", + loggrep => [ + qr/403 Forbidden/, + qr/Server: OpenBSD relayd/, + qr/Connection: close/, + ], + nocheck => 1, + httpnok => 1, + }, + relayd => { + protocol => [ "http", + 'return error', + 'block request url log digest 0ac8ccfc03317891ae2820de10ee2167d31ebd16', + ], + loggrep => { + qr/Forbidden \(403 Forbidden\)/ => 1, + qr/\[0ac8ccfc03317891ae2820de10ee2167d31ebd16\]/ => 1, + }, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + 11aae0018687b40d05d9a639e70ba600e1715205 (mode 644) --- /dev/null +++ regress/args-http-filter-url-file.in @@ -0,0 +1,2 @@ +foo.bar/3 +foo.bar/0 blob - /dev/null blob + 06a7545c0f2338754904fcb52c576b82e54ccd8a (mode 644) --- /dev/null +++ regress/args-http-filter-url-file.pl @@ -0,0 +1,31 @@ +use strict; +use warnings; + +my @lengths = (1, 2, 4, 0, 3, 5); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + loggrep => { + qr/403 Forbidden/ => 4, + }, + }, + relayd => { + protocol => [ "http", + 'return error', + 'pass', + 'block request url log file "$curdir/args-http-filter-url-file.in" value "*" label "test_reject_label"', + ], + loggrep => { + qr/Forbidden/ => 4, + qr/\[test_reject_label\, foo\.bar\/0\]/ => 2, + qr/\[test_reject_label\, foo\.bar\/3\]/ => 2, + }, + }, + server => { + func => \&http_server, + }, + lengths => [1, 2, 4, 5], +); + +1; blob - /dev/null blob + ec325f230b658369b4f7c91c8bb10f40fe3a7498 (mode 644) --- /dev/null +++ regress/args-http-filter-url.pl @@ -0,0 +1,32 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + path => "a/b/c/d/e/f/gindex.html", + loggrep => [ + qr/403 Forbidden/, + qr/Server: OpenBSD relayd/, + qr/Connection: close/, + ], + httpnok => 1, + nocheck => 1, + }, + relayd => { + protocol => [ "http", + 'return error', + 'block request url log "foo.bar/a/b/"', + ], + loggrep => { + qr/Forbidden \(403 Forbidden\)/ => 1, + qr/\[foo.bar\/a\/b\// => 1, + }, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + bf282f7b998cbc7d6ce607ef74b41ffe9af50b27 (mode 644) --- /dev/null +++ regress/args-http-filter.pl @@ -0,0 +1,24 @@ +# test http connection with request filter, triggers lateconnect + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 1, + }, + relayd => { + protocol => [ "http", + 'match request path "/2"', + ], + loggrep => qr/done/, + }, + server => { + func => \&http_server, + }, + len => 1, + md5 => "68b329da9893e34099c7d8ad5cb9c940", +); + +1; blob - /dev/null blob + 235b5693dc25b1c7cac4d98025105d64b3ba6088 (mode 644) --- /dev/null +++ regress/args-http-hash.pl @@ -0,0 +1,26 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + path => "query?foobar", + }, + relayd => { + table => 1, + protocol => [ "http", + 'match request path hash "/query"', + 'match request path log "/query"', + ], + relay => 'forward to port $connectport', + loggrep => { + qr/ (?:done|last write \(done\)), \[\/query: foobar\]/ => 1, + }, + }, + server => { + func => \&http_server, + }, + len => 13, +); + +1; blob - /dev/null blob + 3176d05e33f87fb98cb6f56497938e1e231c1e7b (mode 644) --- /dev/null +++ regress/args-http-head-get.pl @@ -0,0 +1,45 @@ +use strict; +use warnings; + +my $payload_len = 64; +our %args = ( + client => { + func => sub { + my $self = shift; + my @request_stream = split("\n", <<"EOF", -1); +HEAD http://foo.bar/$payload_len HTTP/1.1 + +EOF + pop @request_stream; + print map { "$_\r\n" } @request_stream; + print STDERR map { ">>> $_\n" } @request_stream; + $self->{method} = 'HEAD'; + http_response($self, $payload_len); + @request_stream = split("\n", <<"EOF", -1); +GET http://foo.bar/$payload_len HTTP/1.1 + +EOF + pop @request_stream; + print map { "$_\r\n" } @request_stream; + print STDERR map { ">>> $_\n" } @request_stream; + $self->{method} = 'GET'; + http_response($self, $payload_len); + }, + http_vers => ["1.1"], + nocheck => 1, + }, + relayd => { + protocol => [ "http", + "match request path log \"*\"", + ], + loggrep => { + qr/, done, \[http:\/\/foo.bar\/$payload_len\] HEAD; \[http:\/\/foo.bar\/$payload_len\] GET/ => 1, + }, + }, + server => { + func => \&http_server, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + 1597e2766f833463afa2123efa4085b48630477f (mode 644) --- /dev/null +++ regress/args-http-headerlength.pl @@ -0,0 +1,26 @@ +use strict; +use warnings; + +my %header = ( "Host" => "www.example.com", "Set-Cookie" => "a="."X"x8192 ); +our %args = ( + client => { + func => \&http_client, + header => \%header, + httpnok => 1, + nocheck => 1, + loggrep => qr/HTTP\/1\.0 413 Payload Too Large/, + }, + relayd => { + protocol => [ "http", + 'return error', + 'pass', + ], + loggrep => qr/413 Payload Too Large/, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + cfc27e341b23d111b36ae81042564d734de5fe34 (mode 644) --- /dev/null +++ regress/args-http-headline-callback.pl @@ -0,0 +1,54 @@ +# test persistent http connection over http relay invoking the callback +# The client writes a bad header line in the second request. +# Check that the relay handles the input after the error correctly. + +use strict; +use warnings; + +my @lengths = (4, 3); +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +PUT /4 HTTP/1.1 +Host: foo.bar +Content-Length: 4 + +123 +PUT /3 HTTP/1.1 +XXX +Host: foo.bar +Content-Length: 3 + +12 +EOF + print STDERR "LEN: 4\n"; + print STDERR "LEN: 3\n"; + # relayd does not forward the first request if the second one + # is invalid. So do not expect any response. + #http_response($self, "without len"); + }, + http_vers => ["1.1"], + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/, malformed, PUT/ => 1, + }, + }, + server => { + func => \&http_server, + # The server does not get any connection. + noserver => 1, + nocheck => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + 3e86e0c8e50fc826b82ddc5e8aa121cd73da6211 (mode 644) --- /dev/null +++ regress/args-http-headline-close.pl @@ -0,0 +1,30 @@ +# test http connection over http relay +# The client writes an incomplete header line and closes the connection. +# Check that the relay establishes and also closes the session. + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + print "GET "; # missing new line + }, + nocheck => 1, + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/session 1 .*, done/ => 1, + }, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + a77e852ee083f1980b07d1f697d5915e7bffdca1 (mode 644) --- /dev/null +++ regress/args-http-host.pl @@ -0,0 +1,37 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +GET /1 HTTP/1.1 +Host: www.foo.com +Host: www.bar.com + +EOF + # no http_response($self, 1); + }, + http_vers => ["1.1"], + nocheck => 1, + method => "GET", + }, + relayd => { + protocol => [ "http", + "match request header log Host", + ], + loggrep => { + qr/, malformed header$/ => 1, + qr/\[Host: www.foo.com\] GET/ => 0, + qr/\[Host: www.bar.com\] GET/ => 0, + }, + }, + server => { + func => \&http_server, + noserver => 1, + nocheck => 1, + } +); + +1; blob - /dev/null blob + b34a580665fe7c35f0c07eadec4cefb5a494ee8d (mode 644) --- /dev/null +++ regress/args-http-host2.pl @@ -0,0 +1,34 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +GET http://www.foo.com/1 HTTP/1.1 + +EOF + http_response($self, 1); + }, + http_vers => ["1.1"], + nocheck => 1, + method => "GET", + }, + relayd => { + protocol => [ "http", + "match request header log Host", + "match request path log \"*\"", + ], + loggrep => { + qr/, malformed header$/ => 0, + qr/\[http:\/\/www.foo.com\/1\] GET/ => 1, + }, + }, + server => { + func => \&http_server, + nocheck => 1, + } +); + +1; blob - /dev/null blob + ab2201396ea666fb20d08cd7cc43b93ec5fd2913 (mode 644) --- /dev/null +++ regress/args-http-host3.pl @@ -0,0 +1,36 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +GET http://www.foo.com/1 HTTP/1.1 +Host: www.foo.com + +EOF + http_response($self, 1); + }, + http_vers => ["1.1"], + nocheck => 1, + method => "GET", + }, + relayd => { + protocol => [ "http", + "match request header log Host", + "match request path log \"*\"", + ], + loggrep => { + qr/, malformed host$/ => 0, + qr/\[http:\/\/www.foo.com\/1\] GET/ => 1, + qr/\[Host: www.foo.com\]/ => 1, + }, + }, + server => { + func => \&http_server, + nocheck => 1 + }, +); + +1; blob - /dev/null blob + 11787efe99000de9b719be1b95ca32b298ed8ba3 (mode 644) --- /dev/null +++ regress/args-http-host4.pl @@ -0,0 +1,37 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +GET http://www.foo.com/1 HTTP/1.1 +Host: www.bar.com + +EOF + # no http_response($self, 1); + }, + http_vers => ["1.1"], + nocheck => 1, + method => "GET", + }, + relayd => { + protocol => [ "http", + "match request header log Host", + "match request path log \"*\"", + ], + loggrep => { + qr/, malformed host$/ => 1, + qr/\[http:\/\/www.foo.com\/1\] GET/ => 0, + qr/\[Host: www.bar.com\]/ => 0, + }, + }, + server => { + func => \&http_server, + noserver => 1, + nocheck => 1, + } +); + +1; blob - /dev/null blob + c12f21d659d9d6f5e83d3c2e3405c37ce19514f4 (mode 644) --- /dev/null +++ regress/args-http-invalid-header1.pl @@ -0,0 +1,38 @@ +# Test that relayd aborts the connection if a header name has invalid chars + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + my $self = shift; + print <<"EOF"; +GET /1 HTTP/1.1 +Host: www.foo.com +X-Header Client: ABC + +EOF + # no http_response($self, 1); + }, + http_vers => ["1.1"], + nocheck => 1, + method => "GET", + }, + relayd => { + protocol => [ "http", + "match request header log Host", + ], + loggrep => { + qr/, malformed$/ => 1, + qr/\[Host: www.foo.com\] GET/ => 0, + }, + }, + server => { + func => \&http_server, + nocheck => 1, + noserver => 1, + } +); + +1; blob - /dev/null blob + cb9d8b3d7446344afa2e0dd7775e22bba8a6deb3 (mode 644) --- /dev/null +++ regress/args-http-invalid-header2.pl @@ -0,0 +1,38 @@ +# Test that relayd aborts the connection if a header include a NUL byte + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + my $self = shift; + print <<"EOF"; +GET /1 HTTP/1.1 +Host: www.foo.com +X-Header-Client: ABC\0D + +EOF + # no http_response($self, 1); + }, + http_vers => ["1.1"], + nocheck => 1, + method => "GET", + }, + relayd => { + protocol => [ "http", + "match request header log Host", + ], + loggrep => { + qr/, malformed$/ => 1, + qr/\[Host: www.foo.com\] GET/ => 0, + }, + }, + server => { + func => \&http_server, + nocheck => 1, + noserver => 1, + } +); + +1; blob - /dev/null blob + b103f402f4cf792664dab77b6211f3dd36a4e5c8 (mode 644) --- /dev/null +++ regress/args-http-label.pl @@ -0,0 +1,29 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + loggrep => { + qr/403 Forbidden/ => 1, + qr/Content-Type: text\/html/ => 1 + }, + path => "query?foo=bar&ok=yes", + httpnok => 1, + nocheck => 1, + }, + relayd => { + protocol => [ "http", + 'return error', + 'block', + 'match request query log "foo" value "bar" label "expect_foobar_label"', + ], + loggrep => qr/Forbidden.*403 Forbidden.*expect_foobar_label.*foo: bar/, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + 1e2602b1a6ad115ed83cb1b42002fdc284c6bdf8 (mode 644) --- /dev/null +++ regress/args-http-log.pl @@ -0,0 +1,27 @@ +# test http connection over http relay + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + }, + relayd => { + protocol => [ "http", + "match request header log Host", + "match response header log Server", + ], + loggrep => { + qr/\[Host: foo.bar\]/ => 1, + qr/\{Server: Perl\/[^\s]+\s*\};/ => 1, + }, + }, + server => { + func => \&http_server, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 7beec7496e0d0e55410c4a94a67febd90a7b5a8a (mode 644) --- /dev/null +++ regress/args-http-mark-marked.pl @@ -0,0 +1,22 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 33, + }, + relayd => { + protocol => [ "http", + 'match request path "/3*" value "*" tag RING0', + 'match request tagged RING0 tag RINGX', + ], + loggrep => { ", RINGX,.*done" => 1 }, + }, + server => { + func => \&http_server, + }, + len => 33, +); + +1; blob - /dev/null blob + 2eb8b42e3b3fe3e5bb50b34e806709f7e5a179e5 (mode 644) --- /dev/null +++ regress/args-http-mark-marked2.pl @@ -0,0 +1,43 @@ +# match and set header with tags + +use strict; +use warnings; + +my %header_client = ( + "User-Agent" => "Mozilla Bla", + "MyHeader" => "UnmatchableContent", +); + +our %args = ( + client => { + func => \&http_client, + header => \%header_client, + len => 33, + }, + relayd => { + protocol => [ "http", + # setting the User-Agent should succeed + 'match request header "User-Agent" value "Mozilla*" tag BORK', + 'match request header set "User-Agent" value "BORK" tagged BORK', + 'match request header log "User-Agent"', + # setting MyHeader should not happen + 'match request header "MyHeader" value "SomethingDifferent" tag FOO', + 'match request header set "MyHeader" value "FOO" tagged FOO', + 'match request header log "MyHeader"', + ], + loggrep => { + '\[User-Agent: BORK\]' => 1, + 'MyHeader: FOO' => 0, + }, + }, + server => { + func => \&http_server, + loggrep => { + "User-Agent: BORK" => 1, + "MyHeader: FOO" => 0, + } + }, + len => 33, +); + +1; blob - /dev/null blob + dfaa5290947cc189132d765bd3699a66daf3cdc7 (mode 644) --- /dev/null +++ regress/args-http-mark.pl @@ -0,0 +1,23 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + path => "foobar?path", + }, + relayd => { + protocol => [ "http", + 'match request path "/foobar" value "*" tag RING0', + 'block request', + 'pass request quick tagged RING0', + ], + loggrep => { ", RING0,.*done" => 1 }, + }, + server => { + func => \&http_server, + }, + len => 12, +); + +1; blob - /dev/null blob + 2cc16566451ce55ef13e1a489b9b7152942e8eb6 (mode 644) --- /dev/null +++ regress/args-http-multi.pl @@ -0,0 +1,25 @@ +# test 50 http get with length 1 over http relay + +use strict; +use warnings; + +my @lengths = map { 1 } (1..50); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + method => "GET", + }, + relayd => { + protocol => [ "http" ], + loggrep => { + qr/, (?:done|last write \(done\)), GET/ => (1 + @lengths), + }, + }, + server => { + func => \&http_server, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + b9e052b8c998cf5113bac4c639e5a4c2a6b03f46 (mode 644) --- /dev/null +++ regress/args-http-persistent.pl @@ -0,0 +1,45 @@ +# test persistent http 1.1 connection over http relay + +use strict; +use warnings; + +my @lengths = (251, 16384, 0, 1, 2, 3, 4, 5); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/, (?:done|last write \(done\))/ => (1 + @lengths), + } + }, + server => { + func => \&http_server, + }, + lengths => \@lengths, + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + ], +); + +1; blob - /dev/null blob + 380d7166bdedefe3423681ed93177ce383c3756a (mode 644) --- /dev/null +++ regress/args-http-put-multi.pl @@ -0,0 +1,25 @@ +# test 50 http put with length 1 over http relay + +use strict; +use warnings; + +my @lengths = map { 1 } (1..50); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http" ], + loggrep => { + qr/, (?:done|last write \(done\)), PUT/ => (1 + @lengths), + }, + }, + server => { + func => \&http_server, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + 5fa727db7f53d7e293f6f0379c315036296287b8 (mode 644) --- /dev/null +++ regress/args-http-put.pl @@ -0,0 +1,46 @@ +# test persistent http 1.1 put over http relay + +use strict; +use warnings; + +my @lengths = (251, 16384, 0, 1, 2, 3, 4, 5); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/, (?:done|last write \(done\))/ => (1 + @lengths), + } + }, + server => { + func => \&http_server, + }, + lengths => \@lengths, + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + ], +); + +1; blob - /dev/null blob + 6a57f89bc4a3b6cc4ec59cda68d324485381525d (mode 644) --- /dev/null +++ regress/args-http-remove.pl @@ -0,0 +1,33 @@ +use strict; +use warnings; + +my %header = ( + "X-Header-Foo" => "foo", + "X-Header-Bar" => "bar", +); +our %args = ( + client => { + func => \&http_client, + loggrep => { + "X-Header-Foo: foo" => 0, + "X-Header-Bar: bar" => 1, + }, + }, + relayd => { + protocol => [ "http", + 'match response header remove X-Header-Foo', + 'match response header log "*Foo"', + ], + loggrep => { qr/ (?:done|last write \(done\)), GET \{X-Header-Foo: foo \(removed\)\s*\};/ => 1 }, + }, + server => { + func => \&http_server, + header => \%header, + loggrep => { + "X-Header-Foo: foo" => 1, + "X-Header-Bar: bar" => 1, + }, + }, +); + +1; blob - /dev/null blob + 686729f129159785c982b99f399fbd467d50be0b (mode 644) --- /dev/null +++ regress/args-http-return.pl @@ -0,0 +1,27 @@ +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + # XXX add more paths to match a case where it pass + path => "query?foo=bar&ok=yes", + nocheck => 1, + httpnok => 1, + }, + relayd => { + protocol => [ "http", + 'return error', + 'pass', + 'block request query log "foo" value "bar" label \ + "expect_foobar_return_test"', + ], + loggrep => { 'Forbidden \(403 Forbidden\), \[expect_foobar_return_test, foo: bar\]' => 1 }, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + e8e6680b35cada26662040d128f7cc00f6abadcc (mode 644) --- /dev/null +++ regress/args-http-slow-consumer.pl @@ -0,0 +1,48 @@ +# test that a slow (in this case sleeping) client causes relayd to slow down +# reading from the server (instead of balooning its buffers) + +use strict; +use warnings; +use Errno ':POSIX'; + +my @errors = (EWOULDBLOCK); +my $errors = "(". join("|", map { $! = $_ } @errors). ")"; + +my $size = 2**21; + +our %args = ( + client => { + fast => 1, + max => 100, + func => sub { + my $self = shift; + http_request($self , $size, "1.0", ""); + http_response($self , $size); + print STDERR "going to sleep\n"; + ${$self->{server}}->loggrep(qr/blocked write/, 8) + or die "no blocked write in server.log"; + read_char($self, $size); + return; + }, + rcvbuf => 2**12, + nocheck => 1, + }, + relayd => { + protocol => [ "http", + "tcp socket buffer 1024", + "match request header log", + "match request path log", + ], + }, + server => { + fast => 1, + func => \&http_server, + sndbuf => 2**12, + sndtimeo => 2, + loggrep => qr/blocked write .*: $errors/, + + }, + lengths => [$size], +); + +1; blob - /dev/null blob + 3c9a4f3fc9bd3772e447c4156db9ab1b87610651 (mode 644) --- /dev/null +++ regress/args-http-tcp.pl @@ -0,0 +1,17 @@ +# test http connection over tcp relay + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + }, + server => { + func => \&http_server, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 180fe00d13811ecddaef35c59b1226a30e5b64cf (mode 644) --- /dev/null +++ regress/args-http.pl @@ -0,0 +1,20 @@ +# test http connection over http relay + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + }, + relayd => { + protocol => [ "http" ], + }, + server => { + func => \&http_server, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + f017f7c5519e956fbc7c6c2c66c7398fdf229b11 (mode 644) --- /dev/null +++ regress/args-https-callback.pl @@ -0,0 +1,58 @@ +# test https connection over http relay invoking the callback. +# The client uses a bad method in the second request. +# Check that the relay handles the input after the error correctly. + +use strict; +use warnings; + +my @lengths = (4, 3); +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +PUT /4 HTTP/1.1 +Host: foo.bar +Content-Length: 4 + +123 +XXX +PUT /3 HTTP/1.1 +Host: foo.bar +Content-Length: 3 + +12 +EOF + print STDERR "LEN: 4\n"; + print STDERR "LEN: 3\n"; + # relayd does not forward the first request if the second one + # is invalid. So do not expect any response. + #http_response($self, "without len"); + }, + ssl => 1, + http_vers => ["1.1"], + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + forwardssl => 1, + listenssl => 1, + loggrep => { + qr/, malformed, PUT/ => 1, + }, + }, + server => { + func => \&http_server, + ssl => 1, + # The server does not get any connection. + noserver => 1, + nocheck => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + a4fe57a33b95818e17cc1caed189b34baeae7d3b (mode 644) --- /dev/null +++ regress/args-https-chunked-callback.pl @@ -0,0 +1,59 @@ +# test chunked https connection over http relay invoking the callback +# The client writes a bad chunk length in the second chunk. +# Check that the relay handles the input after the error correctly. + +use strict; +use warnings; + +my @lengths = ([4, 3]); +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +PUT /4/3 HTTP/1.1 +Host: foo.bar +Transfer-Encoding: chunked + +4 +123 + +XXX +3 +12 + +0 + +EOF + print STDERR "LEN: 4\n"; + print STDERR "LEN: 3\n"; + # relayd does not forward the first chunk if the second one + # is invalid. So do not expect any response. + #http_response($self, "without len"); + }, + ssl => 1, + http_vers => ["1.1"], + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + forwardssl => 1, + listenssl => 1, + loggrep => { + qr/, invalid chunk size, PUT/ => 1, + }, + }, + server => { + func => \&http_server, + # relayd only connects but does no ssl handshake + #ssl => 1, + nocheck => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + 32c950b10d4d25ce03ca7c61db1a310ab9fbd69c (mode 644) --- /dev/null +++ regress/args-https-chunked-put.pl @@ -0,0 +1,42 @@ +# test chunked https request over http relay + +use strict; +use warnings; + +my @lengths = ([ 251, 10000, 10 ], 1, [2, 3]); +our %args = ( + client => { + func => \&http_client, + ssl => 1, + lengths => \@lengths, + http_vers => ["1.1"], + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log Transfer-Encoding", + "match response header log bar", + ], + forwardssl => 1, + listenssl => 1, + loggrep => { + qr/\[Transfer-Encoding: chunked\]/ => 1, + qr/\[\(null\)\]/ => 0, + }, + }, + server => { + func => \&http_server, + ssl => 1, + }, + lengths => \@lengths, + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "fccd8d69acceb0cc35f2fd4e2f6938d3", + "c47658d102d5b989e0da09ce403f7463", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + ], +); + +1; blob - /dev/null blob + f13f5897271812e4b06caedd14213fcd158fc859 (mode 644) --- /dev/null +++ regress/args-https-chunked.pl @@ -0,0 +1,41 @@ +# test chunked https 1.1 connection over http relay + +use strict; +use warnings; + +my @lengths = ([ 251, 10000, 10 ], 1, [2, 3]); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + http_vers => ["1.1"], + ssl => 1, + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log Transfer-Encoding", + ], + loggrep => { + "{Transfer-Encoding: chunked}" => 1, + qr/\[\(null\)\]/ => 0, + }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + ssl => 1, + }, + lengths => \@lengths, + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "fccd8d69acceb0cc35f2fd4e2f6938d3", + "c47658d102d5b989e0da09ce403f7463", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + ], +); + +1; blob - /dev/null blob + 227d255da8f26687232004b292c773901b3198b4 (mode 644) --- /dev/null +++ regress/args-https-contentlength.pl @@ -0,0 +1,41 @@ +# test persistent https 1.1 connection and grep for content length + +use strict; +use warnings; + +my @lengths = (1, 2, 0, 3, 4); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + ssl => 1, + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log Content-Length", + ], + loggrep => [ map { "Content-Length: $_" } @lengths ], + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + ssl => 1, + }, + lengths => \@lengths, + md5 => [ + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "d41d8cd98f00b204e9800998ecf8427e", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "d41d8cd98f00b204e9800998ecf8427e", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + ], +); + +1; blob - /dev/null blob + 5272c6a44a53359d44d88e089fc64f50520ad33b (mode 644) --- /dev/null +++ regress/args-https-filter-persistent.pl @@ -0,0 +1,48 @@ +# test persistent https connection with request filter + +use strict; +use warnings; + +my @lengths = (251, 16384, 0, 1, 2, 3, 4, 5); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + loggrep => qr/Client missing http 2 response/, + ssl => 1, + }, + relayd => { + protocol => [ "http", + 'block request path "/2"', + ], + loggrep => [ + qr/tls, tls client/ => 1, + qr/Forbidden/ => 1, + ], + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + ssl => 1, + }, + lengths => [251, 16384, 0, 1, 3, 4, 5], + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + ], +); + +1; blob - /dev/null blob + 0cd1600dc53e7ad4a54b1c9c911bab2ce79febac (mode 644) --- /dev/null +++ regress/args-https-headline-callback.pl @@ -0,0 +1,58 @@ +# test persistent https connection over http relay invoking the callback +# The client writes a bad header line in the second request. +# Check that the relay handles the input after the error correctly. + +use strict; +use warnings; + +my @lengths = (4, 3); +our %args = ( + client => { + func => sub { + my $self = shift; + print <<'EOF'; +PUT /4 HTTP/1.1 +Host: foo.bar +Content-Length: 4 + +123 +PUT /3 HTTP/1.1 +XXX +Host: foo.bar +Content-Length: 3 + +12 +EOF + print STDERR "LEN: 4\n"; + print STDERR "LEN: 3\n"; + # relayd does not forward the first request if the second one + # is invalid. So do not expect any response. + #http_response($self, "without len"); + }, + ssl => 1, + http_vers => ["1.1"], + lengths => \@lengths, + method => "PUT", + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + forwardssl => 1, + listenssl => 1, + loggrep => { + qr/, malformed, PUT/ => 1, + }, + }, + server => { + func => \&http_server, + ssl => 1, + # The server does not get any connection. + noserver => 1, + nocheck => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + 9ae14f06d3cfc4f1c6d740ead38f769a81ea7bb5 (mode 644) --- /dev/null +++ regress/args-https-headline-close.pl @@ -0,0 +1,34 @@ +# test https connection over http relay +# The client writes an incomplete header line and closes the connection. +# Check that the relay establishes and also closes the session. + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + print "GET "; # missing new line + }, + ssl => 1, + nocheck => 1, + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + forwardssl => 1, + listenssl => 1, + loggrep => { + qr/session 1 established/ => 1, + qr/session 1 .*, done/ => 1, + }, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + 5db6c695ab93617b601e94a9f31031edab0a6a11 (mode 644) --- /dev/null +++ regress/args-https-inspect.pl @@ -0,0 +1,27 @@ +# test https connection over http relay with TLS inspection + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + ssl => 1, + loggrep => 'Issuer.*/OU=ca/', + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + inspectssl => 1, + }, + server => { + func => \&http_server, + ssl => 1, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 32aa8fd6357fa34a561a4a1a8017f03cb28564eb (mode 644) --- /dev/null +++ regress/args-https-multi.pl @@ -0,0 +1,29 @@ +# test 50 https get with length 1 over http relay + +use strict; +use warnings; + +my @lengths = map { 1 } (1..50); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + method => "GET", + ssl => 1, + }, + relayd => { + protocol => [ "http" ], + loggrep => { + qr/, (?:done|last write \(done\)), GET/ => (1 + @lengths), + }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + ssl => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + 30e690dae96f24996fbc524ceefb970f67329b05 (mode 644) --- /dev/null +++ regress/args-https-persistent.pl @@ -0,0 +1,49 @@ +# test persistent https 1.1 connection over http relay + +use strict; +use warnings; + +my @lengths = (251, 16384, 0, 1, 2, 3, 4, 5); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + ssl => 1, + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/, (?:done|last write \(done\))/ => (1 + @lengths), + }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + ssl => 1, + }, + lengths => \@lengths, + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + ], +); + +1; blob - /dev/null blob + 74ea56ee5921b7d7cac9ba8adb973b7b3b429700 (mode 644) --- /dev/null +++ regress/args-https-put-multi.pl @@ -0,0 +1,29 @@ +# test 50 https put with length 1 over http relay + +use strict; +use warnings; + +my @lengths = map { 1 } (1..50); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + method => "PUT", + ssl => 1, + }, + relayd => { + protocol => [ "http" ], + loggrep => { + qr/, (?:done|last write \(done\)), PUT/ => (1 + @lengths), + }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + ssl => 1, + }, + lengths => \@lengths, +); + +1; blob - /dev/null blob + e0f87f12ab33dabf014937bca844e8bcc67a6356 (mode 644) --- /dev/null +++ regress/args-https-put.pl @@ -0,0 +1,50 @@ +# test persistent https 1.1 put over http relay + +use strict; +use warnings; + +my @lengths = (251, 16384, 0, 1, 2, 3, 4, 5); +our %args = ( + client => { + func => \&http_client, + lengths => \@lengths, + method => "PUT", + ssl => 1, + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + loggrep => { + qr/, (?:done|last write \(done\))/ => (1 + @lengths), + }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + ssl => 1, + }, + lengths => \@lengths, + md5 => [ + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + "bc3a3f39af35fe5b1687903da2b00c7f", + "52afece07e61264c3087ddf52f729376", + "d41d8cd98f00b204e9800998ecf8427e", + "68b329da9893e34099c7d8ad5cb9c940", + "897316929176464ebc9ad085f31e7284", + "0ade138937c4b9cb36a28e2edb6485fc", + "e686f5db1f8610b65f98f3718e1a5b72", + "e5870c1091c20ed693976546d23b4841", + ], +); + +1; blob - /dev/null blob + 6779cc864e4b72da51471d2e90419748c0112c99 (mode 644) --- /dev/null +++ regress/args-https.pl @@ -0,0 +1,29 @@ +# test https connection over http relay + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + protocol => [ "http", + "match request header log foo", + "match response header log bar", + ], + forwardssl => 1, + listenssl => 1, + + }, + server => { + func => \&http_server, + ssl => 1, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + d9005e6f00f33b767ce3b0ea8e40e0d76ffc29af (mode 644) --- /dev/null +++ regress/args-reverse.pl @@ -0,0 +1,17 @@ +# test reverse sending from server to client + +use strict; +use warnings; + +our %args = ( + client => { + func => \&read_char, + }, + server => { + func => \&write_char, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 45099a1674b3a91061be2125a808a984162b578f (mode 644) --- /dev/null +++ regress/args-ssl-client-verify-fail.pl @@ -0,0 +1,33 @@ +# test client ssl certificate verification + +use strict; +use warnings; + +our %args = ( + client => { + ssl => 1, + offertlscert => 0, + # no-op func as we cannot connect without presenting a client certificate, + # hence the default write_char function won't work here and block forever. + func => sub { + errignore(); + sleep(2); + }, + dryrun => 1, + nocheck => 1, + }, + relayd => { + listenssl => 1, + verifyclient => 1, + loggrep => { + qr/peer did not return a certificate/ => 1, + qr/tls session \d+ established/ => 0, + }, + }, + server => { + noserver => 1, + nocheck => 1, + }, +); + +1; blob - /dev/null blob + c5cc3110724f1fb08eb66ca759e45e684d0abaf5 (mode 644) --- /dev/null +++ regress/args-ssl-client-verify.pl @@ -0,0 +1,19 @@ +# test client ssl certificate verification + +use strict; +use warnings; + +our %args = ( + client => { + ssl => 1, + offertlscert => 1, + }, + relayd => { + listenssl => 1, + verifyclient => 1, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 4c08abeea68077f1f823d915d11329d079b0d056 (mode 644) --- /dev/null +++ regress/args-ssl-client.pl @@ -0,0 +1,17 @@ +# test client ssl connection + +use strict; +use warnings; + +our %args = ( + client => { + ssl => 1, + }, + relayd => { + listenssl => 1, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + b4059a7bf259690fca5a40e6140af541389fd56c (mode 644) --- /dev/null +++ regress/args-ssl-ec.pl @@ -0,0 +1,22 @@ +# test ssl connection with EC key + +use strict; +use warnings; + +our %args = ( + client => { + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + forwardssl => 1, + listenssl => 1, + }, + server => { + ssl => 1, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 3c360494eaa1d488dfa4d9ee5e9c06f7218e96de (mode 644) --- /dev/null +++ regress/args-ssl-inspect.pl @@ -0,0 +1,21 @@ +# test both client and server ssl connection with TLS inspection + +use strict; +use warnings; + +our %args = ( + client => { + ssl => 1, + loggrep => 'Issuer.*/OU=ca/', + }, + relayd => { + inspectssl => 1, + }, + server => { + ssl => 1, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 1f5e4106f6731b50913b79ee18b7e81d0d36e651 (mode 644) --- /dev/null +++ regress/args-ssl-server.pl @@ -0,0 +1,17 @@ +# test server ssl connection + +use strict; +use warnings; + +our %args = ( + relayd => { + forwardssl => 1, + }, + server => { + ssl => 1, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + a277f24b9aa4d7eb4c7e9ab1c49323fc40222bc2 (mode 644) --- /dev/null +++ regress/args-ssl.pl @@ -0,0 +1,22 @@ +# test both client and server ssl connection + +use strict; +use warnings; + +our %args = ( + client => { + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + forwardssl => 1, + listenssl => 1, + }, + server => { + ssl => 1, + }, + len => 251, + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", +); + +1; blob - /dev/null blob + 0e18d2f14abd3cdccec52a88311f4e4f273660f3 (mode 644) --- /dev/null +++ regress/args-timeget-http.pl @@ -0,0 +1,25 @@ +# test that 2 seconds timeout does not occur while server writes for 4 seconds + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 5, + method => "GET", + timefile => "", + }, + relayd => { + relay => [ "session timeout 2" ], + loggrep => { qr/(buffer event|splice) timeout/ => 0 }, + }, + server => { + func => \&http_server, + sleep => 1, + method => "GET", + }, + len => 5, +); + +1; blob - /dev/null blob + a2e381b5243936fac8c81acb66e9fb0712686587 (mode 644) --- /dev/null +++ regress/args-timeget-https.pl @@ -0,0 +1,30 @@ +# test that 2 seconds timeout does not occur while server writes for 4 seconds + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 5, + method => "GET", + timefile => "", + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + relay => [ "session timeout 2" ], + loggrep => { qr/buffer event timeout/ => 0 }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + sleep => 1, + method => "GET", + ssl => 1, + }, + len => 5, +); + +1; blob - /dev/null blob + 5f2fcc7ed59bd2ca048ff4c63ed2cb5d3d8cbdab (mode 644) --- /dev/null +++ regress/args-timeget-ssl.pl @@ -0,0 +1,28 @@ +# test that 2 seconds timeout does not occur while server writes for 4 seconds + +use strict; +use warnings; + +our %args = ( + client => { + func => \&read_char, + timefile => "", + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + relay => [ "session timeout 2" ], + loggrep => { qr/buffer event timeout/ => 0 }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&write_char, + len => 5, + sleep => 1, + ssl => 1, + }, + len => 5, +); + +1; blob - /dev/null blob + ec915b86ab5f11ce1caa1914831441f4ab8dc387 (mode 644) --- /dev/null +++ regress/args-timeget.pl @@ -0,0 +1,23 @@ +# test that 2 seconds timeout does not occur while server writes for 4 seconds + +use strict; +use warnings; + +our %args = ( + client => { + func => \&read_char, + timefile => "", + }, + relayd => { + relay => [ "session timeout 2" ], + loggrep => { qr/(buffer event|splice) timeout/ => 0 }, + }, + server => { + func => \&write_char, + len => 5, + sleep => 1, + }, + len => 5, +); + +1; blob - /dev/null blob + 1bc155761b7870f87ec5b599b75641a8ab4a0fae (mode 644) --- /dev/null +++ regress/args-timein-http.pl @@ -0,0 +1,30 @@ +# test that 3 seconds timeout does not occur within 2 seconds idle in http + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 5, + timefile => "", + }, + relayd => { + protocol => [ "http" ], + relay => [ "session timeout 3" ], + loggrep => { qr/(buffer event|splice) timeout/ => 0 }, + }, + server => { + func => sub { + errignore(); + http_server(@_); + sleep 2; + write_char(@_, 4); + }, + sleep => 1, + nocheck => 1, + }, + len => 9, +); + +1; blob - /dev/null blob + 89e14493c4ff5a546774e8692226eeddc532cc08 (mode 644) --- /dev/null +++ regress/args-timein-https.pl @@ -0,0 +1,35 @@ +# test that 3 seconds timeout does not occur within 2 seconds idle in http + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 5, + timefile => "", + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + protocol => [ "http" ], + relay => [ "session timeout 3" ], + loggrep => { qr/buffer event timeout/ => 0 }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => sub { + errignore(); + http_server(@_); + sleep 2; + write_char(@_, 4); + }, + sleep => 1, + nocheck => 1, + ssl => 1, + }, + len => 9, +); + +1; blob - /dev/null blob + caa024bcf20b9e512a3a3675342b76ea2df2b575 (mode 644) --- /dev/null +++ regress/args-timein-ssl.pl @@ -0,0 +1,32 @@ +# test that 3 seconds timeout does not occur within 2 seconds idle + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + errignore(); + write_char(@_, 5); + sleep 2; + write_char(@_, 4); + }, + sleep => 1, + timefile => "", + nocheck => 1, + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + relay => [ "session timeout 3" ], + loggrep => { qr/buffer event timeout/ => 0 }, + forwardssl => 1, + listenssl => 1, + }, + server => { + ssl => 1, + }, + len => 9, +); + +1; blob - /dev/null blob + 81071616a880403dfab16c6a45cf07bdaf9c9e0d (mode 644) --- /dev/null +++ regress/args-timein.pl @@ -0,0 +1,25 @@ +# test that 3 seconds timeout does not occur within 2 seconds idle + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + errignore(); + write_char(@_, 5); + sleep 2; + write_char(@_, 4); + }, + sleep => 1, + timefile => "", + nocheck => 1, + }, + relayd => { + relay => [ "session timeout 3" ], + loggrep => { qr/(buffer event|splice) timeout/ => 0 }, + }, + len => 9, +); + +1; blob - /dev/null blob + 38f9b28e96d11b428cf23aee12d7f2dd1353fb6d (mode 644) --- /dev/null +++ regress/args-timeout-http.pl @@ -0,0 +1,31 @@ +# test that 3 seconds timeout occurs within 4 seconds idle in http + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 5, + timefile => "", + }, + relayd => { + protocol => [ "http" ], + relay => [ "session timeout 3" ], + loggrep => { qr/(buffer event|splice) timeout/ => 1 }, + }, + server => { + func => sub { + errignore(); + http_server(@_); + sleep 4; + write_char(@_, 4); + }, + sleep => 1, + down => "Broken pipe", + nocheck => 1, + }, + len => 5, +); + +1; blob - /dev/null blob + 449a5391c772ceb770287662276c2636500b5505 (mode 644) --- /dev/null +++ regress/args-timeout-https.pl @@ -0,0 +1,36 @@ +# test that 3 seconds timeout occurs within 4 seconds idle in http + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 5, + timefile => "", + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + protocol => [ "http" ], + relay => [ "session timeout 3" ], + loggrep => { qr/buffer event timeout/ => 1 }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => sub { + errignore(); + http_server(@_); + sleep 4; + write_char(@_, 4); + }, + sleep => 1, + down => "Broken pipe", + nocheck => 1, + ssl => 1, + }, + len => 5, +); + +1; blob - /dev/null blob + 3cf7492d9731074fbd95695a9c0ce069921e06dd (mode 644) --- /dev/null +++ regress/args-timeout-ssl.pl @@ -0,0 +1,33 @@ +# test that 3 seconds timeout occurs within 4 seconds idle + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + errignore(); + write_char(@_, 5); + sleep 4; + write_char(@_, 4); + }, + sleep => 1, + down => "Broken pipe", + timefile => "", + nocheck => 1, + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + relay => [ "session timeout 3" ], + loggrep => { qr/buffer event timeout/ => 1 }, + forwardssl => 1, + listenssl => 1, + }, + server => { + ssl => 1, + }, + len => 5, +); + +1; blob - /dev/null blob + bf4319fa7967c02582897d3098ecc07a6b42909b (mode 644) --- /dev/null +++ regress/args-timeout.pl @@ -0,0 +1,26 @@ +# test that 3 seconds timeout occurs within 4 seconds idle + +use strict; +use warnings; + +our %args = ( + client => { + func => sub { + errignore(); + write_char(@_, 5); + sleep 4; + write_char(@_, 4); + }, + sleep => 1, + down => "Broken pipe", + timefile => "", + nocheck => 1, + }, + relayd => { + relay => [ "session timeout 3" ], + loggrep => { qr/(buffer event|splice) timeout/ => 1 }, + }, + len => 5, +); + +1; blob - /dev/null blob + cfe709ab9a3be5032115368b00784fda4275dd99 (mode 644) --- /dev/null +++ regress/args-timeput-http.pl @@ -0,0 +1,25 @@ +# test that 2 seconds timeout does not occur while client writes for 4 seconds + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 5, + sleep => 1, + method => "PUT", + timefile => "", + }, + relayd => { + relay => [ "session timeout 2" ], + loggrep => { qr/(buffer event|splice) timeout/ => 0 }, + }, + server => { + func => \&http_server, + method => "PUT", + }, + len => 5, +); + +1; blob - /dev/null blob + bdc406bebfd73caf29b82a46835bfb69884621b5 (mode 644) --- /dev/null +++ regress/args-timeput-https.pl @@ -0,0 +1,30 @@ +# test that 2 seconds timeout does not occur while client writes for 4 seconds + +use strict; +use warnings; + +our %args = ( + client => { + func => \&http_client, + len => 5, + sleep => 1, + method => "PUT", + timefile => "", + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + relay => [ "session timeout 2" ], + loggrep => { qr/buffer event timeout/ => 0 }, + forwardssl => 1, + listenssl => 1, + }, + server => { + func => \&http_server, + method => "PUT", + ssl => 1, + }, + len => 5, +); + +1; blob - /dev/null blob + 3ae402dff4a970aafdf0db09e7204124212ea145 (mode 644) --- /dev/null +++ regress/args-timeput-ssl.pl @@ -0,0 +1,27 @@ +# test that 2 seconds timeout does not occur while client writes for 4 seconds + +use strict; +use warnings; + +our %args = ( + client => { + func => \&write_char, + len => 5, + sleep => 1, + timefile => "", + ssl => 1, + loggrep => 'Issuer.*/OU=relayd/', + }, + relayd => { + relay => [ "session timeout 2" ], + loggrep => { qr/buffer event timeout/ => 0 }, + forwardssl => 1, + listenssl => 1, + }, + server => { + ssl => 1, + }, + len => 5, +); + +1; blob - /dev/null blob + 8322d2e9107423fbfa848c8d356fcf64c07feeec (mode 644) --- /dev/null +++ regress/args-timeput.pl @@ -0,0 +1,20 @@ +# test that 2 seconds timeout does not occur while client writes for 4 seconds + +use strict; +use warnings; + +our %args = ( + client => { + func => \&write_char, + len => 5, + sleep => 1, + timefile => "", + }, + relayd => { + relay => [ "session timeout 2" ], + loggrep => { qr/(buffer event|splice) timeout/ => 0 }, + }, + len => 5, +); + +1; blob - /dev/null blob + e8950440aaa1c0e156c678007b972944d59c5c03 (mode 755) --- /dev/null +++ regress/direct.pl @@ -0,0 +1,62 @@ +#!/usr/bin/perl +# $OpenBSD: direct.pl,v 1.3 2014/08/18 22:58:19 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# +# 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 Socket; +use Socket6; + +use Client; +use Server; +require 'funcs.pl'; + +sub usage { + die "usage: direct.pl [test-args.pl]\n"; +} + +my $testfile; +our %args; +if (@ARGV and -f $ARGV[-1]) { + $testfile = pop; + do $testfile + or die "Do test file $testfile failed: ", $@ || $!; +} + +@ARGV == 0 or usage(); + +my $s = Server->new( + func => \&read_char, + %{$args{server}}, + listendomain => AF_INET, + listenaddr => "127.0.0.1", +); +my $c = Client->new( + func => \&write_char, + %{$args{client}}, + connectdomain => AF_INET, + connectaddr => "127.0.0.1", + connectport => $s->{listenport}, +); + +$s->run; +$c->run->up; +$s->up; + +$c->down; +$s->down; + +check_logs($c, undef, $s, %args); blob - /dev/null blob + 694a1c8ae07fe3d89735404489b459b8fc44e169 (mode 644) --- /dev/null +++ regress/funcs.pl @@ -0,0 +1,616 @@ +# $OpenBSD: funcs.pl,v 1.26 2024/06/14 15:12:57 bluhm Exp $ + +# Copyright (c) 2010-2021 Alexander Bluhm +# +# 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 Errno; +use Digest::MD5; +use Socket; +use Socket6; +use IO::Socket; +use IO::Socket::IP; + +sub find_ports { + my %args = @_; + my $num = delete $args{num} // 1; + my $domain = delete $args{domain} // AF_INET; + my $addr = delete $args{addr} // "127.0.0.1"; + + my @sockets = (1..$num); + foreach my $s (@sockets) { + $s = IO::Socket::IP->new( + Proto => "tcp", + Family => $domain, + $addr ? (LocalAddr => $addr) : (), + ) or die "find_ports: create and bind socket failed: $!"; + } + my @ports = map { $_->sockport() } @sockets; + + return @ports; +} + +######################################################################## +# Client funcs +######################################################################## + +sub write_syswrite { + my $self = shift; + my $buf = shift; + + IO::Handle::flush(\*STDOUT); + my $size = length($buf); + my $len = 0; + while ($len < $size) { + my $n = syswrite(STDOUT, $buf, $size, $len); + if (!defined($n)) { + $!{EWOULDBLOCK} + or die ref($self), " syswrite failed: $!"; + print STDERR "blocked write at $len of $size: $!\n"; + next; + } + if ($len + $n != $size) { + print STDERR "short write $n at $len of $size\n"; + } + $len += $n; + } + return $len; +} + +sub write_block { + my $self = shift; + my $len = shift; + + my $data; + my $outb = 0; + my $blocks = int($len / 1000); + my $rest = $len % 1000; + + for (my $i = 1; $i <= 100 ; $i++) { + $data .= "012345678\n"; + } + + my $opct = 0; + for (my $i = 1; $i <= $blocks; $i++) { + $outb += write_syswrite($self, $data); + my $pct = ($outb / $len) * 100.0; + if ($pct >= $opct + 1) { + printf(STDERR "%.2f%% $outb/$len\n", $pct); + $opct = $pct; + } + } + + if ($rest>0) { + for (my $i = 1; $i < $rest-1 ; $i++) { + $outb += write_syswrite($self, 'r'); + my $pct = ($outb / $len) * 100.0; + if ($pct >= $opct + 1) { + printf(STDERR "%.2f%% $outb/$len\n", $pct); + $opct = $pct; + } + } + } + $outb += write_syswrite($self, "\n\n"); + IO::Handle::flush(\*STDOUT); + print STDERR "LEN: ", $outb, "\n"; +} + +sub write_char { + my $self = shift; + my $len = shift // $self->{len} // 251; + my $sleep = $self->{sleep}; + + if ($self->{fast}) { + write_block($self, $len); + return; + } + + my $ctx = Digest::MD5->new(); + my $char = '0'; + for (my $i = 1; $i < $len; $i++) { + $ctx->add($char); + print $char + or die ref($self), " print failed: $!"; + if ($char =~ /9/) { $char = 'A' } + elsif ($char =~ /Z/) { $char = 'a' } + elsif ($char =~ /z/) { $char = "\n" } + elsif ($char =~ /\n/) { print STDERR "."; $char = '0' } + else { $char++ } + if ($self->{sleep}) { + IO::Handle::flush(\*STDOUT); + sleep $self->{sleep}; + } + } + if ($len) { + $char = "\n"; + $ctx->add($char); + print $char + or die ref($self), " print failed: $!"; + print STDERR ".\n"; + } + IO::Handle::flush(\*STDOUT); + + print STDERR "LEN: ", $len, "\n"; + print STDERR "MD5: ", $ctx->hexdigest, "\n"; +} + +sub http_client { + my $self = shift; + + unless ($self->{lengths}) { + # only a single http request + my $len = shift // $self->{len} // 251; + my $cookie = $self->{cookie}; + http_request($self, $len, "1.0", $cookie); + http_response($self, $len); + return; + } + + $self->{http_vers} ||= ["1.1", "1.0"]; + my $vers = $self->{http_vers}[0]; + my @lengths = @{$self->{redo}{lengths} || $self->{lengths}}; + my @cookies = @{$self->{redo}{cookies} || $self->{cookies} || []}; + while (defined (my $len = shift @lengths)) { + my $cookie = shift @cookies || $self->{cookie}; + eval { + http_request($self, $len, $vers, $cookie); + http_response($self, $len); + }; + warn $@ if $@; + if (@lengths && ($@ || $vers eq "1.0")) { + # reconnect and redo the outstanding requests + $self->{redo} = { + lengths => \@lengths, + cookies => \@cookies, + }; + return; + } + } + delete $self->{redo}; + shift @{$self->{http_vers}}; + if (@{$self->{http_vers}}) { + # run the tests again with other persistence + $self->{redo} = { + lengths => [@{$self->{lengths}}], + cookies => [@{$self->{cookies} || []}], + }; + } +} + +sub http_request { + my ($self, $len, $vers, $cookie) = @_; + my $method = $self->{method} || "GET"; + my %header = %{$self->{header} || {}}; + + # encode the requested length or chunks into the url + my $path = ref($len) eq 'ARRAY' ? join("/", @$len) : $len; + # overwrite path with custom path + if (defined($self->{path})) { + $path = $self->{path}; + } + my @request = ("$method /$path HTTP/$vers"); + push @request, "Host: foo.bar" unless defined $header{Host}; + if ($vers eq "1.1" && $method eq "PUT") { + if (ref($len) eq 'ARRAY') { + push @request, "Transfer-Encoding: chunked" + if !defined $header{'Transfer-Encoding'}; + } else { + push @request, "Content-Length: $len" + if !defined $header{'Content-Length'}; + } + } + foreach my $key (sort keys %header) { + my $val = $header{$key}; + if (ref($val) eq 'ARRAY') { + push @request, "$key: $_" + foreach @{$val}; + } else { + push @request, "$key: $val"; + } + } + push @request, "Cookie: $cookie" if $cookie; + push @request, ""; + print STDERR map { ">>> $_\n" } @request; + print map { "$_\r\n" } @request; + if ($method eq "PUT") { + if (ref($len) eq 'ARRAY') { + if ($vers eq "1.1") { + write_chunked($self, @$len); + } else { + write_char($self, $_) foreach (@$len); + } + } else { + write_char($self, $len); + } + } + IO::Handle::flush(\*STDOUT); + # XXX client shutdown seems to be broken in relayd + #shutdown(\*STDOUT, SHUT_WR) + # or die ref($self), " shutdown write failed: $!" + # if $vers ne "1.1"; +} + +sub http_response { + my ($self, $len) = @_; + my $method = $self->{method} || "GET"; + + my $vers; + my $chunked = 0; + { + local $/ = "\r\n"; + local $_ = ; + defined + or die ref($self), " missing http $len response"; + chomp; + print STDERR "<<< $_\n"; + m{^HTTP/(\d\.\d) 200 OK$} + or die ref($self), " http response not ok" + unless $self->{httpnok}; + $vers = $1; + while () { + chomp; + print STDERR "<<< $_\n"; + last if /^$/; + if (/^Content-Length: (.*)/) { + if ($self->{httpnok}) { + $len = $1; + } else { + $1 == $len or die ref($self), + " bad content length $1"; + } + } + if (/^Transfer-Encoding: chunked$/) { + $chunked = 1; + } + } + } + if ($method ne 'HEAD') { + if ($chunked) { + read_chunked($self); + } else { + undef $len unless defined($vers) && $vers eq "1.1"; + read_char($self, $len) + if $method eq "GET"; + } + } +} + +sub read_chunked { + my $self = shift; + + for (;;) { + my $len; + { + local $/ = "\r\n"; + local $_ = ; + defined or die ref($self), " missing chunk size"; + chomp; + print STDERR "<<< $_\n"; + /^[[:xdigit:]]+$/ + or die ref($self), " chunk size not hex: $_"; + $len = hex; + } + last unless $len > 0; + read_char($self, $len); + { + local $/ = "\r\n"; + local $_ = ; + defined or die ref($self), " missing chunk data end"; + chomp; + print STDERR "<<< $_\n"; + /^$/ or die ref($self), " no chunk data end: $_"; + } + } + { + local $/ = "\r\n"; + while () { + chomp; + print STDERR "<<< $_\n"; + last if /^$/; + } + defined or die ref($self), " missing chunk trailer"; + } +} + +sub errignore { + $SIG{PIPE} = 'IGNORE'; + $SIG{__DIE__} = sub { + die @_ if $^S; + warn "Error ignored"; + warn @_; + IO::Handle::flush(\*STDERR); + POSIX::_exit(0); + }; +} + +######################################################################## +# Common funcs +######################################################################## + +sub read_char { + my $self = shift; + my $max = shift // $self->{max}; + + if ($self->{fast}) { + read_block($self, $max); + return; + } + + my $ctx = Digest::MD5->new(); + my $len = 0; + if (defined($max) && $max == 0) { + print STDERR "Max\n"; + } else { + while () { + $len += length($_); + $ctx->add($_); + print STDERR "."; + if (defined($max) && $len >= $max) { + print STDERR "\nMax"; + last; + } + } + print STDERR "\n"; + } + + print STDERR "LEN: ", $len, "\n"; + print STDERR "MD5: ", $ctx->hexdigest, "\n"; +} + +sub read_block { + my $self = shift; + my $max = shift // $self->{max}; + + my $opct = 0; + my $ctx = Digest::MD5->new(); + my $len = 0; + for (;;) { + if (defined($max) && $len >= $max) { + print STDERR "Max\n"; + last; + } + my $rlen = POSIX::BUFSIZ; + if (defined($max) && $rlen > $max - $len) { + $rlen = $max - $len; + } + defined(my $n = read(STDIN, my $buf, $rlen)) + or die ref($self), " read failed: $!"; + $n or last; + $len += $n; + $ctx->add($buf); + my $pct = ($len / $max) * 100.0; + if ($pct >= $opct + 1) { + printf(STDERR "%.2f%% $len/$max\n", $pct); + $opct = $pct; + } + } + + print STDERR "LEN: ", $len, "\n"; + print STDERR "MD5: ", $ctx->hexdigest, "\n"; +} + +######################################################################## +# Server funcs +######################################################################## + +sub http_server { + my $self = shift; + my %header = %{$self->{header} || { Server => "Perl/".$^V }}; + my $cookie = $self->{cookie} || ""; + + my($method, $url, $vers); + do { + my $len; + { + local $/ = "\r\n"; + local $_ = ; + return unless defined $_; + chomp; + print STDERR "<<< $_\n"; + ($method, $url, $vers) = m{^(\w+) (.*) HTTP/(1\.[01])$} + or die ref($self), " http request not ok"; + $method =~ /^(GET|HEAD|PUT)$/ + or die ref($self), " unknown method: $method"; + ($len, my @chunks) = $url =~ /(\d+)/g; + $len = [ $len, @chunks ] if @chunks; + while () { + chomp; + print STDERR "<<< $_\n"; + last if /^$/; + if ($method eq "PUT" && + /^Content-Length: (.*)/) { + $1 == $len or die ref($self), + " bad content length $1"; + } + $cookie ||= $1 if /^Cookie: (.*)/; + } + } + if ($method eq "PUT" ) { + if (ref($len) eq 'ARRAY') { + read_chunked($self); + } else { + read_char($self, $len); + } + } + + my @response = ("HTTP/$vers 200 OK"); + $len = defined($len) ? $len : scalar(split /|/,$url); + if ($vers eq "1.1" && $method =~ /^(GET|HEAD)$/) { + if (ref($len) eq 'ARRAY') { + push @response, "Transfer-Encoding: chunked"; + } else { + push @response, "Content-Length: $len"; + } + } + foreach my $key (sort keys %header) { + my $val = $header{$key}; + if (ref($val) eq 'ARRAY') { + push @response, "$key: $_" + foreach @{$val}; + } else { + push @response, "$key: $val"; + } + } + push @response, "Set-Cookie: $cookie" if $cookie; + push @response, ""; + + print STDERR map { ">>> $_\n" } @response; + print map { "$_\r\n" } @response; + + if ($method eq "GET") { + if (ref($len) eq 'ARRAY') { + if ($vers eq "1.1") { + write_chunked($self, @$len); + } else { + write_char($self, $_) foreach (@$len); + } + } else { + write_char($self, $len); + } + } + IO::Handle::flush(\*STDOUT); + } while ($vers eq "1.1"); + $self->{redo}-- if $self->{redo}; +} + +sub write_chunked { + my $self = shift; + my @chunks = @_; + + foreach my $len (@chunks) { + printf STDERR ">>> %x\n", $len; + printf "%x\r\n", $len; + write_char($self, $len); + printf STDERR ">>> \n"; + print "\r\n"; + } + my @trailer = ("0", "X-Chunk-Trailer: @chunks", ""); + print STDERR map { ">>> $_\n" } @trailer; + print map { "$_\r\n" } @trailer; +} + +######################################################################## +# Script funcs +######################################################################## + +sub check_logs { + my ($c, $r, $s, %args) = @_; + + return if $args{nocheck}; + + check_len($c, $r, $s, %args); + check_md5($c, $r, $s, %args); + check_loggrep($c, $r, $s, %args); + $r->loggrep("lost child") + and die "relayd lost child"; +} + +sub array_eq { + my ($a, $b) = @_; + return if @$a != @$b; + for (my $i = 0; $i < @$a; $i++) { + return if $$a[$i] ne $$b[$i]; + } + return 1; +} + +sub check_len { + my ($c, $r, $s, %args) = @_; + + $args{len} ||= 251 unless $args{lengths}; + + my (@clen, @slen); + @clen = $c->loggrep(qr/^LEN: /) or die "no client len" + unless $args{client}{nocheck}; + @slen = $s->loggrep(qr/^LEN: /) or die "no server len" + unless $args{server}{nocheck}; + !@clen || !@slen || array_eq \@clen, \@slen + or die "client: @clen", "server: @slen", "len mismatch"; + !defined($args{len}) || !$clen[0] || $clen[0] eq "LEN: $args{len}\n" + or die "client: $clen[0]", "len $args{len} expected"; + !defined($args{len}) || !$slen[0] || $slen[0] eq "LEN: $args{len}\n" + or die "server: $slen[0]", "len $args{len} expected"; + my @lengths = map { ref eq 'ARRAY' ? @$_ : $_ } + @{$args{lengths} || []}; + foreach my $len (@lengths) { + unless ($args{client}{nocheck}) { + my $clen = shift @clen; + $clen eq "LEN: $len\n" + or die "client: $clen", "len $len expected"; + } + unless ($args{server}{nocheck}) { + my $slen = shift @slen; + $slen eq "LEN: $len\n" + or die "server: $slen", "len $len expected"; + } + } +} + +sub check_md5 { + my ($c, $r, $s, %args) = @_; + + my (@cmd5, @smd5); + @cmd5 = $c->loggrep(qr/^MD5: /) unless $args{client}{nocheck}; + @smd5 = $s->loggrep(qr/^MD5: /) unless $args{server}{nocheck}; + !@cmd5 || !@smd5 || $cmd5[0] eq $smd5[0] + or die "client: $cmd5[0]", "server: $smd5[0]", "md5 mismatch"; + + my @md5 = ref($args{md5}) eq 'ARRAY' ? @{$args{md5}} : $args{md5} || () + or return; + foreach my $md5 (@md5) { + unless ($args{client}{nocheck}) { + my $cmd5 = shift @cmd5 + or die "too few md5 in client log"; + $cmd5 =~ /^MD5: ($md5)$/ + or die "client: $cmd5", "md5 $md5 expected"; + } + unless ($args{server}{nocheck}) { + my $smd5 = shift @smd5 + or die "too few md5 in server log"; + $smd5 =~ /^MD5: ($md5)$/ + or die "server: $smd5", "md5 $md5 expected"; + } + } + @cmd5 && ref($args{md5}) eq 'ARRAY' + and die "too many md5 in client log"; + @smd5 && ref($args{md5}) eq 'ARRAY' + and die "too many md5 in server log"; +} + +sub check_loggrep { + my ($c, $r, $s, %args) = @_; + + my %name2proc = (client => $c, relayd => $r, server => $s); + foreach my $name (qw(client relayd server)) { + my $p = $name2proc{$name} or next; + my $pattern = $args{$name}{loggrep} or next; + $pattern = [ $pattern ] unless ref($pattern) eq 'ARRAY'; + foreach my $pat (@$pattern) { + if (ref($pat) eq 'HASH') { + while (my($re, $num) = each %$pat) { + my @matches = $p->loggrep($re); + @matches == $num + or die "$name matches '@matches': ", + "'$re' => $num"; + } + } else { + $p->loggrep($pat) + or die "$name log missing pattern: '$pat'"; + } + } + } +} + +1; blob - /dev/null blob + 07c34dab0f417f282f78b68c6cd2d50c50393a1c (mode 644) --- /dev/null +++ regress/relayd.pl @@ -0,0 +1,89 @@ +#!/usr/bin/perl +# $OpenBSD: relayd.pl,v 1.15 2016/08/25 22:56:13 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# +# 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 Socket; +use Socket6; + +use Client; +use Relayd; +use Server; +require 'funcs.pl'; + +sub usage { + die "usage: relay.pl copy|splice [test-args.pl]\n"; +} + +my $testfile; +our %args; +if (@ARGV and -f $ARGV[-1]) { + $testfile = pop; + do $testfile + or die "Do test file $testfile failed: ", $@ || $!; +} +@ARGV == 1 or usage(); + +my $redo = $args{lengths} && @{$args{lengths}}; +$redo = 0 if $args{client}{http_vers}; # run only one persistent connection +my($sport, $rport) = find_ports(num => 2); +my($s, $r, $c); +$s = Server->new( + forward => $ARGV[0], + func => \&read_char, + listendomain => AF_INET, + listenaddr => "127.0.0.1", + listenport => $sport, + redo => $redo, + %{$args{server}}, + testfile => $testfile, + client => \$c, +) unless $args{server}{noserver}; +$r = Relayd->new( + forward => $ARGV[0], + listendomain => AF_INET, + listenaddr => "127.0.0.1", + listenport => $rport, + connectdomain => AF_INET, + connectaddr => "127.0.0.1", + connectport => $sport, + %{$args{relayd}}, + testfile => $testfile, +); +$c = Client->new( + forward => $ARGV[0], + func => \&write_char, + connectdomain => AF_INET, + connectaddr => "127.0.0.1", + connectport => $rport, + %{$args{client}}, + testfile => $testfile, + server => \$s, +) unless $args{client}{noclient}; + +$s->run unless $args{server}{noserver}; +$r->run; +$r->up; +$c->run->up unless $args{client}{noclient}; +$s->up unless $args{server}{noserver}; + +$c->down unless $args{client}{noclient}; +$s->down unless $args{server}{noserver}; +$r->kill_child; +$r->down; + +check_logs($c, $r, $s, %args); blob - /dev/null blob + c8f29ca39cb752481c5274a96f1781eb4e49fab4 (mode 644) --- /dev/null +++ regress/remote.pl @@ -0,0 +1,145 @@ +#!/usr/bin/perl +# $OpenBSD: remote.pl,v 1.9 2016/08/25 22:56:13 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# +# 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 Relayd; +use Server; +use Remote; +require 'funcs.pl'; + +sub usage { + die <<"EOF"; +usage: + remote.pl localport remoteaddr remoteport [test-args.pl] + Run test with local client and server. Remote relayd + forwarding from remoteaddr remoteport to server localport + has to be started manually. + remote.pl copy|splice listenaddr connectaddr connectport [test-args.pl] + Only start remote relayd. + remote.pl copy|splice localaddr remoteaddr remotessh [test-args.pl] + Run test with local client and server. Remote relayd is + started automatically with ssh on remotessh. +EOF +} + +my $testfile; +our %args; +if (@ARGV and -f $ARGV[-1]) { + $testfile = pop; + do $testfile + or die "Do test file $testfile failed: ", $@ || $!; +} +my $mode = + @ARGV == 3 && $ARGV[0] =~ /^\d+$/ && $ARGV[2] =~ /^\d+$/ ? "manual" : + @ARGV == 4 && $ARGV[1] !~ /^\d+$/ && $ARGV[3] =~ /^\d+$/ ? "relay" : + @ARGV == 4 && $ARGV[1] !~ /^\d+$/ && $ARGV[3] !~ /^\d+$/ ? "auto" : + usage(); + +my($s, $r, $c); +if ($mode eq "relay") { + my($rport) = find_ports(num => 1); + $r = Relayd->new( + forward => $ARGV[0], + %{$args{relayd}}, + listendomain => AF_INET, + listenaddr => $ARGV[1], + listenport => $rport, + connectdomain => AF_INET, + connectaddr => $ARGV[2], + connectport => $ARGV[3], + logfile => dirname($0)."/remote.log", + conffile => dirname($0)."/relayd.conf", + testfile => $testfile, + ); + open(my $log, '<', $r->{logfile}) + or die "Remote log file open failed: $!"; + $SIG{__DIE__} = sub { + die @_ if $^S; + copy($log, \*STDERR); + warn @_; + exit 255; + }; + copy($log, \*STDERR); + $r->run; + copy($log, \*STDERR); + $r->up; + copy($log, \*STDERR); + print STDERR "listen sock: $ARGV[1] $rport\n"; + ; + copy($log, \*STDERR); + print STDERR "stdin closed\n"; + $r->kill_child; + $r->down; + copy($log, \*STDERR); + + exit; +} + +my $redo = $args{lengths} && @{$args{lengths}}; +$redo = 0 if $args{client}{http_vers}; # run only one persistent connection +$s = Server->new( + forward => $ARGV[0], + func => \&read_char, + redo => $redo, + %{$args{server}}, + listendomain => AF_INET, + listenaddr => ($mode eq "auto" ? $ARGV[1] : undef), + listenport => ($mode eq "manual" ? $ARGV[0] : undef), + testfile => $testfile, + client => \$c, +) unless $args{server}{noserver}; +if ($mode eq "auto") { + $r = Remote->new( + forward => $ARGV[0], + logfile => "relayd.log", + %{$args{relayd}}, + remotessh => $ARGV[3], + listenaddr => $ARGV[2], + connectaddr => $ARGV[1], + connectport => $s ? $s->{listenport} : 1, + testfile => $testfile, + ); + $r->run->up; +} +$c = Client->new( + forward => $ARGV[0], + func => \&write_char, + %{$args{client}}, + connectdomain => AF_INET, + connectaddr => ($mode eq "manual" ? $ARGV[1] : $r->{listenaddr}), + connectport => ($mode eq "manual" ? $ARGV[2] : $r->{listenport}), + testfile => $testfile, + server => \$s, +) unless $args{client}{noclient}; + +$s->run unless $args{server}{noserver}; +$c->run->up unless $args{client}{noclient}; +$s->up unless $args{server}{noserver}; + +$c->down unless $args{client}{noclient}; +$s->down unless $args{server}{noserver}; +$r->close_child; +$r->down; + +check_logs($c, $r, $s, %args);