An open and free bittorrent tracker https://erdgeist.org/gitweb/opentracker
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

295 lines
10 KiB

  1. #!/usr/bin/perl
  2. # This software was written by Philipp Wuensche <cryx-otsync@h3q.com>
  3. # It is considered beerware.
  4. use strict;
  5. #use Convert::Bencode_XS qw(:all);
  6. use Convert::Bencode qw(:all);
  7. use Data::Dumper;
  8. use LWP::UserAgent;
  9. use URI::Escape;
  10. # enable verbose output
  11. my $debug = 0;
  12. # tracker from where we get our sync data
  13. my @trackers = ('127.0.0.1:8989');
  14. # tracker to upload merged data
  15. my @client_tracker = ('127.0.0.1:8989');
  16. # time to wait between syncs
  17. my $sleeptime = '300';
  18. # SSL cert and key
  19. my $ssl_cert = 'cert.pem';
  20. my $ssl_key = 'key.pem';
  21. foreach(@trackers) {
  22. print "Syncing from: $_\n";
  23. }
  24. foreach(@client_tracker) {
  25. print "Syncing to: $_\n";
  26. }
  27. my $file = shift;
  28. # global hash for storing the merged syncs
  29. my %merged_syncs;
  30. while(1) {
  31. %merged_syncs;
  32. my @bencoded_sync_data;
  33. # fetch the sync from every tracker and put it into an array in bencoded form
  34. foreach my $tracker (@trackers) {
  35. my $bencoded_sync = fetch_sync($tracker);
  36. # my $bencoded_sync = fetch_sync_from_file($file);
  37. if($bencoded_sync ne 0 && $bencoded_sync =~ /^d4\:sync/) {
  38. push(@bencoded_sync_data,$bencoded_sync);
  39. }
  40. }
  41. # bdecode every sync and throw it into the merged-sync
  42. foreach my $bencoded_sync (@bencoded_sync_data) {
  43. print "Doing merge...\n";
  44. merge_sync(bdecode($bencoded_sync));
  45. my $num_torrents = keys(%merged_syncs);
  46. print "number of torrents: $num_torrents\n";
  47. }
  48. # number of max. peers in one changeset
  49. my $peer_limit = 500;
  50. # max number of changesets per commit
  51. my $max_changesets = 10;
  52. my $hash_count = 0;
  53. my $peer_count = 0;
  54. my $changeset;
  55. my @escaped_changesets;
  56. # run until all hashes are put into changesets and commited to the trackers
  57. while(keys(%merged_syncs) != 0) {
  58. foreach my $hash (keys(%merged_syncs)) {
  59. print "Starting new changeset\n" if($peer_count == 0 && $debug);
  60. my $num_peers = keys(%{$merged_syncs{$hash}});
  61. print "\t$peer_count peers for $hash_count hashes in changeset\n" if($debug);
  62. my $pack_hash = pack('H*',$hash);
  63. # as long as the peer_limit is not reached, add new hashes with peers to the changeset hash-table
  64. if($peer_count < $peer_limit) {
  65. print "\t\tAdd $num_peers peers for $hash changeset\n" if($debug);
  66. $peer_count = $peer_count + $num_peers;
  67. foreach my $peer_socket (keys(%{$merged_syncs{$hash}})) {
  68. my $flags = $merged_syncs{$hash}{$peer_socket};
  69. print "\t\t\tAdd $peer_socket $flags\n" if($debug);
  70. my $pack_peer = packme($peer_socket,$flags);
  71. $changeset->{'sync'}->{$pack_hash} = $changeset->{'sync'}->{$pack_hash}.$pack_peer;
  72. }
  73. $hash_count++;
  74. # hash is stored in the changeset, delete it from the hash-table
  75. delete $merged_syncs{$hash};
  76. }
  77. # the peer_limit is reached or we are out of torrents, so start preparing a changeset
  78. if($peer_count >= $peer_limit || keys(%merged_syncs) == 0) {
  79. print "Commit changeset for $hash_count hashes with $peer_count peers total\n" if($debug);
  80. # bencode the changeset
  81. my $enc_changeset = bencode($changeset);
  82. # URL-escape the changeset and put into an array of changesets
  83. my $foobar = uri_escape($enc_changeset);
  84. push(@escaped_changesets,$foobar);
  85. # the changeset is ready and stored, so delete it from the changeset hash-table
  86. delete $changeset->{'sync'};
  87. $hash_count = 0;
  88. $peer_count = 0;
  89. print "\n\n\n" if($debug);
  90. }
  91. # if enought changesets are prepared or we are out of torrents for more changesets,
  92. # sync the changesets to the trackers
  93. if($#escaped_changesets == $max_changesets || keys(%merged_syncs) == 0) {
  94. print "\tSync...\n";
  95. sync_to_tracker(\@escaped_changesets);
  96. undef @escaped_changesets;
  97. }
  98. }
  99. }
  100. print "Sleeping for $sleeptime seconds\n";
  101. sleep $sleeptime;
  102. }
  103. sub connect_tracker {
  104. # connect a tracker via HTTPS, returns the body of the response
  105. my $url = shift;
  106. $ENV{HTTPS_DEBUG} = 0;
  107. $ENV{HTTPS_CERT_FILE} = $ssl_cert;
  108. $ENV{HTTPS_KEY_FILE} = $ssl_key;
  109. my $ua = new LWP::UserAgent;
  110. my $req = new HTTP::Request('GET', $url);
  111. my $res = $ua->request($req);
  112. my $content = $res->content;
  113. if($res->is_success()) {
  114. return $content;
  115. } else {
  116. print $res->code."|".$res->status_line."\n";
  117. return 0;
  118. }
  119. }
  120. sub sync_to_tracker {
  121. # commit changesets to a tracker
  122. my @changesets = @{(shift)};
  123. # prepare the URI with URL-encoded changesets concatenated by a &
  124. my $uri = 'sync?';
  125. foreach my $set (@changesets) {
  126. $uri .= 'changeset='.$set.'&';
  127. }
  128. my $uri_length = length($uri);
  129. # commit the collection of changesets to the tracker via HTTPS
  130. foreach my $tracker (@client_tracker) {
  131. print "\t\tTracker: $tracker (URI: $uri_length)\n";
  132. my $url = "https://$tracker/".$uri;
  133. connect_tracker($url);
  134. }
  135. }
  136. sub packme {
  137. # pack data
  138. # returns ipaddr, port and flags in packed format
  139. my $peer_socket = shift;
  140. my $flags = shift;
  141. my($a,$b,$c,$d,$port) = split(/[\.,\:]/,$peer_socket);
  142. my $pack_peer = pack('C4 n1 b16',$a,$b,$c,$d,$port,$flags);
  143. return $pack_peer;
  144. }
  145. sub unpackme {
  146. # unpack packed data
  147. # returns ipaddr. in quad-form with port (a.b.c.d:port) and flags as bitfield
  148. # data is packed as:
  149. # - 4 byte ipaddr. (unsigned char value)
  150. # - 2 byte port (unsigned short in "network" (big-endian) order)
  151. # - 2 byte flags (bit string (ascending bit order inside each byte))
  152. my $data = shift;
  153. my($a, $b, $c, $d, $port, $flags) = unpack('C4 n1 b16',$data);
  154. my $peer_socket = "$a\.$b\.$c\.$d\:$port";
  155. return($peer_socket,$flags);
  156. }
  157. sub fetch_sync {
  158. # fetch sync from a tracker
  159. my $tracker = shift;
  160. my $url = "https://$tracker/sync";
  161. print "Fetching from $url\n";
  162. my $body = connect_tracker($url);
  163. if($body && $body =~ /^d4\:sync/) {
  164. return $body;
  165. } else {
  166. return 0;
  167. }
  168. }
  169. sub fetch_sync_from_file {
  170. # fetch sync from a file
  171. my $file = shift;
  172. my $body;
  173. print "Fetching from file $file\n";
  174. open(FILE,"<$file");
  175. while(<FILE>) {
  176. $body .= $_;
  177. }
  178. close(FILE);
  179. return $body;
  180. }
  181. sub merge_sync {
  182. # This builds a hash table with the torrenthash as keys. The value is a hash table again with the peer-socket as keys
  183. # and flags in the value
  184. # Example:
  185. # 60dd2beb4197f71677c0f5ba92b956f7d04651e5 =>
  186. # 192.168.23.23:2323 => 0000000000000000
  187. # 23.23.23.23:2342 => 0000000100000000
  188. # b220b4d7136e84a88abc090db88bec8604a808f3 =>
  189. # 42.23.42.23:55555 => 0000000000000000
  190. my $hashref = shift;
  191. my $nonuniq_hash_counter = 0;
  192. my $nonuniq_peer_counter = 0;
  193. my $hash_counter = 0;
  194. my $peer_counter = 0;
  195. foreach my $key (keys(%{$hashref->{'sync'}})) {
  196. # start merge for every sha1-hash in the sync
  197. my $hash = unpack('H*',$key);
  198. $hash_counter++;
  199. $nonuniq_hash_counter++ if exists $merged_syncs{$hash};
  200. while(${$hashref->{'sync'}}{$key} ne "")
  201. {
  202. # split the value into 8-byte and unpack it for getting peer-socket and flags
  203. my($peer_socket,$flags) = unpackme(substr(${$hashref->{'sync'}}{$key},0,8,''));
  204. $peer_counter++;
  205. $nonuniq_peer_counter++ if exists $merged_syncs{$hash}{$peer_socket};
  206. # Create a hash table with sha1-hash as key and a hash table as value.
  207. # The hash table in the value has the peer-socket as key and flags as value
  208. # If the entry already exists, the flags are ORed together, if not it is ORed with 0
  209. $merged_syncs{$hash}{$peer_socket} = $flags | $merged_syncs{$hash}{$peer_socket};
  210. }
  211. }
  212. print "$hash_counter hashes $nonuniq_hash_counter non-uniq, $peer_counter peers $nonuniq_peer_counter non-uniq.\n";
  213. }
  214. sub test_decode {
  215. my $hashref = shift;
  216. print "CHANGESET DEBUG OUTPUT\n";
  217. print Dumper $hashref;
  218. foreach my $key (keys(%{$hashref->{'sync'}})) {
  219. my $hash = unpack('H*',$key);
  220. print "Changeset for $hash\n";
  221. while(${$hashref->{'sync'}}{$key} ne "")
  222. {
  223. my($peer_socket,$flags) = unpackme(substr(${$hashref->{'sync'}}{$key},0,8,''));
  224. print "\tSocket: $peer_socket Flags: $flags\n";
  225. }
  226. }
  227. }