You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
9.2 KiB
421 lines
9.2 KiB
#!/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;
|
|
}
|