line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Net::BitTorrent::DHT; |
2
|
2
|
|
|
2
|
|
13706
|
use Moose; |
|
2
|
|
|
|
|
958586
|
|
|
2
|
|
|
|
|
20
|
|
3
|
2
|
|
|
2
|
|
14933
|
use Moose::Util; |
|
2
|
|
|
|
|
7
|
|
|
2
|
|
|
|
|
22
|
|
4
|
2
|
|
|
2
|
|
2187
|
use AnyEvent; |
|
2
|
|
|
|
|
6345
|
|
|
2
|
|
|
|
|
75
|
|
5
|
2
|
|
|
2
|
|
2332
|
use AnyEvent::Socket qw[]; |
|
2
|
|
|
|
|
62199
|
|
|
2
|
|
|
|
|
73
|
|
6
|
2
|
|
|
2
|
|
2157
|
use AnyEvent::HTTP; |
|
2
|
|
|
|
|
37813
|
|
|
2
|
|
|
|
|
253
|
|
7
|
2
|
|
|
|
|
1326
|
use Socket qw[/SOCK_/ /F_INET/ inet_aton /sockaddr_in/ inet_ntoa |
8
|
|
|
|
|
|
|
SOL_SOCKET SO_REUSEADDR |
9
|
2
|
|
|
2
|
|
25
|
]; |
|
2
|
|
|
|
|
3
|
|
10
|
2
|
|
|
2
|
|
2102
|
use Net::BitTorrent::Protocol qw[:bencode :compact]; |
|
2
|
|
|
|
|
39490
|
|
|
2
|
|
|
|
|
455
|
|
11
|
2
|
|
|
2
|
|
1138
|
use Bit::Vector; |
|
2
|
|
|
|
|
5402
|
|
|
2
|
|
|
|
|
131
|
|
12
|
2
|
|
|
2
|
|
1355
|
use Net::BitTorrent::DHT::Node; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
use Net::BitTorrent::DHT::RoutingTable; |
14
|
|
|
|
|
|
|
use 5.10.0; |
15
|
|
|
|
|
|
|
our $VERSION = 'v1.0.2'; |
16
|
|
|
|
|
|
|
eval $VERSION; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# Stub |
19
|
|
|
|
|
|
|
sub BUILD {1} |
20
|
|
|
|
|
|
|
after 'BUILD' => sub { |
21
|
|
|
|
|
|
|
my ($s, $a) = @_; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# Hey! Open up! |
24
|
|
|
|
|
|
|
$s->udp6; |
25
|
|
|
|
|
|
|
$s->udp4; |
26
|
|
|
|
|
|
|
}; |
27
|
|
|
|
|
|
|
# |
28
|
|
|
|
|
|
|
for my $type (qw[requests replies]) { |
29
|
|
|
|
|
|
|
for my $var (qw[count length]) { |
30
|
|
|
|
|
|
|
my $attr = join '_', '', 'recv_invalid', $var; |
31
|
|
|
|
|
|
|
has $attr => (isa => 'Int', |
32
|
|
|
|
|
|
|
is => 'ro', |
33
|
|
|
|
|
|
|
init_arg => undef, |
34
|
|
|
|
|
|
|
traits => ['Counter'], |
35
|
|
|
|
|
|
|
handles => {'_inc' . $attr => 'inc'}, |
36
|
|
|
|
|
|
|
default => 0 |
37
|
|
|
|
|
|
|
); |
38
|
|
|
|
|
|
|
for my $dir (qw[recv send]) { |
39
|
|
|
|
|
|
|
my $attr = join '_', '', $dir, $type, $var; |
40
|
|
|
|
|
|
|
has $attr => (isa => 'Int', |
41
|
|
|
|
|
|
|
is => 'ro', |
42
|
|
|
|
|
|
|
init_arg => undef, |
43
|
|
|
|
|
|
|
traits => ['Counter'], |
44
|
|
|
|
|
|
|
handles => {'_inc' . $attr => 'inc'}, |
45
|
|
|
|
|
|
|
default => 0 |
46
|
|
|
|
|
|
|
); |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
has nodeid => (isa => 'Bit::Vector', |
51
|
|
|
|
|
|
|
is => 'ro', |
52
|
|
|
|
|
|
|
builder => '_build_nodeid' |
53
|
|
|
|
|
|
|
); |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub _build_nodeid { |
56
|
|
|
|
|
|
|
my $s = shift; |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# TODO: Base on DHT security extention: http://libtorrent.org/dht_sec.html |
59
|
|
|
|
|
|
|
AnyEvent::HTTP::http_get( |
60
|
|
|
|
|
|
|
'http://icanhazip.com', |
61
|
|
|
|
|
|
|
sub { |
62
|
|
|
|
|
|
|
chomp $_[0]; |
63
|
|
|
|
|
|
|
$s->nodeid->from_Hex( |
64
|
|
|
|
|
|
|
unpack 'H*', join '', |
65
|
|
|
|
|
|
|
AnyEvent::Socket::parse_address($_[0]), # Ext ipv4 address |
66
|
|
|
|
|
|
|
(map { chr rand 16 } 1 .. 16) |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
); |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# alt services: |
72
|
|
|
|
|
|
|
# myip.dnsomatic.com |
73
|
|
|
|
|
|
|
# ipecho.net/plain |
74
|
|
|
|
|
|
|
# ipv4.icanhazip.com |
75
|
|
|
|
|
|
|
# bot.whatismyipaddress.com |
76
|
|
|
|
|
|
|
# www.myip.ru |
77
|
|
|
|
|
|
|
return Bit::Vector->new(160); |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
# |
80
|
|
|
|
|
|
|
sub send { |
81
|
|
|
|
|
|
|
my ($s, $node, $packet, $reply) = @_; |
82
|
|
|
|
|
|
|
if (defined $s->ip_filter) { |
83
|
|
|
|
|
|
|
my $rule = $s->ip_filter->is_banned($node->host); |
84
|
|
|
|
|
|
|
if (defined $rule) { |
85
|
|
|
|
|
|
|
$s->trigger_ip_filter( |
86
|
|
|
|
|
|
|
{protocol => ($node->ipv6 ? 'udp6' : 'udp4'), |
87
|
|
|
|
|
|
|
severity => 'debug', |
88
|
|
|
|
|
|
|
event => 'ip_filter', |
89
|
|
|
|
|
|
|
address => [$node->host, $node->port], |
90
|
|
|
|
|
|
|
rule => $rule, |
91
|
|
|
|
|
|
|
message => 'Outgoing data was blocked by ipfilter' |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
); |
94
|
|
|
|
|
|
|
return $s->routing_table->del_node($node); |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
my $sock |
98
|
|
|
|
|
|
|
= $node->ipv6 && $s->has_udp6_sock ? $s->udp6_sock |
99
|
|
|
|
|
|
|
: $s->has_udp4_sock ? $s->udp4_sock |
100
|
|
|
|
|
|
|
: (); |
101
|
|
|
|
|
|
|
my $sent = $sock ? send $sock, $packet, 0, $node->sockaddr : return; |
102
|
|
|
|
|
|
|
if ($reply) { |
103
|
|
|
|
|
|
|
$s->_inc_send_replies_count; |
104
|
|
|
|
|
|
|
$s->_inc_send_replies_length($sent); |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
else { |
107
|
|
|
|
|
|
|
$s->_inc_send_requests_count; |
108
|
|
|
|
|
|
|
$s->_inc_send_requests_length($sent); |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
return $sent; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
# |
113
|
|
|
|
|
|
|
has ipv4_routing_table => (isa => 'Net::BitTorrent::DHT::RoutingTable', |
114
|
|
|
|
|
|
|
is => 'ro', |
115
|
|
|
|
|
|
|
lazy_build => 1, |
116
|
|
|
|
|
|
|
handles => { |
117
|
|
|
|
|
|
|
ipv4_add_node => 'add_node', |
118
|
|
|
|
|
|
|
ipv4_buckets => 'buckets' |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
); |
121
|
|
|
|
|
|
|
has ipv6_routing_table => (isa => 'Net::BitTorrent::DHT::RoutingTable', |
122
|
|
|
|
|
|
|
is => 'ro', |
123
|
|
|
|
|
|
|
lazy_build => 1, |
124
|
|
|
|
|
|
|
handles => { |
125
|
|
|
|
|
|
|
ipv6_add_node => 'add_node', |
126
|
|
|
|
|
|
|
ipv6_buckets => 'buckets' |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
); |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
sub _build_ipv4_routing_table { |
131
|
|
|
|
|
|
|
Net::BitTorrent::DHT::RoutingTable->new(dht => shift); |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
sub _build_ipv6_routing_table { |
135
|
|
|
|
|
|
|
Net::BitTorrent::DHT::RoutingTable->new(dht => shift); |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub add_node { |
139
|
|
|
|
|
|
|
my ($s, $n) = @_; |
140
|
|
|
|
|
|
|
AnyEvent::Socket::resolve_sockaddr( |
141
|
|
|
|
|
|
|
$n->[0], |
142
|
|
|
|
|
|
|
$n->[1], |
143
|
|
|
|
|
|
|
0, undef, undef, |
144
|
|
|
|
|
|
|
sub { |
145
|
|
|
|
|
|
|
my $sockaddr = $_[0]->[3]; |
146
|
|
|
|
|
|
|
return if !$sockaddr; |
147
|
|
|
|
|
|
|
$n |
148
|
|
|
|
|
|
|
= blessed $n ? $n |
149
|
|
|
|
|
|
|
: Net::BitTorrent::DHT::Node->new( |
150
|
|
|
|
|
|
|
host => $n->[0], |
151
|
|
|
|
|
|
|
port => $n->[1], |
152
|
|
|
|
|
|
|
sockaddr => $sockaddr, |
153
|
|
|
|
|
|
|
routing_table => ( |
154
|
|
|
|
|
|
|
length $sockaddr == 28 ? $s->ipv6_routing_table |
155
|
|
|
|
|
|
|
: $s->ipv4_routing_table |
156
|
|
|
|
|
|
|
) |
157
|
|
|
|
|
|
|
); |
158
|
|
|
|
|
|
|
($n->ipv6 ? |
159
|
|
|
|
|
|
|
$s->ipv6_routing_table->add_node($n) |
160
|
|
|
|
|
|
|
: $s->ipv4_routing_table->add_node($n) |
161
|
|
|
|
|
|
|
)->find_node($s->nodeid) |
162
|
|
|
|
|
|
|
if !$s->nodeid->is_empty; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
); |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
after 'BUILD' => sub { |
167
|
|
|
|
|
|
|
my ($self, $args) = @_; |
168
|
|
|
|
|
|
|
return if !defined $args->{'boot_nodes'}; |
169
|
|
|
|
|
|
|
$self->add_node($_) for @{$args->{'boot_nodes'}}; |
170
|
|
|
|
|
|
|
}; |
171
|
|
|
|
|
|
|
# |
172
|
|
|
|
|
|
|
for my $type (qw[get_peers announce_peer find_node]) { |
173
|
|
|
|
|
|
|
has "_${type}_quests" => (isa => 'ArrayRef[Ref]', |
174
|
|
|
|
|
|
|
is => 'ro', |
175
|
|
|
|
|
|
|
init_arg => undef, |
176
|
|
|
|
|
|
|
traits => ['Array'], |
177
|
|
|
|
|
|
|
handles => { |
178
|
|
|
|
|
|
|
"add_${type}_quest" => 'push', |
179
|
|
|
|
|
|
|
"${type}_quests" => 'elements', |
180
|
|
|
|
|
|
|
"get_${type}_quest" => 'get', |
181
|
|
|
|
|
|
|
"grep_${type}_quests" => 'grep', |
182
|
|
|
|
|
|
|
"map_${type}_quests" => 'map' |
183
|
|
|
|
|
|
|
}, |
184
|
|
|
|
|
|
|
default => sub { [] } |
185
|
|
|
|
|
|
|
); |
186
|
|
|
|
|
|
|
after "add_${type}_quest" => sub { |
187
|
|
|
|
|
|
|
Scalar::Util::weaken $_[0]->{"_${type}_quests"}->[-1]; |
188
|
|
|
|
|
|
|
}; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
# |
191
|
|
|
|
|
|
|
sub get_peers { |
192
|
|
|
|
|
|
|
my ($self, $infohash, $code) = @_; |
193
|
|
|
|
|
|
|
Scalar::Util::weaken $self; |
194
|
|
|
|
|
|
|
my $quest = [ |
195
|
|
|
|
|
|
|
$infohash, |
196
|
|
|
|
|
|
|
$code, |
197
|
|
|
|
|
|
|
[], |
198
|
|
|
|
|
|
|
AE::timer( |
199
|
|
|
|
|
|
|
0, |
200
|
|
|
|
|
|
|
0.25 * 60, |
201
|
|
|
|
|
|
|
sub { |
202
|
|
|
|
|
|
|
return if !$self; |
203
|
|
|
|
|
|
|
for my $rt ($self->ipv6_routing_table, |
204
|
|
|
|
|
|
|
$self->ipv4_routing_table) |
205
|
|
|
|
|
|
|
{ for my $node (@{$rt->nearest_bucket($infohash)->nodes}) { |
206
|
|
|
|
|
|
|
$node->get_peers($infohash); |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
) |
211
|
|
|
|
|
|
|
]; |
212
|
|
|
|
|
|
|
$self->add_get_peers_quest($quest); |
213
|
|
|
|
|
|
|
return $quest; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
sub announce_peer { |
217
|
|
|
|
|
|
|
my ($self, $infohash, $port, $code) = @_; |
218
|
|
|
|
|
|
|
Scalar::Util::weaken $self; |
219
|
|
|
|
|
|
|
my $quest = [ |
220
|
|
|
|
|
|
|
$infohash, |
221
|
|
|
|
|
|
|
$code, $port, |
222
|
|
|
|
|
|
|
[], |
223
|
|
|
|
|
|
|
AE::timer( |
224
|
|
|
|
|
|
|
10, |
225
|
|
|
|
|
|
|
0.25 * 60, |
226
|
|
|
|
|
|
|
sub { |
227
|
|
|
|
|
|
|
return if !$self; |
228
|
|
|
|
|
|
|
for my $rt ($self->ipv6_routing_table, |
229
|
|
|
|
|
|
|
$self->ipv4_routing_table) |
230
|
|
|
|
|
|
|
{ for my $node (@{$rt->nearest_bucket($infohash)->nodes}) { |
231
|
|
|
|
|
|
|
$node->announce_peer($infohash, $port); |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
) |
236
|
|
|
|
|
|
|
]; |
237
|
|
|
|
|
|
|
$self->add_announce_peer_quest($quest); |
238
|
|
|
|
|
|
|
return $quest; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub find_node { |
242
|
|
|
|
|
|
|
my ($self, $target, $code) = @_; |
243
|
|
|
|
|
|
|
Scalar::Util::weaken $self; |
244
|
|
|
|
|
|
|
my $quest = [ |
245
|
|
|
|
|
|
|
$target, $code, |
246
|
|
|
|
|
|
|
[], |
247
|
|
|
|
|
|
|
AE::timer( |
248
|
|
|
|
|
|
|
0, |
249
|
|
|
|
|
|
|
0.25 * 60, |
250
|
|
|
|
|
|
|
sub { |
251
|
|
|
|
|
|
|
return if !$self; |
252
|
|
|
|
|
|
|
for my $rt ($self->ipv6_routing_table, |
253
|
|
|
|
|
|
|
$self->ipv4_routing_table) |
254
|
|
|
|
|
|
|
{ for my $node (@{$rt->nearest_bucket($target)->nodes}) { |
255
|
|
|
|
|
|
|
$node->find_node($target); |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
) |
260
|
|
|
|
|
|
|
]; |
261
|
|
|
|
|
|
|
$self->add_find_node_quest($quest); |
262
|
|
|
|
|
|
|
return $quest; |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
# |
265
|
|
|
|
|
|
|
sub _on_udp6_in { |
266
|
|
|
|
|
|
|
my ($self, $sock, $sockaddr, $host, $port, $data, $flags) = @_; |
267
|
|
|
|
|
|
|
my $packet = bdecode $data; |
268
|
|
|
|
|
|
|
if ( !$packet |
269
|
|
|
|
|
|
|
|| !ref $packet |
270
|
|
|
|
|
|
|
|| ref $packet ne 'HASH' |
271
|
|
|
|
|
|
|
|| !keys %$packet) |
272
|
|
|
|
|
|
|
{ $self->_inc_recv_invalid_count; |
273
|
|
|
|
|
|
|
$self->_inc_recv_invalid_length(length $data); |
274
|
|
|
|
|
|
|
return; |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
my $node = $self->ipv6_routing_table->find_node_by_sockaddr($sockaddr); |
277
|
|
|
|
|
|
|
if (!defined $node) { |
278
|
|
|
|
|
|
|
$node = |
279
|
|
|
|
|
|
|
Net::BitTorrent::DHT::Node->new( |
280
|
|
|
|
|
|
|
host => $host, |
281
|
|
|
|
|
|
|
port => $port, |
282
|
|
|
|
|
|
|
routing_table => $self->ipv6_routing_table, |
283
|
|
|
|
|
|
|
sockaddr => $sockaddr |
284
|
|
|
|
|
|
|
); |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
sub _on_udp4_in { |
289
|
|
|
|
|
|
|
my ($self, $sock, $sockaddr, $host, $port, $data, $flags) = @_; |
290
|
|
|
|
|
|
|
my $packet = bdecode $data; |
291
|
|
|
|
|
|
|
if ( !$packet |
292
|
|
|
|
|
|
|
|| !ref $packet |
293
|
|
|
|
|
|
|
|| ref $packet ne 'HASH' |
294
|
|
|
|
|
|
|
|| !keys %$packet |
295
|
|
|
|
|
|
|
|| !defined $packet->{'y'}) |
296
|
|
|
|
|
|
|
{ $self->_inc_recv_invalid_count; |
297
|
|
|
|
|
|
|
$self->_inc_recv_invalid_length(length $data); |
298
|
|
|
|
|
|
|
return; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
my $node = $self->ipv4_routing_table->find_node_by_sockaddr($sockaddr); |
301
|
|
|
|
|
|
|
if (!defined $node) { |
302
|
|
|
|
|
|
|
$node = |
303
|
|
|
|
|
|
|
Net::BitTorrent::DHT::Node->new( |
304
|
|
|
|
|
|
|
host => $host, |
305
|
|
|
|
|
|
|
port => $port, |
306
|
|
|
|
|
|
|
routing_table => $self->ipv4_routing_table, |
307
|
|
|
|
|
|
|
sockaddr => $sockaddr |
308
|
|
|
|
|
|
|
); |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
# Basic identity checks |
312
|
|
|
|
|
|
|
# TODO - if v is set, make sure it matches |
313
|
|
|
|
|
|
|
# - make note of changes in nodeid/sockaddr combinations |
314
|
|
|
|
|
|
|
return $node->routing_table->del_node($node) |
315
|
|
|
|
|
|
|
if $node->has_nodeid # Wait, this is me! |
316
|
|
|
|
|
|
|
&& ($node->nodeid->Lexicompare($self->nodeid) == 0); |
317
|
|
|
|
|
|
|
$node->touch; |
318
|
|
|
|
|
|
|
# |
319
|
|
|
|
|
|
|
if ($packet->{'y'} eq 'r') { |
320
|
|
|
|
|
|
|
if (defined $packet->{'r'}) { |
321
|
|
|
|
|
|
|
if ($node->is_expecting($packet->{'t'})) { |
322
|
|
|
|
|
|
|
$self->_inc_recv_replies_count; |
323
|
|
|
|
|
|
|
$self->_inc_recv_replies_length(length $data); |
324
|
|
|
|
|
|
|
$node->_v($packet->{'v'}) |
325
|
|
|
|
|
|
|
if !$node->has_v && defined $packet->{'v'}; |
326
|
|
|
|
|
|
|
my $req = $node->del_request($packet->{'t'}); # For future ref |
327
|
|
|
|
|
|
|
$req->{'cb'}->($packet, $host, $port) |
328
|
|
|
|
|
|
|
if defined $req->{'cb'}; |
329
|
|
|
|
|
|
|
my $type = $req->{'type'}; |
330
|
|
|
|
|
|
|
$node->_set_nodeid(Bit::Vector->new_Hex( |
331
|
|
|
|
|
|
|
160, unpack 'H*', $packet->{'r'}{'id'} |
332
|
|
|
|
|
|
|
) |
333
|
|
|
|
|
|
|
) if !$node->has_nodeid; # Adds node to router table |
334
|
|
|
|
|
|
|
if ($type eq 'ping') { |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
elsif ($type eq 'find_node') { |
337
|
|
|
|
|
|
|
my ($quest) = $self->grep_find_node_quests( |
338
|
|
|
|
|
|
|
sub { |
339
|
|
|
|
|
|
|
defined $_ |
340
|
|
|
|
|
|
|
&& $req->{'target'}->equal($_->[0]); |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
); |
343
|
|
|
|
|
|
|
return if !defined $quest; |
344
|
|
|
|
|
|
|
my @nodes |
345
|
|
|
|
|
|
|
= map { uncompact_ipv4($_) } |
346
|
|
|
|
|
|
|
ref $packet->{'r'}{'nodes'} |
347
|
|
|
|
|
|
|
? |
348
|
|
|
|
|
|
|
@{$packet->{'r'}{'nodes'}} |
349
|
|
|
|
|
|
|
: $packet->{'r'}{'nodes'}; |
350
|
|
|
|
|
|
|
{ |
351
|
|
|
|
|
|
|
my %seen = (); |
352
|
|
|
|
|
|
|
@{$quest->[2]} |
353
|
|
|
|
|
|
|
= grep { !$seen{$_->[0]}{$_->[1]}++ } |
354
|
|
|
|
|
|
|
@{$quest->[2]}, @nodes; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
$self->ipv4_add_node($_) for @nodes; |
357
|
|
|
|
|
|
|
$quest->[1]->($quest->[0], $node, \@nodes); |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
elsif ($type eq 'get_peers') { |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# TODO - store token by id |
362
|
|
|
|
|
|
|
if (!( defined $packet->{'r'}{'nodes'} |
363
|
|
|
|
|
|
|
|| defined $packet->{'r'}{'values'} |
364
|
|
|
|
|
|
|
) |
365
|
|
|
|
|
|
|
) |
366
|
|
|
|
|
|
|
{ # Malformed packet |
367
|
|
|
|
|
|
|
die '...'; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
if (defined $packet->{'r'}{'nodes'}) { |
370
|
|
|
|
|
|
|
for my $new_node ( # XXX - may be ipv6 |
371
|
|
|
|
|
|
|
uncompact_ipv4($packet->{'r'}{'nodes'}) |
372
|
|
|
|
|
|
|
) |
373
|
|
|
|
|
|
|
{ $new_node = $self->ipv4_add_node($new_node); |
374
|
|
|
|
|
|
|
$new_node->get_peers($req->{'info_hash'}) |
375
|
|
|
|
|
|
|
if $new_node; |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
if (defined $packet->{'r'}{'values'}) { # peers |
378
|
|
|
|
|
|
|
my ($quest) = $self->grep_get_peers_quests( |
379
|
|
|
|
|
|
|
sub { |
380
|
|
|
|
|
|
|
defined $_ |
381
|
|
|
|
|
|
|
&& $req->{'info_hash'} |
382
|
|
|
|
|
|
|
->equal($_->[0]); |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
); |
385
|
|
|
|
|
|
|
return if !defined $quest; |
386
|
|
|
|
|
|
|
my @peers |
387
|
|
|
|
|
|
|
= map { uncompact_ipv4($_) } |
388
|
|
|
|
|
|
|
ref $packet->{'r'}{'values'} |
389
|
|
|
|
|
|
|
? |
390
|
|
|
|
|
|
|
@{$packet->{'r'}{'values'}} |
391
|
|
|
|
|
|
|
: $packet->{'r'}{'values'}; |
392
|
|
|
|
|
|
|
{ |
393
|
|
|
|
|
|
|
my %seen = (); |
394
|
|
|
|
|
|
|
@{$quest->[2]} |
395
|
|
|
|
|
|
|
= grep { !$seen{$_->[0]}{$_->[1]}++ } |
396
|
|
|
|
|
|
|
@{$quest->[2]}, @peers; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
$quest->[1] |
399
|
|
|
|
|
|
|
->($req->{'info_hash'}, $node, \@peers); |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
if (defined $packet->{'r'}{'token'}) |
402
|
|
|
|
|
|
|
{ # for announce_peer |
403
|
|
|
|
|
|
|
$node->_set_announce_peer_token_in( |
404
|
|
|
|
|
|
|
$req->{'info_hash'}->to_Hex, |
405
|
|
|
|
|
|
|
$packet->{'r'}{'token'}); |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
elsif ($type eq 'announce_peer') { |
410
|
|
|
|
|
|
|
my ($quest) = $self->grep_announce_peer_quests( |
411
|
|
|
|
|
|
|
sub { |
412
|
|
|
|
|
|
|
defined $_ |
413
|
|
|
|
|
|
|
&& $req->{'info_hash'}->equal($_->[0]); |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
); |
416
|
|
|
|
|
|
|
return if !defined $quest; |
417
|
|
|
|
|
|
|
push @{$quest->[3]}, [$node->host, $node->port]; |
418
|
|
|
|
|
|
|
$quest->[1]->($req->{'info_hash'}, $node, $quest->[2]); |
419
|
|
|
|
|
|
|
$node->get_prev_get_peers(0) |
420
|
|
|
|
|
|
|
if # seek peers sooner than we should |
421
|
|
|
|
|
|
|
$node->defined_prev_get_peers($req->{'info_hash'}); |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
else { |
424
|
|
|
|
|
|
|
warn sprintf '%s:%d', $node->host, $node->port; |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
#ddx $packet; |
427
|
|
|
|
|
|
|
#ddx $req; |
428
|
|
|
|
|
|
|
#...; |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
else { # A reply we are not expecting. Strange. |
432
|
|
|
|
|
|
|
$node->inc_fail; |
433
|
|
|
|
|
|
|
$self->_inc_recv_invalid_count; |
434
|
|
|
|
|
|
|
$self->_inc_recv_invalid_length(length $data); |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
#...; |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
elsif ($packet->{'y'} eq 'q' && defined $packet->{'a'}) { |
441
|
|
|
|
|
|
|
$self->_inc_recv_requests_count; |
442
|
|
|
|
|
|
|
$self->_inc_recv_requests_length(length $data); |
443
|
|
|
|
|
|
|
my $type = $packet->{'q'}; |
444
|
|
|
|
|
|
|
$node->_set_nodeid( |
445
|
|
|
|
|
|
|
Bit::Vector->new_Hex(160, unpack 'H*', $packet->{'a'}{'id'})) |
446
|
|
|
|
|
|
|
if !$node->has_nodeid; # Adds node to router table |
447
|
|
|
|
|
|
|
if ($type eq 'ping' && defined $packet->{'t'}) { |
448
|
|
|
|
|
|
|
return $node->_reply_ping($packet->{'t'}); |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
elsif ($type eq 'get_peers' |
451
|
|
|
|
|
|
|
&& defined $packet->{'a'}{'info_hash'}) |
452
|
|
|
|
|
|
|
{ return |
453
|
|
|
|
|
|
|
$node->_reply_get_peers( |
454
|
|
|
|
|
|
|
$packet->{'t'}, |
455
|
|
|
|
|
|
|
Bit::Vector->new_Hex(160, unpack 'H*', |
456
|
|
|
|
|
|
|
$packet->{'a'}{'info_hash'} |
457
|
|
|
|
|
|
|
) |
458
|
|
|
|
|
|
|
); |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
elsif ($type eq 'find_node' |
461
|
|
|
|
|
|
|
&& defined $packet->{'a'}{'target'}) |
462
|
|
|
|
|
|
|
{ return |
463
|
|
|
|
|
|
|
$node->_reply_find_node( |
464
|
|
|
|
|
|
|
$packet->{'t'}, |
465
|
|
|
|
|
|
|
Bit::Vector->new_Hex(160, unpack 'H*', |
466
|
|
|
|
|
|
|
$packet->{'a'}{'target'} |
467
|
|
|
|
|
|
|
) |
468
|
|
|
|
|
|
|
); |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
elsif ($type eq 'announce_peer' |
471
|
|
|
|
|
|
|
&& defined $packet->{'a'}{'info_hash'}) |
472
|
|
|
|
|
|
|
{ return |
473
|
|
|
|
|
|
|
$node->_reply_announce_peer( |
474
|
|
|
|
|
|
|
$packet->{'t'}, |
475
|
|
|
|
|
|
|
Bit::Vector->new_Hex(160, unpack 'H*', |
476
|
|
|
|
|
|
|
$packet->{'a'}{'info_hash'} |
477
|
|
|
|
|
|
|
), |
478
|
|
|
|
|
|
|
$packet->{'a'}, |
479
|
|
|
|
|
|
|
); |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
else { |
482
|
|
|
|
|
|
|
die '...'; |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
elsif ($packet->{'y'} eq 'q' && defined $packet->{'a'}) { |
486
|
|
|
|
|
|
|
warn sprintf 'Error from %s:%d', $node->host, $node->port; |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
#use Data::Dump; |
489
|
|
|
|
|
|
|
#ddx $packet; |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
else { |
492
|
|
|
|
|
|
|
#use Data::Dump; |
493
|
|
|
|
|
|
|
#warn sprintf '%s:%d', $node->host, $node->port; |
494
|
|
|
|
|
|
|
#ddx $packet; |
495
|
|
|
|
|
|
|
#ddx $data; |
496
|
|
|
|
|
|
|
#...; |
497
|
|
|
|
|
|
|
# TODO: ID checks against $packet->{'a'}{'id'} |
498
|
|
|
|
|
|
|
} |
499
|
|
|
|
|
|
|
} |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
sub dump_ipv4_buckets { |
502
|
|
|
|
|
|
|
my @return = _dump_buckets($_[0], $_[0]->ipv4_routing_table()); |
503
|
|
|
|
|
|
|
return wantarray ? @return : sub { say $_ for @_ } |
504
|
|
|
|
|
|
|
->(@return); |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
sub dump_ipv6_buckets { |
508
|
|
|
|
|
|
|
my @return = _dump_buckets($_[0], $_[0]->ipv6_routing_table()); |
509
|
|
|
|
|
|
|
return wantarray ? @return : sub { say $_ for @_ } |
510
|
|
|
|
|
|
|
->(@return); |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
sub _dump_buckets { |
514
|
|
|
|
|
|
|
my ($self, $routing_table) = @_; |
515
|
|
|
|
|
|
|
my @return = sprintf 'Num buckets: %d. My DHT ID: %s', |
516
|
|
|
|
|
|
|
$routing_table->count_buckets, $self->nodeid->to_Hex; |
517
|
|
|
|
|
|
|
my ($x, $t_primary, $t_backup) = (0, 0, 0); |
518
|
|
|
|
|
|
|
for my $bucket (@{$routing_table->buckets}) { |
519
|
|
|
|
|
|
|
push @return, sprintf 'Bucket %s: %s (replacement cache: %d)', |
520
|
|
|
|
|
|
|
$x++, $bucket->floor->to_Hex, $bucket->count_backup_nodes; |
521
|
|
|
|
|
|
|
for my $node (@{$bucket->nodes}) { |
522
|
|
|
|
|
|
|
push @return, |
523
|
|
|
|
|
|
|
sprintf ' %s %s:%d fail:%d seen:%d age:%s ver:%s', |
524
|
|
|
|
|
|
|
$node->nodeid->to_Hex, $node->host, |
525
|
|
|
|
|
|
|
$node->port, $node->fail || 0, $node->seen, |
526
|
|
|
|
|
|
|
__duration(time - $node->birth), $node->v || '?'; |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
$t_primary += $bucket->count_nodes; |
529
|
|
|
|
|
|
|
$t_backup += $bucket->count_backup_nodes; |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
push @return, sprintf 'Total peers: %d (in replacement cache %d)', |
532
|
|
|
|
|
|
|
$t_primary + $t_backup, $t_backup; |
533
|
|
|
|
|
|
|
push @return, sprintf 'Outstanding add nodes: %d', |
534
|
|
|
|
|
|
|
scalar $routing_table->outstanding_add_nodes; |
535
|
|
|
|
|
|
|
push @return, |
536
|
|
|
|
|
|
|
sprintf |
537
|
|
|
|
|
|
|
'Received: %d requests (%s), %d replies (%s), %d invalid (%s)', |
538
|
|
|
|
|
|
|
$self->_recv_requests_count, |
539
|
|
|
|
|
|
|
__data($self->_recv_requests_length), |
540
|
|
|
|
|
|
|
$self->_recv_replies_count, |
541
|
|
|
|
|
|
|
__data($self->_recv_replies_length), |
542
|
|
|
|
|
|
|
$self->_recv_invalid_count, |
543
|
|
|
|
|
|
|
__data($self->_recv_invalid_length); |
544
|
|
|
|
|
|
|
push @return, sprintf 'Sent: %d requests (%s), %d replies (%s)', |
545
|
|
|
|
|
|
|
$self->_send_requests_count, |
546
|
|
|
|
|
|
|
__data($self->_send_requests_length), |
547
|
|
|
|
|
|
|
$self->_send_replies_count, |
548
|
|
|
|
|
|
|
__data($self->_send_replies_length); |
549
|
|
|
|
|
|
|
return @return; |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
has 'port' => (is => 'ro', |
552
|
|
|
|
|
|
|
isa => 'Int|ArrayRef[Int]', |
553
|
|
|
|
|
|
|
builder => '_build_port', |
554
|
|
|
|
|
|
|
writer => '_set_port' |
555
|
|
|
|
|
|
|
); |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
sub _build_port { |
558
|
|
|
|
|
|
|
0; # Let the system pick |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
my %_sock_types = (4 => '0.0.0.0', 6 => '::'); |
561
|
|
|
|
|
|
|
for my $ipv (keys %_sock_types) { |
562
|
|
|
|
|
|
|
has 'udp' |
563
|
|
|
|
|
|
|
. $ipv => (is => 'ro', |
564
|
|
|
|
|
|
|
init_arg => undef, |
565
|
|
|
|
|
|
|
isa => 'Maybe[Object]', |
566
|
|
|
|
|
|
|
lazy_build => 1, |
567
|
|
|
|
|
|
|
writer => '_set_udp' . $ipv |
568
|
|
|
|
|
|
|
); |
569
|
|
|
|
|
|
|
has 'udp' |
570
|
|
|
|
|
|
|
. $ipv |
571
|
|
|
|
|
|
|
. '_sock' => (is => 'ro', |
572
|
|
|
|
|
|
|
init_arg => undef, |
573
|
|
|
|
|
|
|
isa => 'GlobRef', |
574
|
|
|
|
|
|
|
lazy_build => 1, |
575
|
|
|
|
|
|
|
weak_ref => 1, |
576
|
|
|
|
|
|
|
writer => '_set_udp' . $ipv . '_sock' |
577
|
|
|
|
|
|
|
); |
578
|
|
|
|
|
|
|
has 'udp' |
579
|
|
|
|
|
|
|
. $ipv |
580
|
|
|
|
|
|
|
. '_host' => (is => 'ro', |
581
|
|
|
|
|
|
|
isa => 'Str', |
582
|
|
|
|
|
|
|
default => $_sock_types{$ipv}, |
583
|
|
|
|
|
|
|
writer => '_set_udp' . $ipv . '_host' |
584
|
|
|
|
|
|
|
); |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
# |
587
|
|
|
|
|
|
|
has 'ip_filter' => (is => 'ro', |
588
|
|
|
|
|
|
|
isa => 'Maybe[Config::IPFilter]', |
589
|
|
|
|
|
|
|
init_arg => undef, |
590
|
|
|
|
|
|
|
builder => '_build_ip_filter' |
591
|
|
|
|
|
|
|
); |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
sub _build_ip_filter { |
594
|
|
|
|
|
|
|
return eval('require Config::IPFilter;') ? Config::IPFilter->new() : (); |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
sub _build_udp6 { |
598
|
|
|
|
|
|
|
my $s = shift; |
599
|
|
|
|
|
|
|
my ($server, $actual_socket, $actual_host, $actual_port); |
600
|
|
|
|
|
|
|
for my $port (ref $s->port ? @{$s->port} : $s->port) { |
601
|
|
|
|
|
|
|
$server = server( |
602
|
|
|
|
|
|
|
$s->udp6_host, |
603
|
|
|
|
|
|
|
$port, |
604
|
|
|
|
|
|
|
sub { $s->_on_udp6_in(@_); }, |
605
|
|
|
|
|
|
|
sub { |
606
|
|
|
|
|
|
|
($actual_socket, $actual_host, $actual_port) = @_; |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
#if ($self->port != $port) { ...; } |
609
|
|
|
|
|
|
|
$s->_set_udp6_sock($actual_socket); |
610
|
|
|
|
|
|
|
$s->_set_udp6_host($actual_host); |
611
|
|
|
|
|
|
|
$s->_set_port($actual_port); |
612
|
|
|
|
|
|
|
}, |
613
|
|
|
|
|
|
|
'udp' |
614
|
|
|
|
|
|
|
); |
615
|
|
|
|
|
|
|
last if defined $server; |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
if ($server) { |
618
|
|
|
|
|
|
|
$s->trigger_listen_success( |
619
|
|
|
|
|
|
|
{port => $actual_port, |
620
|
|
|
|
|
|
|
protocol => 'udp6', |
621
|
|
|
|
|
|
|
severity => 'debug', |
622
|
|
|
|
|
|
|
event => 'listen_success', |
623
|
|
|
|
|
|
|
message => sprintf |
624
|
|
|
|
|
|
|
'Bound UDP port %d to the outside world over IPv6', |
625
|
|
|
|
|
|
|
$actual_port |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
); |
628
|
|
|
|
|
|
|
} |
629
|
|
|
|
|
|
|
else { |
630
|
|
|
|
|
|
|
$s->trigger_listen_failure( |
631
|
|
|
|
|
|
|
{port => $s->port, |
632
|
|
|
|
|
|
|
protocol => 'udp6', |
633
|
|
|
|
|
|
|
severity => 'fatal', |
634
|
|
|
|
|
|
|
event => 'listen_failure', |
635
|
|
|
|
|
|
|
message => |
636
|
|
|
|
|
|
|
'Failed to bind UDP port for the outside world over IPv6' |
637
|
|
|
|
|
|
|
} |
638
|
|
|
|
|
|
|
); |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
return $server; |
641
|
|
|
|
|
|
|
} |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
sub _build_udp4 { |
644
|
|
|
|
|
|
|
my $s = shift; |
645
|
|
|
|
|
|
|
my ($server, $actual_socket, $actual_host, $actual_port); |
646
|
|
|
|
|
|
|
for my $port (ref $s->port ? @{$s->port} : $s->port) { |
647
|
|
|
|
|
|
|
$server = server( |
648
|
|
|
|
|
|
|
$s->udp4_host, |
649
|
|
|
|
|
|
|
$port, |
650
|
|
|
|
|
|
|
sub { $s->_on_udp4_in(@_); }, |
651
|
|
|
|
|
|
|
sub { |
652
|
|
|
|
|
|
|
($actual_socket, $actual_host, $actual_port) = @_; |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
#if ($self->port != $port) { ...; } |
655
|
|
|
|
|
|
|
$s->_set_udp4_sock($actual_socket); |
656
|
|
|
|
|
|
|
$s->_set_udp4_host($actual_host); |
657
|
|
|
|
|
|
|
$s->_set_port($actual_port); |
658
|
|
|
|
|
|
|
}, |
659
|
|
|
|
|
|
|
'udp' |
660
|
|
|
|
|
|
|
); |
661
|
|
|
|
|
|
|
last if defined $server; |
662
|
|
|
|
|
|
|
} |
663
|
|
|
|
|
|
|
if ($server) { |
664
|
|
|
|
|
|
|
$s->trigger_listen_success( |
665
|
|
|
|
|
|
|
{port => $actual_port, |
666
|
|
|
|
|
|
|
protocol => 'udp4', |
667
|
|
|
|
|
|
|
severity => 'debug', |
668
|
|
|
|
|
|
|
event => 'listen_success', |
669
|
|
|
|
|
|
|
message => sprintf |
670
|
|
|
|
|
|
|
'Bound UDP port %d to the outside world over IPv4', |
671
|
|
|
|
|
|
|
$actual_port |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
); |
674
|
|
|
|
|
|
|
} |
675
|
|
|
|
|
|
|
else { |
676
|
|
|
|
|
|
|
$s->trigger_listen_failure( |
677
|
|
|
|
|
|
|
{port => $s->port, |
678
|
|
|
|
|
|
|
protocol => 'udp4', |
679
|
|
|
|
|
|
|
severity => 'fatal', |
680
|
|
|
|
|
|
|
event => 'listen_failure', |
681
|
|
|
|
|
|
|
message => |
682
|
|
|
|
|
|
|
'Failed to bind UDP port for the outside world over IPv4' |
683
|
|
|
|
|
|
|
} |
684
|
|
|
|
|
|
|
); |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
return $server; |
687
|
|
|
|
|
|
|
} |
688
|
|
|
|
|
|
|
around '_on_udp4_in' => sub { |
689
|
|
|
|
|
|
|
my ($c, $s, $sock, $sockaddr, $host, $port, $data, $flags) = @_; |
690
|
|
|
|
|
|
|
if (defined $s->ip_filter) { |
691
|
|
|
|
|
|
|
my $rule = $s->ip_filter->is_banned($host); |
692
|
|
|
|
|
|
|
if (defined $rule) { |
693
|
|
|
|
|
|
|
$s->trigger_ip_filter( |
694
|
|
|
|
|
|
|
{protocol => 'udp4', |
695
|
|
|
|
|
|
|
severity => 'debug', |
696
|
|
|
|
|
|
|
event => 'ip_filter', |
697
|
|
|
|
|
|
|
address => [$host, $port], |
698
|
|
|
|
|
|
|
rule => $rule, |
699
|
|
|
|
|
|
|
message => 'Incoming data was blocked by ipfilter' |
700
|
|
|
|
|
|
|
} |
701
|
|
|
|
|
|
|
); |
702
|
|
|
|
|
|
|
return; |
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
} |
705
|
|
|
|
|
|
|
$c->($s, $sock, $sockaddr, $host, $port, $data, $flags); |
706
|
|
|
|
|
|
|
}; |
707
|
|
|
|
|
|
|
around '_on_udp6_in' => sub { |
708
|
|
|
|
|
|
|
my ($c, $s, $sock, $sockaddr, $host, $port, $data, $flags) = @_; |
709
|
|
|
|
|
|
|
my $rule = $s->ip_filter->is_banned($host); |
710
|
|
|
|
|
|
|
if (defined $rule) { |
711
|
|
|
|
|
|
|
$s->trigger_ip_filter( |
712
|
|
|
|
|
|
|
{protocol => 'udp6', |
713
|
|
|
|
|
|
|
severity => 'debug', |
714
|
|
|
|
|
|
|
event => 'ip_filter', |
715
|
|
|
|
|
|
|
address => [$host, $port], |
716
|
|
|
|
|
|
|
rule => $rule, |
717
|
|
|
|
|
|
|
message => 'Incoming data was blocked by ipfilter' |
718
|
|
|
|
|
|
|
} |
719
|
|
|
|
|
|
|
); |
720
|
|
|
|
|
|
|
return; |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
$c->($s, $sock, $sockaddr, $host, $port, $data, $flags); |
723
|
|
|
|
|
|
|
}; |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
# Callback system |
726
|
|
|
|
|
|
|
sub _build_callback_no_op { |
727
|
|
|
|
|
|
|
sub {1} |
728
|
|
|
|
|
|
|
} |
729
|
|
|
|
|
|
|
has "on_$_" => (isa => 'CodeRef', |
730
|
|
|
|
|
|
|
is => 'ro', |
731
|
|
|
|
|
|
|
traits => ['Code'], |
732
|
|
|
|
|
|
|
handles => {"trigger_$_" => 'execute_method'}, |
733
|
|
|
|
|
|
|
lazy_build => 1, |
734
|
|
|
|
|
|
|
builder => '_build_callback_no_op', |
735
|
|
|
|
|
|
|
clearer => "_no_$_", |
736
|
|
|
|
|
|
|
weak_ref => 1 |
737
|
|
|
|
|
|
|
) |
738
|
|
|
|
|
|
|
for qw[ |
739
|
|
|
|
|
|
|
listen_failure listen_success |
740
|
|
|
|
|
|
|
]; |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
sub server ($$&;&$) { |
743
|
|
|
|
|
|
|
my ($host, $port, $callback, $prepare, $proto) = @_; |
744
|
|
|
|
|
|
|
$proto //= 'tcp'; |
745
|
|
|
|
|
|
|
my $sockaddr = Net::BitTorrent::DHT::sockaddr($host, $port) or return; |
746
|
|
|
|
|
|
|
my $type = length $sockaddr == 16 ? PF_INET : PF_INET6; |
747
|
|
|
|
|
|
|
socket my ($socket), $type, |
748
|
|
|
|
|
|
|
$proto eq 'udp' ? SOCK_DGRAM : SOCK_STREAM, getprotobyname($proto) |
749
|
|
|
|
|
|
|
or return; |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
# - What is the difference between SO_REUSEADDR and SO_REUSEPORT? |
752
|
|
|
|
|
|
|
# [http://www.unixguide.net/network/socketfaq/4.11.shtml] |
753
|
|
|
|
|
|
|
# SO_REUSEPORT is undefined on Win32 and pre-2.4.15 Linux distros. |
754
|
|
|
|
|
|
|
setsockopt $socket, SOL_SOCKET, SO_REUSEADDR, pack('l', 1) |
755
|
|
|
|
|
|
|
or return |
756
|
|
|
|
|
|
|
if $^O !~ m[Win32]; |
757
|
|
|
|
|
|
|
return if !bind $socket, $sockaddr; |
758
|
|
|
|
|
|
|
my $listen = 8; |
759
|
|
|
|
|
|
|
if (defined $prepare) { |
760
|
|
|
|
|
|
|
my ($_port, $packed_ip) |
761
|
|
|
|
|
|
|
= Net::BitTorrent::DHT::unpack_sockaddr(getsockname $socket); |
762
|
|
|
|
|
|
|
my $return = $prepare->($socket, paddr2ip($packed_ip), $_port); |
763
|
|
|
|
|
|
|
$listen = $return if defined $return; |
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
require AnyEvent::Util; |
766
|
|
|
|
|
|
|
AnyEvent::Util::fh_nonblocking $socket, 1; |
767
|
|
|
|
|
|
|
listen $socket, $listen or return if $proto ne 'udp'; |
768
|
|
|
|
|
|
|
return AE::io( |
769
|
|
|
|
|
|
|
$socket, 0, |
770
|
|
|
|
|
|
|
$proto eq 'udp' ? |
771
|
|
|
|
|
|
|
sub { |
772
|
|
|
|
|
|
|
my $flags = 0; |
773
|
|
|
|
|
|
|
if ($socket |
774
|
|
|
|
|
|
|
&& (my $peer = recv $socket, my ($data), 16 * 1024, $flags)) |
775
|
|
|
|
|
|
|
{ my ($service, $host) |
776
|
|
|
|
|
|
|
= Net::BitTorrent::DHT::unpack_sockaddr($peer); |
777
|
|
|
|
|
|
|
$callback->($socket, $peer, paddr2ip($host), $service, |
778
|
|
|
|
|
|
|
$data, $flags |
779
|
|
|
|
|
|
|
); |
780
|
|
|
|
|
|
|
} |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
: sub { |
783
|
|
|
|
|
|
|
while ($socket |
784
|
|
|
|
|
|
|
&& (my $peer = accept my ($fh), $socket)) |
785
|
|
|
|
|
|
|
{ my ($service, $host) |
786
|
|
|
|
|
|
|
= Net::BitTorrent::DHT::unpack_sockaddr($peer); |
787
|
|
|
|
|
|
|
$callback->($fh, $peer, paddr2ip($host), $service); |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
} |
790
|
|
|
|
|
|
|
); |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
sub paddr2ip ($) { |
794
|
|
|
|
|
|
|
return inet_ntoa($_[0]) if length $_[0] == 4; # ipv4 |
795
|
|
|
|
|
|
|
return inet_ntoa($1) |
796
|
|
|
|
|
|
|
if length $_[0] == 16 |
797
|
|
|
|
|
|
|
&& $_[0] =~ m[^\0{10}\xff{2}(.{4})$]; # ipv4 |
798
|
|
|
|
|
|
|
return unless length($_[0]) == 16; |
799
|
|
|
|
|
|
|
my @hex = (unpack('n8', $_[0])); |
800
|
|
|
|
|
|
|
$hex[9] = $hex[7] & 0xff; |
801
|
|
|
|
|
|
|
$hex[8] = $hex[7] >> 8; |
802
|
|
|
|
|
|
|
$hex[7] = $hex[6] & 0xff; |
803
|
|
|
|
|
|
|
$hex[6] >>= 8; |
804
|
|
|
|
|
|
|
my $return = sprintf '%X:%X:%X:%X:%X:%X:%D:%D:%D:%D', @hex; |
805
|
|
|
|
|
|
|
$return =~ s|(0+:)+|:|x; |
806
|
|
|
|
|
|
|
$return =~ s|^0+ ||x; |
807
|
|
|
|
|
|
|
$return =~ s|^:+ |::|x; |
808
|
|
|
|
|
|
|
$return =~ s|::0+ |::|x; |
809
|
|
|
|
|
|
|
$return =~ s|^::(\d+):(\d+):(\d+):(\d+)|$1.$2.$3.$4|x; |
810
|
|
|
|
|
|
|
return $return; |
811
|
|
|
|
|
|
|
} |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
sub __duration ($) { |
814
|
|
|
|
|
|
|
my %dhms = (d => int($_[0] / (24 * 60 * 60)), |
815
|
|
|
|
|
|
|
h => ($_[0] / (60 * 60)) % 24, |
816
|
|
|
|
|
|
|
m => ($_[0] / 60) % 60, |
817
|
|
|
|
|
|
|
s => $_[0] % 60 |
818
|
|
|
|
|
|
|
); |
819
|
|
|
|
|
|
|
return join ' ', map { $dhms{$_} ? $dhms{$_} . $_ : () } sort keys %dhms; |
820
|
|
|
|
|
|
|
} |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
sub unpack_sockaddr ($) { |
823
|
|
|
|
|
|
|
my ($packed_host) = @_; |
824
|
|
|
|
|
|
|
return length $packed_host == 28 ? |
825
|
|
|
|
|
|
|
(unpack('SnLa16L', $packed_host))[1, 3] |
826
|
|
|
|
|
|
|
: unpack_sockaddr_in($packed_host); |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
sub sockaddr ($$) { |
830
|
|
|
|
|
|
|
my $resolver = AE::cv(); |
831
|
|
|
|
|
|
|
AnyEvent::Socket::resolve_sockaddr( |
832
|
|
|
|
|
|
|
$_[0], |
833
|
|
|
|
|
|
|
$_[1], |
834
|
|
|
|
|
|
|
0, undef, undef, |
835
|
|
|
|
|
|
|
sub { |
836
|
|
|
|
|
|
|
$resolver->send($_[0]->[3]); |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
); |
839
|
|
|
|
|
|
|
return $resolver->recv(); |
840
|
|
|
|
|
|
|
} |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
sub __data($) { |
843
|
|
|
|
|
|
|
$_[0] >= 1073741824 ? sprintf('%0.2f GB', $_[0] / 1073741824) |
844
|
|
|
|
|
|
|
: $_[0] >= 1048576 ? sprintf('%0.2f MB', $_[0] / 1048576) |
845
|
|
|
|
|
|
|
: $_[0] >= 1024 ? sprintf('%0.2f KB', $_[0] / 1024) |
846
|
|
|
|
|
|
|
: $_[0] . ' bytes'; |
847
|
|
|
|
|
|
|
} |
848
|
|
|
|
|
|
|
1; |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
=pod |
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
=head1 NAME |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
Net::BitTorrent::DHT - Kademlia-like DHT Node for BitTorrent |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
=head1 Synopsis |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
use Net::BitTorrent::DHT; |
859
|
|
|
|
|
|
|
use AnyEvent; |
860
|
|
|
|
|
|
|
use Bit::Vector; |
861
|
|
|
|
|
|
|
# Standalone node with user-defined port and boot_nodes |
862
|
|
|
|
|
|
|
my $dht = Net::BitTorrent::DHT->new( |
863
|
|
|
|
|
|
|
port => [1337 .. 1340, 0], |
864
|
|
|
|
|
|
|
boot_nodes => |
865
|
|
|
|
|
|
|
[['router.bittorrent.com', 6881], ['router.utorrent.com', 6881]] |
866
|
|
|
|
|
|
|
); |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
my $peer_quest |
869
|
|
|
|
|
|
|
= $dht->get_peers(Bit::Vector->new_Hex('ab97a7bca78f2628380e6609a8241a7fb02aa981'), \&dht_cb); |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
# tick, tick, tick, ... |
872
|
|
|
|
|
|
|
AnyEvent->condvar->recv; |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
sub dht_cb { |
875
|
|
|
|
|
|
|
my ($infohash, $node, $peers) = @_; |
876
|
|
|
|
|
|
|
printf "We found %d peers for %s from %s:%d via DHT\n\t%s\n", |
877
|
|
|
|
|
|
|
scalar(@$peers), |
878
|
|
|
|
|
|
|
$infohash->to_Hex, $node->host, $node->port, |
879
|
|
|
|
|
|
|
join ', ', map { sprintf '%s:%d', @$_ } @$peers; |
880
|
|
|
|
|
|
|
} |
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
=head1 Description |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
BitTorrent uses a "distributed sloppy hash table" (DHT) for storing peer |
885
|
|
|
|
|
|
|
contact information for "trackerless" torrents. In effect, each peer becomes a |
886
|
|
|
|
|
|
|
tracker. The protocol is based on L<Kademila|/Kademlia> and is implemented |
887
|
|
|
|
|
|
|
over UDP. |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
=head1 Methods |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
L<Net::BitTorrent::DHT|Net::BitTorrent::DHT>'s API is simple but powerful. |
892
|
|
|
|
|
|
|
...well, I think so anyway. |
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
=head1 Net::BitTorrent::DHT->new( ) |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
The constructor accepts a number different arguments which all greatly affect |
897
|
|
|
|
|
|
|
the function of your DHT node. Any combination of the following arguments may |
898
|
|
|
|
|
|
|
be used during construction. |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
For brevity, the following examples assume you are building a |
901
|
|
|
|
|
|
|
L<standalone node|Net::BitTorrent::DHT::Standalone> (for reasearch, etc.). |
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
=head2 Net::BitTorrent::DHT->new( nodeid => ... ) |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
During construction, our local DHT nodeID can be set during construction. This |
906
|
|
|
|
|
|
|
is mostly useful when creating a |
907
|
|
|
|
|
|
|
L<standalone DHT node|Net::BitTorrent::DHT::Standalone>. |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
use Net::BitTorrent::DHT; |
910
|
|
|
|
|
|
|
# Bit::Vector object |
911
|
|
|
|
|
|
|
use Bit::Vector; |
912
|
|
|
|
|
|
|
my $node_c = Net::BitTorrent::DHT->new( |
913
|
|
|
|
|
|
|
nodeid => Bit::Vector->new_Hex( 160, 'ABCD' x 10 ) |
914
|
|
|
|
|
|
|
); |
915
|
|
|
|
|
|
|
# A SHA1 digest |
916
|
|
|
|
|
|
|
use Digest::SHA; |
917
|
|
|
|
|
|
|
my $node_d = Net::BitTorrent::DHT->new( |
918
|
|
|
|
|
|
|
nodeid => Bit::Vector->new_Hex( 160, Digest::SHA::sha1( $possibly_random_value ) ) |
919
|
|
|
|
|
|
|
); |
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
Note that storing and reusing DHT nodeIDs over a number of sessions may seem |
922
|
|
|
|
|
|
|
advantagious (as if you had a "reserved parking place" in the DHT network) but |
923
|
|
|
|
|
|
|
will likely not improve performance as unseen nodeIDs are removed from remote |
924
|
|
|
|
|
|
|
routing tables after a half hour. |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
NodeIDs, are 160-bit integers. |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
=head2 Net::BitTorrent::DHT->new( port => ... ) |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
Opens a specific UDP port number to the outside world on both IPv4 and IPv6. |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
use Net::BitTorrent::DHT; |
933
|
|
|
|
|
|
|
# A single possible port |
934
|
|
|
|
|
|
|
my $node_a = Net::BitTorrent::DHT->new( port => 1123 ); |
935
|
|
|
|
|
|
|
# A list of ports |
936
|
|
|
|
|
|
|
my $node_b = Net::BitTorrent::DHT->new( port => [1235 .. 9875] ); |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
Note that when handed a list of ports, they are each tried until we are able |
939
|
|
|
|
|
|
|
to bind to the specific port. |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
=head1 Net::BitTorrent::DHT->find_node( $target, $callback ) |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
This method asks for remote nodes with nodeIDs closer to our target. As the |
944
|
|
|
|
|
|
|
remote nodes respond, the callback is called with the following arguments: |
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
=over |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
=item * target |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
This is the target nodeid. This is useful when you've set the same callback |
951
|
|
|
|
|
|
|
for multiple, concurrent C<find_node( )> L<quest|/"Quests and Callbacks">. |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
Targets are 160-bit L<Bit::Vector|Bit::Vector> objects. |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
=item * node |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
This is a blessed object. TODO. |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
=item * nodes |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
This is a list of ip:port combinations the remote node claims are close to our |
962
|
|
|
|
|
|
|
target. |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
=back |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
A single C<find_node> L<quest|Net::BitTorrent::Notes/"Quests and Callbacks"> |
967
|
|
|
|
|
|
|
is an array ref which contains the following data: |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
=over |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
=item * target |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
This is the target nodeID. |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
=item * coderef |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
This is the callback triggered as we locate new peers. |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
=item * nodes |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
This is a list of nodes we have announced to so far. |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
=item * timer |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
This is an L<AnyEvent|AnyEvent> timer which is triggered every few minutes. |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
Don't modify this. |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
=back |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
=head1 Net::BitTorrent::DHT->get_peers( $infohash, $callback ) |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
This method initiates a search for peers serving a torrent with this infohash. |
994
|
|
|
|
|
|
|
As they are found, the callback is called with the following arguments: |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
=over |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
=item * infohash |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
This is the infohash related to these peers. This is useful when you've set |
1001
|
|
|
|
|
|
|
the same callback for multiple, concurrent C<get_peers( )> quests. This is a |
1002
|
|
|
|
|
|
|
160-bit L<Bit::Vector|Bit::Vector> object. |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
=item * node |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
This is a blessed object. TODO. |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
=item * peers |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
This is an array ref of peers sent to us by aforementioned remote node. |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
=back |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
A single C<get_peers> L<quest|Net::BitTorrent::Notes/"Quests and Callbacks"> |
1015
|
|
|
|
|
|
|
is an array ref which contains the following data: |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
=over |
1018
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
=item * infohash |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
This is the infohash related to these peers. This is a 160-bit |
1022
|
|
|
|
|
|
|
L<Bit::Vector|Bit::Vector> object. |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
=item * coderef |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
This is the callback triggered as we locate new peers. |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=item * peers |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
This is a compacted list of all peers found so far. This is probably more |
1031
|
|
|
|
|
|
|
useful than the list passed to the callback. |
1032
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
=item * timer |
1034
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
This is an L<AnyEvent|AnyEvent> timer which is triggered every five minutes. |
1036
|
|
|
|
|
|
|
When triggered, the node requests new peers from nodes in the bucket nearest |
1037
|
|
|
|
|
|
|
to the infohash. |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
Don't modify this. |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
=back |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
=head1 Net::BitTorrent::DHT->B<announce_peer>( $infohash, $port, $callback ) |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
This method announces that the peer controlling the querying node is |
1046
|
|
|
|
|
|
|
downloading a torrent on a port. These outgoing queries are sent to nodes |
1047
|
|
|
|
|
|
|
'close' to the target infohash. As the remote nodes respond, the callback is |
1048
|
|
|
|
|
|
|
called with the following arguments: |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
=over |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
=item * infohash |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
This is the infohash related to this announcment. This is useful when you've |
1055
|
|
|
|
|
|
|
set the same callback for multiple, concurrent C<announce_peer( )> |
1056
|
|
|
|
|
|
|
L<quest|/"Quests and Callbacks">. Infohashes are 160-bit |
1057
|
|
|
|
|
|
|
L<Bit::Vector|Bit::Vector> objects. |
1058
|
|
|
|
|
|
|
|
1059
|
|
|
|
|
|
|
=item * port |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
This is port you defined above. |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
=item * node |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
This is a blessed object. TODO. |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
=back |
1068
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
A single C<announce_peer> L<quest|/"Quests and Callbacks"> is an array ref |
1070
|
|
|
|
|
|
|
which contains the following data: |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
=over |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
=item * infohash |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
This is the infohash related to these peers. This is a 160-bit |
1077
|
|
|
|
|
|
|
L<Bit::Vector|Bit::Vector> object. |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
=item * coderef |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
This is the callback triggered as we locate new peers. |
1082
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
=item * port |
1084
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
This is port you defined above. |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
=item * nodes |
1088
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
This is a list of nodes we have announced to so far. |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
=item * timer |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
This is an L<AnyEvent|AnyEvent> timer which is triggered every few minutes. |
1094
|
|
|
|
|
|
|
|
1095
|
|
|
|
|
|
|
Don't modify this. |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
=back |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
C<announce_peer> queries require a token sent in reply to a C<get_peers> query |
1100
|
|
|
|
|
|
|
so they should be used together. |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
=for meditation |
1103
|
|
|
|
|
|
|
Should I automatically send get_peers queries before an announce if the token |
1104
|
|
|
|
|
|
|
is missing? |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
use Net::BitTorrent::DHT; |
1107
|
|
|
|
|
|
|
my $node = Net::BitTorrent::DHT->new( ); |
1108
|
|
|
|
|
|
|
my $quest_a = $dht->announce_peer(Bit::Vector->new_Hex('A' x 40), 6881, \&dht_cb); |
1109
|
|
|
|
|
|
|
my $quest_b = $dht->announce_peer(Bit::Vector->new_Hex('1' x 40), 9585, \&dht_cb); |
1110
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
sub dht_cb { |
1112
|
|
|
|
|
|
|
my ($infohash, $port, $node) = @_; |
1113
|
|
|
|
|
|
|
say sprintf '%s:%d now knows we are serving %s on port %d', |
1114
|
|
|
|
|
|
|
$node->host, $node->port, $infohash->to_Hex, $port; |
1115
|
|
|
|
|
|
|
} |
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
=head1 Net::BitTorrent::DHT->dump_ipv4_buckets( ) |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
This is a quick utility method which returns or prints (depending on context) |
1120
|
|
|
|
|
|
|
a list of the IPv4-based routing table's bucket structure. |
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
use Net::BitTorrent::DHT; |
1123
|
|
|
|
|
|
|
my $node = Net::BitTorrent::DHT->new( ); |
1124
|
|
|
|
|
|
|
# After some time has passed... |
1125
|
|
|
|
|
|
|
$node->dump_ipv4_buckets; # prints to STDOUT with say |
1126
|
|
|
|
|
|
|
my @dump = $node->dump_ipv4_buckets; # returns list of lines |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
=head1 Net::BitTorrent::DHT->dump_ipv6_buckets( ) |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
This is a quick utility method which returns or prints (depending on context) |
1131
|
|
|
|
|
|
|
a list of the IPv6-based routing table's bucket structure. |
1132
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
use Net::BitTorrent::DHT; |
1134
|
|
|
|
|
|
|
my $node = Net::BitTorrent::DHT->new( ); |
1135
|
|
|
|
|
|
|
# After some time has passed... |
1136
|
|
|
|
|
|
|
$node->dump_ipv6_buckets; # prints to STDOUT with say |
1137
|
|
|
|
|
|
|
my @dump = $node->dump_ipv6_buckets; # returns list of lines |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
=head1 Author |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
Sanko Robinson <sanko@cpan.org> - http://sankorobinson.com/ |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
CPAN ID: SANKO |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
=head1 License and Legal |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
Copyright (C) 2008-2014 by Sanko Robinson <sanko@cpan.org> |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under |
1150
|
|
|
|
|
|
|
the terms of |
1151
|
|
|
|
|
|
|
L<The Artistic License 2.0|http://www.perlfoundation.org/artistic_license_2_0>. |
1152
|
|
|
|
|
|
|
See the F<LICENSE> file included with this distribution or |
1153
|
|
|
|
|
|
|
L<notes on the Artistic License 2.0|http://www.perlfoundation.org/artistic_2_0_notes> |
1154
|
|
|
|
|
|
|
for clarification. |
1155
|
|
|
|
|
|
|
|
1156
|
|
|
|
|
|
|
When separated from the distribution, all original POD documentation is |
1157
|
|
|
|
|
|
|
covered by the |
1158
|
|
|
|
|
|
|
L<Creative Commons Attribution-Share Alike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/us/legalcode>. |
1159
|
|
|
|
|
|
|
See the |
1160
|
|
|
|
|
|
|
L<clarification of the CCA-SA3.0|http://creativecommons.org/licenses/by-sa/3.0/us/>. |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
Neither this module nor the L<Author|/Author> is affiliated with BitTorrent, |
1163
|
|
|
|
|
|
|
Inc. |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
=cut |