|
|
@@ -2,27 +2,43 @@ |
|
|
|
|
|
|
|
use strict; |
|
|
|
use POSIX ":sys_wait_h"; |
|
|
|
use List::Util qw(shuffle); |
|
|
|
use Data::Dumper; |
|
|
|
use warnings; |
|
|
|
|
|
|
|
$|=1; |
|
|
|
|
|
|
|
my %conf = ( |
|
|
|
avail => 2, |
|
|
|
avail => 10, |
|
|
|
spare => 2, |
|
|
|
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_source4 => '193.110.95.0/24', |
|
|
|
ipt_source6 => '2001:1a8f:ffff::/48', |
|
|
|
socks_offset => 9000, |
|
|
|
path => '/opt/tor', |
|
|
|
dns_check => [ |
|
|
|
'www.microsoft.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 => { |
|
|
|
ipv4 => 1, |
|
|
|
ipv6 => 1, |
|
|
|
}, |
|
|
|
loopinterval => 60, |
|
|
|
loopinterval => 10, |
|
|
|
statefile => 'var/status.txt', |
|
|
|
); |
|
|
|
|
|
|
|
# states: startup, ready, problem, dying, killed |
|
|
@@ -36,52 +52,71 @@ while(1) { run(); } |
|
|
|
|
|
|
|
sub run { |
|
|
|
|
|
|
|
# check state / start new spares / etc.. |
|
|
|
check_processes(); |
|
|
|
|
|
|
|
# update conntrack session usage per client |
|
|
|
ipt_conn(); |
|
|
|
|
|
|
|
# display state of all clients |
|
|
|
show_state(); |
|
|
|
|
|
|
|
# commit log |
|
|
|
log_commit(); |
|
|
|
|
|
|
|
# wait for next loop |
|
|
|
sleep($conf{loopinterval}); |
|
|
|
} |
|
|
|
|
|
|
|
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++) { |
|
|
|
printf("%3s %-7s %-5s %4s %4s\n", |
|
|
|
log_add("%3s %-7s %-5s %8s %8s %15s %35s\n", |
|
|
|
$id, |
|
|
|
$var[$id]{state}, |
|
|
|
$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 { |
|
|
|
my $inuse=0; |
|
|
|
my @spares; |
|
|
|
|
|
|
|
# check for dead tor clients (killed or crashed) |
|
|
|
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++) { |
|
|
|
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++) { |
|
|
|
if ( $var[$id]{state} eq "ready" && $var[$id]{mode} eq 'live' ) { |
|
|
|
if ( $var[$id]{state} =~ /^(ready|problem)$/ && $var[$id]{mode} eq 'live' ) { |
|
|
|
$inuse++; |
|
|
|
} |
|
|
|
|
|
|
@@ -91,82 +126,134 @@ sub check_processes { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
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} ) { |
|
|
|
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++) { |
|
|
|
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} ) { |
|
|
|
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++) { |
|
|
|
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 { |
|
|
|
|
|
|
|
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 { |
|
|
|
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'; |
|
|
|
} |
|
|
|
} |
|
|
|
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 { |
|
|
|
my $next; |
|
|
|
|
|
|
|
print "initiating a new tor client\n"; |
|
|
|
|
|
|
|
# try to recycle a position |
|
|
|
for(my $id=0; defined $var[$id]; $id++) { |
|
|
|
if ( $var[$id]{state} eq 'killed' ) { |
|
|
|
print "recovering id $id\n"; |
|
|
|
$next = $id; |
|
|
|
last; |
|
|
|
} |
|
|
@@ -175,7 +262,6 @@ sub tor_new { |
|
|
|
# alternatively, create a new position |
|
|
|
if ( !defined $next ) { |
|
|
|
$next = @var; |
|
|
|
print "new id $next\n"; |
|
|
|
} |
|
|
|
|
|
|
|
$var[$next] = { |
|
|
@@ -183,10 +269,11 @@ sub tor_new { |
|
|
|
mode => "spare", |
|
|
|
port => $conf{socks_offset} + $next, |
|
|
|
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 |
|
|
|
my $datadir = "$conf{path}/var/lib/$next"; |
|
|
|
my %files = ( |
|
|
@@ -230,14 +317,13 @@ Log notice syslog |
|
|
|
EOF |
|
|
|
close(C); |
|
|
|
|
|
|
|
print "forking...\n"; |
|
|
|
my $pid = fork(); |
|
|
|
|
|
|
|
if ( !defined $pid ) { |
|
|
|
die "Failed to fork, something has gone badly wrong!\n"; |
|
|
|
} |
|
|
|
elsif ( $pid ) { |
|
|
|
print "tor client pid $pid\n"; |
|
|
|
log_add("new tor client pid $pid\n"); |
|
|
|
$var[$next]{pid} = $pid; |
|
|
|
} |
|
|
|
else { |
|
|
@@ -254,14 +340,39 @@ sub tor_prod { |
|
|
|
# move process from spare to prod |
|
|
|
|
|
|
|
$var[$id]{mode} = 'live'; |
|
|
|
|
|
|
|
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 { |
|
|
|
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(); |
|
|
|
} |
|
|
@@ -320,7 +431,7 @@ sub ipt_recalc { |
|
|
|
|
|
|
|
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)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@@ -341,7 +452,7 @@ sub ipt_recalc { |
|
|
|
|
|
|
|
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)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@@ -381,5 +492,19 @@ sub ipt_conn { |
|
|
|
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}; |
|
|
|
} |