1#!/usr/bin/perl -w
2
3use strict;
4use Socket qw(inet_pton PF_INET PF_INET6 SOCK_STREAM SOMAXCONN sockaddr_in sockaddr_in6);
5use Errno;
6
7my @socka = ();
8my ($pf, $host, $sin, $sock, $badsock);
9
10
11if (@ARGV < 3 or @ARGV > 4) {
12	die "usage: $0 <pf> <listen> <first> [count]\n"
13}
14
15if ($> != 0) {
16	die "run this script as root\n"
17}
18
19my ($af, $test_listen, $test_first, $test_count) = @ARGV;
20
21$test_count = SOMAXCONN if (not defined $test_count);
22
23my $test_last = $test_first + $test_count;
24
25if ($test_first  <= 0 || 65536 <= $test_first ||
26    $test_last   <= 0 || 65536 <= $test_last ||
27    $test_listen <= 0 || 65536 <= $test_listen) {
28	die "invalid port number\n";
29}
30
31if ($test_first > $test_last) {
32	die "first must be lower than last\n";
33}
34
35if ($test_listen >= $test_first && $test_listen <= $test_last) {
36	die "listen must be outside the [first..last] range\n";
37}
38
39if ($af == "4") {
40	$pf = PF_INET;
41	$sin = sockaddr_in($test_listen, inet_pton($pf,"127.0.0.1"));
42}
43elsif ($af == "6") {
44	$pf = PF_INET6;
45	$sin = sockaddr_in6($test_listen, inet_pton($pf,"::1"));
46}
47else {
48	die "af must be 4 or 6\n";
49}
50
51
52my $orig_first = qx( sysctl -n net.inet.ip.portfirst );
53chomp $orig_first;
54my $orig_last  = qx( sysctl -n net.inet.ip.portlast );
55chomp $orig_last;
56
57
58# first < last
59
60socket(SERVSOCK, $pf, SOCK_STREAM, getprotobyname("tcp"));
61bind(SERVSOCK, $sin);
62listen(SERVSOCK, SOMAXCONN);
63
64my $rc_f = 0;
65
66print "testing with portfirst < portlast\n";
67
68system("sysctl net.inet.ip.portfirst=$test_first > /dev/null");
69system("sysctl net.inet.ip.portlast=$test_last > /dev/null");
70
71for ($test_first .. $test_last) {
72	socket($sock, $pf, SOCK_STREAM, getprotobyname("tcp"));
73	unless (connect($sock, $sin)) {
74		print "failed to connect with errno $!\n";
75		$rc_f = 1;
76	}
77	push @socka, $sock;
78}
79
80socket($badsock, $pf, SOCK_STREAM, getprotobyname("tcp"));
81if (connect($badsock, $sin)) {
82	print "connect() succeeded but should have failed\n";
83	$rc_f = 1;
84}
85elsif (not $!{EADDRNOTAVAIL}) {
86	print "connect() failed with errno $!, should have been EADDRNOTAVAIL\n";
87	$rc_f = 1;
88}
89close($badsock);
90
91while ($sock = pop @socka) {
92	close($sock);
93}
94
95close(SERVSOCK);
96
97sleep 1;
98
99if ($rc_f == 0) {
100	print "test OK\n"
101}
102else {
103	print "test failed\n"
104}
105
106# first > last
107
108socket(SERVSOCK, $pf, SOCK_STREAM, getprotobyname("tcp"));
109bind(SERVSOCK, $sin);
110listen(SERVSOCK, SOMAXCONN);
111
112my $rc_b = 0;
113
114print "testing with portfirst > portlast\n";
115
116system("sysctl net.inet.ip.portfirst=$test_last > /dev/null");
117system("sysctl net.inet.ip.portlast=$test_first > /dev/null");
118
119for ($test_first .. $test_last) {
120	socket($sock, $pf, SOCK_STREAM, getprotobyname("tcp"));
121	unless (connect($sock, $sin)) {
122		print "failed to connect with errno $!\n";
123		$rc_b = 1;
124	}
125	push @socka, $sock;
126}
127
128socket($badsock, $pf, SOCK_STREAM, getprotobyname("tcp"));
129if (connect($badsock, $sin)) {
130	print "connect() succeeded but should have failed\n";
131	$rc_b = 1;
132}
133elsif (not $!{EADDRNOTAVAIL}) {
134	print "connect() failed with errno $!, should have been EADDRNOTAVAIL\n";
135	$rc_b = 1;
136}
137close($badsock);
138
139while ($sock = pop @socka) {
140	close($sock);
141}
142
143close(SERVSOCK);
144
145sleep 1;
146
147if ($rc_b == 0) {
148	print "test OK\n"
149}
150else {
151	print "test failed\n"
152}
153
154system("sysctl net.inet.ip.portfirst=$orig_first > /dev/null");
155system("sysctl net.inet.ip.portlast=$orig_last > /dev/null");
156
157exit ($rc_f || $rc_b);
158