chiark / gitweb /
Bugfixes.
[userv-utils] / ipif / udptunnel
CommitLineData
46ab1c83 1#!/usr/bin/perl
2# Simple tunnel for userv-ipif tunnels.
3#
4# usage:
5# udptunnel
6# <public-local-host/addr>,<public-local-port>
7# <public-remote-host/addr>,<public-remote-port>
8# <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
9# <keepalive>,<timeout>
10# <extra-local-nets> <extra-remote-nets>
11# [ <remote-command> [<remote-args> ...] ]
12#
13# <local-public-port> may be number or `print' or `silent'
14#
15# <remote-public-port> may number or `command', in which case
16# <remote-command> must be specified and should run udptunnel at the
17# remote end; it will be invoked as
18# <remote-command> <public-remote-host/addr>,print
19# <public-local-addr>,<public-local-port>
20# <private-remote-addr>,<private-local-addr>,<mtu>,<proto>
217afbe6 21# <keepalive>,<timeout>
22# <extra-remote-nets> <extra-local-nets>
46ab1c83 23#
24# udptunnel will userv ipif locally, as
217afbe6 25# userv root ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
26# <extra-local-nets>
46ab1c83 27
28use Socket;
29use POSIX;
30use Fcntl;
31
32$progname= $0; $progname =~ s,.*/,,;
33$|=1;
34
35chomp($hostname= `uname -n`);
36$? and die "$progname: cannot get hostname (uname failed with code $?)\n";
37
38sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; }
39sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; }
40sub fail ($) { quit("unexpected system call failure: $_[0]: $!\n"); }
41sub warning ($) { warn "$progname - $hostname: $_[0]\n"; }
42
43sub eat_addr_port ($) {
44 my ($x) = @_;
45 @ARGV or quit("<host/addr>,<port> missing");
46 $_= shift(@ARGV);
47 $_ =~ m/^([^,]+)\,(\d+|$x)$/ or quit("$_: <host/addr>,<port> bad syntax");
48 return ($1,$2);
49}
50sub conv_host_addr ($) {
51 my ($s,$r) = @_;
52 defined($r= inet_aton($s)) or quit("$s: cannot convert to address");
53 return $r;
54}
217afbe6 55sub conv_port_number ($) {
46ab1c83 56 my ($s,$r) = @_;
57 if ($s =~ m/\d/) {
58 $r= $s+0;
59 $r>0 && $r<65536 or quit("$s: port out of range");
60 } else {
61 $r= 0;
62 }
63 return $r;
64}
65sub show_addr_port ($) {
66 my ($s,@s) = @_;
67 @s= unpack_sockaddr_in($s);
68 return inet_ntoa($s[1]).','.$s[0];
69}
70
71($las,$lps)= eat_addr_port('print|silent');
72$la= conv_host_addr($las);
73$lp= conv_port_number($lps);
74$ls= pack_sockaddr_in $lp,$la;
75
76($ras,$rps)= eat_addr_port('command');
77$rp= conv_port_number($rps);
78$ra= $rps eq 'command' ? '' : conv_host_addr($ras);
79
80$_= shift @ARGV;
81m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/
82 or quit("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip");
217afbe6 83($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4);
46ab1c83 84
85$_= shift @ARGV;
217afbe6 86m/^(\d+),(\d+)$/ or quit("keepalive,timeout missing or bad syntax");
87($keepalive,$timeout)= ($1,$2);
46ab1c83 88$keepalive && ($timeout > $keepalive*2) or quit("timeout must be < 2*keepalive")
89 if $timeout;
90
91$lepn= shift @ARGV;
92$repn= shift @ARGV;
93
94alarm($timeout);
95
96defined($udp= getprotobyname('udp')) or fail("getprotobyname udp");
97
98socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket");
99bind(L,$ls) or quit("bind failed: $!");
100defined($ls= getsockname(L)) or fail("getsockname");
101$lsp= show_addr_port($ls);
102
103if ($rps eq 'command') {
46ab1c83 104 quit("when using ,command for remote, must supply command") unless @ARGV;
217afbe6 105 @rcmd= (@ARGV, "$ras,print", "$lsp", "$rva,$lva,$mtu,$proto",
106 "$keepalive,$timeout", $repn, $lepn);
107 debug("remote command @rcmd");
46ab1c83 108 defined($c= open C,"-|") or fail("fork for remote");
109 if (!$c) {
217afbe6 110 exec @rcmd; die "$progname: error: failed to execute $rcmd[0]: $!\n";
46ab1c83 111 }
112 $_= <C>;
113 if (!length) {
114 close C;
115 quit($? ? "remote command failed (code $?)" : "no details received from remote");
116 }
117 chomp;
118 m/^([.0-9]+)\,(\d+)$/ or quit("invalid details from remote end ($_)");
119 ($ras,$rps) = ($1,$2);
120 $ra= conv_host_addr($ras);
217afbe6 121 $rp= conv_port_number($rps);
46ab1c83 122 defined($c2= fork) or fail("fork for cat");
123 if (!$c2) {
217afbe6 124 open(STDIN,"<&C") or fail("redirect remote pipe to stdin");
46ab1c83 125 close C;
126 exec "cat"; fail("execute cat");
127 }
128} else {
129 quit("when not using ,command for remote, must not supply command") if @ARGV;
130}
131
132$rs= pack_sockaddr_in $rp,$ra;
133$rsp= show_addr_port($rs);
134
217afbe6 135if ($lps eq 'print') { print($lsp,"\n") or quit("write port to stdout: $!"); }
46ab1c83 136
217afbe6 137debug("using remote $rsp local $lsp");
46ab1c83 138
139pipe(UR,UW) or fail("up pipe");
140pipe(DR,DW) or fail("down pipe");
141
142defined($c3= fork) or fail("fork for ipif");
143if (!$c3) {
144 close UR; close DW;
217afbe6 145 open(STDIN,"<&DR") or fail("reopen stdin for packets");
146 open(STDOUT,">&UW") or fail("reopen stdout for packets");
147 exec "userv","root","ipif","$lva,$rva,$mtu,$proto",$lepn;
46ab1c83 148 quit("cannot execute userv ipif: $!");
149}
150close UW;
151close DR;
152
153$upyet= 0;
154$downyet= 0;
155
156$wantreadfds='';
217afbe6 157vec($wantreadfds,fileno(UR),1)= 1;
158vec($wantreadfds,fileno(L),1)= 1;
46ab1c83 159
160sub nonblock ($) {
161 my ($fh,$fl) = @_;
162 ($fl= fcntl($fh,F_GETFL,0)) or fail("nonblock F_GETFL");
163 $fl |= O_NONBLOCK;
164 fcntl($fh, F_SETFL, $fl) or fail("nonblock F_SETFL");
165}
166
167nonblock('UR');
168nonblock('L');
169
170$upbuf= '';
171
217afbe6 172sub now () { my ($v); defined($v= time) or fail("get time"); return $v; }
173if ($keepalive) { $nextsendka= now(); }
174
46ab1c83 175for (;;) {
217afbe6 176 if ($keepalive) {
177 $now= now();
178 $timeout= $nextsendka-$now;
179 if ($timeout < 0) {
180 defined(send L,"\300",0,$rs)
181 or warning("transmit keepalive error: $!");
182 $nextsendka= $now+$keepalive;
183 $timeout= $keepalive;
184 }
185 } else {
186 $timeout= undef;
187 }
188 select($readfds=$wantreadfds,'','',$timeout);
46ab1c83 189 for (;;) {
190 if (!defined($r= sysread(UR,$upbuf,$mtu*2+3,length($upbuf)))) {
191 $! == EAGAIN || warning("tunnel endpoint read error: $!");
192 last;
193 }
194 if (!$r) {
195 quit "tunnel endpoint closed by system";
196 }
197 while (($p= index($upbuf,"\300")) >= 0) {
217afbe6 198 if (!defined(send L,substr($upbuf,0,$p),0,$rs)) {
199 warning("transmit error: $!");
200 } elsif (!$upyet) {
46ab1c83 201 $upyet= 1;
33b946d9 202 debug($downyet ? "tunnel open at this end" : "transmit channel open");
46ab1c83 203 }
204 $upbuf= substr($upbuf+1,$p);
205 }
206 }
207 while (defined($rs_from= recv L,$downbuf,$mtu*2+3,0)) {
208 $rsp_from= show_addr_port($rs_from);
209 if ($rsp_from ne $rsp) {
210 warning("got packet from incorrect peer $rsp_from");
217afbe6 211 next;
46ab1c83 212 }
213 if (!defined($r= syswrite(DW,$downbuf,length $downbuf))) {
214 warning("tunnel endpoint write error: $!");
217afbe6 215 } elsif ($r != length $downbuf) {
46ab1c83 216 warning("tunnel endpoint wrong write length");
217 } else {
217afbe6 218 if (!$downyet) {
219 $downyet= 1;
33b946d9 220 debug($upyet ? "tunnel open at this end" : "receive channel open");
217afbe6 221 }
222 alarm($timeout) if $timeout;
46ab1c83 223 }
224 }
225 $! == EAGAIN || warning("receive error: $!");
226}