1
# Written by Bram Cohen
2
# modified for multitracker operation by John Hoffman
3
# see LICENSE.txt for license information
5
from BitTornado.zurllib import urlopen, quote
6
from urlparse import urlparse, urlunparse
7
from socket import gethostbyname
8
from btformats import check_peers
9
from BitTornado.bencode import bdecode
10
from threading import Thread, Lock
11
from cStringIO import StringIO
12
from traceback import print_exc
13
from socket import error, gethostbyname
14
from random import shuffle
29
mapbase64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-'
31
basekeydata = str(getpid()) + repr(time()) + 'tracker'
35
for i in sha(basekeydata+tracker).digest()[-6:]:
36
key += mapbase64[ord(i) & 0x3F]
41
return "&key="+keys[tracker]
44
return "&key="+keys[tracker]
47
def __init__(self, state=False):
55
def __init__( self, port, myid, infohash, trackerlist, config,
56
sched, externalsched, errorfunc, excfunc, connect,
57
howmany, amount_left, up, down, upratefunc, downratefunc,
58
doneflag, unpauseflag = fakeflag(True),
59
seededfunc = None, force_rapid_update = False ):
62
self.externalsched = externalsched
63
self.errorfunc = errorfunc
64
self.excfunc = excfunc
65
self.connect = connect
66
self.howmany = howmany
67
self.amount_left = amount_left
70
self.upratefunc = upratefunc
71
self.downratefunc = downratefunc
72
self.doneflag = doneflag
73
self.unpauseflag = unpauseflag
74
self.seededfunc = seededfunc
75
self.force_rapid_update = force_rapid_update
77
self.ip = config.get('ip','')
78
self.minpeers = config['min_peers']
79
self.maxpeers = config['max_initiate']
80
self.interval = config['rerequest_interval']
81
self.timeout = config['http_timeout']
84
for tier in trackerlist:
87
newtrackerlist += [tier]
88
self.trackerlist = newtrackerlist
90
self.lastsuccessful = ''
91
self.rejectedmessage = 'rejected by tracker - '
93
self.url = ('info_hash=%s&peer_id=%s' %
94
(quote(infohash), quote(myid)))
95
if not config.get('crypto_allowed'):
98
self.url += "&supportcrypto=1"
99
if not config.get('crypto_only'):
102
self.url += "&requirecrypto=1"
103
if not config.get('crypto_stealth'):
106
self.url += "&port=0&cryptoport="
107
self.url += str(port)
109
seed_id = config.get('dedicated_seed_id')
111
self.url += '&seed_id='+quote(seed_id)
113
self.url += '&check_seeded=1'
116
self.trackerid = None
117
self.announce_interval = 30 * 60
118
self.last_failed = True
119
self.never_succeeded = True
121
self.lock = SuccessLock()
126
self.sched(self.c, self.interval/2)
132
if not self.unpauseflag.isSet() and (
133
self.howmany() < self.minpeers or self.force_rapid_update ):
134
self.announce(3, self._c)
139
self.sched(self.c, self.interval)
141
def d(self, event = 3):
144
if not self.unpauseflag.isSet():
147
self.announce(event, self._d)
150
if self.never_succeeded:
151
self.sched(self.d, 60) # retry in 60 seconds
152
elif self.force_rapid_update:
155
self.sched(self.d, self.announce_interval)
158
def hit(self, event = 3):
159
if not self.unpauseflag.isSet() and (
160
self.howmany() < self.minpeers or self.force_rapid_update ):
163
def announce(self, event = 3, callback = lambda: None, specialurl = None):
165
if specialurl is not None:
166
s = self.url+'&uploaded=0&downloaded=0&left=1' # don't add to statistics
167
if self.howmany() >= self.maxpeers:
170
s += '&no_peer_id=1&compact=1'
171
self.last_failed = True # force true, so will display an error
172
self.special = specialurl
173
self.rerequest(s, callback)
177
s = ('%s&uploaded=%s&downloaded=%s&left=%s' %
178
(self.url, str(self.up()), str(self.down()),
179
str(self.amount_left())))
180
if self.last is not None:
181
s += '&last=' + quote(str(self.last))
182
if self.trackerid is not None:
183
s += '&trackerid=' + quote(str(self.trackerid))
184
if self.howmany() >= self.maxpeers:
187
s += '&no_peer_id=1&compact=1'
189
s += '&event=' + ['started', 'completed', 'stopped'][event]
192
self.rerequest(s, callback)
195
def snoop(self, peers, callback = lambda: None): # tracker call support
196
self.rerequest(self.url
197
+'&event=stopped&port=0&uploaded=0&downloaded=0&left=1&tracker=1&numwant='
198
+str(peers), callback)
201
def rerequest(self, s, callback):
202
if not self.lock.isfinished(): # still waiting for prior cycle to complete??
203
def retry(self = self, s = s, callback = callback):
204
self.rerequest(s, callback)
205
self.sched(retry,5) # retry in 5 seconds
208
rq = Thread(target = self._rerequest, args = [s, callback])
212
def _rerequest(self, s, callback):
214
def fail (self = self, callback = callback):
218
s += '&ip=' + gethostbyname(self.ip)
220
self.errorcodes['troublecode'] = 'unable to resolve: '+self.ip
221
self.externalsched(fail)
223
if self.special is None:
224
for t in range(len(self.trackerlist)):
225
for tr in range(len(self.trackerlist[t])):
226
tracker = self.trackerlist[t][tr]
227
if self.rerequest_single(tracker, s, callback):
228
if not self.last_failed and tr != 0:
229
del self.trackerlist[t][tr]
230
self.trackerlist[t] = [tracker] + self.trackerlist[t]
233
tracker = self.special
235
if self.rerequest_single(tracker, s, callback):
237
# no success from any tracker
238
self.externalsched(fail)
240
self.exception(callback)
243
def _fail(self, callback):
244
if ( (self.upratefunc() < 100 and self.downratefunc() < 100)
245
or not self.amount_left() ):
246
for f in ['rejected', 'bad_data', 'troublecode']:
247
if self.errorcodes.has_key(f):
248
r = self.errorcodes[f]
251
r = 'Problem connecting to tracker - unspecified error'
254
self.last_failed = True
256
self.externalsched(callback)
259
def rerequest_single(self, t, s, callback):
261
rq = Thread(target = self._rerequest_single, args = [t, s+get_key(t), l, callback])
265
if self.lock.success:
266
self.lastsuccessful = t
267
self.last_failed = False
268
self.never_succeeded = False
270
if not self.last_failed and self.lastsuccessful == t:
271
# if the last tracker hit was successful, and you've just tried the tracker
272
# you'd contacted before, don't go any further, just fail silently.
273
self.last_failed = True
274
self.externalsched(callback)
277
return False # returns true if it wants rerequest() to exit
280
def _rerequest_single(self, t, s, l, callback):
283
def timedout(self = self, l = l, closer = closer):
284
if self.lock.trip(l):
285
self.errorcodes['troublecode'] = 'Problem connecting to tracker - timeout exceeded'
292
self.externalsched(timedout, self.timeout)
296
url,q = t.split('?',1)
302
h = urlopen(url+'?'+q)
305
except (IOError, error), e:
306
err = 'Problem connecting to tracker - ' + str(e)
308
err = 'Problem connecting to tracker'
314
if self.lock.trip(l):
315
self.errorcodes['troublecode'] = err
320
if self.lock.trip(l):
321
self.errorcodes['troublecode'] = 'no data from tracker'
326
r = bdecode(data, sloppy=1)
328
except ValueError, e:
329
if self.lock.trip(l):
330
self.errorcodes['bad_data'] = 'bad data from tracker - ' + str(e)
334
if r.has_key('failure reason'):
335
if self.lock.trip(l):
336
self.errorcodes['rejected'] = self.rejectedmessage + r['failure reason']
340
if self.lock.trip(l, True): # success!
343
callback = lambda: None # attempt timed out, don't do a callback
345
# even if the attempt timed out, go ahead and process data
346
def add(self = self, r = r, callback = callback):
347
self.postrequest(r, callback)
348
self.externalsched(add)
350
self.exception(callback)
353
def postrequest(self, r, callback):
354
if r.has_key('warning message'):
355
self.errorfunc('warning from tracker - ' + r['warning message'])
356
self.announce_interval = r.get('interval', self.announce_interval)
357
self.interval = r.get('min interval', self.interval)
358
self.trackerid = r.get('tracker id', self.trackerid)
359
self.last = r.get('last')
360
# ps = len(r['peers']) + self.howmany()
363
if type(p) == type(''):
367
cflags = r.get('crypto_flags')
368
if type(cflags) != type('') or len(cflags) != lenpeers:
371
cflags = [None for i in xrange(lenpeers)]
373
cflags = [ord(x) for x in cflags]
374
if type(p) == type(''):
375
for x in xrange(0, len(p), 6):
376
ip = '.'.join([str(ord(i)) for i in p[x:x+4]])
377
port = (ord(p[x+4]) << 8) | ord(p[x+5])
378
peers.append(((ip, port), 0, cflags[int(x/6)]))
380
for i in xrange(len(p)):
382
peers.append(((x['ip'].strip(), x['port']),
383
x.get('peer id',0), cflags[i]))
384
ps = len(peers) + self.howmany()
385
if ps < self.maxpeers:
386
if self.doneflag.isSet():
387
if r.get('num peers', 1000) - r.get('done peers', 0) > ps * 1.2:
390
if r.get('num peers', 1000) > ps * 1.2:
392
if self.seededfunc and r.get('seeded'):
399
def exception(self, callback):
401
print_exc(file = data)
402
def r(s = data.getvalue(), callback = callback):
408
self.externalsched(r)
421
self.finished = False
425
if not self.pause.locked():
432
def trip(self, code, s = False):
435
if code == self.code and not self.finished:
454
def unwait(self, code):
455
if code == self.code and self.pause.locked():
458
def isfinished(self):