chiark / gitweb /
Initial draft of udptunnel; coding finished, not yet compiled/tested.
[userv-utils.git] / ipif / udptunnel
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 }