| 1 | #!/usr/bin/perl |
| 2 | # Simple tunnel for userv-ipif tunnels. |
| 3 | # |
| 4 | # usage: |
| 5 | # udptunnel |
| 6 | # [ -l[<local-command/arg>] ... . |
| 7 | # -e<encryption-mech>[/<encryption-parameter>...] |
| 8 | # ... |
| 9 | # ] |
| 10 | # <public-local-addr>,<public-local-port> |
| 11 | # <public-remote-addr>,<public-remote-port> |
| 12 | # <private-local-addr>,<private-remote-addr>,<mtu>,<proto> |
| 13 | # <keepalive>,<timeout> |
| 14 | # <extra-local-nets> <extra-remote-nets> |
| 15 | # [ <remote-command> [<remote-args> ...] ] |
| 16 | # |
| 17 | # |
| 18 | # <..-addr> may also be hostname |
| 19 | # |
| 20 | # <local-public-port> may be number or `print' or `silent' |
| 21 | # |
| 22 | # <remote-public-port> may number or `command', in which case |
| 23 | # <remote-command> must be specified and should run udptunnel at the |
| 24 | # remote end; it will be invoked as |
| 25 | # <remote-command> <public-remote-addr>,print |
| 26 | # <public-local-addr>,<public-local-port> |
| 27 | # <private-remote-addr>,<private-local-addr>,<mtu>,<proto> |
| 28 | # <keepalive>,<timeout> |
| 29 | # <extra-remote-nets> <extra-local-nets> |
| 30 | # |
| 31 | # udptunnel will userv ipif locally, as |
| 32 | # userv root ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto> |
| 33 | # <extra-local-nets> |
| 34 | # or, if -l was given, userv root ipif is replaced with the argument(s) to |
| 35 | # successive -l options. |
| 36 | |
| 37 | # Copyright (C) 1999 Ian Jackson |
| 38 | # |
| 39 | # This is free software; you can redistribute it and/or modify it |
| 40 | # under the terms of the GNU General Public License as published by |
| 41 | # the Free Software Foundation; either version 2 of the License, or |
| 42 | # (at your option) any later version. |
| 43 | # |
| 44 | # This program is distributed in the hope that it will be useful, but |
| 45 | # WITHOUT ANY WARRANTY; without even the implied warranty of |
| 46 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 47 | # General Public License for more details. |
| 48 | # |
| 49 | # You should have received a copy of the GNU General Public License |
| 50 | # along with userv-utils; if not, write to the Free Software |
| 51 | # Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 52 | # |
| 53 | # $Id: udptunnel,v 1.10 2000/05/29 17:16:29 ian Exp $ |
| 54 | |
| 55 | use Socket; |
| 56 | use POSIX; |
| 57 | use Fcntl; |
| 58 | |
| 59 | $progname= $0; $progname =~ s,.*/,,; |
| 60 | $|=1; |
| 61 | |
| 62 | chomp($hostname= `uname -n`); |
| 63 | $? and die "$progname: cannot get hostname (uname failed with code $?)\n"; |
| 64 | |
| 65 | sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; } |
| 66 | sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; } |
| 67 | sub fail ($) { quit("unexpected system call failure: $_[0]: $!\n"); } |
| 68 | sub warning ($) { warn "$progname - $hostname: $_[0]\n"; } |
| 69 | |
| 70 | sub eat_addr_port ($) { |
| 71 | my ($x) = @_; |
| 72 | @ARGV or quit("<addr>,<port> missing"); |
| 73 | $_= shift(@ARGV); |
| 74 | $_ =~ m/^([^,]+)\,(\d+|$x)$/ or quit("$_: <host/addr>,<port> bad syntax"); |
| 75 | return ($1,$2); |
| 76 | } |
| 77 | sub conv_host_addr ($) { |
| 78 | my ($s,$r) = @_; |
| 79 | defined($r= inet_aton($s)) or quit("$s: cannot convert to address"); |
| 80 | return $r; |
| 81 | } |
| 82 | sub conv_port_number ($) { |
| 83 | my ($s,$r) = @_; |
| 84 | if ($s =~ m/\d/) { |
| 85 | $r= $s+0; |
| 86 | $r>0 && $r<65536 or quit("$s: port out of range"); |
| 87 | } else { |
| 88 | $r= 0; |
| 89 | } |
| 90 | return $r; |
| 91 | } |
| 92 | sub show_addr_port ($) { |
| 93 | my ($s,@s) = @_; |
| 94 | @s= unpack_sockaddr_in($s); |
| 95 | return inet_ntoa($s[1]).','.$s[0]; |
| 96 | } |
| 97 | |
| 98 | @lcmd= (); |
| 99 | |
| 100 | while ($ARGV[0] =~ m/^-/) { |
| 101 | $_= shift @ARGV; |
| 102 | last if $_ eq '--'; |
| 103 | if (s/^-l//) { |
| 104 | push @lcmd,$_ if length; |
| 105 | while (@ARGV && ($_= shift @ARGV) ne '.') { push @lcmd, $_; } |
| 106 | } else { |
| 107 | quit("unknown option \`$_'"); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | ($las,$lps)= eat_addr_port('print|silent'); |
| 112 | $la= conv_host_addr($las); |
| 113 | $lp= conv_port_number($lps); |
| 114 | $ls= pack_sockaddr_in $lp,$la; |
| 115 | |
| 116 | ($ras,$rps)= eat_addr_port('command'); |
| 117 | $rp= conv_port_number($rps); |
| 118 | $ra= $rps eq 'command' ? '' : conv_host_addr($ras); |
| 119 | |
| 120 | $_= shift @ARGV; |
| 121 | m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/ |
| 122 | or quit("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip"); |
| 123 | ($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4); |
| 124 | |
| 125 | $_= shift @ARGV; |
| 126 | m/^(\d+),(\d+)$/ or quit("keepalive,timeout missing or bad syntax"); |
| 127 | ($keepalive,$timeout)= ($1,$2); |
| 128 | $keepalive && ($timeout > $keepalive*2) or quit("timeout must be < 2*keepalive") |
| 129 | if $timeout; |
| 130 | |
| 131 | $lepn= shift @ARGV; |
| 132 | $repn= shift @ARGV; |
| 133 | |
| 134 | alarm($timeout); |
| 135 | |
| 136 | defined($udp= getprotobyname('udp')) or fail("getprotobyname udp"); |
| 137 | |
| 138 | socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket"); |
| 139 | bind(L,$ls) or quit("bind failed: $!"); |
| 140 | defined($ls= getsockname(L)) or fail("getsockname"); |
| 141 | $lsp= show_addr_port($ls); |
| 142 | |
| 143 | if ($rps eq 'command') { |
| 144 | quit("when using ,command for remote, must supply command") unless @ARGV; |
| 145 | @rcmd= (@ARGV, "$ras,print", "$lsp", "$rva,$lva,$mtu,$proto", |
| 146 | "$keepalive,$timeout", $repn, $lepn); |
| 147 | debug("remote command @rcmd"); |
| 148 | defined($c= open C,"-|") or fail("fork for remote"); |
| 149 | if (!$c) { |
| 150 | exec @rcmd; die "$progname: error: failed to execute $rcmd[0]: $!\n"; |
| 151 | } |
| 152 | $_= <C>; |
| 153 | if (!length) { |
| 154 | close C; |
| 155 | quit($? ? "remote command failed (code $?)" : "no details received from remote"); |
| 156 | } |
| 157 | chomp; |
| 158 | m/^([.0-9]+)\,(\d+)$/ or quit("invalid details from remote end ($_)"); |
| 159 | ($ras,$rps) = ($1,$2); |
| 160 | $ra= conv_host_addr($ras); |
| 161 | $rp= conv_port_number($rps); |
| 162 | defined($c2= fork) or fail("fork for cat"); |
| 163 | if (!$c2) { |
| 164 | open(STDIN,"<&C") or fail("redirect remote pipe to stdin"); |
| 165 | close C; |
| 166 | exec "cat"; fail("execute cat"); |
| 167 | } |
| 168 | } else { |
| 169 | quit("when not using ,command for remote, must not supply command") if @ARGV; |
| 170 | } |
| 171 | |
| 172 | $rs= pack_sockaddr_in $rp,$ra; |
| 173 | $rsp= show_addr_port($rs); |
| 174 | |
| 175 | if ($lps eq 'print') { print($lsp,"\n") or quit("write port to stdout: $!"); } |
| 176 | |
| 177 | @lcmd= qw(userv root ipif) unless @lcmd; |
| 178 | |
| 179 | debug("using remote $rsp local $lsp"); |
| 180 | push @lcmd, ("$lva,$rva,$mtu,$proto",$lepn); |
| 181 | debug("local command @lcmd"); |
| 182 | |
| 183 | pipe(UR,UW) or fail("up pipe"); |
| 184 | pipe(DR,DW) or fail("down pipe"); |
| 185 | |
| 186 | defined($c3= fork) or fail("fork for ipif"); |
| 187 | if (!$c3) { |
| 188 | close UR; close DW; |
| 189 | open(STDIN,"<&DR") or fail("reopen stdin for packets"); |
| 190 | open(STDOUT,">&UW") or fail("reopen stdout for packets"); |
| 191 | exec @lcmd; |
| 192 | quit("cannot execute $lcmd[0]: $!"); |
| 193 | } |
| 194 | close UW; |
| 195 | close DR; |
| 196 | |
| 197 | $upyet= 0; |
| 198 | $downyet= 0; |
| 199 | |
| 200 | $wantreadfds=''; |
| 201 | vec($wantreadfds,fileno(UR),1)= 1; |
| 202 | vec($wantreadfds,fileno(L),1)= 1; |
| 203 | |
| 204 | sub nonblock ($) { |
| 205 | my ($fh,$fl) = @_; |
| 206 | ($fl= fcntl($fh,F_GETFL,0)) or fail("nonblock F_GETFL"); |
| 207 | $fl |= O_NONBLOCK; |
| 208 | fcntl($fh, F_SETFL, $fl) or fail("nonblock F_SETFL"); |
| 209 | } |
| 210 | |
| 211 | nonblock('UR'); |
| 212 | nonblock('L'); |
| 213 | |
| 214 | $upbuf= ''; |
| 215 | |
| 216 | sub now () { my ($v); defined($v= time) or fail("get time"); return $v; } |
| 217 | if ($keepalive) { $nextsendka= now(); } |
| 218 | |
| 219 | for (;;) { |
| 220 | if ($keepalive) { |
| 221 | $now= now(); |
| 222 | $thistimeout= $nextsendka-$now; |
| 223 | if ($thistimeout < 0) { |
| 224 | defined(send L,"\300",0,$rs) |
| 225 | or warning("transmit keepalive error: $!"); |
| 226 | $nextsendka= $now+$keepalive; |
| 227 | $thistimeout= $keepalive; |
| 228 | } |
| 229 | } else { |
| 230 | $thistimeout= undef; |
| 231 | } |
| 232 | select($readfds=$wantreadfds,'','',$thistimeout); |
| 233 | for (;;) { |
| 234 | if (!defined($r= sysread(UR,$upbuf,$mtu*2+3,length($upbuf)))) { |
| 235 | $! == EAGAIN || warning("tunnel endpoint read error: $!"); |
| 236 | last; |
| 237 | } |
| 238 | if (!$r) { |
| 239 | quit "tunnel endpoint closed by system"; |
| 240 | } |
| 241 | while (($p= index($upbuf,"\300")) >= 0) { |
| 242 | if ($p && !defined(send L,substr($upbuf,0,$p),0,$rs)) { |
| 243 | warning("transmit error: $!"); |
| 244 | } else { |
| 245 | if (!$upyet) { |
| 246 | $upyet= 1; |
| 247 | debug($downyet ? "tunnel open at this end" : "transmitting"); |
| 248 | } |
| 249 | if ($keepalive) { $nextsendka= now()+$keepalive; } |
| 250 | } |
| 251 | $upbuf= substr($upbuf,$p+1); |
| 252 | } |
| 253 | } |
| 254 | while (defined($rs_from= recv L,$downbuf,$mtu*2+3,0)) { |
| 255 | $rsp_from= show_addr_port($rs_from); |
| 256 | if ($rsp_from ne $rsp) { |
| 257 | warning("got packet from incorrect peer $rsp_from"); |
| 258 | next; |
| 259 | } |
| 260 | $downbuf= "\300".$downbuf."\300"; |
| 261 | if (!defined($r= syswrite(DW,$downbuf,length $downbuf))) { |
| 262 | warning("tunnel endpoint write error: $!"); |
| 263 | } elsif ($r != length $downbuf) { |
| 264 | warning("tunnel endpoint wrong write length"); |
| 265 | } else { |
| 266 | if (!$downyet) { |
| 267 | $downyet= 1; |
| 268 | debug($upyet ? "tunnel open at this end" : "receiving"); |
| 269 | } |
| 270 | alarm($timeout) if $timeout; |
| 271 | } |
| 272 | } |
| 273 | if ($! == ECONNREFUSED) { quit("tunnel closed at remote end"); } |
| 274 | $! == EAGAIN || warning("receive error: $!"); |
| 275 | } |