X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=ipif%2Fudptunnel;h=963245c49f01e326e097e98cf027a0b46993df86;hb=70b9d2e0dbecb8fe1e2045a1752465cd35885bb0;hp=e4ce4506c5bfb3850b35a8fc0c7a727d9b3d7425;hpb=caa68336fa6c005906fd0f88e444ce6a350cfa79;p=userv-utils.git diff --git a/ipif/udptunnel b/ipif/udptunnel index e4ce450..963245c 100755 --- a/ipif/udptunnel +++ b/ipif/udptunnel @@ -1,34 +1,128 @@ #!/usr/bin/perl -# Simple tunnel for userv-ipif tunnels. +# Encrypting VPN tunnel for use with userv-ipif. +# +# This comment is reference documentation. See ipif/INSTALL for the +# installation instructions and setup tutorial. # # usage: -# udptunnel -# [ -l[] ... . ] -# , -# , -# ,,, -# , -# -# [ [ ...] ] +# To make a tunnel between two machines `alice' and `bob', +# on `alice', the active endpoint, run: +# +# udptunnel +# [ -l[] ... . +# | -e [/...] +# | -m (`masquerade support': bob gets `Wait' instead of our addr/port) +# | -d (`dump keys': when no peer, spew keys rather than reading them; +# we always send keys to our peer if there is one) +# | -Dcrypto (debug crypto - use with care, prints keys, packets &c on screen!) +# | -f +# ... +# ] +# , +# , +# ,,, +# ,[,] +# +# [ [ ...] ] +# +# This will run udptunnel-forwarder on alice, and use +# (usually an ssh invocation) to run udptunnel appropriately on bob. +# Key material will be generated by alice and fed to udptunnel on bob +# via 's stdin, and the physical address and port on bob +# will be (if so configured) returned via 's stdout. +# +# The tunnel will stay up until one of the subprocesses involved dies, +# or the keepalive timeout expires. If you want the tunnel to remain +# up permanently, you must arrange to invoke it repeatedly (eg, from +# inittab). See INSTALL. +# +# may be slip or cslip +# +# will be the MTU of the tunnel interfaces; it is best if this +# is enough smaller than the path MTU between the physical interfaces +# that the encapsulated packets will fit without fragmentation. +# +# Any <..-addr> supplied to udptunnel may also be hostname; these will +# all be looked up on alice and IP addresses passed to bob. +# +# The `local' physical address and ports (ie, alice's own details), +# may have these special values: +# `Any' choose one ourselves and do not print it (the port chosen +# will be supplied to bob) +# `Print' choose one ourselves and print both port and addr +# (this is not usually useful specified directly; it's +# used by udptunnel when it invokes itself on bob via +# , to have its other self print the +# relevant value. +# +# The `remote' physical address and port (ie, on alice, bob's details), +# may also have the special values: +# `Command' wait for to tell us the values (this is +# usually the right choice on alice for at least the +# port). must be specified (ie, this +# only makes sense on alice). +# `Wait' alice will wait to receive a packet from bob and use +# whatever address and port it came from +# +# These special values are case-sensitive. When alice runs udptunnel +# on bob they are automatically translated to appropriate other values +# in the arguments to bob's udptunnel. +# +# If is specified it should run udptunnel at the +# bob end; it will be invoked as +# [ ... ] +# [ <-e arguments passed along> ] +# , +# , +# ,,, +# ,[,] +# +# +# If it was given Print for , udptunnel's first stdout +# output will be the real , pair. It +# may then produce more stdout which, if any, will be forwarded to the +# local end's stdout as debugging info. +# +# After this, if any encryption was specified, the encryption key +# material will be fed into its stdin. See the documentation in the +# mech-*.c files for details of the parameters. udptunnel on alice +# will arrange to feed the keys fd of udptunnel-forwarder into the +# stdin of the udptunnel on bob. +# +# is as follows: +# +# actual addr/port that addr/port +# `Command' `Print' +# `Wait' `Any' +# +# is as follows: +# +# (-m not specified) (-m specified) +# actual addr/port that addr/port `Wait' +# `Print' the chosen address `Wait' +# `Any' `Wait' for addr, `Wait' +# chosen port for port # -# may be number or `print' or `silent' +# In each case udptunnel will run userv ipif locally, as +# userv root ipif ,,, +# +# or, if -l was given, userv root ipif is replaced with the argument(s) +# following -l option(s) until `.'. # -# may number or `command', in which case -# must be specified and should run udptunnel at the -# remote end; it will be invoked as -# ,print -# , -# ,,, -# , -# +# udptunnel will also run udptunnel-forwarder with appropriate options. # -# udptunnel will userv ipif locally, as -# userv root ipif ,,, -# -# or, if -lc was given, userv root ipif is replaced with the argument(s) to -# successive -lc options. +# recommended encryption parameters are: +# -e nonce (prepend 32 bit counter) +# -e timestamp// (prepend 32 bit time_t, and check on receipt) +# -e pkcs5/8 (pad as per PKCS#5 to 8-byte boundary) +# -e blowfish-cbcmac/128 (prepend CBC MAC with random IV and 128 bit key) +# -e blowfish-cbc/128 (encrypt with CBC, random IV and 128 bit key) +# where is perhaps 10 and perhaps 30. If your +# clocks are not sufficiently well synchronised, you could replace +# `-e nonce -e timestamp/...' with just `-e sequence'. Do not just +# remove `-e timestamp/...'. -# Copyright (C) 1999 Ian Jackson +# Copyright (C) 1999-2000 Ian Jackson # # This is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -58,58 +152,109 @@ $? and die "$progname: cannot get hostname (uname failed with code $?)\n"; sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; } sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; } -sub fail ($) { quit("unexpected system call failure: $_[0]: $!\n"); } +sub fail ($) { quit("unexpected system call failure: $_[0]: $!"); } sub warning ($) { warn "$progname - $hostname: $_[0]\n"; } sub eat_addr_port ($) { my ($x) = @_; - @ARGV or quit(", missing"); + @ARGV or quit(", missing"); $_= shift(@ARGV); - $_ =~ m/^([^,]+)\,(\d+|$x)$/ or quit("$_: , bad syntax"); + (m/^$x,/i && m/^[a-z]/ || m/,$x$/i && m/,[a-z]/) + and warning("$_: use Mixed Case for special values"); + m/^([0-9a-z][0-9a-z-+.]+|$x)\,(\d+|$x)$/ + or quit("$_: , bad syntax". + (m/[A-Z]/ ? ' (use lowercase for hostnames)' : '')); return ($1,$2); } sub conv_host_addr ($) { - my ($s,$r) = @_; - defined($r= inet_aton($s)) or quit("$s: cannot convert to address"); + my ($s,$r,@h) = @_; + return INADDR_ANY() if $s =~ m/^[A-Z][a-z]/; + return $r if defined($r= inet_aton($s)); + @h= gethostbyname($s) or quit("$s: cannot get address"); + $h[2] eq &AF_INET or quit("$s: address is not IPv4"); + @h < 5 or quit("$s: name maps to no addresses"); + $r= $h[4]; + @h == 5 or warning("$s: name has several addresses, using ".inet_ntoa($r)); return $r; } sub conv_port_number ($) { my ($s,$r) = @_; - if ($s =~ m/\d/) { - $r= $s+0; - $r>0 && $r<65536 or quit("$s: port out of range"); - } else { - $r= 0; - } + return 0 if $s =~ m/^[A-Z][a-z]/; + $r= $s+0; + $r>0 && $r<65536 or quit("$s: port out of range"); return $r; } -sub show_addr_port ($) { +sub show_addr ($) { my ($s,@s) = @_; @s= unpack_sockaddr_in($s); - return inet_ntoa($s[1]).','.$s[0]; + return inet_ntoa($s[1]); +} +sub show_port ($) { + my ($s,@s) = @_; + @s= unpack_sockaddr_in($s); + return $s[0]; +} +sub show_addr_port ($) { + my ($s) = @_; + return show_addr($s).','.show_port($s); +} +sub arg_value ($$) { + my ($val,$opt) = @_; + $_= '-'; + return $val if length $val; + @ARGV or quit("$opt needs value"); + return shift @ARGV; } @lcmd= (); +@encryption= (); +$masq= 0; +$dump= 0; +$fcmd= 'udptunnel-forwarder'; +$xfwdopts= ''; while ($ARGV[0] =~ m/^-/) { $_= shift @ARGV; - last if $_ eq '--'; - if (s/^-l//) { - push @lcmd,$_ if length; - while (@ARGV && ($_= shift @ARGV) ne '-') { push @lcmd, $_; } - } else { - quit("unknown option \`$_'"); + last if m/^--?$/; + while (!m/^-$/) { + if (s/^-l//) { + push @lcmd,$_ if length; + while (@ARGV && ($_= shift @ARGV) ne '.') { push @lcmd, $_; } + $_= '-' + } elsif (s/^-f//) { + $fcmd= arg_value($_,'-f'); + } elsif (s/^-e//) { + $encrarg= arg_value($_,'-e'); + push @remoteopts, "-e$encrarg"; + @thisencryption= split m#/#, $encrarg; + $thisencryption[0] =~ s/^/\|/; + push @encryption, @thisencryption; + } elsif (s/^-m/-/) { + $masq= 1; + } elsif (s/^-d/-/) { + $dump= 1; + } elsif (s/^-Dcrypto$/-/) { + $xfwdopts.= 'K'; + push @remoteopts, '-Dcrypto'; + } else { + quit("unknown option \`$_'"); + } } } -($las,$lps)= eat_addr_port('print|silent'); +# Variables \$[lr]a?p?(|s|d|r) +# Local/Remote Address&/Port +# actualvalue/Specified/Displaypassdown/fromRemote/passtoForwarder +# +($las,$lps)= eat_addr_port('Print|Any'); $la= conv_host_addr($las); $lp= conv_port_number($lps); $ls= pack_sockaddr_in $lp,$la; -($ras,$rps)= eat_addr_port('command'); +($ras,$rps)= eat_addr_port('Wait|Command'); +$ra= conv_host_addr($ras); $rp= conv_port_number($rps); -$ra= $rps eq 'command' ? '' : conv_host_addr($ras); +$rs= pack_sockaddr_in $rp,$ra; $_= shift @ARGV; m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/ @@ -117,68 +262,135 @@ m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/ ($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4); $_= shift @ARGV; -m/^(\d+),(\d+)$/ or quit("keepalive,timeout missing or bad syntax"); -($keepalive,$timeout)= ($1,$2); +if (m/^(\d+),(\d+)$/) { + ($keepalive,$timeout,$reannounce)= ($1+0,$2+0,0); + $ka_to_ra= "$keepalive,$timeout"; +} elsif (m/^(\d+),(\d+),(\d+)$/) { + ($keepalive,$timeout,$reannounce)= ($1+0,$2+0,$3); + "$keepalive,$timeout", + $ka_to_ra= "$keepalive,$timeout,$reannounce"; +} else { + quit("keepalive,timeout missing or bad syntax"); +} $keepalive && ($timeout > $keepalive*2) or quit("timeout must be < 2*keepalive") if $timeout; -$lepn= shift @ARGV; -$repn= shift @ARGV; - -alarm($timeout); +# Variables \$[lr]exn +# Local/Remote Extra Nets +$lexn= shift @ARGV; +$rexn= shift @ARGV; defined($udp= getprotobyname('udp')) or fail("getprotobyname udp"); socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket"); bind(L,$ls) or quit("bind failed: $!"); defined($ls= getsockname(L)) or fail("getsockname"); -$lsp= show_addr_port($ls); +$lad= show_addr($ls); +$lpd= show_port($ls); +$lapd= "$lad,$lpd"; + +print "$lapd\n" or fail("print addr/port") if ($las eq 'Print' || $lps eq 'Print'); + +$rapcmd= ($ras eq 'Command' || $rps eq 'Command'); +quit("need remote-command if Command for remote addr/port") if $rapcmd && !@ARGV; -if ($rps eq 'command') { - quit("when using ,command for remote, must supply command") unless @ARGV; - @rcmd= (@ARGV, "$ras,print", "$lsp", "$rva,$lva,$mtu,$proto", - "$keepalive,$timeout", $repn, $lepn); +sub xform_remote ($$) { + my ($showed,$spec) = @_; + return 'Print' if $spec eq 'Command'; + return 'Any' if $spec eq 'Wait'; + return $showed; +} + +if (@ARGV) { + warning("-d specified with remote command, ignoring") if $dump; + $dump= 1; + + $rad= xform_remote(show_addr($rs),$ras); + $rpd= xform_remote(show_port($rs),$rps); + @rcmd= (@ARGV, + @remoteopts, + "$rad,$rpd", + $masq ? 'Wait,Wait' : $las eq 'Any' ? "Wait,$lpd" : $lapd, + "$rva,$lva,$mtu,$proto", + $ka_to_ra, + $rexn, $lexn); debug("remote command @rcmd"); - defined($c= open C,"-|") or fail("fork for remote"); - if (!$c) { - exec @rcmd; die "$progname: error: failed to execute $rcmd[0]: $!\n"; + + if ($rapcmd) { + pipe(RAPREAD,RCMDREADSUB) or fail("pipe"); } - $_= ; - if (!length) { - close C; - quit($? ? "remote command failed (code $?)" : "no details received from remote"); + pipe(RCMDWRITESUB,DUMPKEYS) or fail("pipe"); + defined($c_rcmd= fork) or fail("fork for remote"); + if (!$c_rcmd) { + open STDIN, "<&RCMDWRITESUB" or fail("reopen stdin for remote command"); + open STDOUT, ">&RCMDREADSUB" or fail("reopen stdout for remote command") + if $rapcmd; + close RAPREAD if $rapcmd; + close DUMPKEYS; + close RCMDWRITESUB; + close RCMDREADSUB; + close L; + exec @rcmd; fail("failed to execute remote command $rcmd[0]"); } - chomp; - m/^([.0-9]+)\,(\d+)$/ or quit("invalid details from remote end ($_)"); - ($ras,$rps) = ($1,$2); - $ra= conv_host_addr($ras); - $rp= conv_port_number($rps); - defined($c2= fork) or fail("fork for cat"); - if (!$c2) { - open(STDIN,"<&C") or fail("redirect remote pipe to stdin"); - close C; - exec "cat"; fail("execute cat"); + close RCMDWRITESUB; + + if ($rapcmd) { + close RCMDREADSUB if $rapcmd; + $_= ''; + while (!m/\n/) { + $!=0; + defined($nread= sysread(RAPREAD,$_,1,length)) + or fail("read from remote command"); + if (!$nread) { + close DUMPKEYS; + close RAPREAD; + waitpid $c_rcmd,0 or fail("wait for remote command"); + quit($? ? "remote command failed (code $?)" : + "no details received from remote"); + } + } + chomp; + m/^([.0-9]+)\,(\d+)$/ or quit("invalid details from remote end: \`$_'"); + ($rar,$rpr) = ($1,$2); + $ra= conv_host_addr($rar); + $rp= conv_port_number($rpr); + + defined($c_catremdebug= fork) or fail("fork for cat remote debug"); + if (!$c_catremdebug) { + open(STDIN,"<&RAPREAD") or fail("redirect remote debug"); + close DUMPKEYS; + close L; + exec "cat"; fail("execute cat"); + } + close RAPREAD; } +} elsif ($dump) { + open DUMPKEYS, ">&STDOUT" or fail("reopen stdout for key material"); + $dump= 1; } else { - quit("when not using ,command for remote, must not supply command") if @ARGV; + open DUMPKEYS, "<&STDIN" or fail("reopen stdout for key material"); } $rs= pack_sockaddr_in $rp,$ra; -$rsp= show_addr_port($rs); - -if ($lps eq 'print') { print($lsp,"\n") or quit("write port to stdout: $!"); } +if ($ras eq 'Wait' || $rps eq 'Wait') { + @rapf= (''); + $rapd= ('Wait,Wait'); +} else { + @rapf= (show_addr($rs), show_port($rs)); + $rapd= show_addr_port($rs); +} @lcmd= qw(userv root ipif) unless @lcmd; -debug("using remote $rsp local $lsp"); -push @lcmd, ("$lva,$rva,$mtu,$proto",$lepn); -debug("local command @lcmd"); +debug("using remote $rapd local $lapd"); +push @lcmd, ("$lva,$rva,$mtu,$proto",$lexn); +debug("local command @lcmd."); pipe(UR,UW) or fail("up pipe"); pipe(DR,DW) or fail("down pipe"); -defined($c3= fork) or fail("fork for ipif"); -if (!$c3) { +defined($c_lcmd= fork) or fail("fork for local command"); +if (!$c_lcmd) { close UR; close DW; open(STDIN,"<&DR") or fail("reopen stdin for packets"); open(STDOUT,">&UW") or fail("reopen stdout for packets"); @@ -188,82 +400,51 @@ if (!$c3) { close UW; close DR; -$upyet= 0; -$downyet= 0; +$xfwdopts.= 'w' if $dump; -$wantreadfds=''; -vec($wantreadfds,fileno(UR),1)= 1; -vec($wantreadfds,fileno(L),1)= 1; +@fcmd= ($fcmd, $xfwdopts, + fileno(L), fileno(DW), fileno(UR), fileno(DUMPKEYS), + $mtu, $keepalive, $timeout, $reannounce, + @rapf, + @encryption); +debug("forwarding command @fcmd."); -sub nonblock ($) { - my ($fh,$fl) = @_; - ($fl= fcntl($fh,F_GETFL,0)) or fail("nonblock F_GETFL"); - $fl |= O_NONBLOCK; - fcntl($fh, F_SETFL, $fl) or fail("nonblock F_SETFL"); +defined($c_fwd= fork) or fail("fork for udptunnel-forwarder"); +if (!$c_fwd) { + foreach $fd (qw(L DW UR DUMPKEYS)) { + fcntl($fd, F_SETFD, 0) or fail("set no-close-on-exec $fd"); + } + exec @fcmd; fail("cannot execute $fcmd[0]"); } -nonblock('UR'); -nonblock('L'); +close L; +close DUMPKEYS; +close UR; +close DW; -$upbuf= ''; +%procs= ($c_fwd, 'forwarder', + $c_lcmd, 'local command'); +$procs{$c_rcmd}= 'remote command' if $c_rcmd; +$procs{$c_catremdebug}= 'debug cat' if $c_catremdebug; -sub now () { my ($v); defined($v= time) or fail("get time"); return $v; } -if ($keepalive) { $nextsendka= now(); } +$estatus= 0; -for (;;) { - if ($keepalive) { - $now= now(); - $thistimeout= $nextsendka-$now; - if ($thistimeout < 0) { - defined(send L,"\300",0,$rs) - or warning("transmit keepalive error: $!"); - $nextsendka= $now+$keepalive; - $thistimeout= $keepalive; - } - } else { - $thistimeout= undef; - } - select($readfds=$wantreadfds,'','',$thistimeout); - for (;;) { - if (!defined($r= sysread(UR,$upbuf,$mtu*2+3,length($upbuf)))) { - $! == EAGAIN || warning("tunnel endpoint read error: $!"); - last; - } - if (!$r) { - quit "tunnel endpoint closed by system"; - } - while (($p= index($upbuf,"\300")) >= 0) { - if ($p && !defined(send L,substr($upbuf,0,$p),0,$rs)) { - warning("transmit error: $!"); - } else { - if (!$upyet) { - $upyet= 1; - debug($downyet ? "tunnel open at this end" : "transmitting"); - } - if ($keepalive) { $nextsendka= now()+$keepalive; } - } - $upbuf= substr($upbuf,$p+1); - } +while (keys %procs) { + ($c= wait) >0 or + fail("wait failed (expecting ". join('; ',keys %procs). ")"); + $status= $?; + warning("unexpected child reaped: pid $c, code $status"), next + unless exists $procs{$c}; + $str= $procs{$c}; + delete $procs{$c}; + $status ? warning("subprocess $str failed with code $status") + : debug("subprocess $str finished"); + if ($c==$c_lcmd || $c==$c_fwd || $c==$c_rcmd) { + kill 15, grep (exists $procs{$_}, $c_fwd, $c_rcmd); } - while (defined($rs_from= recv L,$downbuf,$mtu*2+3,0)) { - $rsp_from= show_addr_port($rs_from); - if ($rsp_from ne $rsp) { - warning("got packet from incorrect peer $rsp_from"); - next; - } - $downbuf= "\300".$downbuf."\300"; - if (!defined($r= syswrite(DW,$downbuf,length $downbuf))) { - warning("tunnel endpoint write error: $!"); - } elsif ($r != length $downbuf) { - warning("tunnel endpoint wrong write length"); - } else { - if (!$downyet) { - $downyet= 1; - debug($upyet ? "tunnel open at this end" : "receiving"); - } - alarm($timeout) if $timeout; - } - } - if ($! == ECONNREFUSED) { quit("tunnel closed at remote end"); } - $! == EAGAIN || warning("receive error: $!"); + $estatus=1 unless $c == $c_catremdebug; } + +debug("all processes terminated, exiting with status $estatus"); + +exit $estatus;