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