chiark / gitweb /
Do not send ENDs. Do not send empty packets.
[userv-utils.git] / ipif / udptunnel
index dff18ed0160795764ca2cb3e6766bfca50285444..1b5031c7d2f1f2e3406e0a4bbb21a8ddaec865ff 100755 (executable)
@@ -3,6 +3,7 @@
 #
 # usage:
 #  udptunnel
+#        [ -l[<local-command/arg>] ... . ]
 #            <public-local-host/addr>,<public-local-port>
 #            <public-remote-host/addr>,<public-remote-port>
 #            <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
@@ -24,6 +25,8 @@
 # udptunnel will userv ipif locally, as
 #    userv root ipif <private-local-addr>,<private-remote-addr>,<mtu>,<proto>
 #                    <extra-local-nets>
+# or, if -lc was given, userv root ipif is replaced with the argument(s) to
+# successive -lc options.
 
 use Socket;
 use POSIX;
@@ -68,6 +71,19 @@ sub show_addr_port ($) {
     return inet_ntoa($s[1]).','.$s[0];
 }
 
+@lcmd= ();
+
+while ($ARGV[0] =~ m/^-/) {
+    $_= shift @ARGV;
+    last if $_ eq '--';
+    if (s/^-l//) {
+       push @lcmd,$_ if length;
+       while (@ARGV && ($_= shift @ARGV) ne '-') { push @lcmd, $_; }
+    } else {
+       quit("unknown option \`$_'");
+    }
+}
+
 ($las,$lps)= eat_addr_port('print|silent');
 $la= conv_host_addr($las);
 $lp= conv_port_number($lps);
@@ -134,7 +150,11 @@ $rsp= show_addr_port($rs);
 
 if ($lps eq 'print') { print($lsp,"\n") or quit("write port to stdout: $!"); }
 
+@lcmd= qw(userv root ipif) unless @lcmd;
+
 debug("using remote $rsp local $lsp");
+push @lcmd, ("$lva,$rva,$mtu,$proto",$lepn);
+debug("local command @lcmd");
 
 pipe(UR,UW) or fail("up pipe");
 pipe(DR,DW) or fail("down pipe");
@@ -144,8 +164,8 @@ if (!$c3) {
     close UR; close DW;
     open(STDIN,"<&DR") or fail("reopen stdin for packets");
     open(STDOUT,">&UW") or fail("reopen stdout for packets");
-    exec "userv","root","ipif","$lva,$rva,$mtu,$proto",$lepn;
-    quit("cannot execute userv ipif: $!");
+    exec @lcmd;
+    quit("cannot execute $lcmd[0]: $!");
 }
 close UW;
 close DR;
@@ -175,17 +195,17 @@ if ($keepalive) { $nextsendka= now(); }
 for (;;) {
     if ($keepalive) {
        $now= now();
-       $timeout= $nextsendka-$now;
-       if ($timeout < 0) {
+       $thistimeout= $nextsendka-$now;
+       if ($thistimeout < 0) {
            defined(send L,"\300",0,$rs)
                or warning("transmit keepalive error: $!");
            $nextsendka= $now+$keepalive;
-           $timeout= $keepalive;
+           $thistimeout= $keepalive;
        }
     } else {
-       $timeout= undef;
+       $thistimeout= undef;
     }
-    select($readfds=$wantreadfds,'','',$timeout);
+    select($readfds=$wantreadfds,'','',$thistimeout);
     for (;;) {
        if (!defined($r= sysread(UR,$upbuf,$mtu*2+3,length($upbuf)))) {
            $! == EAGAIN || warning("tunnel endpoint read error: $!");
@@ -195,13 +215,16 @@ for (;;) {
            quit "tunnel endpoint closed by system";
        }
        while (($p= index($upbuf,"\300")) >= 0) {
-           if (!defined(send L,substr($upbuf,0,$p),0,$rs)) {
+           if ($p && !defined(send L,substr($upbuf,0,$p),0,$rs)) {
                warning("transmit error: $!");
-           } elsif (!$upyet) {
-               $upyet= 1;
-               debug($downyet ? "tunnel open at this end" : "transmit channel open");
+           } else {
+               if (!$upyet) {
+                   $upyet= 1;
+                   debug($downyet ? "tunnel open at this end" : "transmitting");
+               }
+               if ($keepalive) { $nextsendka= now()+$keepalive; }
            }
-           $upbuf= substr($upbuf+1,$p);
+           $upbuf= substr($upbuf,$p+1);
        }
     }
     while (defined($rs_from= recv L,$downbuf,$mtu*2+3,0)) {
@@ -210,6 +233,7 @@ for (;;) {
            warning("got packet from incorrect peer $rsp_from");
            next;
        }
+       $downbuf= "\300".$downbuf."\300";
        if (!defined($r= syswrite(DW,$downbuf,length $downbuf))) {
            warning("tunnel endpoint write error: $!");
        } elsif ($r != length $downbuf) {
@@ -217,10 +241,11 @@ for (;;) {
        } else {
            if (!$downyet) {
                $downyet= 1;
-               debug($upyet ? "tunnel open at this end" : "receive channel open");
+               debug($upyet ? "tunnel open at this end" : "receiving");
            }
            alarm($timeout) if $timeout;
        }
     }
+    if ($! == ECONNREFUSED) { quit("tunnel closed at remote end"); }
     $! == EAGAIN || warning("receive error: $!");
 }