2 # Simple tunnel for userv-ipif tunnels.
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> ...] ]
13 # <local-public-port> may be number or `print' or `silent'
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 # <keepalive>,<timeout>
22 # <extra-remote-nets> <extra-local-nets>
24 # udptunnel will userv ipif locally, as
25 # userv root ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
32 $progname= $0; $progname =~ s,.*/,,;
35 chomp($hostname= `uname -n`);
36 $? and die "$progname: cannot get hostname (uname failed with code $?)\n";
38 sub quit ($) { die "$progname - $hostname: fatal error: $_[0]\n"; }
39 sub debug ($) { print "$progname - $hostname: debug: $_[0]\n"; }
40 sub fail ($) { quit("unexpected system call failure: $_[0]: $!\n"); }
41 sub warning ($) { warn "$progname - $hostname: $_[0]\n"; }
43 sub eat_addr_port ($) {
45 @ARGV or quit("<host/addr>,<port> missing");
47 $_ =~ m/^([^,]+)\,(\d+|$x)$/ or quit("$_: <host/addr>,<port> bad syntax");
50 sub conv_host_addr ($) {
52 defined($r= inet_aton($s)) or quit("$s: cannot convert to address");
55 sub conv_port_number ($) {
59 $r>0 && $r<65536 or quit("$s: port out of range");
65 sub show_addr_port ($) {
67 @s= unpack_sockaddr_in($s);
68 return inet_ntoa($s[1]).','.$s[0];
71 ($las,$lps)= eat_addr_port('print|silent');
72 $la= conv_host_addr($las);
73 $lp= conv_port_number($lps);
74 $ls= pack_sockaddr_in $lp,$la;
76 ($ras,$rps)= eat_addr_port('command');
77 $rp= conv_port_number($rps);
78 $ra= $rps eq 'command' ? '' : conv_host_addr($ras);
81 m/^([.0-9]+),([.0-9]+),(\d+),(slip|cslip)$/
82 or quit("lvaddr,rvaddr,mtu,proto missing or bad syntax or proto not [c]slip");
83 ($lva,$rva,$mtu,$proto) = ($1,$2,$3,$4);
86 m/^(\d+),(\d+)$/ or quit("keepalive,timeout missing or bad syntax");
87 ($keepalive,$timeout)= ($1,$2);
88 $keepalive && ($timeout > $keepalive*2) or quit("timeout must be < 2*keepalive")
96 defined($udp= getprotobyname('udp')) or fail("getprotobyname udp");
98 socket(L,PF_INET,SOCK_DGRAM,$udp) or fail("socket");
99 bind(L,$ls) or quit("bind failed: $!");
100 defined($ls= getsockname(L)) or fail("getsockname");
101 $lsp= show_addr_port($ls);
103 if ($rps eq 'command') {
104 quit("when using ,command for remote, must supply command") unless @ARGV;
105 @rcmd= (@ARGV, "$ras,print", "$lsp", "$rva,$lva,$mtu,$proto",
106 "$keepalive,$timeout", $repn, $lepn);
107 debug("remote command @rcmd");
108 defined($c= open C,"-|") or fail("fork for remote");
110 exec @rcmd; die "$progname: error: failed to execute $rcmd[0]: $!\n";
115 quit($? ? "remote command failed (code $?)" : "no details received from remote");
118 m/^([.0-9]+)\,(\d+)$/ or quit("invalid details from remote end ($_)");
119 ($ras,$rps) = ($1,$2);
120 $ra= conv_host_addr($ras);
121 $rp= conv_port_number($rps);
122 defined($c2= fork) or fail("fork for cat");
124 open(STDIN,"<&C") or fail("redirect remote pipe to stdin");
126 exec "cat"; fail("execute cat");
129 quit("when not using ,command for remote, must not supply command") if @ARGV;
132 $rs= pack_sockaddr_in $rp,$ra;
133 $rsp= show_addr_port($rs);
135 if ($lps eq 'print') { print($lsp,"\n") or quit("write port to stdout: $!"); }
137 debug("using remote $rsp local $lsp");
139 pipe(UR,UW) or fail("up pipe");
140 pipe(DR,DW) or fail("down pipe");
142 defined($c3= fork) or fail("fork for ipif");
145 open(STDIN,"<&DR") or fail("reopen stdin for packets");
146 open(STDOUT,">&UW") or fail("reopen stdout for packets");
147 exec "userv","root","ipif","$lva,$rva,$mtu,$proto",$lepn;
148 quit("cannot execute userv ipif: $!");
157 vec($wantreadfds,fileno(UR),1)= 1;
158 vec($wantreadfds,fileno(L),1)= 1;
162 ($fl= fcntl($fh,F_GETFL,0)) or fail("nonblock F_GETFL");
164 fcntl($fh, F_SETFL, $fl) or fail("nonblock F_SETFL");
172 sub now () { my ($v); defined($v= time) or fail("get time"); return $v; }
173 if ($keepalive) { $nextsendka= now(); }
178 $timeout= $nextsendka-$now;
180 defined(send L,"\300",0,$rs)
181 or warning("transmit keepalive error: $!");
182 $nextsendka= $now+$keepalive;
183 $timeout= $keepalive;
188 select($readfds=$wantreadfds,'','',$timeout);
190 if (!defined($r= sysread(UR,$upbuf,$mtu*2+3,length($upbuf)))) {
191 $! == EAGAIN || warning("tunnel endpoint read error: $!");
195 quit "tunnel endpoint closed by system";
197 while (($p= index($upbuf,"\300")) >= 0) {
198 if (!defined(send L,substr($upbuf,0,$p),0,$rs)) {
199 warning("transmit error: $!");
202 debug($downyet ? "tunnel open at this end" : "transmit channel open");
204 $upbuf= substr($upbuf+1,$p);
207 while (defined($rs_from= recv L,$downbuf,$mtu*2+3,0)) {
208 $rsp_from= show_addr_port($rs_from);
209 if ($rsp_from ne $rsp) {
210 warning("got packet from incorrect peer $rsp_from");
213 if (!defined($r= syswrite(DW,$downbuf,length $downbuf))) {
214 warning("tunnel endpoint write error: $!");
215 } elsif ($r != length $downbuf) {
216 warning("tunnel endpoint wrong write length");
220 debug($upyet ? "tunnel open at this end" : "receive channel open");
222 alarm($timeout) if $timeout;
225 $! == EAGAIN || warning("receive error: $!");