#!/usr/bin/perl # # inet6to4: Act as an ipv6-to-ipv4 relay for tcp applications that # do not support ipv6. # # Usage: inet6to4 <ipv6-listen-port> <ipv4-host:port> # inet6to4 -r <ipv4-listen-port> <ipv6-host:port> # # Examples: inet6to4 5900 localhost:5900 # inet6to4 8080 web1:80 # inet6to4 -r 5900 fe80::217:f2ff:fee6:6f5a%eth0:5900 # # The -r option reverses the direction of translation (e.g. for ipv4 # clients that need to connect to ipv6 servers.) Reversing is the default # if this script is named 'inet4to6' (e.g. by a symlink.) # # Use Ctrl-C to stop this program. You can also supply '-c n' as the # first option to only handle that many connections. # # Also set the env. vars INET6TO4_LOOP=1 or INET6TO4_LOOP=BG # to have an outer loop restarting this program (BG means do that # in the background), and INET6TO4_LOGFILE for a log file. # Also set INET6TO4_VERBOSE to verbosity level and INET6TO4_WAITTIME # and INET6TO4_PIDFILE (see below.) # #------------------------------------------------------------------------- # Copyright (c) 2010 by Karl J. Runge <runge@karlrunge.com> # # inet6to4 is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or (at # your option) any later version. # # inet6to4 is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with inet6to4; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA # or see <http://www.gnu.org/licenses/>. #------------------------------------------------------------------------- my $program = "inet6to4"; # Set up logging: # if (exists $ENV{INET6TO4_LOGFILE}) { close STDOUT; if (!open(STDOUT, ">>$ENV{INET6TO4_LOGFILE}")) { die "$program: $ENV{INET6TO4_LOGFILE} $!\n"; } close STDERR; open(STDERR, ">&STDOUT"); } select(STDERR); $| = 1; select(STDOUT); $| = 1; # interrupt handler: # my $looppid = ''; my $pidfile = ''; my $listen_sock = ''; # declared here for get_out() # sub get_out { print STDERR "$_[0]:\t$$ looppid=$looppid\n"; close $listen_sock if $listen_sock; if ($looppid) { kill 'TERM', $looppid; fsleep(0.2); } unlink $pidfile if $pidfile; exit 0; } $SIG{INT} = \&get_out; $SIG{TERM} = \&get_out; # pidfile: # sub open_pidfile { if (exists $ENV{INET6TO4_PIDFILE}) { my $pf = $ENV{INET6TO4_PIDFILE}; if (open(PID, ">$pf")) { print PID "$$\n"; close PID; $pidfile = $pf; } else { print STDERR "could not open pidfile: $pf - $! - continuing...\n"; } delete $ENV{INET6TO4_PIDFILE}; } } #################################################################### # Set INET6TO4_LOOP=1 to have this script create an outer loop # restarting itself if it ever exits. Set INET6TO4_LOOP=BG to # do this in the background as a daemon. if (exists $ENV{INET6TO4_LOOP}) { my $csl = $ENV{INET6TO4_LOOP}; if ($csl ne 'BG' && $csl ne '1') { die "$program: invalid INET6TO4_LOOP.\n"; } if ($csl eq 'BG') { # go into bg as "daemon": setpgrp(0, 0); my $pid = fork(); if (! defined $pid) { die "$program: $!\n"; } elsif ($pid) { wait; exit 0; } if (fork) { exit 0; } setpgrp(0, 0); close STDIN; if (! $ENV{INET6TO4_LOGFILE}) { close STDOUT; close STDERR; } } delete $ENV{INET6TO4_LOOP}; if (exists $ENV{INET6TO4_PIDFILE}) { open_pidfile(); } print STDERR "$program: starting service at ", scalar(localtime), " master-pid=$$\n"; while (1) { $looppid = fork; if (! defined $looppid) { sleep 10; } elsif ($looppid) { wait; } else { exec $0, @ARGV; exit 1; } print STDERR "$program: re-starting service at ", scalar(localtime), " master-pid=$$\n"; sleep 1; } exit 0; } if (exists $ENV{INET6TO4_PIDFILE}) { open_pidfile(); } use IO::Socket::INET6; use strict; use warnings; # some settings: # my $verbose = 1; # set to 0 for no messages, 2 for more. my $killpid = 1; # does kill(2) at end of connection. my $waittime = 0.25; # time to wait between connections. my $reverse = 0; # -r switch (or file named inet4to6) if (exists $ENV{INET6TO4_VERBOSE}) { $verbose = $ENV{INET6TO4_VERBOSE}; } if (exists $ENV{INET6TO4_WAITTIME}) { $waittime = $ENV{INET6TO4_WAITTIME}; } # process command line args: if (! @ARGV || $ARGV[0] =~ '^-+h') { # -help open(ME, "<$0"); while (<ME>) { last unless /^#/; next if /usr.bin.perl/; $_ =~ s/# ?//; print; } exit; } my $cmax = 0; if ($ARGV[0] eq '-c') { # -c shift; $cmax = shift; } if ($ARGV[0] eq '-r') { # -r shift; $reverse = 1; } elsif ($0 =~ /inet4to6$/) { $reverse = 1; } my $listen_port = shift; # ipv6-listen-port my $connect_to = shift; # ipv4-host:port die "no listen port or connect-to-host:port\n" if ! $listen_port || ! $connect_to; # connect to host: # my $host = ''; my $port = ''; if ($connect_to =~ /^(.*):(\d+)$/) { $host = $1; $port = $2; } die "invalid connect-to-host:port\n" if ! $host || ! $port; setpgrp(0, 0); # create listening socket: # my %opts; $opts{Listen} = 10; $opts{Proto} = "tcp"; $opts{ReuseAddr} = 1; if ($listen_port =~ /^(.*):(\d+)$/) { $opts{LocalAddr} = $1; $listen_port = $2; } $opts{LocalPort} = $listen_port; if (!$reverse) { # force ipv6 interface: $opts{Domain} = AF_INET6; $listen_sock = IO::Socket::INET6->new(%opts); } else { $listen_sock = IO::Socket::INET->new(%opts); if (! $listen_sock && $! =~ /invalid/i) { warn "$program: $!, retrying with AF_UNSPEC:\n"; $opts{Domain} = AF_UNSPEC; $listen_sock = IO::Socket::INET6->new(%opts); } } if (! $listen_sock) { die "$program: $!\n"; } # for use by the xfer helper processes' interrupt handlers: # my $current_fh1 = ''; my $current_fh2 = ''; # connection counter: # my $conn = 0; # loop forever waiting for connections: # while (1) { $conn++; if ($cmax > 0 && $conn > $cmax) { print STDERR "last connection ($cmax)\n" if $verbose; last; } print STDERR "listening for connection: $conn\n" if $verbose; my ($client, $ip) = $listen_sock->accept(); if ($client && !$reverse && $port == $listen_port) { # This happens on Darwin 'tcp46' if ($client->peerhost() =~ /^::ffff:/) { print STDERR "closing client we think is actually us: ", $client->peerhost(), "\n"; close $client; $client = undef; } } if (! $client) { # to throttle runaways fsleep(2 * $waittime); next; } print STDERR "conn: $conn -- ", $client->peerhost(), " at ", scalar(localtime), "\n" if $verbose; # spawn helper: # my $pid = fork(); if (! defined $pid) { die "$program: $!\n"; } elsif ($pid) { wait; # to throttle runaways fsleep($waittime); next; } else { # this is to avoid zombies: close $listen_sock; if (fork) { exit 0; } setpgrp(0, 0); handle_conn($client); } } exit 0; sub handle_conn { my $client = shift; my $start = time(); print STDERR "connecting to: $host:$port\n" if $verbose; my $sock = ''; my %opts; $opts{PeerAddr} = $host; $opts{PeerPort} = $port; $opts{Proto} = "tcp"; if (!$reverse) { $sock = IO::Socket::INET->new(%opts); } else { $opts{Domain} = AF_INET6; $sock = IO::Socket::INET6->new(%opts); } if (! $sock) { warn "$program: $!, retrying with AF_UNSPEC:\n"; $opts{Domain} = AF_UNSPEC; $sock = IO::Socket::INET6->new(%opts); } if (! $sock) { close $client; die "$program: $!\n"; } $current_fh1 = $client; $current_fh2 = $sock; # interrupt handler: # $SIG{TERM} = sub {print STDERR "got sigterm\[$$]\n" if $verbose; close $current_fh1; close $current_fh2; exit 0}; # spawn another helper and transfer the data: # my $parent = $$; if (my $child = fork()) { xfer($sock, $client, 'S->C'); if ($killpid) { fsleep(0.5); kill 'TERM', $child; } } else { xfer($client, $sock, 'C->S'); if ($killpid) { fsleep(0.75); kill 'TERM', $parent; } } # done. # if ($verbose > 1) { my $dt = time() - $start; print STDERR "dt\[$$]: $dt\n"; } exit 0; } # transfers data in one direction: # sub xfer { my($in, $out, $lab) = @_; my ($RIN, $WIN, $EIN, $ROUT); $RIN = $WIN = $EIN = ""; $ROUT = ""; vec($RIN, fileno($in), 1) = 1; vec($WIN, fileno($in), 1) = 1; $EIN = $RIN | $WIN; my $buf; while (1) { my $nf = 0; while (! $nf) { $nf = select($ROUT=$RIN, undef, undef, undef); } my $len = sysread($in, $buf, 8192); if (! defined($len)) { next if $! =~ /^Interrupted/; print STDERR "$program\[$lab/$conn/$$]: $!\n"; last; } elsif ($len == 0) { print STDERR "$program\[$lab/$conn/$$]: " . "Input is EOF.\n"; last; } if ($verbose > 4) { # verbose debugging of data: syswrite(STDERR , "\n$lab: ", 6); syswrite(STDERR , $buf, $len); } my $offset = 0; my $quit = 0; while ($len) { my $written = syswrite($out, $buf, $len, $offset); if (! defined $written) { print STDERR "$program\[$lab/$conn/$$]: " . "Output is EOF. $!\n"; $quit = 1; last; } $len -= $written; $offset += $written; } last if $quit; } close($in); close($out); } # sleep a fraction of a second: # sub fsleep { my ($time) = @_; select(undef, undef, undef, $time) if $time; }