Update to GPLv3+; update copyright notices everywhere.
[userv-utils] / ipif / udptunnel
CommitLineData
46ab1c83 1#!/usr/bin/perl
5124214b 2# Encrypting VPN tunnel for use with userv-ipif.
46ab1c83 3#
5124214b 4# This comment is reference documentation. See ipif/INSTALL for the
5# installation instructions and setup tutorial.
8f1bfa81 6#
46ab1c83 7# usage:
5124214b 8# To make a tunnel between two machines `alice' and `bob',
9# on `alice', the active endpoint, run:
10#
11# udptunnel
12# [ -l[<alice-command/arg>] ... .
9d4e63db 13# | -e <encryption-mech>[/<encryption-parameter>...]
5124214b 14# | -m (`masquerade support': bob gets `Wait' instead of our addr/port)
15# | -d (`dump keys': when no peer, spew keys rather than reading them;
16# we always send keys to our peer if there is one)
09966b49 17# | -Dcrypto (debug crypto - use with care, prints keys, packets &c on screen!)
9d4e63db 18# | -f<path-to-udptunnel-forwarder>
1fb3cba0 19# ...
20# ]
5124214b 21# <alice-phys-addr>,<alice-phys-port>
22# <bob-phys-addr>,<bob-phys-port>
23# <alice-virt-addr>,<bob-virt-addr>,<mtu>,<proto>
92bbf44b 24# <keepalive>,<timeout>[,<reannounce>]
5124214b 25# <alice-priv-nets> <bob-priv-nets>
26# [ <bob-command> [<bob-args> ...] ]
46ab1c83 27#
5124214b 28# This will run udptunnel-forwarder on alice, and use <bob-command>
29# (usually an ssh invocation) to run udptunnel appropriately on bob.
30# Key material will be generated by alice and fed to udptunnel on bob
31# via <bob-command>'s stdin, and the physical address and port on bob
32# will be (if so configured) returned via <bob-command>'s stdout.
1fb3cba0 33#
5124214b 34# The tunnel will stay up until one of the subprocesses involved dies,
35# or the keepalive timeout expires. If you want the tunnel to remain
36# up permanently, you must arrange to invoke it repeatedly (eg, from
37# inittab). See INSTALL.
1fb3cba0 38#
5124214b 39# <proto> may be slip or cslip
46ab1c83 40#
5124214b 41# <mtu> will be the MTU of the tunnel interfaces; it is best if this
42# is enough smaller than the path MTU between the physical interfaces
43# that the encapsulated packets will fit without fragmentation.
44#
45# Any <..-addr> supplied to udptunnel may also be hostname; these will
46# all be looked up on alice and IP addresses passed to bob.
47#
48# The `local' physical address and ports (ie, alice's own details),
49# may have these special values:
50# `Any' choose one ourselves and do not print it (the port chosen
51# will be supplied to bob)
52# `Print' choose one ourselves and print both port and addr
53# (this is not usually useful specified directly; it's
54# used by udptunnel when it invokes itself on bob via
55# <bob-command>, to have its other self print the
56# relevant value.
57#
58# The `remote' physical address and port (ie, on alice, bob's details),
59# may also have the special values:
60# `Command' wait for <bob-command> to tell us the values (this is
61# usually the right choice on alice for at least the
62# port). <bob-command> must be specified (ie, this
63# only makes sense on alice).
64# `Wait' alice will wait to receive a packet from bob and use
65# whatever address and port it came from
66#
67# These special values are case-sensitive. When alice runs udptunnel
68# on bob they are automatically translated to appropriate other values
69# in the arguments to bob's udptunnel.
70#
71# If <bob-command> is specified it should run udptunnel at the
72# bob end; it will be invoked as
73# <bob-command> [ <bob-args> ... ]
74# [ <-e arguments passed along> ]
75# <bob-phys-addr'>,<bob-phys-port'>
76# <alice-phys-addr'>,<alice-phys-port'>
77# <bob-virt-addr>,<alice-virt-addr>,<mtu>,<proto>
78# <keepalive>,<timeout>[,<reannounce>]
79# <bob-priv-nets> <alice-priv-nets>
80#
81# If it was given Print for <bob-phys-foo'>, udptunnel's first stdout
82# output will be the real <bob-phys-addr>,<bob-phys-port> pair. It
83# may then produce more stdout which, if any, will be forwarded to the
84# local end's stdout as debugging info.
85#
86# After this, if any encryption was specified, the encryption key
87# material will be fed into its stdin. See the documentation in the
88# mech-*.c files for details of the parameters. udptunnel on alice
89# will arrange to feed the keys fd of udptunnel-forwarder into the
90# stdin of the udptunnel on bob.
91#
92# <bob-phys-foo'> is as follows:
93# <bob-phys-foo> <bob-phys-foo'>
94# actual addr/port that addr/port
95# `Command' `Print'
96# `Wait' `Any'
97#
98# <alice-phys-foo'> is as follows:
99# <alice-phys-foo> <alice-phys-foo'> <alice-phys-foo'>
100# (-m not specified) (-m specified)
9d4e63db 101# actual addr/port that addr/port `Wait'
9d4e63db 102# `Print' the chosen address `Wait'
31dcbf98 103# `Any' `Wait' for addr, `Wait'
104# chosen port for port
9d4e63db 105#
5124214b 106# In each case udptunnel will run userv ipif locally, as
107# userv root ipif <local-virt-addr>,<remote-virt-addr>,<mtu>,<proto>
108# <remote-priv-nets>
9d4e63db 109# or, if -l was given, userv root ipif is replaced with the argument(s)
110# following -l option(s) until `.'.
111#
5124214b 112# udptunnel will also run udptunnel-forwarder with appropriate options.
9d4e63db 113#
114# recommended encryption parameters are:
115# -e nonce (prepend 32 bit counter)
116# -e timestamp/<max-skew>/<max-age> (prepend 32 bit time_t, and check on receipt)
117# -e pkcs5/8 (pad as per PKCS#5 to 8-byte boundary)
118# -e blowfish-cbcmac/128 (prepend CBC MAC with random IV and 128 bit key)
119# -e blowfish-cbc/128 (encrypt with CBC, random IV and 128 bit key)
5124214b 120# where <max-skew> is perhaps 10 and <max-age> perhaps 30. If your
121# clocks are not sufficiently well synchronised, you could replace
122# `-e nonce -e timestamp/...' with just `-e sequence'. Do not just
123# remove `-e timestamp/...'.
46ab1c83 124
9028e234
IJ
125# Copyright 1996-2013 Ian Jackson <ijackson@chiark.greenend.org.uk>
126# Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk>
127# Copyright 1999,2003
128# Chancellor Masters and Scholars of the University of Cambridge
129# Copyright 2010 Tony Finch <fanf@dotat.at>
caa68336 130#
131# This is free software; you can redistribute it and/or modify it
132# under the terms of the GNU General Public License as published by
9028e234 133# the Free Software Foundation; either version 3 of the License, or
caa68336 134# (at your option) any later version.
135#
136# This program is distributed in the hope that it will be useful, but
137# WITHOUT ANY WARRANTY; without even the implied warranty of
138# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
139# General Public License for more details.
140#
141# You should have received a copy of the GNU General Public License
9028e234 142# along with userv-utils; if not, see http://www.gnu.org/licenses/.
caa68336 143
46ab1c83 144use Socket;
145use POSIX;
146use Fcntl;
147
148$progname= $0; $progname =~ s,.*/,,;
149$|=1;
150
151chomp($hostname= `uname -n`);
152$? and die "$progname: cannot get hostname (uname failed with code $?)\n";
153
154sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; }
155sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; }
9d4e63db 156sub fail ($) { quit("unexpected system call failure: $_[0]: $!"); }
46ab1c83 157sub warning ($) { warn "$progname - $hostname: $_[0]\n"; }
158
159sub eat_addr_port ($) {
160 my ($x) = @_;
1fb3cba0 161 @ARGV or quit("<addr>,<port> missing");
46ab1c83 162 $_= shift(@ARGV);
9d4e63db 163 (m/^$x,/i && m/^[a-z]/ || m/,$x$/i && m/,[a-z]/)
164 and warning("$_: use Mixed Case for special values");
165 m/^([0-9a-z][0-9a-z-+.]+|$x)\,(\d+|$x)$/
166 or quit("$_: <host/addr>,<port> bad syntax".
167 (m/[A-Z]/ ? ' (use lowercase for hostnames)' : ''));
46ab1c83 168 return ($1,$2);
169}
170sub conv_host_addr ($) {
9d4e63db 171 my ($s,$r,@h) = @_;
172 return INADDR_ANY() if $s =~ m/^[A-Z][a-z]/;
173 return $r if defined($r= inet_aton($s));
174 @h= gethostbyname($s) or quit("$s: cannot get address");
175 $h[2] eq &AF_INET or quit("$s: address is not IPv4");
176 @h < 5 or quit("$s: name maps to no addresses");
177 $r= $h[4];
178 @h == 5 or warning("$s: name has several addresses, using ".inet_ntoa($r));
46ab1c83 179 return $r;
180}
217afbe6 181sub conv_port_number ($) {
46ab1c83 182 my ($s,$r) = @_;
9d4e63db 183 return 0 if $s =~ m/^[A-Z][a-z]/;
184 $r= $s+0;
185 $r>0 && $r<65536 or quit("$s: port out of range");
46ab1c83 186 return $r;
187}
9d4e63db 188sub show_addr ($) {
189 my ($s,@s) = @_;
190 @s= unpack_sockaddr_in($s);
191 return inet_ntoa($s[1]);
192}
193sub show_port ($) {
46ab1c83 194 my ($s,@s) = @_;
195 @s= unpack_sockaddr_in($s);
9d4e63db 196 return $s[0];
197}
198sub show_addr_port ($) {
199 my ($s) = @_;
200 return show_addr($s).','.show_port($s);
46ab1c83 201}
9d4e63db 202sub arg_value ($$) {
0f4b558c 203 my ($val,$opt) = @_;
9d4e63db 204 $_= '-';
205 return $val if length $val;
206 @ARGV or quit("$opt needs value");
207 return shift @ARGV;
208}
209
abe75cda 210@lcmd= ();
9d4e63db 211@encryption= ();
212$masq= 0;
213$dump= 0;
214$fcmd= 'udptunnel-forwarder';
09966b49 215$xfwdopts= '';
abe75cda 216
217while ($ARGV[0] =~ m/^-/) {
218 $_= shift @ARGV;
9d4e63db 219 last if m/^--?$/;
220 while (!m/^-$/) {
221 if (s/^-l//) {
222 push @lcmd,$_ if length;
223 while (@ARGV && ($_= shift @ARGV) ne '.') { push @lcmd, $_; }
224 $_= '-'
225 } elsif (s/^-f//) {
226 $fcmd= arg_value($_,'-f');
227 } elsif (s/^-e//) {
228 $encrarg= arg_value($_,'-e');
0f4b558c 229 push @remoteopts, "-e$encrarg";
230 @thisencryption= split m#/#, $encrarg;
ed509ebd 231 $thisencryption[0] =~ s/^/\|/;
0f4b558c 232 push @encryption, @thisencryption;
9d4e63db 233 } elsif (s/^-m/-/) {
234 $masq= 1;
235 } elsif (s/^-d/-/) {
236 $dump= 1;
09966b49 237 } elsif (s/^-Dcrypto$/-/) {
238 $xfwdopts.= 'K';
0f4b558c 239 push @remoteopts, '-Dcrypto';
9d4e63db 240 } else {
241 quit("unknown option \`$_'");
242 }
abe75cda 243 }
244}
245
9d4e63db 246# Variables \$[lr]a?p?(|s|d|r)
247# Local/Remote Address&/Port
248# actualvalue/Specified/Displaypassdown/fromRemote/passtoForwarder
249#
250($las,$lps)= eat_addr_port('Print|Any');
46ab1c83 251$la= conv_host_addr($las);
252$lp= conv_port_number($lps);
253$ls= pack_sockaddr_in $lp,$la;
254
9d4e63db 255($ras,$rps)= eat_addr_port('Wait|Command');
256$ra= conv_host_addr($ras);
46ab1c83 257$rp= conv_port_number($rps);
9d4e63db 258$rs= pack_sockaddr_in $rp,$ra;
46ab1c83 259
260$_= shift @ARGV;
261m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/
262 or quit("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip");
217afbe6 263($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4);
46ab1c83 264
265$_= shift @ARGV;
92bbf44b 266if (m/^(\d+),(\d+)$/) {
267 ($keepalive,$timeout,$reannounce)= ($1+0,$2+0,0);
268 $ka_to_ra= "$keepalive,$timeout";
269} elsif (m/^(\d+),(\d+),(\d+)$/) {
270 ($keepalive,$timeout,$reannounce)= ($1+0,$2+0,$3);
271 "$keepalive,$timeout",
272 $ka_to_ra= "$keepalive,$timeout,$reannounce";
273} else {
274 quit("keepalive,timeout missing or bad syntax");
275}
46ab1c83 276$keepalive && ($timeout > $keepalive*2) or quit("timeout must be < 2*keepalive")
277 if $timeout;
278
9d4e63db 279# Variables \$[lr]exn
280# Local/Remote Extra Nets
281$lexn= shift @ARGV;
282$rexn= shift @ARGV;
46ab1c83 283
284defined($udp= getprotobyname('udp')) or fail("getprotobyname udp");
285
286socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket");
287bind(L,$ls) or quit("bind failed: $!");
288defined($ls= getsockname(L)) or fail("getsockname");
9d4e63db 289$lad= show_addr($ls);
290$lpd= show_port($ls);
291$lapd= "$lad,$lpd";
292
293print "$lapd\n" or fail("print addr/port") if ($las eq 'Print' || $lps eq 'Print');
46ab1c83 294
9d4e63db 295$rapcmd= ($ras eq 'Command' || $rps eq 'Command');
296quit("need remote-command if Command for remote addr/port") if $rapcmd && !@ARGV;
297
298sub xform_remote ($$) {
299 my ($showed,$spec) = @_;
300 return 'Print' if $spec eq 'Command';
301 return 'Any' if $spec eq 'Wait';
302 return $showed;
303}
304
305if (@ARGV) {
306 warning("-d specified with remote command, ignoring") if $dump;
307 $dump= 1;
308
309 $rad= xform_remote(show_addr($rs),$ras);
310 $rpd= xform_remote(show_port($rs),$rps);
311 @rcmd= (@ARGV,
0f4b558c 312 @remoteopts,
9d4e63db 313 "$rad,$rpd",
31dcbf98 314 $masq ? 'Wait,Wait' : $las eq 'Any' ? "Wait,$lpd" : $lapd,
9d4e63db 315 "$rva,$lva,$mtu,$proto",
92bbf44b 316 $ka_to_ra,
9d4e63db 317 $rexn, $lexn);
217afbe6 318 debug("remote command @rcmd");
9d4e63db 319
320 if ($rapcmd) {
321 pipe(RAPREAD,RCMDREADSUB) or fail("pipe");
46ab1c83 322 }
0f4b558c 323 pipe(RCMDWRITESUB,DUMPKEYS) or fail("pipe");
9d4e63db 324 defined($c_rcmd= fork) or fail("fork for remote");
325 if (!$c_rcmd) {
0f4b558c 326 open STDIN, "<&RCMDWRITESUB" or fail("reopen stdin for remote command");
9d4e63db 327 open STDOUT, ">&RCMDREADSUB" or fail("reopen stdout for remote command")
328 if $rapcmd;
329 close RAPREAD if $rapcmd;
330 close DUMPKEYS;
331 close RCMDWRITESUB;
332 close RCMDREADSUB;
333 close L;
334 exec @rcmd; fail("failed to execute remote command $rcmd[0]");
46ab1c83 335 }
9d4e63db 336 close RCMDWRITESUB;
337
338 if ($rapcmd) {
339 close RCMDREADSUB if $rapcmd;
4ffb4653 340 $_= '';
341 while (!m/\n/) {
342 $!=0;
343 defined($nread= sysread(RAPREAD,$_,1,length))
344 or fail("read from remote command");
345 if (!$nread) {
346 close DUMPKEYS;
347 close RAPREAD;
348 waitpid $c_rcmd,0 or fail("wait for remote command");
349 quit($? ? "remote command failed (code $?)" :
350 "no details received from remote");
351 }
352 }
353 chomp;
354 m/^([.0-9]+)\,(\d+)$/ or quit("invalid details from remote end: \`$_'");
355 ($rar,$rpr) = ($1,$2);
356 $ra= conv_host_addr($rar);
357 $rp= conv_port_number($rpr);
9d4e63db 358
359 defined($c_catremdebug= fork) or fail("fork for cat remote debug");
360 if (!$c_catremdebug) {
361 open(STDIN,"<&RAPREAD") or fail("redirect remote debug");
9d4e63db 362 close DUMPKEYS;
363 close L;
364 exec "cat"; fail("execute cat");
365 }
366 close RAPREAD;
46ab1c83 367 }
9d4e63db 368} elsif ($dump) {
369 open DUMPKEYS, ">&STDOUT" or fail("reopen stdout for key material");
370 $dump= 1;
46ab1c83 371} else {
9d4e63db 372 open DUMPKEYS, "<&STDIN" or fail("reopen stdout for key material");
46ab1c83 373}
374
375$rs= pack_sockaddr_in $rp,$ra;
46ab1c83 376
9d4e63db 377if ($ras eq 'Wait' || $rps eq 'Wait') {
378 @rapf= ('');
379 $rapd= ('Wait,Wait');
380} else {
381 @rapf= (show_addr($rs), show_port($rs));
382 $rapd= show_addr_port($rs);
383}
abe75cda 384@lcmd= qw(userv root ipif) unless @lcmd;
385
9d4e63db 386debug("using remote $rapd local $lapd");
387push @lcmd, ("$lva,$rva,$mtu,$proto",$lexn);
388debug("local command @lcmd.");
46ab1c83 389
390pipe(UR,UW) or fail("up pipe");
391pipe(DR,DW) or fail("down pipe");
392
9d4e63db 393defined($c_lcmd= fork) or fail("fork for local command");
394if (!$c_lcmd) {
46ab1c83 395 close UR; close DW;
217afbe6 396 open(STDIN,"<&DR") or fail("reopen stdin for packets");
397 open(STDOUT,">&UW") or fail("reopen stdout for packets");
abe75cda 398 exec @lcmd;
399 quit("cannot execute $lcmd[0]: $!");
46ab1c83 400}
401close UW;
402close DR;
403
09966b49 404$xfwdopts.= 'w' if $dump;
405
406@fcmd= ($fcmd, $xfwdopts,
407 fileno(L), fileno(DW), fileno(UR), fileno(DUMPKEYS),
92bbf44b 408 $mtu, $keepalive, $timeout, $reannounce,
9d4e63db 409 @rapf,
9d4e63db 410 @encryption);
411debug("forwarding command @fcmd.");
46ab1c83 412
9d4e63db 413defined($c_fwd= fork) or fail("fork for udptunnel-forwarder");
414if (!$c_fwd) {
0f4b558c 415 foreach $fd (qw(L DW UR DUMPKEYS)) {
9d4e63db 416 fcntl($fd, F_SETFD, 0) or fail("set no-close-on-exec $fd");
417 }
418 exec @fcmd; fail("cannot execute $fcmd[0]");
46ab1c83 419}
420
9d4e63db 421close L;
422close DUMPKEYS;
423close UR;
424close DW;
46ab1c83 425
9d4e63db 426%procs= ($c_fwd, 'forwarder',
427 $c_lcmd, 'local command');
428$procs{$c_rcmd}= 'remote command' if $c_rcmd;
429$procs{$c_catremdebug}= 'debug cat' if $c_catremdebug;
46ab1c83 430
9d4e63db 431$estatus= 0;
217afbe6 432
9d4e63db 433while (keys %procs) {
434 ($c= wait) >0 or
435 fail("wait failed (expecting ". join('; ',keys %procs). ")");
0f4b558c 436 $status= $?;
437 warning("unexpected child reaped: pid $c, code $status"), next
9d4e63db 438 unless exists $procs{$c};
439 $str= $procs{$c};
440 delete $procs{$c};
0f4b558c 441 $status ? warning("subprocess $str failed with code $status")
9d4e63db 442 : debug("subprocess $str finished");
443 if ($c==$c_lcmd || $c==$c_fwd || $c==$c_rcmd) {
444 kill 15, grep (exists $procs{$_}, $c_fwd, $c_rcmd);
46ab1c83 445 }
9d4e63db 446 $estatus=1 unless $c == $c_catremdebug;
46ab1c83 447}
9d4e63db 448
449debug("all processes terminated, exiting with status $estatus");
450
451exit $estatus;