File Coverage

blib/lib/Net/BitTorrent/DHT.pm
Criterion Covered Total %
statement 241 307 78.5
branch 79 144 54.8
condition 18 54 33.3
subroutine 32 36 88.8
pod 5 5 100.0
total 375 546 68.6


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             package Net::BitTorrent::DHT;
3             {
4 11     11   202641 use strict;
  11         25  
  11         453  
5 11     11   65 use warnings;
  11         1327  
  11         445  
6 11     11   1160 use Digest::SHA qw[sha1_hex];
  11         4778  
  11         795  
7 11     11   76 use Scalar::Util qw[blessed weaken refaddr];
  11         25  
  11         734  
8 11     11   70 use Carp qw[carp];
  11         24  
  11         545  
9 11     11   1252 use Socket qw[/inet_/ /pack_sockaddr_in/];
  11         4646  
  11         5606  
10 11     11   82 use lib q[../../../lib/];
  11         26  
  11         100  
11 11     11   2750 use Net::BitTorrent::Util qw[:bencode :compact];
  11         24  
  11         1612  
12 11     11   839 use Net::BitTorrent::Protocol qw[:dht];
  11         20  
  11         1784  
13 11     11   835 use Net::BitTorrent::Version;
  11         24  
  11         316  
14 11     11   67 use version qw[qv];
  11         21  
  11         96  
15             our $VERSION_BASE = 50; our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ? q[%.3f_%03d] : q[%.3f]), (version->new(($VERSION_BASE))->numify / 1000), $UNSTABLE_RELEASE);
16             my @CONTENTS
17             = \my (%_client, %tid, %node_id, %outstanding_p, %nodes, %tracking);
18             my %REGISTRY;
19              
20             sub new {
21 47     47 1 151 my ($class, $args) = @_;
22 47         103 my $self = undef;
23 47 50 33     773 if (!$args or (ref($args) ne q[HASH])) {
24 0         0 carp __PACKAGE__
25             . q[->new( { ... } ) requires parameters passed as a hashref];
26 0         0 return;
27             }
28 47 50 33     1246 if ( !$args->{q[Client]}
      33        
29             or !blessed($args->{q[Client]})
30             or !$args->{q[Client]}->isa(q[Net::BitTorrent]))
31 0         0 { carp __PACKAGE__
32             . q[->new( { ... } ) requires parameters passed as a hashref];
33 0         0 return;
34             }
35 47         651 my $node_id = Net::BitTorrent::Version->gen_node_id();
36 47         406 $self = bless \$node_id, $class;
37              
38             # Defaults
39 47         364 $_client{refaddr $self} = $args->{q[Client]};
40 47         278 weaken $_client{refaddr $self};
41 47         258 $node_id{refaddr $self} = $node_id;
42 47         215 $nodes{refaddr $self} = {};
43 47         220 $tid{refaddr $self} = q[aaaaa];
44              
45             # Boot
46             $_client{refaddr $self}->_schedule(
47 22     22   200 {Code => sub { shift->_pulse() },
48 47         1435 Time => time + 3,
49             Object => $self
50             }
51             );
52             $_client{refaddr $self}->_schedule( # boot up
53             {Code => sub {
54 22     22   68 my ($s) = @_;
55 22         50 for my $node (values %{$nodes{refaddr $s}}) {
  22         210  
56 8         67 $self->_ping_out($node);
57 8         64 $self->_find_node_out($node, $node_id{refaddr $s});
58             }
59             },
60 47         665 Time => time + 2,
61             Object => $self
62             }
63             );
64 47         287 weaken($REGISTRY{refaddr $self} = $self);
65 47         249 return $self;
66             }
67              
68             # Accessors | Private
69             sub _client {
70 0 0   0   0 return if defined $_[1];
71 0         0 return $_client{refaddr + $_[0]};
72             }
73              
74             sub _peers {
75 133     133   281 my ($self, $info_hash) = @_;
76 133 100       13012 return q[] if !$tracking{refaddr $self}{$info_hash};
77 18         122 $tracking{refaddr $self}{$info_hash}{q[touch]} = time;
78 18         150 return $tracking{refaddr $self}{$info_hash}{q[peers]};
79             }
80              
81             # Accesors | Public
82             sub node_id {
83 3 50   3 1 14 return if defined $_[1];
84 3         64 return $node_id{refaddr + $_[0]};
85             }
86              
87             sub nodes {
88 2 50   2 1 13 return if defined $_[1];
89 1         33 return [map { {ip => $_->{q[ip]}, port => $_->{q[port]}} }
  2         25  
90 2         4 values %{$nodes{refaddr + $_[0]}}];
91             }
92              
93             # Setters | Private
94             sub _set_node_id {
95 0 0   0   0 return if not defined $_[1];
96 0         0 return $node_id{refaddr + $_[0]} = $_[1];
97             }
98              
99             # Methods | Public
100             sub add_node {
101 358     358 1 745 my ($self, $args) = @_;
102 358 50       1786 return if !$_client{refaddr $self}->_use_dht;
103 358 50       558 return if scalar keys %{$nodes{refaddr $self}} >= 300; # max 300 nodes
  358         1660  
104 358 50       1258 return if ref $args ne q[HASH];
105 358 50       919 return if !$args->{q[port]};
106 358 50       810 return if !$args->{q[ip]};
107 358         3606 my $ok = $_client{refaddr $self}->_event(q[ip_filter],
108             {Address => sprintf q[%s:%d], $args->{q[ip]}, $args->{q[port]}});
109 358 50 33     2867 if (defined $ok and $ok == 0) { return; }
  0         0  
110 358         2363 my $_resolved = inet_aton($args->{q[ip]});
111 358 50       814 return if !$_resolved;
112 358         1254 my $paddr = pack_sockaddr_in($args->{q[port]}, $_resolved);
113 358 100       2920 $nodes{refaddr $self}{$paddr} = {
114             birth => time,
115             fail => 0,
116             id => undef,
117             ip => $args->{q[ip]},
118             okay => 0,
119             paddr => $paddr,
120             ping => time - 61, # lies
121             port => $args->{q[port]},
122             prev_find => 0,
123             prev_get => 0,
124             prev_ann => 0,
125             seen => time - 60, # lies
126             token_i => undef,
127             token_o => undef
128             }
129             if !$nodes{refaddr $self}{$paddr};
130 358         1399 return $nodes{refaddr $self}{$paddr};
131             }
132              
133             sub _pulse {
134 37     37   119 my ($self) = @_;
135 37 100       368 return if !$_client{refaddr $self}->_use_dht;
136 31         124 for my $tid (
  8         73  
137             grep {
138 31         306 $outstanding_p{refaddr $self}{$_}{q[sent]} < time - 20
139             }
140             keys %{$outstanding_p{refaddr $self}}
141             )
142             { # old packets
143 0         0 $nodes{refaddr $self}
144             {$outstanding_p{refaddr $self}{$tid}{q[paddr]}}{q[fail]}++;
145 0         0 delete $outstanding_p{refaddr $self}{$tid};
146             }
147 31 50 33     84 for my $paddr (
  58         810  
148             grep {
149 31         247 (!defined $nodes{refaddr $self}{$_}
150             {q[seen]}) # XXX - mystery bug
151             or
152             (($nodes{refaddr $self}{$_}{q[seen]} < time - (60 * 15)))
153             or ($nodes{refaddr $self}{$_}{q[fail]} > 10)
154             } keys %{$nodes{refaddr $self}}
155             )
156             { # old/bad nodes
157 0         0 delete $nodes{refaddr $self}{$paddr};
158             }
159 31 50       92 for my $paddr (
  58         370  
160             grep {
161 31         177 (($nodes{refaddr $self}{$_}{q[ping]}
162             > $nodes{refaddr $self}{$_}{q[seen]}
163             )
164             and
165             ($nodes{refaddr $self}{$_}{q[ping]} < time - (60 * 8))
166             )
167             } keys %{$nodes{refaddr $self}}
168             )
169             { # old/bad nodes
170 0         0 $self->_ping_out($nodes{refaddr $self}{$paddr});
171             }
172 31         74 for my $info_hash (keys %{$tracking{refaddr $self}})
  31         190  
173             { # stale tracker data
174 11 50       89 delete $tracking{refaddr $self}{$info_hash}
175             if $tracking{refaddr $self}{$info_hash}{q[touch]}
176             < time - (60 * 30);
177             }
178              
179             # TODO: remove bad nodes, etc.
180             $_client{refaddr $self}->_schedule(
181 15     15   116 {Code => sub { shift->_pulse() },
182 31         530 Time => time + 45,
183             Object => $self
184             }
185             );
186             }
187              
188             sub _on_data {
189 198     198   2016 my ($self, $paddr, $data) = @_;
190 198 50       1671 return if !$_client{refaddr $self}->_use_dht;
191 198         1094 my ($packet, $leftover) = bdecode($data);
192 198         512 my $node;
193 198 50 33     2155 if ( (defined $packet)
      33        
194             and (ref $packet eq q[HASH])
195             and $packet->{q[y]})
196 198 100       953 { if ($packet->{q[y]} eq q[q]) { # query
    50          
    0          
197 99 50       694 if ($packet->{q[q]} eq q[ping]) {
    100          
    100          
    50          
198 0         0 $self->_ping_reply($paddr, $packet->{q[t]});
199 0 0       0 if (q[XXX - I don't want this in the final version. ...do I?]
200             and !$nodes{refaddr $self}{$paddr})
201 0         0 { my ($_port, $_ip) = unpack_sockaddr_in($paddr);
202 0         0 $_ip = inet_ntoa($_ip);
203 0         0 my $ok = $_client{refaddr $self}->_event(q[ip_filter],
204             {Address => sprintf q[%s:%d], $_ip, $_port});
205 0 0 0     0 if (defined $ok and $ok == 0) { return; }
  0         0  
206 0         0 my $new_node
207             = $self->add_node({ip => $_ip, port => $_port});
208 0 0       0 return if !$new_node;
209             }
210 0 0       0 if (defined $nodes{refaddr $self}{$paddr}) {
211 0   0     0 $nodes{refaddr $self}{$paddr}{q[id]}
212             ||= $packet->{q[a]}{q[id]};
213 0         0 $nodes{refaddr $self}{$paddr}{q[ping]} = time;
214 0         0 $nodes{refaddr $self}{$paddr}{q[seen]} = time;
215 0         0 $self->_find_node_out($nodes{refaddr $self}{$paddr},
216             $node_id{refaddr $self});
217             }
218 0         0 return;
219             }
220             elsif ($packet->{q[q]} eq q[find_node]) {
221 47         306 my ($_port, $_ip) = unpack_sockaddr_in($paddr);
222 47         4696 $_ip = inet_ntoa($_ip);
223 47         670 my $ok = $_client{refaddr $self}->_event(q[ip_filter],
224             {Address => sprintf q[%s:%d], $_ip, $_port});
225 47 50 33     248 if (defined $ok and $ok == 0) { return; }
  0         0  
226              
227             # if (!$nodes{refaddr $self}{$paddr}) {
228             # my ($port, $host) = unpack_sockaddr_in($paddr);
229             # $self->add_node({ip=>inet_ntoa($host), port=>$port}) || return;
230             #}
231             #$node = $nodes{refaddr $self}{$paddr};
232             #$nodes{refaddr $self}{$paddr}{q[id]}||=
233             # $packet->{q[a]}{q[id]};
234 217         1217 my $nodes = compact(
235 217         1031 map { sprintf q[%s:%d], $_->{q[ip]}, $_->{q[port]} }
236 47         302 grep { $_->{q[ip]} =~ m[^[\d\.]+$] }
237             $self->_locate_nodes_near_target(
238             $packet->{q[a]}{q[target]}
239             )
240             );
241 47         527 $self->_find_node_reply($paddr, $packet->{q[t]},
242             $packet->{q[a]}{q[id]}, $nodes)
243              
244             #if $nodes;
245             }
246             elsif ($packet->{q[q]} eq q[get_peers]) {
247 34 100       272 if (!$nodes{refaddr $self}{$paddr}) {
248 23         175 my ($port, $host) = unpack_sockaddr_in($paddr);
249 23         354 $self->add_node(
250             {ip => inet_ntoa($host), port => $port});
251 23 50       155 return if !$nodes{refaddr $self}{$paddr};
252             }
253 34         213 $node = $nodes{refaddr $self}{$paddr};
254 34   66     201 $node->{q[id]} ||= $packet->{q[a]}{q[id]};
255 34         63 $node->{q[seen]} = time;
256 34         83 $node->{q[okay]}++;
257 34         67 $node->{q[fail]} = 0;
258 34         138 $node->{q[token_o]} = q[NB_] . $self->_generate_token;
259 34 100       227 if ($tracking{refaddr $self}
260             {$packet->{q[a]}{q[info_hash]}})
261 8         59 { my @values = uncompact($tracking{refaddr $self}
262             {$packet->{q[a]}{q[info_hash]}}{q[peers]});
263 18         47 @values = map { compact($_) }
  64         79  
264 8         38 grep {$_} @values[0 .. 7]; # max 8
265 8         63 my $outgoing_packet
266             = _build_dht_reply_values(
267             $packet->{q[t]}, $packet->{q[a]}{q[id]},
268             \@values, $node->{q[token_o]});
269 8         91 send($_client{refaddr $self}->_udp(),
270             $outgoing_packet, 0, $paddr);
271 8         190 $tracking{refaddr $self}
272             {$packet->{q[a]}{q[info_hash]}}{q[touch]} = time;
273             }
274             else {
275 106         566 my $nodes = compact(
276             map {
277 106         929 sprintf q[%s:%d], $_->{q[ip]}, $_->{q[port]}
278 26         169 } grep { $_->{q[ip]} =~ m[^[\d\.]+$] }
279             $self->_locate_nodes_near_target(
280             $packet->{q[a]}{q[info_hash]}
281             )
282             );
283 26         221 send($_client{refaddr $self}->_udp(),
284             _build_dht_reply_get_peers(
285             $packet->{q[t]}, $packet->{q[a]}{q[id]},
286             $nodes, $node->{q[token_o]}
287             ),
288             0, $paddr
289             );
290             }
291             }
292             elsif ($packet->{q[q]} eq q[announce_peer]) {
293 18 50       147 if (!$nodes{refaddr $self}{$paddr}) {
294              
295             # XXX - reply with an error msg
296             #die q[...we don't know this node];
297 0         0 return;
298             }
299 18         83 $node = $nodes{refaddr $self}{$paddr};
300 18   33     80 $node->{q[id]} ||= $packet->{q[a]}{q[id]};
301 18         53 $node->{q[seen]} = time;
302 18         42 $node->{q[okay]}++;
303 18         36 $node->{q[fail]} = 0;
304 18 50 33     268 if ( (!$node->{q[token_o]})
    50 66        
305             || ($packet->{q[a]}{q[token]} ne $node->{q[token_o]}))
306 6         40 { # XXX - reply with token error msg
307             #die pp $node;
308 0         0 return;
309             }
310             elsif ((!$tracking{refaddr $self}
311             {$packet->{q[a]}{q[info_hash]}}
312             )
313             and (scalar(keys %{$tracking{refaddr $self}}) > 64)
314             )
315             { # enough torrents
316             # XXX - reply with error msg?
317             #
318 0         0 return;
319             }
320             else {
321 18         259 my @current_peers = uncompact($tracking{refaddr $self}
322             {$packet->{q[a]}{q[info_hash]}}{q[peers]});
323 18 50       75 if (scalar(@current_peers) > 128)
324             { # enough peers for this torrent
325             # XXX - reply with error msg?
326             #
327 0         0 return;
328             }
329 18         165 $tracking{refaddr $self}
330             {$packet->{q[a]}{q[info_hash]}}{q[peers]}
331             = compact(@current_peers,
332             sprintf(q[%s:%d],
333             $node->{q[ip]},
334             $packet->{q[a]}{q[port]})
335             );
336 18         110 $self->_ping_reply($paddr, $packet->{q[t]});
337 18         388 $tracking{refaddr $self}
338             {$packet->{q[a]}{q[info_hash]}}{q[touch]} = time;
339              
340             #warn q[Now on hand: ]
341             # . pp uncompact($tracking{refaddr $self}
342             # {$packet->{q[a]}{q[info_hash]}});
343             }
344             }
345             else {
346              
347             #die pp $packet;
348             }
349             }
350             elsif ($packet->{q[y]} eq q[r]) { # reply
351 99         1001 my $original_packet
352             = $outstanding_p{refaddr $self}{$packet->{q[t]}};
353 99 50       304 if (!$original_packet) {
354              
355             #warn q[...unexpected reply: ] . pp $packet;
356             #warn pp $outstanding_p{refaddr $self}{$packet->{q[t]}};
357             #
358 0         0 return;
359             }
360 99 50       651 return if !$nodes{refaddr $self}{$paddr};
361 99         346 $node = $nodes{refaddr $self}{$paddr};
362 99 50       459 if ($original_packet->{q[paddr]} ne $paddr) {
363 0         0 my ($fake_port, $fake_host) = unpack_sockaddr_in($paddr);
364 0         0 $fake_host = inet_ntoa($fake_host);
365 0         0 my ($real_port, $real_host)
366             = unpack_sockaddr_in($original_packet->{q[paddr]});
367 0         0 $real_host = inet_ntoa($real_host);
368              
369             #warn sprintf
370             # qq[...wrong remote node sent this reply %s to %s |\n %s:%d|%s\n vs\n %s:%d|%s],
371             # pp($packet),
372             # pp($original_packet),
373             # $fake_host, $fake_port, pp($paddr),
374             # $real_host, $real_port,
375             # pp($original_packet->{q[paddr]});
376 0         0 return;
377             }
378 99         593 delete $outstanding_p{refaddr $self}{$packet->{q[t]}};
379 99         297 $node->{q[seen]} = time;
380 99         264 $node->{q[okay]}++;
381 99         268 $node->{q[fail]} = 0;
382 99 100       394 $node->{q[id]}
383             = $node->{q[id]}
384             ? $node->{q[id]}
385             : $packet->{q[r]}{q[id]};
386 99 100       406 $node->{q[token_i]}
387             = $packet->{q[r]}{q[token]}
388             ? $packet->{q[r]}{q[token]}
389             : $node->{q[token_i]};
390              
391             #warn sprintf q[%s:%d sent us %s in reply to %s],
392             # $node->{q[ip]}, $node->{q[port]},
393             # pp(bdecode($data)), pp($original_packet);
394 99 100       329 if ($packet->{q[r]}{q[nodes]}) {
395 69         423 for my $_node (uncompact($packet->{q[r]}{q[nodes]})) {
396 323         1230 my ($ip, $port) = split q[:], $_node, 2;
397 323         1874 my $new_node
398             = $self->add_node({ip => $ip, port => $port});
399 323 50       1083 next if !$new_node;
400              
401             #$self->_ping_out($new_node);
402             #
403             #warn pp $original_packet;
404 323         1059 my $_data = bdecode($original_packet->{q[packet]});
405              
406             #warn pp $_data;
407 323 100       1343 my $info_hash
408             = $_data->{q[a]}{q[target]}
409             ? $_data->{q[a]}{q[target]}
410             : $_data->{q[a]}{q[info_hash]};
411              
412             # warn pp $info_hash;
413 323         1202 $self->_get_peers_out($new_node, $info_hash);
414             }
415             }
416 99 100       1677 if ($packet->{q[r]}{q[values]}) {
417              
418             #warn pp $original_packet;
419 8         62 my $torrent =
420             $_client{refaddr $self}->_locate_torrent(
421             unpack q[H40],
422             bdecode($original_packet->{q[packet]})
423             ->{q[a]}{q[info_hash]}
424             );
425 8 50       54 if ($torrent) {
426 18         72 $tracking{refaddr $self}{$torrent->infohash} = {
427             peers =>
428             compact(
429             uncompact(
430             $tracking{refaddr $self}
431             {$torrent->infohash}{q[peers]}
432             ),
433 8         32 (map { uncompact($_) }
434 8         208 @{$packet->{q[r]}{q[values]}}
435             )
436             ),
437             touch => time
438             };
439             }
440 8         77 $self->_find_node_out($node, $node_id{refaddr $self});
441             }
442             }
443             elsif ($packet->{q[y]} eq q[e]) { # error
444             #if ( $packet->{q[e]}->[0] == ) { }
445             # XXX - Should DHT have events?
446             #use Data::Dump qw[pp];
447             #warn sprintf qq[Error: %s\from %s\nnoriginal packet: %s],
448             # pp($packet), pp($nodes{refaddr $self}{$paddr}),
449             # pp(scalar bdecode(
450             # $outstanding_p{refaddr $self}{$packet->{q[t]}}
451             # {q[packet]}
452             # )
453             # );
454 0         0 delete $outstanding_p{refaddr $self}{$packet->{q[t]}};
455             }
456             else { #warn q[...what just happend? ] . pp bdecode($data)
457             }
458             }
459             else {
460              
461             # AZ or garbage. ...as if the two were different.
462             #use Data::Dump qw[pp];
463             #warn q[Bad packet: ] . pp($data);
464             }
465             }
466              
467             sub _ping_out {
468 8     8   20 my ($self, $node) = @_;
469 8 50       67 return if !$_client{refaddr $self}->_use_dht;
470 8 50       56 return if $node->{q[seen]} > time - 120;
471 0         0 my $tid = $self->_generate_token;
472 0         0 my $packet = _build_dht_query_ping($tid, $node_id{refaddr $self});
473 0         0 $outstanding_p{refaddr $self}{$tid} = {attempts => 1,
474             sent => time,
475             packet => $packet,
476             paddr => $node->{q[paddr]}
477             };
478             return
479 0         0 send($_client{refaddr $self}->_udp(),
480             $packet, 0, $node->{q[paddr]});
481             }
482              
483             sub _ping_reply {
484 18     18   47 my ($self, $paddr, $tid) = @_;
485 18 50       120 return if !$_client{refaddr $self}->_use_dht;
486             return
487 18         126 send($_client{refaddr $self}->_udp(),
488             _build_dht_reply_ping($tid, $node_id{refaddr $self}),
489             0, $paddr);
490             }
491              
492             sub _announce_peer_out {
493 26     26   48 my ($self, $node, $infohash) = @_;
494 26 50       132 return if !$_client{refaddr $self}->_use_dht;
495 26 50       85 return if $node->{q[prev_ann]} > time - (60 * 15);
496 26         56 my $tid = $self->_generate_token;
497 26 100       91 return if !$node->{q[token_i]};
498 18         118 my $packet =
499             _build_dht_query_announce($tid,
500             $node_id{refaddr $self},
501             $infohash,
502             $node->{q[token_i]},
503             $_client{refaddr $self}->_tcp_port
504             );
505 18         206 $outstanding_p{refaddr $self}{$tid} = {attempts => 1,
506             sent => time,
507             packet => $packet,
508             paddr => $node->{q[paddr]}
509             };
510 18         36 $node->{q[prev_find]} = 0;
511 18         30 $node->{q[prev_ann]} = time;
512             return
513 18         88 send($_client{refaddr $self}->_udp(),
514             $packet, 0, $node->{q[paddr]});
515             }
516              
517             sub _find_node_out {
518 68     68   151 my ($self, $node, $target) = @_;
519 68 50       344 return if !$_client{refaddr $self}->_use_dht;
520 68 100       412 return if $node->{q[prev_find]} > time - (60 * 5);
521 47         157 my $tid = $self->_generate_token;
522 47         317 my $packet = _build_dht_query_find_node($tid, $node_id{refaddr $self},
523             $target);
524 47         583 $outstanding_p{refaddr $self}{$tid} = {attempts => 1,
525             sent => time,
526             packet => $packet,
527             paddr => $node->{q[paddr]}
528             };
529 47         99 $node->{q[prev_find]} = time;
530             return
531 47         306 send($_client{refaddr $self}->_udp(),
532             $packet, 0, $node->{q[paddr]});
533             }
534              
535             # Send find_node result to peer
536             sub _find_node_reply {
537 47     47   159 my ($self, $paddr, $tid, $id, $nodes) = @_;
538 47 50       389 return if !$_client{refaddr $self}->_use_dht;
539             return
540 47         350 send($_client{refaddr $self}->_udp(),
541             _build_dht_reply_find_node($tid, $id, $nodes),
542             0, $paddr);
543             }
544              
545             sub _get_peers_out {
546 349     349   645 my ($self, $node, $info_hash) = @_;
547 349 50       2048 return if !$_client{refaddr $self}->_use_dht;
548 349 100       2822 return if $node->{q[prev_get]} > time - (60 * 10);
549 34         112 my $tid = $self->_generate_token;
550 34         598 my $packet = _build_dht_query_get_peers($tid, $node_id{refaddr $self},
551             $info_hash);
552 34         465 $outstanding_p{refaddr $self}{$tid} = {attempts => 1,
553             sent => time,
554             packet => $packet,
555             paddr => $node->{q[paddr]}
556             };
557 34         76 $node->{q[prev_get]} = time;
558             return
559 34         227 send($_client{refaddr $self}->_udp(),
560             $packet, 0, $node->{q[paddr]});
561             }
562              
563             # Methods | Private | Fake routing table
564             sub _locate_nodes_near_target {
565 110     110   450 my ($self, $target) = @_;
566 110 50       575 return if !$_client{refaddr $self}->_use_dht;
567 110         1034 my $_target = hex unpack q[H4], $target;
568 110         220 my @nodes;
569 110         231 for my $node (
  532         2634  
570             sort {
571 393         1502 hex(unpack q[H4], $a->{q[id]}) ^ $_target cmp
572             hex(unpack q[H4], $b->{q[id]}) ^ $_target
573             }
574 110         755 grep { $_->{q[id]} } values %{$nodes{refaddr $self}}
575             )
576 375         542 { push @nodes, $node;
577 375 50       1245 last if scalar @nodes == 8;
578             }
579 110         569 return @nodes;
580             }
581              
582             # Methods | Private | Peer search
583             sub _scrape {
584 25     25   63 my ($self, $torrent) = @_;
585 25 50       162 return if !$_client{refaddr $self}->_use_dht;
586 25 50 33     453 if ( (!$torrent)
      33        
587             || (!blessed $torrent)
588             || (!$torrent->isa(q[Net::BitTorrent::Torrent])))
589 0         0 { carp
590             q[Net::BitTorrent::DHT::Node->_scrape() requires a Net::BitTorrent::Torrent];
591 0         0 return;
592             }
593 25 50       211 if ($torrent->private) {
594 0         0 carp q[Private torrents disallow DHT];
595 0         0 return;
596             }
597 25         258 my $info_hash = pack q[H40], $torrent->infohash;
598 25         132 for my $node ($self->_locate_nodes_near_target($info_hash)) {
599 26         91 $self->_find_node_out($node, $info_hash);
600 26         93 $self->_get_peers_out($node, $info_hash);
601             }
602 25         100 return 1;
603             }
604              
605             sub _announce {
606 12     12   33 my ($self, $torrent) = @_;
607 12 50       83 return if !$_client{refaddr $self}->_use_dht;
608 12 50 33     285 if ( (!$torrent)
      33        
609             || (!blessed $torrent)
610             || (!$torrent->isa(q[Net::BitTorrent::Torrent])))
611 0         0 { carp
612             q[Net::BitTorrent::DHT::Node->_scrape() requires a Net::BitTorrent::Torrent];
613 0         0 return;
614             }
615 12 50       188 if ($torrent->private) {
616 0         0 carp q[Private torrents disallow DHT];
617 0         0 return;
618             }
619 12         61 my $info_hash = pack q[H40], $torrent->infohash;
620 12         62 for my $node ($self->_locate_nodes_near_target($info_hash)) {
621              
622             #if !$node->{q[token_i]};
623 26         160 $self->_find_node_out($node, $info_hash);
624 26         89 $self->_announce_peer_out($node, $info_hash);
625             }
626 12         226 return 1;
627             }
628              
629             sub _generate_token {
630 141     141   257 my ($self) = @_;
631 141 50       929 return if !$_client{refaddr $self}->_use_dht;
632 141         740 return ++$tid{refaddr $self};
633             }
634              
635             sub as_string {
636 0     0 1 0 my ($self, $advanced) = @_;
637 0 0       0 my $dump = !$advanced ? $node_id{refaddr $self} : sprintf <<'END',
638             Net::BitTorrent::DHT
639              
640             Node ID: %s
641             END
642             $node_id{refaddr $self};
643 0 0       0 return defined wantarray ? $dump : print STDERR qq[$dump\n];
644             }
645              
646             sub CLONE {
647 0     0   0 for my $_oID (keys %REGISTRY) {
648 0         0 my $_obj = $REGISTRY{$_oID};
649 0         0 my $_nID = refaddr $_obj;
650 0         0 for (@CONTENTS) {
651 0         0 $_->{$_nID} = $_->{$_oID};
652 0         0 delete $_->{$_oID};
653             }
654 0         0 weaken $_client{$_nID};
655 0         0 delete $outstanding_p{$_nID};
656 0         0 weaken($REGISTRY{$_nID} = $_obj);
657 0         0 delete $REGISTRY{$_oID};
658             }
659 0         0 return 1;
660             }
661             DESTROY {
662 6     6   10 my ($self) = @_;
663 6         11 for (@CONTENTS) { delete $_->{refaddr $self}; }
  36         88  
664 6         34 return delete $REGISTRY{refaddr $self};
665             }
666             }
667             1;
668              
669             =pod
670              
671             =head1 NAME
672              
673             Net::BitTorrent::DHT - Kademlia based Distributed Hash Table
674              
675             =head1 Constructor
676              
677             =over
678              
679             =item C
680              
681             Creates a C object. This constructor should not be
682             used directly.
683              
684             =back
685              
686             =head1 Methods
687              
688             =over
689              
690             =item C
691              
692             Adds a single node to the routing table. Expects a hashref with the
693             following keys:
694              
695             =over
696              
697             =item C
698              
699             The hostname/IP address of the remote node.
700              
701             =item C
702              
703             The port the remote node has open for DHT.
704              
705             =back
706              
707             This is an advanced method and should not (normally) should not be used.
708              
709             =item C
710              
711             Get the Node ID used to identify this L in the
712             DHT swarm.
713              
714             =item C
715              
716             Returns a list of nodes from the routing table in a format suitable for
717             handing off to L one by one.
718              
719             =item C
720              
721             Returns a 'ready to print' dump of the object's data structure. If
722             called in void context, the structure is printed to C.
723             C is a boolean value.
724              
725             =back
726              
727             =head1 Bugs
728              
729             In this alpha, there are a number of places where I break away from the
730             specification. These will all be fixed in a future version.
731              
732             =over
733              
734             =item *
735              
736             The routing table is flat.
737              
738             =back
739              
740             =head1 Notes
741              
742             While bandwidth to/from DHT nodes will probably never be limited like
743             other traffic, in the future, it will be taken into account and "drained"
744             from the rate limiter. If there's a burst of DHT traffic, the peer
745             traffic may be limited to avoid the total to exceed the global limit.
746              
747             =head1 See Also
748              
749             I have used a number of references for implementation second opinions:
750              
751             =over
752              
753             =item The Kademlia Paper
754              
755             http://pdos.csail.mit.edu/~petar/papers/maymounkov-kademlia-lncs.pdf
756              
757             =item BEP 5: DHT
758              
759             http://www.bittorrent.org/beps/bep_0005.html
760              
761             =item Notes about the BitTorrent DHT Protocol from GetRight
762              
763             http://getright.com/torrentdev.html
764              
765             =back
766              
767              
768              
769             =head1 Author
770              
771             Sanko Robinson - http://sankorobinson.com/
772              
773             CPAN ID: SANKO
774              
775             =head1 License and Legal
776              
777             Copyright (C) 2008-2009 by Sanko Robinson Esanko@cpan.orgE
778              
779             This program is free software; you can redistribute it and/or modify
780             it under the terms of The Artistic License 2.0. See the F
781             file included with this distribution or
782             http://www.perlfoundation.org/artistic_license_2_0. For
783             clarification, see http://www.perlfoundation.org/artistic_2_0_notes.
784              
785             When separated from the distribution, all POD documentation is covered
786             by the Creative Commons Attribution-Share Alike 3.0 License. See
787             http://creativecommons.org/licenses/by-sa/3.0/us/legalcode. For
788             clarification, see http://creativecommons.org/licenses/by-sa/3.0/us/.
789              
790             Neither this module nor the L is affiliated with
791             BitTorrent, Inc.
792              
793             =for svn $Id: DHT.pm d3c97de 2009-09-12 04:31:46Z sanko@cpan.org $
794              
795             =cut