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> |
abe75cda |
28 | # or, if -lc was given, userv root ipif is replaced with the argument(s) to |
29 | # successive -lc options. |
46ab1c83 |
30 | |
12ecfeab |
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: udptunnel,v 1.8 1999/11/09 22:35:41 ian Exp $ |
48 | |
46ab1c83 |
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 | } |
217afbe6 |
76 | sub 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 | } |
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 | |
abe75cda |
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 | |
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; |
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"); |
217afbe6 |
117 | ($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4); |
46ab1c83 |
118 | |
119 | $_= shift @ARGV; |
217afbe6 |
120 | m/^(\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 | |
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') { |
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 |
169 | if ($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 |
173 | debug("using remote $rsp local $lsp"); |
abe75cda |
174 | push @lcmd, ("$lva,$rva,$mtu,$proto",$lepn); |
175 | debug("local command @lcmd"); |
46ab1c83 |
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; |
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 | } |
188 | close UW; |
189 | close DR; |
190 | |
191 | $upyet= 0; |
192 | $downyet= 0; |
193 | |
194 | $wantreadfds=''; |
217afbe6 |
195 | vec($wantreadfds,fileno(UR),1)= 1; |
196 | vec($wantreadfds,fileno(L),1)= 1; |
46ab1c83 |
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 | |
217afbe6 |
210 | sub now () { my ($v); defined($v= time) or fail("get time"); return $v; } |
211 | if ($keepalive) { $nextsendka= now(); } |
212 | |
46ab1c83 |
213 | for (;;) { |
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 | } |