chiark / gitweb /
Added copyright messages.
[userv-utils.git] / ipif / udptunnel
1 #!/usr/bin/perl
2 # Simple tunnel for userv-ipif tunnels.
3 #
4 # usage:
5 #  udptunnel
6 #        [ -l[<local-command/arg>] ... . ]
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>
22 #                     <keepalive>,<timeout>
23 #                     <extra-remote-nets> <extra-local-nets>
24 #
25 # udptunnel will userv ipif locally, as
26 #    userv root ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
27 #                    <extra-local-nets>
28 # or, if -lc was given, userv root ipif is replaced with the argument(s) to
29 # successive -lc options.
30
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
49 use Socket;
50 use POSIX;
51 use Fcntl;
52
53 $progname= $0; $progname =~ s,.*/,,;
54 $|=1;
55
56 chomp($hostname= `uname -n`);
57 $? and die "$progname: cannot get hostname (uname failed with code $?)\n";
58
59 sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; }
60 sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; }
61 sub fail ($) { quit("unexpected system call failure: $_[0]: $!\n"); }
62 sub warning ($) { warn "$progname - $hostname: $_[0]\n"; }
63
64 sub 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 }
71 sub conv_host_addr ($) {
72     my ($s,$r) = @_;
73     defined($r= inet_aton($s)) or quit("$s: cannot convert to address");
74     return $r;
75 }
76 sub conv_port_number ($) {
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 }
86 sub show_addr_port ($) {
87     my ($s,@s) = @_;
88     @s= unpack_sockaddr_in($s);
89     return inet_ntoa($s[1]).','.$s[0];
90 }
91
92 @lcmd= ();
93
94 while ($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
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;
115 m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/
116     or quit("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip");
117 ($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4);
118
119 $_= shift @ARGV;
120 m/^(\d+),(\d+)$/ or quit("keepalive,timeout missing or bad syntax");
121 ($keepalive,$timeout)= ($1,$2);
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
128 alarm($timeout);
129
130 defined($udp= getprotobyname('udp')) or fail("getprotobyname udp");
131
132 socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket");
133 bind(L,$ls) or quit("bind failed: $!");
134 defined($ls= getsockname(L)) or fail("getsockname");
135 $lsp= show_addr_port($ls);
136
137 if ($rps eq 'command') {
138     quit("when using ,command for remote, must supply command") unless @ARGV;
139     @rcmd= (@ARGV, "$ras,print", "$lsp", "$rva,$lva,$mtu,$proto",
140             "$keepalive,$timeout", $repn, $lepn);
141     debug("remote command @rcmd");
142     defined($c= open C,"-|") or fail("fork for remote");
143     if (!$c) {
144         exec @rcmd; die "$progname: error: failed to execute $rcmd[0]: $!\n";
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);
155     $rp= conv_port_number($rps);
156     defined($c2= fork) or fail("fork for cat");
157     if (!$c2) {
158         open(STDIN,"<&C") or fail("redirect remote pipe to stdin");
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
169 if ($lps eq 'print') { print($lsp,"\n") or quit("write port to stdout: $!"); }
170
171 @lcmd= qw(userv root ipif) unless @lcmd;
172
173 debug("using remote $rsp local $lsp");
174 push @lcmd, ("$lva,$rva,$mtu,$proto",$lepn);
175 debug("local command @lcmd");
176
177 pipe(UR,UW) or fail("up pipe");
178 pipe(DR,DW) or fail("down pipe");
179
180 defined($c3= fork) or fail("fork for ipif");
181 if (!$c3) {
182     close UR; close DW;
183     open(STDIN,"<&DR") or fail("reopen stdin for packets");
184     open(STDOUT,">&UW") or fail("reopen stdout for packets");
185     exec @lcmd;
186     quit("cannot execute $lcmd[0]: $!");
187 }
188 close UW;
189 close DR;
190
191 $upyet= 0;
192 $downyet= 0;
193
194 $wantreadfds='';
195 vec($wantreadfds,fileno(UR),1)= 1;
196 vec($wantreadfds,fileno(L),1)= 1;
197
198 sub 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
205 nonblock('UR');
206 nonblock('L');
207
208 $upbuf= '';
209
210 sub now () { my ($v); defined($v= time) or fail("get time"); return $v; }
211 if ($keepalive) { $nextsendka= now(); }
212
213 for (;;) {
214     if ($keepalive) {
215         $now= now();
216         $thistimeout= $nextsendka-$now;
217         if ($thistimeout < 0) {
218             defined(send L,"\300",0,$rs)
219                 or warning("transmit keepalive error: $!");
220             $nextsendka= $now+$keepalive;
221             $thistimeout= $keepalive;
222         }
223     } else {
224         $thistimeout= undef;
225     }
226     select($readfds=$wantreadfds,'','',$thistimeout);
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) {
236             if ($p && !defined(send L,substr($upbuf,0,$p),0,$rs)) {
237                 warning("transmit error: $!");
238             } else {
239                 if (!$upyet) {
240                     $upyet= 1;
241                     debug($downyet ? "tunnel open at this end" : "transmitting");
242                 }
243                 if ($keepalive) { $nextsendka= now()+$keepalive; }
244             }
245             $upbuf= substr($upbuf,$p+1);
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");
252             next;
253         }
254         $downbuf= "\300".$downbuf."\300";
255         if (!defined($r= syswrite(DW,$downbuf,length $downbuf))) {
256             warning("tunnel endpoint write error: $!");
257         } elsif ($r != length $downbuf) {
258             warning("tunnel endpoint wrong write length");
259         } else {
260             if (!$downyet) {
261                 $downyet= 1;
262                 debug($upyet ? "tunnel open at this end" : "receiving");
263             }
264             alarm($timeout) if $timeout;
265         }
266     }
267     if ($! == ECONNREFUSED) { quit("tunnel closed at remote end"); }
268     $! == EAGAIN || warning("receive error: $!");
269 }