46ab1c83 |
1 | #!/usr/bin/perl |
2 | # Simple tunnel for userv-ipif tunnels. |
3 | # |
4 | # usage: |
5 | # udptunnel |
6 | # <public-local-host/addr>,<public-local-port> |
7 | # <public-remote-host/addr>,<public-remote-port> |
8 | # <private-local-addr>,<private-remote-addr>,<mtu>,<proto> |
9 | # <keepalive>,<timeout> |
10 | # <extra-local-nets> <extra-remote-nets> |
11 | # [ <remote-command> [<remote-args> ...] ] |
12 | # |
13 | # <local-public-port> may be number or `print' or `silent' |
14 | # |
15 | # <remote-public-port> may number or `command', in which case |
16 | # <remote-command> must be specified and should run udptunnel at the |
17 | # remote end; it will be invoked as |
18 | # <remote-command> <public-remote-host/addr>,print |
19 | # <public-local-addr>,<public-local-port> |
20 | # <private-remote-addr>,<private-local-addr>,<mtu>,<proto> |
21 | # <extra-local-nets> |
22 | # |
23 | # udptunnel will userv ipif locally, as |
24 | # userv ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto> |
25 | # <extra-local-nets> |
26 | |
27 | use Socket; |
28 | use POSIX; |
29 | use Fcntl; |
30 | |
31 | $progname= $0; $progname =~ s,.*/,,; |
32 | $|=1; |
33 | |
34 | chomp($hostname= `uname -n`); |
35 | $? and die "$progname: cannot get hostname (uname failed with code $?)\n"; |
36 | |
37 | sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; } |
38 | sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; } |
39 | sub fail ($) { quit("unexpected system call failure: $_[0]: $!\n"); } |
40 | sub warning ($) { warn "$progname - $hostname: $_[0]\n"; } |
41 | |
42 | sub eat_addr_port ($) { |
43 | my ($x) = @_; |
44 | @ARGV or quit("<host/addr>,<port> missing"); |
45 | $_= shift(@ARGV); |
46 | $_ =~ m/^([^,]+)\,(\d+|$x)$/ or quit("$_: <host/addr>,<port> bad syntax"); |
47 | return ($1,$2); |
48 | } |
49 | sub conv_host_addr ($) { |
50 | my ($s,$r) = @_; |
51 | defined($r= inet_aton($s)) or quit("$s: cannot convert to address"); |
52 | return $r; |
53 | } |
54 | sub conv_port_addr ($) { |
55 | my ($s,$r) = @_; |
56 | if ($s =~ m/\d/) { |
57 | $r= $s+0; |
58 | $r>0 && $r<65536 or quit("$s: port out of range"); |
59 | } else { |
60 | $r= 0; |
61 | } |
62 | return $r; |
63 | } |
64 | sub show_addr_port ($) { |
65 | my ($s,@s) = @_; |
66 | @s= unpack_sockaddr_in($s); |
67 | return inet_ntoa($s[1]).','.$s[0]; |
68 | } |
69 | |
70 | ($las,$lps)= eat_addr_port('print|silent'); |
71 | $la= conv_host_addr($las); |
72 | $lp= conv_port_number($lps); |
73 | $ls= pack_sockaddr_in $lp,$la; |
74 | |
75 | ($ras,$rps)= eat_addr_port('command'); |
76 | $rp= conv_port_number($rps); |
77 | $ra= $rps eq 'command' ? '' : conv_host_addr($ras); |
78 | |
79 | $_= shift @ARGV; |
80 | m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/ |
81 | or quit("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip"); |
82 | ($lva,$rva,$mtu,$proto= $1,$2; |
83 | |
84 | $_= shift @ARGV; |
85 | m/^(\d+),(\d+)$/ or quit("timeout missing or bad syntax"); |
86 | ($keepalive,$timeout)= $1,$2; |
87 | $keepalive && ($timeout > $keepalive*2) or quit("timeout must be < 2*keepalive") |
88 | if $timeout; |
89 | |
90 | $lepn= shift @ARGV; |
91 | $repn= shift @ARGV; |
92 | |
93 | alarm($timeout); |
94 | |
95 | defined($udp= getprotobyname('udp')) or fail("getprotobyname udp"); |
96 | |
97 | socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket"); |
98 | bind(L,$ls) or quit("bind failed: $!"); |
99 | defined($ls= getsockname(L)) or fail("getsockname"); |
100 | $lsp= show_addr_port($ls); |
101 | |
102 | if ($rps eq 'command') { |
103 | $lepn= shift @ARGV; |
104 | quit("when using ,command for remote, must supply command") unless @ARGV; |
105 | defined($c= open C,"-|") or fail("fork for remote"); |
106 | if (!$c) { |
107 | push @ARGV, "$ras,print", "$lsp", "$rva,$lva,$mtu,$proto", $lepn; |
108 | debug("remote command @ARGV"); |
109 | exec @ARGV; die "$progname: error: failed to execute $ARGV[0]: $!\n"; |
110 | } |
111 | $_= <C>; |
112 | if (!length) { |
113 | close C; |
114 | quit($? ? "remote command failed (code $?)" : "no details received from remote"); |
115 | } |
116 | chomp; |
117 | m/^([.0-9]+)\,(\d+)$/ or quit("invalid details from remote end ($_)"); |
118 | ($ras,$rps) = ($1,$2); |
119 | $ra= conv_host_addr($ras); |
120 | $rp= conv_port_addr($rps); |
121 | defined($c2= fork) or fail("fork for cat"); |
122 | if (!$c2) { |
123 | open STDIN,">&C" or fail("redirect remote pipe to stdin"); |
124 | close C; |
125 | exec "cat"; fail("execute cat"); |
126 | } |
127 | } else { |
128 | quit("when not using ,command for remote, must not supply command") if @ARGV; |
129 | } |
130 | |
131 | $rs= pack_sockaddr_in $rp,$ra; |
132 | $rsp= show_addr_port($rs); |
133 | |
134 | debug("using remote $rsp"); |
135 | |
136 | if ($lps eq 'print') { print $lsp,"\n" or quit("write port to stdout: $!"); } |
137 | |
138 | debug("public address $lsp"); |
139 | |
140 | pipe(UR,UW) or fail("up pipe"); |
141 | pipe(DR,DW) or fail("down pipe"); |
142 | |
143 | defined($c3= fork) or fail("fork for ipif"); |
144 | if (!$c3) { |
145 | close UR; close DW; |
146 | open STDIN,"<&DR" or fail("reopen stdin for packets"); |
147 | open STDOUT,"<&UW" or fail("reopen stdout for packets"); |
148 | exec "userv","ipif","$lva,$rva,$mtu,$proto",$lepn; |
149 | quit("cannot execute userv ipif: $!"); |
150 | } |
151 | close UW; |
152 | close DR; |
153 | |
154 | $upyet= 0; |
155 | $downyet= 0; |
156 | |
157 | $wantreadfds=''; |
158 | vec($wantreadfds,fileno(UR))= 1; |
159 | vec($wantreadfds,fileno(L))= 1; |
160 | |
161 | sub nonblock ($) { |
162 | my ($fh,$fl) = @_; |
163 | ($fl= fcntl($fh,F_GETFL,0)) or fail("nonblock F_GETFL"); |
164 | $fl |= O_NONBLOCK; |
165 | fcntl($fh, F_SETFL, $fl) or fail("nonblock F_SETFL"); |
166 | } |
167 | |
168 | nonblock('UR'); |
169 | nonblock('L'); |
170 | |
171 | $upbuf= ''; |
172 | |
173 | for (;;) { |
174 | select($readfds=$wantreadfds,'','',undef); |
175 | for (;;) { |
176 | if (!defined($r= sysread(UR,$upbuf,$mtu*2+3,length($upbuf)))) { |
177 | $! == EAGAIN || warning("tunnel endpoint read error: $!"); |
178 | last; |
179 | } |
180 | if (!$r) { |
181 | quit "tunnel endpoint closed by system"; |
182 | } |
183 | while (($p= index($upbuf,"\300")) >= 0) { |
184 | if (defined(send L,substr($upbuf,0,$p),0,$rs)) { |
185 | $upyet= 1; |
186 | debug($downyet ? "tunnel open" : "transmit channel open"); |
187 | } else { |
188 | warning("transmit error: $!"); |
189 | } |
190 | $upbuf= substr($upbuf+1,$p); |
191 | } |
192 | } |
193 | while (defined($rs_from= recv L,$downbuf,$mtu*2+3,0)) { |
194 | $rsp_from= show_addr_port($rs_from); |
195 | if ($rsp_from ne $rsp) { |
196 | warning("got packet from incorrect peer $rsp_from"); |
197 | continue; |
198 | } |
199 | if (!defined($r= syswrite(DW,$downbuf,length $downbuf))) { |
200 | warning("tunnel endpoint write error: $!"); |
201 | } else if ($r != length $downbuf) { |
202 | warning("tunnel endpoint wrong write length"); |
203 | } else { |
204 | $downyet= 1; |
205 | debug($upyet ? "tunnel open" : "receive channel open"); |
206 | } |
207 | } |
208 | $! == EAGAIN || warning("receive error: $!"); |
209 | } |