|
|
@@ -0,0 +1,222 @@ |
|
|
|
#!/usr/bin/perl |
|
|
|
|
|
|
|
use strict; |
|
|
|
use Data::Dumper; |
|
|
|
use warnings; |
|
|
|
|
|
|
|
|
|
|
|
my %conf = ( |
|
|
|
avail => 10, |
|
|
|
spare => 3, |
|
|
|
ipt_chain => "PREROUTING", |
|
|
|
ipt_rule => "-p tcp -m tcp --dport 777 -m statistic --mode random --probability %s -j REDIRECT --to-ports %s", |
|
|
|
ipt_table => 'nat', |
|
|
|
socks_offset => 9000, |
|
|
|
path => '/opt/tor', |
|
|
|
dns_check => [ |
|
|
|
'www.microsoft.com', |
|
|
|
'www.google.com', |
|
|
|
], |
|
|
|
); |
|
|
|
|
|
|
|
my @var; |
|
|
|
|
|
|
|
|
|
|
|
run(); |
|
|
|
|
|
|
|
|
|
|
|
sub run { |
|
|
|
check_processes(); |
|
|
|
check_dns(); |
|
|
|
check_download(); |
|
|
|
|
|
|
|
sleep(10); |
|
|
|
} |
|
|
|
|
|
|
|
sub check_processes { |
|
|
|
my $inuse=0; |
|
|
|
my @spares; |
|
|
|
|
|
|
|
for(my $id=0; defined $var[$id]; $id++) { |
|
|
|
if ( $var[$id]{state} eq "ready" ) { |
|
|
|
if ( $var[$id]{mode} eq "spare" ) { |
|
|
|
push @spares, $id; |
|
|
|
} |
|
|
|
elsif ( $var[$id]{mode} eq "live" ) { |
|
|
|
$inuse++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if ( $inuse < $conf{avail} ) { |
|
|
|
print "missing live processes. looking for spares.\n"; |
|
|
|
|
|
|
|
for(my $x=0; $x<($conf{avail}-$inuse; $x++) { |
|
|
|
my $new = pop @spares; |
|
|
|
if ( defined $new ) { |
|
|
|
print "moving spare to prod.\n"; |
|
|
|
tor_prod($id); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if ( @spares < $conf{spares} ) { |
|
|
|
print "missing spare processes, starting new ones.\n"; |
|
|
|
|
|
|
|
for(my $x=0; $x< @spares-$conf{spares}; $x++) { |
|
|
|
tor_new(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
sub check_dns { |
|
|
|
|
|
|
|
my($id) = @_; |
|
|
|
|
|
|
|
my $ok = 0; |
|
|
|
foreach my $hostname ( @{$conf{dns_check} ) { |
|
|
|
system("tor-resolve $hostname 127.0.0.1:$var[$id]{port} 2>/dev/null 2>&1"); |
|
|
|
$ok++ if ! $?; |
|
|
|
} |
|
|
|
|
|
|
|
switch(get_state($id)) { |
|
|
|
case "STARTUP" { set_state("SPARE") if $ok; } |
|
|
|
case "SPARE" { set_state("PROBLEM") if !$ok; } |
|
|
|
} |
|
|
|
if ( $s eq 'DNS_CHECK' && $ok ) { |
|
|
|
$var[$id]{state}++; |
|
|
|
} |
|
|
|
elsif ( $s eq 'AVAILABLE' && ! $ok ) { |
|
|
|
$var[$id]{state}++; |
|
|
|
} |
|
|
|
elsif ( $s eq 'TEMPORARY_UNAVAILABLE' ) { |
|
|
|
$var[$id}{state}++ |
|
|
|
|
|
|
|
if ( ! $ok ) { |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sub tor_new { |
|
|
|
my $next; |
|
|
|
|
|
|
|
# try to recycle a position |
|
|
|
for(my $id=0; defined $var[$id]; $id++) { |
|
|
|
if ( $state[$var[$id]{state}] eq 'KILLED' ) { |
|
|
|
$next = $id; |
|
|
|
last; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
# alternatively, create a new position |
|
|
|
if ( !defined $next ) { |
|
|
|
$next = @var; |
|
|
|
} |
|
|
|
|
|
|
|
$var[$next] = { |
|
|
|
state => 0, |
|
|
|
port => $conf{port_offset} + $next, |
|
|
|
pid => undef, |
|
|
|
}; |
|
|
|
|
|
|
|
# create a config file and the required files and directories |
|
|
|
my $datadir = "$conf{path}/var/lib/$id"; |
|
|
|
my %files = ( |
|
|
|
config => "$conf{path}/etc/$id.conf", |
|
|
|
pidfile => "$conf{path}/run/$id.pid", |
|
|
|
socket => "$conf{path}/run/$id.socks", |
|
|
|
ctrl => "$conf{path}/run/$id.ctrl", |
|
|
|
cookie => "$conf{path}/run/$id.cookie", |
|
|
|
); |
|
|
|
|
|
|
|
system("mkdir -p $datadir"); |
|
|
|
system("chmod 700 $datadir"); |
|
|
|
system("chown debian-tor:debian-tor $datadir"); |
|
|
|
|
|
|
|
foreach my $f ( keys $files ) { |
|
|
|
system("touch $files{$f}"); |
|
|
|
system("chown debian-tor:debian-tor $files{$f}"); |
|
|
|
} |
|
|
|
|
|
|
|
open(C,">$files{config}"); |
|
|
|
print C <<EOF; |
|
|
|
DataDirectory $datadir |
|
|
|
PidFile $files{pidfile} |
|
|
|
RunAsDaemon 0 |
|
|
|
User debian-tor |
|
|
|
|
|
|
|
ControlSocket $files{ctrl} GroupWritable RelaxDirModeCheck |
|
|
|
ControlSocketsGroupWritable 1 |
|
|
|
SocksPort unix:$files{socks} WorldWritable |
|
|
|
SocksPort $var[$id]{port} |
|
|
|
|
|
|
|
CookieAuthentication 1 |
|
|
|
CookieAuthFileGroupReadable 1 |
|
|
|
CookieAuthFile $files{cookie} |
|
|
|
|
|
|
|
BandwidthRate 10MB |
|
|
|
|
|
|
|
Log notice syslog |
|
|
|
|
|
|
|
EOF |
|
|
|
close(C); |
|
|
|
|
|
|
|
system("cd $conf{dir} && tor -f $files{config}"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sub tor_prod { |
|
|
|
my($id) = @_; |
|
|
|
|
|
|
|
# move process from spare to prod |
|
|
|
|
|
|
|
$var[$id]{state}++; |
|
|
|
ipt_add($var[$id]{port}); |
|
|
|
} |
|
|
|
|
|
|
|
sub ipt_add { |
|
|
|
my($port) = @_; |
|
|
|
|
|
|
|
system("iptables -t $conf{ipt_table} -A $conf{ipt_chain} " . sprintf($conf{ipt_rule}, "1.0", $port)); |
|
|
|
|
|
|
|
ipt_recalc(); |
|
|
|
} |
|
|
|
|
|
|
|
sub ipt_del { |
|
|
|
my($port) = @_; |
|
|
|
|
|
|
|
open(IPT,"iptables -t $conf{ipt_table} -L $conf{ipt_chain} -n --line-numbers |"); |
|
|
|
while(<IPT>) { |
|
|
|
chomp; |
|
|
|
if ( /^(\d+) .*mode random probability .* redir ports $port$/ ) { |
|
|
|
system("iptables -t $conf{ipt_table} -D $conf{ipt_chain} $1"); |
|
|
|
} |
|
|
|
} |
|
|
|
close(IPT); |
|
|
|
|
|
|
|
ipt_recalc(); |
|
|
|
} |
|
|
|
|
|
|
|
sub ipt_recalc { |
|
|
|
my @rules; |
|
|
|
|
|
|
|
open(IPT,"iptables -t $conf{ipt_table} -L $conf{ipt_chain} -n --line-numbers |"); |
|
|
|
while(<IPT>) { |
|
|
|
chomp; |
|
|
|
if ( /^(\d+) .*mode random probability .* redir ports (\d+)$/ ) { |
|
|
|
push @rules, [ $1, $2 ]; |
|
|
|
} |
|
|
|
} |
|
|
|
close(IPT); |
|
|
|
|
|
|
|
my $nums = @rules; |
|
|
|
|
|
|
|
for(my $num = 0; defined $rules[$num]; $num++) { |
|
|
|
my($rulenum,$port)=@{$rules[$num]}; |
|
|
|
|
|
|
|
my $prob = 1 / ($nums - $num); |
|
|
|
|
|
|
|
system("iptables -t $conf{ipt_table} -R $conf{ipt_chain} $rulenum " . sprintf($conf{ipt_rule}, $prob, $port)); |
|
|
|
} |
|
|
|
|
|
|
|
} |