#!/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 <) { 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() { 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)); } }