1#	$OpenBSD: funcs.pl,v 1.6 2017/08/15 04:11:20 bluhm Exp $
2
3# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
4#
5# Permission to use, copy, modify, and distribute this software for any
6# purpose with or without fee is hereby granted, provided that the above
7# copyright notice and this permission notice appear in all copies.
8#
9# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17use strict;
18use warnings;
19use Socket;
20use Socket6;
21
22########################################################################
23# Client and Server funcs
24########################################################################
25
26sub write_read_stream {
27	my $self = shift;
28
29	my $out = ref($self). "\n";
30	print $out;
31	IO::Handle::flush(\*STDOUT);
32	print STDERR ">>> $out";
33
34	my $in = <STDIN>;
35	print STDERR "<<< $in";
36}
37
38sub write_datagram {
39	my $self = shift;
40	my $dgram = shift;
41
42	my $out = $dgram || ref($self). "\n";
43	my $addr = $self->{toaddr};
44	my $port = $self->{toport};
45	if ($addr) {
46		my ($to, $netaddr);
47		if ($self->{af} eq "inet") {
48			$netaddr = inet_pton(AF_INET, $addr);
49			$to = pack_sockaddr_in($port, $netaddr);
50		} else {
51			$netaddr = inet_pton(AF_INET6, $addr);
52			$to = pack_sockaddr_in6($port, $netaddr);
53		}
54		$self->{toaddr} = $addr;
55		$self->{toport} = $port;
56		print STDERR "send to: $addr $port\n";
57
58		send(STDOUT, $out, 0, $to)
59		    or die ref($self), " send to failed: $!";
60	} else {
61		send(STDOUT, $out, 0)
62		    or die ref($self), " send failed: $!";
63	}
64
65	unless ($dgram) {
66		print STDERR ">>> $out";
67	}
68}
69
70sub read_datagram {
71	my $self = shift;
72	my $dgram = shift;
73
74	my $from = recv(STDIN, my $in, 70000, 0)
75	    or die ref($self), " recv from failed: $!";
76	# Raw sockets include the IPv4 header.
77	if ($self->{socktype} && $self->{socktype} == Socket::SOCK_RAW &&
78	    $self->{af} eq "inet") {
79		substr($in, 0, 20, "");
80	}
81
82	my ($port, $netaddr, $addr);
83	if ($self->{af} eq "inet") {
84		($port, $netaddr) = unpack_sockaddr_in($from);
85		$addr = inet_ntop(AF_INET, $netaddr);
86	} else {
87		($port, $netaddr) = unpack_sockaddr_in6($from);
88		$addr = inet_ntop(AF_INET6, $netaddr);
89	}
90	$self->{fromaddr} = $addr;
91	$self->{fromport} = $port;
92	print STDERR "recv from: $addr $port\n";
93
94	if ($dgram) {
95		$$dgram = $in;
96	} else {
97		print STDERR "<<< $in";
98	}
99}
100
101sub write_read_datagram {
102	my $self = shift;
103	write_datagram($self);
104	read_datagram($self);
105}
106
107sub read_write_datagram {
108	my $self = shift;
109	read_datagram($self);
110	$self->{toaddr} = $self->{fromaddr};
111	$self->{toport} = $self->{fromport};
112	write_datagram($self);
113}
114
115sub read_write_packet {
116	my $self = shift;
117
118	my $packet;
119	read_datagram($self, \$packet);
120	my $hexin = unpack("H*", $packet);
121	print STDERR "<<< $hexin\n";
122
123	$packet =~ s/Client|Server/Packet/;
124	$self->{toaddr} = $self->{fromaddr};
125	$self->{toport} = $self->{fromport};
126	write_datagram($self, $packet);
127	my $hexout = unpack("H*", $packet);
128	print STDERR ">>> $hexout\n";
129}
130
131sub in_cksum {
132	my $data = shift;
133	my $sum = 0;
134
135	$data .= pack("x") if (length($data) & 1);
136	while (length($data)) {
137		$sum += unpack("n", substr($data, 0, 2, ""));
138		$sum = ($sum >> 16) + ($sum & 0xffff) if ($sum > 0xffff);
139	}
140	return (~$sum & 0xffff);
141}
142
143use constant IPPROTO_ICMPV6	=> 58;
144use constant ICMP_ECHO		=> 8;
145use constant ICMP_ECHOREPLY	=> 0;
146use constant ICMP6_ECHO_REQUEST	=> 128;
147use constant ICMP6_ECHO_REPLY	=> 129;
148
149my $seq = 0;
150sub write_icmp_echo {
151	my $self = shift;
152	my $pid = shift || $$;
153	my $af = $self->{af};
154
155	my $type = $af eq "inet" ? ICMP_ECHO : ICMP6_ECHO_REQUEST;
156	# type, code, cksum, id, seq
157	my $icmp = pack("CCnnn", $type, 0, 0, $pid, ++$seq);
158	if ($af eq "inet") {
159		substr($icmp, 2, 2, pack("n", in_cksum($icmp)));
160	} else {
161		# src, dst, plen, pad, next
162		my $phdr = "";
163		$phdr .= inet_pton(AF_INET6, $self->{srcaddr});
164		$phdr .= inet_pton(AF_INET6, $self->{dstaddr});
165		$phdr .= pack("NxxxC", length($icmp), IPPROTO_ICMPV6);
166		print STDERR "pseudo header: ", unpack("H*", $phdr), "\n";
167		substr($icmp, 2, 2, pack("n", in_cksum($phdr. $icmp)));
168	}
169
170	write_datagram($self, $icmp);
171	my $text = $af eq "inet" ? "ICMP" : "ICMP6";
172	print STDERR ">>> $text ", unpack("H*", $icmp), "\n";
173}
174
175sub read_icmp_echo {
176	my $self = shift;
177	my $reply = shift;
178	my $af = $self->{af};
179
180	my $icmp;
181	read_datagram($self, \$icmp);
182
183	my $text = $af eq "inet" ? "ICMP" : "ICMP6";
184	$text .= " reply" if $reply;
185	my $phdr = "";
186	if ($af eq "inet6") {
187		# src, dst, plen, pad, next
188		$phdr .= inet_pton(AF_INET6, $self->{srcaddr});
189		$phdr .= inet_pton(AF_INET6, $self->{dstaddr});
190		$phdr .= pack("NxxxC", length($icmp), IPPROTO_ICMPV6);
191		print STDERR "pseudo header: ", unpack("H*", $phdr), "\n";
192	}
193	if (length($icmp) < 8) {
194		$text = "BAD $text LENGTH";
195	} elsif (in_cksum($phdr. $icmp) != 0) {
196		$text = "BAD $text CHECKSUM";
197	} else {
198		my($type, $code, $cksum, $id, $seq) = unpack("CCnnn", $icmp);
199		my $t = $reply ?
200		    ($af eq "inet" ? ICMP_ECHOREPLY : ICMP6_ECHO_REPLY) :
201		    ($af eq "inet" ? ICMP_ECHO : ICMP6_ECHO_REQUEST);
202		if ($type != $t) {
203			$text = "BAD $text TYPE";
204		} elsif ($code != 0) {
205			$text = "BAD $text CODE";
206		}
207	}
208
209	print STDERR "<<< $text ", unpack("H*", $icmp), "\n";
210}
211
212########################################################################
213# Script funcs
214########################################################################
215
216sub check_logs {
217	my ($c, $r, $s, %args) = @_;
218
219	return if $args{nocheck};
220
221	check_inout($c, $r, $s, %args);
222}
223
224sub check_inout {
225	my ($c, $r, $s, %args) = @_;
226
227	if ($args{client} && !$args{client}{nocheck}) {
228		my $out = $args{client}{out} || "Client";
229		$c->loggrep(qr/^>>> $out/) or die "no client output"
230		    unless $args{client}{noout};
231		my $in = $args{client}{in} || "Server";
232		$c->loggrep(qr/^<<< $in/) or die "no client input"
233		    unless $args{client}{noin};
234	}
235	if ($args{packet} && !$args{packet}{nocheck}) {
236		my $hex;
237		my $in = $args{packet}{in} || $args{packet}{noin}
238		    or die "no packet input regex";
239		$hex = unpack("H*", $in);
240		$r->loggrep(qr/Packet: <<< .*$hex/) or die "no packet input"
241		    unless $args{packet}{noin};
242		my $out = $args{packet}{out} || "Packet";
243		$hex = unpack("H*", $out);
244		$r->loggrep(qr/Packet: >>> .*$hex/) or die "no packet output"
245		    unless $args{packet}{noout};
246	}
247	if ($args{server} && !$args{server}{nocheck}) {
248		my $in = $args{server}{in} || "Client";
249		$s->loggrep(qr/^<<< $in/) or die "no server input"
250		    unless $args{server}{noin};
251		my $out = $args{server}{out} || "Server";
252		$s->loggrep(qr/^>>> $out/) or die "no server output"
253		    unless $args{server}{noout};
254	}
255}
256
2571;
258