chiark / gitweb /
@@ -1,6 +1,7 @@
[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 #          -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$
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 }