2 # Simple tunnel for userv-ipif tunnels.
4 # Example test invocation
6 # ./udptunnel -e nonce -e timestamp/10/30 -e pkcs5/8 -e blowfish-cbcmac/128 -e blowfish-cbc/128 -m -f ./udptunnel-forwarder davenant,Any anarres,Command 172.30.206.1,172.30.206.2,1000,cslip 15,70 '' '' rsh anarres things/userv-utils/ipif/udptunnel -f things/userv-utils/ipif/udptunnel-forwarder
10 # [ -l[<local-command/arg>] ... .
11 # | -e <encryption-mech>[/<encryption-parameter>...]
12 # | -m (`masquerade support': subcommand gets `Wait' instead of our addr/port)
13 # | -d (`dump keys': when no subcmd, spew keys rather than reading them;
14 # we always send keys to our subcmd if there is one)
15 # | -Dcrypto (debug crypto - use with care, prints keys, packets &c on screen!)
16 # | -f<path-to-udptunnel-forwarder>
19 # <public-local-addr>,<public-local-port>
20 # <public-remote-addr>,<public-remote-port>
21 # <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
22 # <keepalive>,<timeout>[,<reannounce>]
23 # <extra-nets-for-local-cmd> <extra-nets-for-remote-cmd>
24 # [ <remote-command> [<remote-args> ...] ]
26 # proto may be slip or cslip
28 # Any <..-addr> may also be hostname
30 # Local addr's and ports may also be:
31 # `Print' choose one ourselves and print both port and addr
32 # `Any' choose one ourselves and do not print it
33 # Remote addr's and ports may also be:
34 # `Wait' wait to receive a packet before assigning address
35 # `Command' run a subcommand and wait for it to tell us the values
36 # When any addr or port is `Command' then <remote-command> must be specified.
38 # If <remote-command> is specified it should run udptunnel at the
39 # remote end; it will be invoked as
40 # <remote-command> [ <-e arguments passed along> ]
41 # <public-remote-addr'>,<public-remote-port'>
42 # <public-local-addr'>,<public-local-port'>
43 # <private-remote-addr>,<private-local-addr>,<mtu>,<proto>
44 # <keepalive>,<timeout>[,<reannounce>]
45 # <extra-nets-for-remote-cmd> <extra-nets-for-local-cmd>
48 # If it was given Print for <public-remote-foo'>, this command's first
49 # stdout output should be the real
50 # <public-remote-addr>,<public-remote-port> pair (and of course this
51 # udptunnel's output will be). It may then produce more stdout which,
52 # if any, will be forwarded to the local end's stdout as debugging info.
54 # After this, if any encryption was specified, the encryption
55 # parameters will be fed into its stdin. See the documentation in the
56 # mech-*.c files for details of the parameters. udptunnel will
57 # arrange to feed the keys fd of udptunnel-forwarder into the stdin of
60 # <public-remote-foo'> is as follows:
61 # <public-remote-foo> <public-remote-foo'>
62 # actual addr/port that addr/port
66 # <public-local-foo'> is as follows:
67 # <public-local-foo> <public-local-foo'> <public-local-foo'>
68 # (-m not specified) (-m specified)
69 # actual addr/port that addr/port `Wait'
70 # `Print' the chosen address `Wait'
71 # `Any' `Wait' for addr, `Wait'
72 # chosen port for port
74 # udptunnel will userv ipif locally, as
75 # userv root ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
76 # <extra-nets-for-local-cmd>
77 # or, if -l was given, userv root ipif is replaced with the argument(s)
78 # following -l option(s) until `.'.
80 # udptunnel will also run udptunnel-forwarder with appropriate options
82 # recommended encryption parameters are:
83 # -e nonce (prepend 32 bit counter)
84 # -e timestamp/<max-skew>/<max-age> (prepend 32 bit time_t, and check on receipt)
85 # -e pkcs5/8 (pad as per PKCS#5 to 8-byte boundary)
86 # -e blowfish-cbcmac/128 (prepend CBC MAC with random IV and 128 bit key)
87 # -e blowfish-cbc/128 (encrypt with CBC, random IV and 128 bit key)
88 # where <max-skew> is perhaps 10 and <max-age> perhaps 30.
90 # Copyright (C) 1999-2000 Ian Jackson
92 # This is free software; you can redistribute it and/or modify it
93 # under the terms of the GNU General Public License as published by
94 # the Free Software Foundation; either version 2 of the License, or
95 # (at your option) any later version.
97 # This program is distributed in the hope that it will be useful, but
98 # WITHOUT ANY WARRANTY; without even the implied warranty of
99 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
100 # General Public License for more details.
102 # You should have received a copy of the GNU General Public License
103 # along with userv-utils; if not, write to the Free Software
104 # Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
112 $progname= $0; $progname =~ s
,.*/,,;
115 chomp($hostname= `uname -n`);
116 $?
and die "$progname: cannot get hostname (uname failed with code $?)\n";
118 sub quit
($) { die "$progname - $hostname: fatal error: $_[0]\n"; }
119 sub debug
($) { print "$progname - $hostname: debug: $_[0]\n"; }
120 sub fail
($) { quit
("unexpected system call failure: $_[0]: $!"); }
121 sub warning
($) { warn "$progname - $hostname: $_[0]\n"; }
123 sub eat_addr_port
($) {
125 @ARGV or quit
("<addr>,<port> missing");
127 (m/^$x,/i && m/^[a-z]/ || m/,$x$/i && m/,[a-z]/)
128 and warning
("$_: use Mixed Case for special values");
129 m/^([0-9a-z][0-9a-z-+.]+|$x)\,(\d+|$x)$/
130 or quit
("$_: <host/addr>,<port> bad syntax".
131 (m/[A-Z]/ ?
' (use lowercase for hostnames)' : ''));
134 sub conv_host_addr
($) {
136 return INADDR_ANY
() if $s =~ m/^[A-Z][a-z]/;
137 return $r if defined($r= inet_aton
($s));
138 @h= gethostbyname($s) or quit
("$s: cannot get address");
139 $h[2] eq &AF_INET
or quit
("$s: address is not IPv4");
140 @h < 5 or quit
("$s: name maps to no addresses");
142 @h == 5 or warning
("$s: name has several addresses, using ".inet_ntoa
($r));
145 sub conv_port_number
($) {
147 return 0 if $s =~ m/^[A-Z][a-z]/;
149 $r>0 && $r<65536 or quit
("$s: port out of range");
154 @s= unpack_sockaddr_in
($s);
155 return inet_ntoa
($s[1]);
159 @s= unpack_sockaddr_in
($s);
162 sub show_addr_port
($) {
164 return show_addr
($s).','.show_port
($s);
169 return $val if length $val;
170 @ARGV or quit
("$opt needs value");
178 $fcmd= 'udptunnel-forwarder';
181 while ($ARGV[0] =~ m/^-/) {
186 push @lcmd,$_ if length;
187 while (@ARGV && ($_= shift @ARGV) ne '.') { push @lcmd, $_; }
190 $fcmd= arg_value
($_,'-f');
192 $encrarg= arg_value
($_,'-e');
193 push @remoteopts, "-e$encrarg";
194 @thisencryption= split m
#/#, $encrarg;
195 $thisencryption[0] =~ s/^/\|/;
196 push @encryption, @thisencryption;
201 } elsif (s/^-Dcrypto$/-/) {
203 push @remoteopts, '-Dcrypto';
205 quit
("unknown option \`$_'");
210 # Variables \$[lr]a?p?(|s|d|r)
211 # Local/Remote Address&/Port
212 # actualvalue/Specified/Displaypassdown/fromRemote/passtoForwarder
214 ($las,$lps)= eat_addr_port
('Print|Any');
215 $la= conv_host_addr
($las);
216 $lp= conv_port_number
($lps);
217 $ls= pack_sockaddr_in
$lp,$la;
219 ($ras,$rps)= eat_addr_port
('Wait|Command');
220 $ra= conv_host_addr
($ras);
221 $rp= conv_port_number
($rps);
222 $rs= pack_sockaddr_in
$rp,$ra;
225 m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/
226 or quit
("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip");
227 ($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4);
230 if (m/^(\d+),(\d+)$/) {
231 ($keepalive,$timeout,$reannounce)= ($1+0,$2+0,0);
232 $ka_to_ra= "$keepalive,$timeout";
233 } elsif (m/^(\d+),(\d+),(\d+)$/) {
234 ($keepalive,$timeout,$reannounce)= ($1+0,$2+0,$3);
235 "$keepalive,$timeout",
236 $ka_to_ra= "$keepalive,$timeout,$reannounce";
238 quit
("keepalive,timeout missing or bad syntax");
240 $keepalive && ($timeout > $keepalive*2) or quit
("timeout must be < 2*keepalive")
243 # Variables \$[lr]exn
244 # Local/Remote Extra Nets
248 defined($udp= getprotobyname('udp')) or fail
("getprotobyname udp");
250 socket(L
,PF_INET
,SOCK_DGRAM
,$udp) or fail
("socket");
251 bind(L
,$ls) or quit
("bind failed: $!");
252 defined($ls= getsockname(L
)) or fail
("getsockname");
253 $lad= show_addr
($ls);
254 $lpd= show_port
($ls);
257 print "$lapd\n" or fail
("print addr/port") if ($las eq 'Print' || $lps eq 'Print');
259 $rapcmd= ($ras eq 'Command' || $rps eq 'Command');
260 quit
("need remote-command if Command for remote addr/port") if $rapcmd && !@ARGV;
262 sub xform_remote
($$) {
263 my ($showed,$spec) = @_;
264 return 'Print' if $spec eq 'Command';
265 return 'Any' if $spec eq 'Wait';
270 warning
("-d specified with remote command, ignoring") if $dump;
273 $rad= xform_remote
(show_addr
($rs),$ras);
274 $rpd= xform_remote
(show_port
($rs),$rps);
278 $masq ?
'Wait,Wait' : $las eq 'Any' ?
"Wait,$lpd" : $lapd,
279 "$rva,$lva,$mtu,$proto",
282 debug
("remote command @rcmd");
285 pipe(RAPREAD
,RCMDREADSUB
) or fail
("pipe");
287 pipe(RCMDWRITESUB
,DUMPKEYS
) or fail
("pipe");
288 defined($c_rcmd= fork) or fail
("fork for remote");
290 open STDIN
, "<&RCMDWRITESUB" or fail
("reopen stdin for remote command");
291 open STDOUT
, ">&RCMDREADSUB" or fail
("reopen stdout for remote command")
293 close RAPREAD
if $rapcmd;
298 exec @rcmd; fail
("failed to execute remote command $rcmd[0]");
303 close RCMDREADSUB
if $rapcmd;
307 defined($nread= sysread(RAPREAD
,$_,1,length))
308 or fail
("read from remote command");
312 waitpid $c_rcmd,0 or fail
("wait for remote command");
313 quit
($? ?
"remote command failed (code $?)" :
314 "no details received from remote");
318 m/^([.0-9]+)\,(\d+)$/ or quit
("invalid details from remote end: \`$_'");
319 ($rar,$rpr) = ($1,$2);
320 $ra= conv_host_addr
($rar);
321 $rp= conv_port_number
($rpr);
323 defined($c_catremdebug= fork) or fail
("fork for cat remote debug");
324 if (!$c_catremdebug) {
325 open(STDIN
,"<&RAPREAD") or fail
("redirect remote debug");
328 exec "cat"; fail
("execute cat");
333 open DUMPKEYS
, ">&STDOUT" or fail
("reopen stdout for key material");
336 open DUMPKEYS
, "<&STDIN" or fail
("reopen stdout for key material");
339 $rs= pack_sockaddr_in
$rp,$ra;
341 if ($ras eq 'Wait' || $rps eq 'Wait') {
343 $rapd= ('Wait,Wait');
345 @rapf= (show_addr
($rs), show_port
($rs));
346 $rapd= show_addr_port
($rs);
348 @lcmd= qw(userv root ipif
) unless @lcmd;
350 debug
("using remote $rapd local $lapd");
351 push @lcmd, ("$lva,$rva,$mtu,$proto",$lexn);
352 debug
("local command @lcmd.");
354 pipe(UR
,UW
) or fail
("up pipe");
355 pipe(DR
,DW
) or fail
("down pipe");
357 defined($c_lcmd= fork) or fail
("fork for local command");
360 open(STDIN
,"<&DR") or fail
("reopen stdin for packets");
361 open(STDOUT
,">&UW") or fail
("reopen stdout for packets");
363 quit
("cannot execute $lcmd[0]: $!");
368 $xfwdopts.= 'w' if $dump;
370 @fcmd= ($fcmd, $xfwdopts,
371 fileno(L
), fileno(DW
), fileno(UR
), fileno(DUMPKEYS
),
372 $mtu, $keepalive, $timeout, $reannounce,
375 debug
("forwarding command @fcmd.");
377 defined($c_fwd= fork) or fail
("fork for udptunnel-forwarder");
379 foreach $fd (qw(L DW UR DUMPKEYS
)) {
380 fcntl($fd, F_SETFD
, 0) or fail
("set no-close-on-exec $fd");
382 exec @fcmd; fail
("cannot execute $fcmd[0]");
390 %procs= ($c_fwd, 'forwarder',
391 $c_lcmd, 'local command');
392 $procs{$c_rcmd}= 'remote command' if $c_rcmd;
393 $procs{$c_catremdebug}= 'debug cat' if $c_catremdebug;
397 while (keys %procs) {
399 fail
("wait failed (expecting ". join('; ',keys %procs). ")");
401 warning
("unexpected child reaped: pid $c, code $status"), next
402 unless exists $procs{$c};
405 $status ? warning
("subprocess $str failed with code $status")
406 : debug
("subprocess $str finished");
407 if ($c==$c_lcmd || $c==$c_fwd || $c==$c_rcmd) {
408 kill 15, grep (exists $procs{$_}, $c_fwd, $c_rcmd);
410 $estatus=1 unless $c == $c_catremdebug;
413 debug
("all processes terminated, exiting with status $estatus");