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