Browse Source

mass improvements

master
root 6 years ago
parent
commit
18bef66867
1 changed files with 194 additions and 69 deletions
  1. 194
    69
      bin/tor_controller.pl

+ 194
- 69
bin/tor_controller.pl View File



use strict; use strict;
use POSIX ":sys_wait_h"; use POSIX ":sys_wait_h";
use List::Util qw(shuffle);
use Data::Dumper; use Data::Dumper;
use warnings; use warnings;


$|=1;


my %conf = ( my %conf = (
avail => 2,
avail => 10,
spare => 2, spare => 2,
ipt_chain => "PREROUTING", ipt_chain => "PREROUTING",
ipt_rule => "-p tcp -m tcp --dport 777 -m statistic --mode random --probability %s -j REDIRECT --to-ports %s",
ipt_rule => "-s %s -p tcp -m tcp --dport 777 -m statistic --mode random --probability %s -j REDIRECT --to-ports %s",
ipt_table => 'nat', ipt_table => 'nat',
ipt_source4 => '193.110.95.0/24',
ipt_source6 => '2001:1a8f:ffff::/48',
socks_offset => 9000, socks_offset => 9000,
path => '/opt/tor', path => '/opt/tor',
dns_check => [ dns_check => [
'www.microsoft.com',
'www.google.com', 'www.google.com',
'www.youtube.com',
'www.facebook.com',
'www.baidu.com',
'www.wikipedia.org',
'www.qq.com',
'www.taobao.com',
'www.tmall.com',
'www.yahoo.com',
'www.amazon.com',
'www.twitter.com',
'www.suho.com',
], ],
getip => 'http://www.spale.com/cgi-bin/ip.cgi',
proto => { proto => {
ipv4 => 1, ipv4 => 1,
ipv6 => 1, ipv6 => 1,
}, },
loopinterval => 60,
loopinterval => 10,
statefile => 'var/status.txt',
); );


# states: startup, ready, problem, dying, killed # states: startup, ready, problem, dying, killed


sub run { sub run {


# check state / start new spares / etc..
check_processes(); check_processes();

# update conntrack session usage per client
ipt_conn();

# display state of all clients
show_state(); show_state();


# commit log
log_commit();

# wait for next loop
sleep($conf{loopinterval}); sleep($conf{loopinterval});
} }


sub show_state { sub show_state {


my $s = ipt_conn();

printf("======================================\n");
printf("%3s %-7s %-5s %4s %4s\n", "id", "state", "mode", "ipv4", "ipv6");
log_add("=======================================================================================\n");
log_add("%3s %-7s %-5s %8s %8s %15s %35s\n", "id", "state", "mode", "ipt_ipv4", "ipt_ipv6", "ipv4", "ipv6");
log_add("---------------------------------------------------------------------------------------\n");
for(my $id=0; defined $var[$id]; $id++) { for(my $id=0; defined $var[$id]; $id++) {
printf("%3s %-7s %-5s %4s %4s\n",
log_add("%3s %-7s %-5s %8s %8s %15s %35s\n",
$id, $id,
$var[$id]{state}, $var[$id]{state},
$var[$id]{mode}, $var[$id]{mode},
exists $s->{$id}{ipv4} ? $s->{$id}{ipv4} : 0,
exists $s->{$id}{ipv6} ? $s->{$id}{ipv6} : 0,
$var[$id]{ipt}{ipv4},
$var[$id]{ipt}{ipv6},
$var[$id]{ipv4},
$var[$id]{ipv6},
); );
} }
printf("======================================\n");
log_add("=======================================================================================\n");
} }


sub check_processes { sub check_processes {
my $inuse=0; my $inuse=0;
my @spares; my @spares;


# check for dead tor clients (killed or crashed)
for(my $id=0; defined $var[$id]; $id++) { for(my $id=0; defined $var[$id]; $id++) {
if ( $var[$id]{pid} eq waitpid($var[$id]{pid}, WNOHANG) ) {
print "child $var[$id]{pid} died. resetting instance $id\n";
ipt_del($id);
$var[$id]{pid}=undef;
$var[$id]{state}='killed';
$var[$id]{mode}='none';
if ( defined $var[$id]{pid} && $var[$id]{pid} eq waitpid($var[$id]{pid}, WNOHANG) ) {
log_add("child $var[$id]{pid} died. resetting instance $id\n");
tor_dead($id);
} }
} }


# check for conntrack on dying processes
for(my $id=0; defined $var[$id]; $id++) { for(my $id=0; defined $var[$id]; $id++) {
if ( $var[$id]{state} =~ /^(startup|ready|problem)$/ ) {
check_dns($id);
if ( $var[$id]{state} eq 'dying' &&
$var[$id]{ipt}{ipv4} eq 0 &&
$var[$id]{ipt}{ipv6} eq 0 ) {

log_add("client $id was dying and has no conntrack anymore, killing\n");
tor_kill($id);
} }
} }

# check dns query for startup, read and problem clients
check_dns();


# count/list available live and available spare clients
for(my $id=0; defined $var[$id]; $id++) { for(my $id=0; defined $var[$id]; $id++) {
if ( $var[$id]{state} eq "ready" && $var[$id]{mode} eq 'live' ) {
if ( $var[$id]{state} =~ /^(ready|problem)$/ && $var[$id]{mode} eq 'live' ) {
$inuse++; $inuse++;
} }




} }


print "found " . scalar(@spares) . " spare clients and $inuse live clients\n";
log_add("found " . scalar(@spares) . " spare clients and $inuse live clients\n");


# check if available live processes are sufficient
# and get from spare if missing
if ( $inuse < $conf{avail} ) { if ( $inuse < $conf{avail} ) {
print "missing live processes. looking for spares.\n";
log_add("missing live processes. looking for spares.\n");


for(my $x=0; $x<($conf{avail}-$inuse); $x++) { for(my $x=0; $x<($conf{avail}-$inuse); $x++) {
my $new = pop @spares;
if ( defined $new ) {
if ( $var[$new]{state} eq 'ready' ) {
print "moving spare to prod.\n";
tor_prod($new);
}
else {
push @spares, $new;
if ( defined $spares[0] ) {
if ( $var[$spares[0]]{state} eq 'ready' ) {
log_add("moving spare to prod.\n");
tor_prod(shift @spares);
$inuse++;
} }
} }
} }
} }


# if spares arent enough, start some more
# especially useful for quick availability after initial startup
for(my $x=0; $x<($conf{avail}-$inuse)/2; $x++) { tor_new(); }

# check if there are enough spares
if ( scalar(@spares) < $conf{spare} ) { if ( scalar(@spares) < $conf{spare} ) {
print "missing spare processes, starting new ones.\n";
log_add("missing spare processes, starting new ones.\n");


for(my $x=0; $x < ($conf{spare}-scalar(@spares)); $x++) { for(my $x=0; $x < ($conf{spare}-scalar(@spares)); $x++) {
tor_new(); tor_new();
} }
} }

# update public ipv4/ipv6 for tunnels
for(my $id=0; defined $var[$id]; $id++) {
if ( $var[$id]{state} =~ /^(ready|problem)$/ ) {
getip($id,4) if $var[$id]{ipv4} eq '-';
getip($id,6) if $var[$id]{ipv6} eq '-';
}
}

}

sub getip {
my($id,$proto) = @_;


open(CURL,"timeout 10 curl -f -s -$proto --socks5 localhost:$var[$id]{port} $conf{getip} |");
while(<CURL>) {
if ( /^([0-9a-f\:\.]+)$/ ) {
$var[$id]{"ipv".$proto} = $1;
}
}
close(CURL);
} }


sub check_dns { sub check_dns {


my($id) = @_;
my @pids;


my $ok = 0;
foreach my $hostname ( @{$conf{dns_check}} ) {
print "Checking DNS ($hostname) for $id ";
system("timeout 5 tor-resolve $hostname 127.0.0.1:$var[$id]{port} >/dev/null 2>&1");
if ( $? ) {
print "ERROR\n";
log_add("DNS Check Start ");
for(my $id=0; defined $var[$id]; $id++) {

# ignore clients not in bad state
if ( $var[$id]{state} !~ /^(startup|ready|problem)$/ ) {
next;
}

my $pid = fork();


if ( $pid ) {
push @pids, { id => $id, pid => $pid };
log_add(".");
} }
else { else {
print "OK\n";
$ok++;
my $ok = 0;
foreach my $hostname ( shuffle @{$conf{dns_check}} ) {
system("timeout 5 tor-resolve $hostname 127.0.0.1:$var[$id]{port} >/dev/null 2>&1");
if ( ! $? ) { $ok=1; last; }
}

if ( $ok ) { exit 0; }
else { exit 1; }
} }
} }


if ( $var[$id]{state} eq 'startup' ) {
if ( $ok ) {
$var[$id]{state} = 'ready';
log_add("\n");

log_add("DNS Check results: ");

foreach my $x ( @pids ) {
my $id = $x->{id};
my $pid = $x->{pid};

waitpid($pid, 0);

my $ok = $? ? 0 : 1;

log_add($ok ? "+" : "-");

if ( $var[$id]{state} eq 'startup' ) {
if ( $ok ) {
$var[$id]{state} = 'ready';
}
else {
$var[$id]{state} = 'problem';
}
} }
else {
elsif ( $var[$id]{state} eq 'ready' && !$ok ) {
$var[$id]{state} = 'problem'; $var[$id]{state} = 'problem';
} }
}
elsif ( $var[$id]{state} eq 'ready' && !$ok ) {
$var[$id]{state} = 'problem';
}
elsif ( $var[$id]{state} eq 'problem' ) {
if ( $ok ) {
$var[$id]{state} = 'ready';
}
else {
$var[$id]{state} = 'dying';
ipt_del($id);
elsif ( $var[$id]{state} eq 'problem' ) {
if ( $ok ) {
$var[$id]{state} = 'ready';
}
else {
tor_dying($id);
}
} }
} }
log_add("\n");
} }


sub tor_new { sub tor_new {
my $next; my $next;


print "initiating a new tor client\n";

# try to recycle a position # try to recycle a position
for(my $id=0; defined $var[$id]; $id++) { for(my $id=0; defined $var[$id]; $id++) {
if ( $var[$id]{state} eq 'killed' ) { if ( $var[$id]{state} eq 'killed' ) {
print "recovering id $id\n";
$next = $id; $next = $id;
last; last;
} }
# alternatively, create a new position # alternatively, create a new position
if ( !defined $next ) { if ( !defined $next ) {
$next = @var; $next = @var;
print "new id $next\n";
} }


$var[$next] = { $var[$next] = {
mode => "spare", mode => "spare",
port => $conf{socks_offset} + $next, port => $conf{socks_offset} + $next,
pid => undef, pid => undef,
ipt => { ipv4 => 0, ipv6 => 0 },
ipv4 => '-',
ipv6 => '-',
}; };


print "creating configuration file and dependencies\n";

# create a config file and the required files and directories # create a config file and the required files and directories
my $datadir = "$conf{path}/var/lib/$next"; my $datadir = "$conf{path}/var/lib/$next";
my %files = ( my %files = (
EOF EOF
close(C); close(C);


print "forking...\n";
my $pid = fork(); my $pid = fork();


if ( !defined $pid ) { if ( !defined $pid ) {
die "Failed to fork, something has gone badly wrong!\n"; die "Failed to fork, something has gone badly wrong!\n";
} }
elsif ( $pid ) { elsif ( $pid ) {
print "tor client pid $pid\n";
log_add("new tor client pid $pid\n");
$var[$next]{pid} = $pid; $var[$next]{pid} = $pid;
} }
else { else {
# move process from spare to prod # move process from spare to prod


$var[$id]{mode} = 'live'; $var[$id]{mode} = 'live';

ipt_add($var[$id]{port}); ipt_add($var[$id]{port});
} }


sub tor_dying {
my($id) = @_;

$var[$id]{state} = 'dying';
$var[$id]{mode} = 'none';
ipt_del($id);
}

sub tor_dead {
my($id) = @_;

ipt_del($var[$id]{port});

$var[$id]{pid}=undef;
$var[$id]{state}='killed';
$var[$id]{mode}='none';
}

sub tor_kill {
my($id) = @_;

kill 'KILL', $var[$id]{pid};
}

sub ipt_add { sub ipt_add {
my($port) = @_; my($port) = @_;


system("iptables -t $conf{ipt_table} -A $conf{ipt_chain} " . sprintf($conf{ipt_rule}, "1.0", $port)) if $conf{proto}{ipv4};
system("ip6tables -t $conf{ipt_table} -A $conf{ipt_chain} " . sprintf($conf{ipt_rule}, "1.0", $port)) if $conf{proto}{ipv6};
system("iptables -t $conf{ipt_table} -A $conf{ipt_chain} " . sprintf($conf{ipt_rule}, $conf{ipt_source4}, "1.0", $port)) if $conf{proto}{ipv4};
system("ip6tables -t $conf{ipt_table} -A $conf{ipt_chain} " . sprintf($conf{ipt_rule}, $conf{ipt_source6}, "1.0", $port)) if $conf{proto}{ipv6};


ipt_recalc(); ipt_recalc();
} }
my $prob = 1 / ($nums - $num); my $prob = 1 / ($nums - $num);
system("iptables -t $conf{ipt_table} -R $conf{ipt_chain} $rulenum " . sprintf($conf{ipt_rule}, $prob, $port));
system("iptables -t $conf{ipt_table} -R $conf{ipt_chain} $rulenum " . sprintf($conf{ipt_rule}, $conf{ipt_source4}, $prob, $port));
} }
} }


my $prob = 1 / ($nums - $num); my $prob = 1 / ($nums - $num);
system("ip6tables -t $conf{ipt_table} -R $conf{ipt_chain} $rulenum " . sprintf($conf{ipt_rule}, $prob, $port));
system("ip6tables -t $conf{ipt_table} -R $conf{ipt_chain} $rulenum " . sprintf($conf{ipt_rule}, $conf{ipt_source6}, $prob, $port));
} }
} }


close(IPT); close(IPT);
} }


return \%s;
for(my $id=0; defined $var[$id]; $id++) {
$var[$id]{ipt}{ipv4} = exists $s{$id}{ipv4} ? $s{$id}{ipv4} : 0;
$var[$id]{ipt}{ipv6} = exists $s{$id}{ipv6} ? $s{$id}{ipv6} : 0;
}

}

sub log_add {
open(F,'>>'.$conf{path}.'/'.$conf{statefile}.'.tmp');
printf F @_;
close(F);
}

sub log_commit {
rename $conf{path}.'/'.$conf{statefile}.'.tmp', $conf{path}.'/'.$conf{statefile};
} }

Loading…
Cancel
Save