A tor server manager running multiple clients load-balanced using iptables.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

tor_controller.pl 11KB

6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
6年前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. #!/usr/bin/perl
  2. use strict;
  3. use POSIX ":sys_wait_h";
  4. use List::Util qw(shuffle);
  5. use Data::Dumper;
  6. use warnings;
  7. $|=1;
  8. my %conf = (
  9. avail => 10,
  10. spare => 2,
  11. ipt_chain => "PREROUTING",
  12. ipt_rule => "-s %s -p tcp -m tcp --dport 777 -m statistic --mode random --probability %s -j REDIRECT --to-ports %s",
  13. ipt_table => 'nat',
  14. ipt_source4 => '193.110.95.0/24',
  15. ipt_source6 => '2001:1a8f:ffff::/48',
  16. socks_offset => 9000,
  17. path => '/opt/tor',
  18. dns_check => [
  19. 'www.google.com',
  20. 'www.youtube.com',
  21. 'www.facebook.com',
  22. 'www.baidu.com',
  23. 'www.wikipedia.org',
  24. 'www.qq.com',
  25. 'www.taobao.com',
  26. 'www.tmall.com',
  27. 'www.yahoo.com',
  28. 'www.amazon.com',
  29. 'www.twitter.com',
  30. 'www.suho.com',
  31. ],
  32. getip => 'http://www.spale.com/cgi-bin/ip.cgi',
  33. proto => {
  34. ipv4 => 1,
  35. ipv6 => 1,
  36. },
  37. loopinterval => 10,
  38. statefile => 'var/status.txt',
  39. );
  40. # states: startup, ready, problem, dying, killed
  41. # modes: spare, live
  42. my @var;
  43. ipt_clean();
  44. while(1) { run(); }
  45. sub run {
  46. # check state / start new spares / etc..
  47. check_processes();
  48. # update conntrack session usage per client
  49. ipt_conn();
  50. # display state of all clients
  51. show_state();
  52. # commit log
  53. log_commit();
  54. # wait for next loop
  55. sleep($conf{loopinterval});
  56. }
  57. sub show_state {
  58. log_add("=======================================================================================\n");
  59. log_add("%3s %-7s %-5s %8s %8s %15s %35s\n", "id", "state", "mode", "ipt_ipv4", "ipt_ipv6", "ipv4", "ipv6");
  60. log_add("---------------------------------------------------------------------------------------\n");
  61. for(my $id=0; defined $var[$id]; $id++) {
  62. log_add("%3s %-7s %-5s %8s %8s %15s %35s\n",
  63. $id,
  64. $var[$id]{state},
  65. $var[$id]{mode},
  66. $var[$id]{ipt}{ipv4},
  67. $var[$id]{ipt}{ipv6},
  68. $var[$id]{ipv4},
  69. $var[$id]{ipv6},
  70. );
  71. }
  72. log_add("=======================================================================================\n");
  73. }
  74. sub check_processes {
  75. my $inuse=0;
  76. my @spares;
  77. # check for dead tor clients (killed or crashed)
  78. for(my $id=0; defined $var[$id]; $id++) {
  79. if ( defined $var[$id]{pid} && $var[$id]{pid} eq waitpid($var[$id]{pid}, WNOHANG) ) {
  80. log_add("child $var[$id]{pid} died. resetting instance $id\n");
  81. tor_dead($id);
  82. }
  83. }
  84. # check for conntrack on dying processes
  85. for(my $id=0; defined $var[$id]; $id++) {
  86. if ( $var[$id]{state} eq 'dying' &&
  87. $var[$id]{ipt}{ipv4} eq 0 &&
  88. $var[$id]{ipt}{ipv6} eq 0 ) {
  89. log_add("client $id was dying and has no conntrack anymore, killing\n");
  90. tor_kill($id);
  91. }
  92. }
  93. # check dns query for startup, read and problem clients
  94. check_dns();
  95. # count/list available live and available spare clients
  96. for(my $id=0; defined $var[$id]; $id++) {
  97. if ( $var[$id]{state} =~ /^(ready|problem)$/ && $var[$id]{mode} eq 'live' ) {
  98. $inuse++;
  99. }
  100. if ( $var[$id]{state} =~ /^(startup|ready|problem)$/ && $var[$id]{mode} eq 'spare' ) {
  101. push @spares, $id;
  102. }
  103. }
  104. log_add("found " . scalar(@spares) . " spare clients and $inuse live clients\n");
  105. # check if available live processes are sufficient
  106. # and get from spare if missing
  107. if ( $inuse < $conf{avail} ) {
  108. log_add("missing live processes. looking for spares.\n");
  109. for(my $x=0; $x<($conf{avail}-$inuse); $x++) {
  110. if ( defined $spares[0] ) {
  111. if ( $var[$spares[0]]{state} eq 'ready' ) {
  112. log_add("moving spare to prod.\n");
  113. tor_prod(shift @spares);
  114. $inuse++;
  115. }
  116. }
  117. }
  118. }
  119. # if spares arent enough, start some more
  120. # especially useful for quick availability after initial startup
  121. for(my $x=0; $x<($conf{avail}-$inuse)/2; $x++) { tor_new(); }
  122. # check if there are enough spares
  123. if ( scalar(@spares) < $conf{spare} ) {
  124. log_add("missing spare processes, starting new ones.\n");
  125. for(my $x=0; $x < ($conf{spare}-scalar(@spares)); $x++) {
  126. tor_new();
  127. }
  128. }
  129. # update public ipv4/ipv6 for tunnels
  130. for(my $id=0; defined $var[$id]; $id++) {
  131. if ( $var[$id]{state} =~ /^(ready|problem)$/ ) {
  132. getip($id,4) if $var[$id]{ipv4} eq '-';
  133. getip($id,6) if $var[$id]{ipv6} eq '-';
  134. }
  135. }
  136. }
  137. sub getip {
  138. my($id,$proto) = @_;
  139. open(CURL,"timeout 10 curl -f -s -$proto --socks5 localhost:$var[$id]{port} $conf{getip} |");
  140. while(<CURL>) {
  141. if ( /^([0-9a-f\:\.]+)$/ ) {
  142. $var[$id]{"ipv".$proto} = $1;
  143. }
  144. }
  145. close(CURL);
  146. }
  147. sub check_dns {
  148. my @pids;
  149. log_add("DNS Check Start ");
  150. for(my $id=0; defined $var[$id]; $id++) {
  151. # ignore clients not in bad state
  152. if ( $var[$id]{state} !~ /^(startup|ready|problem)$/ ) {
  153. next;
  154. }
  155. my $pid = fork();
  156. if ( $pid ) {
  157. push @pids, { id => $id, pid => $pid };
  158. log_add(".");
  159. }
  160. else {
  161. my $ok = 0;
  162. foreach my $hostname ( shuffle @{$conf{dns_check}} ) {
  163. system("timeout 5 tor-resolve $hostname 127.0.0.1:$var[$id]{port} >/dev/null 2>&1");
  164. if ( ! $? ) { $ok=1; last; }
  165. }
  166. if ( $ok ) { exit 0; }
  167. else { exit 1; }
  168. }
  169. }
  170. log_add("\n");
  171. log_add("DNS Check results: ");
  172. foreach my $x ( @pids ) {
  173. my $id = $x->{id};
  174. my $pid = $x->{pid};
  175. waitpid($pid, 0);
  176. my $ok = $? ? 0 : 1;
  177. log_add($ok ? "+" : "-");
  178. if ( $var[$id]{state} eq 'startup' ) {
  179. if ( $ok ) {
  180. $var[$id]{state} = 'ready';
  181. }
  182. else {
  183. $var[$id]{state} = 'problem';
  184. }
  185. }
  186. elsif ( $var[$id]{state} eq 'ready' && !$ok ) {
  187. $var[$id]{state} = 'problem';
  188. }
  189. elsif ( $var[$id]{state} eq 'problem' ) {
  190. if ( $ok ) {
  191. $var[$id]{state} = 'ready';
  192. }
  193. else {
  194. tor_dying($id);
  195. }
  196. }
  197. }
  198. log_add("\n");
  199. }
  200. sub tor_new {
  201. my $next;
  202. # try to recycle a position
  203. for(my $id=0; defined $var[$id]; $id++) {
  204. if ( $var[$id]{state} eq 'killed' ) {
  205. $next = $id;
  206. last;
  207. }
  208. }
  209. # alternatively, create a new position
  210. if ( !defined $next ) {
  211. $next = @var;
  212. }
  213. $var[$next] = {
  214. state => "startup",
  215. mode => "spare",
  216. port => $conf{socks_offset} + $next,
  217. pid => undef,
  218. ipt => { ipv4 => 0, ipv6 => 0 },
  219. ipv4 => '-',
  220. ipv6 => '-',
  221. };
  222. # create a config file and the required files and directories
  223. my $datadir = "$conf{path}/var/lib/$next";
  224. my %files = (
  225. config => "$conf{path}/etc/$next.conf",
  226. pidfile => "$conf{path}/run/$next.pid",
  227. socket => "$conf{path}/run/$next.socks",
  228. ctrl => "$conf{path}/run/$next.ctrl",
  229. cookie => "$conf{path}/run/$next.cookie",
  230. );
  231. system("mkdir -p $datadir");
  232. system("chmod 700 $datadir");
  233. system("chown debian-tor:debian-tor $datadir");
  234. foreach my $f ( keys %files ) {
  235. system("touch $files{$f}");
  236. system("chown debian-tor:debian-tor $files{$f}");
  237. }
  238. open(C,">$files{config}");
  239. print C <<EOF;
  240. DataDirectory $datadir
  241. PidFile $files{pidfile}
  242. RunAsDaemon 0
  243. User debian-tor
  244. ControlSocket $files{ctrl} GroupWritable RelaxDirModeCheck
  245. ControlSocketsGroupWritable 1
  246. #SocksPort unix:$files{socket} WorldWritable
  247. SocksPort 0.0.0.0:$var[$next]{port}
  248. SocksPort [::]:$var[$next]{port}
  249. CookieAuthentication 1
  250. CookieAuthFileGroupReadable 1
  251. CookieAuthFile $files{cookie}
  252. BandwidthRate 10MB
  253. Log notice syslog
  254. EOF
  255. close(C);
  256. my $pid = fork();
  257. if ( !defined $pid ) {
  258. die "Failed to fork, something has gone badly wrong!\n";
  259. }
  260. elsif ( $pid ) {
  261. log_add("new tor client pid $pid\n");
  262. $var[$next]{pid} = $pid;
  263. }
  264. else {
  265. chdir $conf{path};
  266. exec("tor -f $files{config} >/dev/null");
  267. die "something weng really wrong, failed to exec tor";
  268. exit;
  269. }
  270. }
  271. sub tor_prod {
  272. my($id) = @_;
  273. # move process from spare to prod
  274. $var[$id]{mode} = 'live';
  275. ipt_add($var[$id]{port});
  276. }
  277. sub tor_dying {
  278. my($id) = @_;
  279. $var[$id]{state} = 'dying';
  280. $var[$id]{mode} = 'none';
  281. ipt_del($id);
  282. }
  283. sub tor_dead {
  284. my($id) = @_;
  285. ipt_del($var[$id]{port});
  286. $var[$id]{pid}=undef;
  287. $var[$id]{state}='killed';
  288. $var[$id]{mode}='none';
  289. }
  290. sub tor_kill {
  291. my($id) = @_;
  292. kill 'KILL', $var[$id]{pid};
  293. }
  294. sub ipt_add {
  295. my($port) = @_;
  296. system("iptables -t $conf{ipt_table} -A $conf{ipt_chain} " . sprintf($conf{ipt_rule}, $conf{ipt_source4}, "1.0", $port)) if $conf{proto}{ipv4};
  297. system("ip6tables -t $conf{ipt_table} -A $conf{ipt_chain} " . sprintf($conf{ipt_rule}, $conf{ipt_source6}, "1.0", $port)) if $conf{proto}{ipv6};
  298. ipt_recalc();
  299. }
  300. sub ipt_del {
  301. my($port) = @_;
  302. if ( $conf{proto}{ipv4} ) {
  303. open(IPT,"iptables -t $conf{ipt_table} -L $conf{ipt_chain} -n --line-numbers |");
  304. while(<IPT>) {
  305. chomp;
  306. if ( /^(\d+) .*mode random probability .* redir ports $port$/ ) {
  307. system("iptables -t $conf{ipt_table} -D $conf{ipt_chain} $1");
  308. }
  309. }
  310. close(IPT);
  311. }
  312. if ( $conf{proto}{ipv6} ) {
  313. open(IPT,"ip6tables -t $conf{ipt_table} -L $conf{ipt_chain} -n --line-numbers |");
  314. while(<IPT>) {
  315. chomp;
  316. if ( /^(\d+) .*mode random probability .* redir ports $port$/ ) {
  317. system("ip6tables -t $conf{ipt_table} -D $conf{ipt_chain} $1");
  318. }
  319. }
  320. close(IPT);
  321. }
  322. ipt_recalc();
  323. }
  324. sub ipt_clean {
  325. system("iptables -t $conf{ipt_table} -F $conf{ipt_chain}") if $conf{proto}{ipv4};
  326. system("ip6tables -t $conf{ipt_table} -F $conf{ipt_chain}") if $conf{proto}{ipv6};
  327. }
  328. sub ipt_recalc {
  329. my @rules;
  330. if ( $conf{proto}{ipv4} ) {
  331. open(IPT,"iptables -t $conf{ipt_table} -L $conf{ipt_chain} -n --line-numbers |");
  332. while(<IPT>) {
  333. chomp;
  334. if ( /^(\d+) .*mode random probability .* redir ports (\d+)$/ ) {
  335. push @rules, [ $1, $2 ];
  336. }
  337. }
  338. close(IPT);
  339. my $nums = @rules;
  340. for(my $num = 0; defined $rules[$num]; $num++) {
  341. my($rulenum,$port)=@{$rules[$num]};
  342. my $prob = 1 / ($nums - $num);
  343. system("iptables -t $conf{ipt_table} -R $conf{ipt_chain} $rulenum " . sprintf($conf{ipt_rule}, $conf{ipt_source4}, $prob, $port));
  344. }
  345. }
  346. if ( $conf{proto}{ipv6} ) {
  347. open(IPT,"ip6tables -t $conf{ipt_table} -L $conf{ipt_chain} -n --line-numbers |");
  348. while(<IPT>) {
  349. chomp;
  350. if ( /^(\d+) .*mode random probability .* redir ports (\d+)$/ ) {
  351. push @rules, [ $1, $2 ];
  352. }
  353. }
  354. close(IPT);
  355. my $nums = @rules;
  356. for(my $num = 0; defined $rules[$num]; $num++) {
  357. my($rulenum,$port)=@{$rules[$num]};
  358. my $prob = 1 / ($nums - $num);
  359. system("ip6tables -t $conf{ipt_table} -R $conf{ipt_chain} $rulenum " . sprintf($conf{ipt_rule}, $conf{ipt_source6}, $prob, $port));
  360. }
  361. }
  362. }
  363. sub ipt_conn {
  364. my %s;
  365. if ( $conf{proto}{ipv4} ) {
  366. open(IPT,"conntrack -L -f ipv4 2>/dev/null |");
  367. while(<IPT>) {
  368. chomp;
  369. if ( / dport=777 .* sport=(\d+) / ) {
  370. my $port = $1;
  371. if ( $port >= $conf{socks_offset} ) {
  372. my $id = $port - $conf{socks_offset};
  373. $s{$id}{ipv4}++;
  374. }
  375. }
  376. }
  377. close(IPT);
  378. }
  379. if ( $conf{proto}{ipv6} ) {
  380. open(IPT,"conntrack -L -f ipv6 2>/dev/null |");
  381. while(<IPT>) {
  382. chomp;
  383. if ( / dport=777 .* sport=(\d+) / ) {
  384. my $port = $1;
  385. if ( $port >= $conf{socks_offset} ) {
  386. my $id = $port - $conf{socks_offset};
  387. $s{$id}{ipv6}++;
  388. }
  389. }
  390. }
  391. close(IPT);
  392. }
  393. for(my $id=0; defined $var[$id]; $id++) {
  394. $var[$id]{ipt}{ipv4} = exists $s{$id}{ipv4} ? $s{$id}{ipv4} : 0;
  395. $var[$id]{ipt}{ipv6} = exists $s{$id}{ipv6} ? $s{$id}{ipv6} : 0;
  396. }
  397. }
  398. sub log_add {
  399. open(F,'>>'.$conf{path}.'/'.$conf{statefile}.'.tmp');
  400. printf F @_;
  401. close(F);
  402. }
  403. sub log_commit {
  404. rename $conf{path}.'/'.$conf{statefile}.'.tmp', $conf{path}.'/'.$conf{statefile};
  405. }