murder 0.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +1 -0
  2. data/LICENSE +17 -0
  3. data/README +224 -0
  4. data/Rakefile +52 -0
  5. data/VERSION +1 -0
  6. data/dist/BitTornado/BT1/Choker.py +128 -0
  7. data/dist/BitTornado/BT1/Connecter.py +288 -0
  8. data/dist/BitTornado/BT1/Downloader.py +594 -0
  9. data/dist/BitTornado/BT1/DownloaderFeedback.py +155 -0
  10. data/dist/BitTornado/BT1/Encrypter.py +333 -0
  11. data/dist/BitTornado/BT1/FileSelector.py +245 -0
  12. data/dist/BitTornado/BT1/Filter.py +12 -0
  13. data/dist/BitTornado/BT1/HTTPDownloader.py +251 -0
  14. data/dist/BitTornado/BT1/NatCheck.py +95 -0
  15. data/dist/BitTornado/BT1/PiecePicker.py +320 -0
  16. data/dist/BitTornado/BT1/Rerequester.py +426 -0
  17. data/dist/BitTornado/BT1/Statistics.py +177 -0
  18. data/dist/BitTornado/BT1/Storage.py +584 -0
  19. data/dist/BitTornado/BT1/StorageWrapper.py +1045 -0
  20. data/dist/BitTornado/BT1/StreamCheck.py +135 -0
  21. data/dist/BitTornado/BT1/T2T.py +193 -0
  22. data/dist/BitTornado/BT1/Uploader.py +145 -0
  23. data/dist/BitTornado/BT1/__init__.py +1 -0
  24. data/dist/BitTornado/BT1/btformats.py +100 -0
  25. data/dist/BitTornado/BT1/fakeopen.py +89 -0
  26. data/dist/BitTornado/BT1/makemetafile.py +263 -0
  27. data/dist/BitTornado/BT1/track.py +1067 -0
  28. data/dist/BitTornado/ConfigDir.py +401 -0
  29. data/dist/BitTornado/ConfigReader.py +1068 -0
  30. data/dist/BitTornado/ConnChoice.py +31 -0
  31. data/dist/BitTornado/CreateIcons.py +105 -0
  32. data/dist/BitTornado/CurrentRateMeasure.py +37 -0
  33. data/dist/BitTornado/HTTPHandler.py +167 -0
  34. data/dist/BitTornado/PSYCO.py +5 -0
  35. data/dist/BitTornado/RateLimiter.py +153 -0
  36. data/dist/BitTornado/RateMeasure.py +75 -0
  37. data/dist/BitTornado/RawServer.py +195 -0
  38. data/dist/BitTornado/ServerPortHandler.py +188 -0
  39. data/dist/BitTornado/SocketHandler.py +375 -0
  40. data/dist/BitTornado/__init__.py +63 -0
  41. data/dist/BitTornado/bencode.py +319 -0
  42. data/dist/BitTornado/bitfield.py +162 -0
  43. data/dist/BitTornado/clock.py +27 -0
  44. data/dist/BitTornado/download_bt1.py +882 -0
  45. data/dist/BitTornado/inifile.py +169 -0
  46. data/dist/BitTornado/iprangeparse.py +194 -0
  47. data/dist/BitTornado/launchmanycore.py +381 -0
  48. data/dist/BitTornado/natpunch.py +254 -0
  49. data/dist/BitTornado/parseargs.py +137 -0
  50. data/dist/BitTornado/parsedir.py +150 -0
  51. data/dist/BitTornado/piecebuffer.py +86 -0
  52. data/dist/BitTornado/selectpoll.py +109 -0
  53. data/dist/BitTornado/subnetparse.py +218 -0
  54. data/dist/BitTornado/torrentlistparse.py +38 -0
  55. data/dist/BitTornado/zurllib.py +100 -0
  56. data/dist/murder_client.py +291 -0
  57. data/dist/murder_make_torrent.py +46 -0
  58. data/dist/murder_tracker.py +28 -0
  59. data/doc/examples/Capfile +28 -0
  60. data/lib/capistrano/recipes/deploy/strategy/murder.rb +52 -0
  61. data/lib/murder.rb +43 -0
  62. data/lib/murder/admin.rb +47 -0
  63. data/lib/murder/murder.rb +121 -0
  64. data/murder.gemspec +101 -0
  65. metadata +129 -0
@@ -0,0 +1,135 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ from cStringIO import StringIO
5
+ from binascii import b2a_hex
6
+ from socket import error as socketerror
7
+ from urllib import quote
8
+ from traceback import print_exc
9
+ import Connecter
10
+ try:
11
+ True
12
+ except:
13
+ True = 1
14
+ False = 0
15
+
16
+ DEBUG = False
17
+
18
+
19
+ protocol_name = 'BitTorrent protocol'
20
+ option_pattern = chr(0)*8
21
+
22
+ def toint(s):
23
+ return long(b2a_hex(s), 16)
24
+
25
+ def tobinary(i):
26
+ return (chr(i >> 24) + chr((i >> 16) & 0xFF) +
27
+ chr((i >> 8) & 0xFF) + chr(i & 0xFF))
28
+
29
+ hexchars = '0123456789ABCDEF'
30
+ hexmap = []
31
+ for i in xrange(256):
32
+ hexmap.append(hexchars[(i&0xF0)/16]+hexchars[i&0x0F])
33
+
34
+ def tohex(s):
35
+ r = []
36
+ for c in s:
37
+ r.append(hexmap[ord(c)])
38
+ return ''.join(r)
39
+
40
+ def make_readable(s):
41
+ if not s:
42
+ return ''
43
+ if quote(s).find('%') >= 0:
44
+ return tohex(s)
45
+ return '"'+s+'"'
46
+
47
+ def toint(s):
48
+ return long(b2a_hex(s), 16)
49
+
50
+ # header, reserved, download id, my id, [length, message]
51
+
52
+ streamno = 0
53
+
54
+
55
+ class StreamCheck:
56
+ def __init__(self):
57
+ global streamno
58
+ self.no = streamno
59
+ streamno += 1
60
+ self.buffer = StringIO()
61
+ self.next_len, self.next_func = 1, self.read_header_len
62
+
63
+ def read_header_len(self, s):
64
+ if ord(s) != len(protocol_name):
65
+ print self.no, 'BAD HEADER LENGTH'
66
+ return len(protocol_name), self.read_header
67
+
68
+ def read_header(self, s):
69
+ if s != protocol_name:
70
+ print self.no, 'BAD HEADER'
71
+ return 8, self.read_reserved
72
+
73
+ def read_reserved(self, s):
74
+ return 20, self.read_download_id
75
+
76
+ def read_download_id(self, s):
77
+ if DEBUG:
78
+ print self.no, 'download ID ' + tohex(s)
79
+ return 20, self.read_peer_id
80
+
81
+ def read_peer_id(self, s):
82
+ if DEBUG:
83
+ print self.no, 'peer ID' + make_readable(s)
84
+ return 4, self.read_len
85
+
86
+ def read_len(self, s):
87
+ l = toint(s)
88
+ if l > 2 ** 23:
89
+ print self.no, 'BAD LENGTH: '+str(l)+' ('+s+')'
90
+ return l, self.read_message
91
+
92
+ def read_message(self, s):
93
+ if not s:
94
+ return 4, self.read_len
95
+ m = s[0]
96
+ if ord(m) > 8:
97
+ print self.no, 'BAD MESSAGE: '+str(ord(m))
98
+ if m == Connecter.REQUEST:
99
+ if len(s) != 13:
100
+ print self.no, 'BAD REQUEST SIZE: '+str(len(s))
101
+ return 4, self.read_len
102
+ index = toint(s[1:5])
103
+ begin = toint(s[5:9])
104
+ length = toint(s[9:])
105
+ print self.no, 'Request: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
106
+ elif m == Connecter.CANCEL:
107
+ if len(s) != 13:
108
+ print self.no, 'BAD CANCEL SIZE: '+str(len(s))
109
+ return 4, self.read_len
110
+ index = toint(s[1:5])
111
+ begin = toint(s[5:9])
112
+ length = toint(s[9:])
113
+ print self.no, 'Cancel: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
114
+ elif m == Connecter.PIECE:
115
+ index = toint(s[1:5])
116
+ begin = toint(s[5:9])
117
+ length = len(s)-9
118
+ print self.no, 'Piece: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
119
+ else:
120
+ print self.no, 'Message '+str(ord(m))+' (length '+str(len(s))+')'
121
+ return 4, self.read_len
122
+
123
+ def write(self, s):
124
+ while True:
125
+ i = self.next_len - self.buffer.tell()
126
+ if i > len(s):
127
+ self.buffer.write(s)
128
+ return
129
+ self.buffer.write(s[:i])
130
+ s = s[i:]
131
+ m = self.buffer.getvalue()
132
+ self.buffer.reset()
133
+ self.buffer.truncate()
134
+ x = self.next_func(m)
135
+ self.next_len, self.next_func = x
@@ -0,0 +1,193 @@
1
+ # Written by John Hoffman
2
+ # see LICENSE.txt for license information
3
+
4
+ from Rerequester import Rerequester
5
+ from urllib import quote
6
+ from threading import Event
7
+ from random import randrange
8
+ from string import lower
9
+ import sys
10
+ import __init__
11
+ try:
12
+ True
13
+ except:
14
+ True = 1
15
+ False = 0
16
+
17
+ DEBUG = True
18
+
19
+
20
+ def excfunc(x):
21
+ print x
22
+
23
+ class T2TConnection:
24
+ def __init__(self, myid, tracker, hash, interval, peers, timeout,
25
+ rawserver, disallow, isdisallowed):
26
+ self.tracker = tracker
27
+ self.interval = interval
28
+ self.hash = hash
29
+ self.operatinginterval = interval
30
+ self.peers = peers
31
+ self.rawserver = rawserver
32
+ self.disallow = disallow
33
+ self.isdisallowed = isdisallowed
34
+ self.active = True
35
+ self.busy = False
36
+ self.errors = 0
37
+ self.rejected = 0
38
+ self.trackererror = False
39
+ self.peerlists = []
40
+
41
+ self.rerequester = Rerequester([[tracker]], interval,
42
+ rawserver.add_task, lambda: 0, peers, self.addtolist,
43
+ rawserver.add_task, lambda: 1, 0, 0, 0, '',
44
+ myid, hash, timeout, self.errorfunc, excfunc, peers, Event(),
45
+ lambda: 0, lambda: 0)
46
+
47
+ if self.isactive():
48
+ rawserver.add_task(self.refresh, randrange(int(self.interval/10), self.interval))
49
+ # stagger announces
50
+
51
+ def isactive(self):
52
+ if self.isdisallowed(self.tracker): # whoops!
53
+ self.deactivate()
54
+ return self.active
55
+
56
+ def deactivate(self):
57
+ self.active = False
58
+
59
+ def refresh(self):
60
+ if not self.isactive():
61
+ return
62
+ self.lastsuccessful = True
63
+ self.newpeerdata = []
64
+ if DEBUG:
65
+ print 'contacting %s for info_hash=%s' % (self.tracker, quote(self.hash))
66
+ self.rerequester.snoop(self.peers, self.callback)
67
+
68
+ def callback(self):
69
+ self.busy = False
70
+ if self.lastsuccessful:
71
+ self.errors = 0
72
+ self.rejected = 0
73
+ if self.rerequester.announce_interval > (3*self.interval):
74
+ # I think I'm stripping from a regular tracker; boost the number of peers requested
75
+ self.peers = int(self.peers * (self.rerequester.announce_interval / self.interval))
76
+ self.operatinginterval = self.rerequester.announce_interval
77
+ if DEBUG:
78
+ print ("%s with info_hash=%s returned %d peers" %
79
+ (self.tracker, quote(self.hash), len(self.newpeerdata)))
80
+ self.peerlists.append(self.newpeerdata)
81
+ self.peerlists = self.peerlists[-10:] # keep up to the last 10 announces
82
+ if self.isactive():
83
+ self.rawserver.add_task(self.refresh, self.operatinginterval)
84
+
85
+ def addtolist(self, peers):
86
+ for peer in peers:
87
+ self.newpeerdata.append((peer[1],peer[0][0],peer[0][1]))
88
+
89
+ def errorfunc(self, r):
90
+ self.lastsuccessful = False
91
+ if DEBUG:
92
+ print "%s with info_hash=%s gives error: '%s'" % (self.tracker, quote(self.hash), r)
93
+ if r == self.rerequester.rejectedmessage + 'disallowed': # whoops!
94
+ if DEBUG:
95
+ print ' -- disallowed - deactivating'
96
+ self.deactivate()
97
+ self.disallow(self.tracker) # signal other torrents on this tracker
98
+ return
99
+ if lower(r[:8]) == 'rejected': # tracker rejected this particular torrent
100
+ self.rejected += 1
101
+ if self.rejected == 3: # rejected 3 times
102
+ if DEBUG:
103
+ print ' -- rejected 3 times - deactivating'
104
+ self.deactivate()
105
+ return
106
+ self.errors += 1
107
+ if self.errors >= 3: # three or more errors in a row
108
+ self.operatinginterval += self.interval # lengthen the interval
109
+ if DEBUG:
110
+ print ' -- lengthening interval to '+str(self.operatinginterval)+' seconds'
111
+
112
+ def harvest(self):
113
+ x = []
114
+ for list in self.peerlists:
115
+ x += list
116
+ self.peerlists = []
117
+ return x
118
+
119
+
120
+ class T2TList:
121
+ def __init__(self, enabled, trackerid, interval, maxpeers, timeout, rawserver):
122
+ self.enabled = enabled
123
+ self.trackerid = trackerid
124
+ self.interval = interval
125
+ self.maxpeers = maxpeers
126
+ self.timeout = timeout
127
+ self.rawserver = rawserver
128
+ self.list = {}
129
+ self.torrents = {}
130
+ self.disallowed = {}
131
+ self.oldtorrents = []
132
+
133
+ def parse(self, allowed_list):
134
+ if not self.enabled:
135
+ return
136
+
137
+ # step 1: Create a new list with all tracker/torrent combinations in allowed_dir
138
+ newlist = {}
139
+ for hash, data in allowed_list.items():
140
+ if data.has_key('announce-list'):
141
+ for tier in data['announce-list']:
142
+ for tracker in tier:
143
+ self.disallowed.setdefault(tracker, False)
144
+ newlist.setdefault(tracker, {})
145
+ newlist[tracker][hash] = None # placeholder
146
+
147
+ # step 2: Go through and copy old data to the new list.
148
+ # if the new list has no place for it, then it's old, so deactivate it
149
+ for tracker, hashdata in self.list.items():
150
+ for hash, t2t in hashdata.items():
151
+ if not newlist.has_key(tracker) or not newlist[tracker].has_key(hash):
152
+ t2t.deactivate() # this connection is no longer current
153
+ self.oldtorrents += [t2t]
154
+ # keep it referenced in case a thread comes along and tries to access.
155
+ else:
156
+ newlist[tracker][hash] = t2t
157
+ if not newlist.has_key(tracker):
158
+ self.disallowed[tracker] = False # reset when no torrents on it left
159
+
160
+ self.list = newlist
161
+ newtorrents = {}
162
+
163
+ # step 3: If there are any entries that haven't been initialized yet, do so.
164
+ # At the same time, copy all entries onto the by-torrent list.
165
+ for tracker, hashdata in newlist.items():
166
+ for hash, t2t in hashdata.items():
167
+ if t2t is None:
168
+ hashdata[hash] = T2TConnection(self.trackerid, tracker, hash,
169
+ self.interval, self.maxpeers, self.timeout,
170
+ self.rawserver, self._disallow, self._isdisallowed)
171
+ newtorrents.setdefault(hash,[])
172
+ newtorrents[hash] += [hashdata[hash]]
173
+
174
+ self.torrents = newtorrents
175
+
176
+ # structures:
177
+ # list = {tracker: {hash: T2TConnection, ...}, ...}
178
+ # torrents = {hash: [T2TConnection, ...]}
179
+ # disallowed = {tracker: flag, ...}
180
+ # oldtorrents = [T2TConnection, ...]
181
+
182
+ def _disallow(self,tracker):
183
+ self.disallowed[tracker] = True
184
+
185
+ def _isdisallowed(self,tracker):
186
+ return self.disallowed[tracker]
187
+
188
+ def harvest(self,hash):
189
+ harvest = []
190
+ if self.enabled:
191
+ for t2t in self.torrents[hash]:
192
+ harvest += t2t.harvest()
193
+ return harvest
@@ -0,0 +1,145 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ from BitTornado.CurrentRateMeasure import Measure
5
+
6
+ try:
7
+ True
8
+ except:
9
+ True = 1
10
+ False = 0
11
+
12
+ class Upload:
13
+ def __init__(self, connection, ratelimiter, totalup, choker, storage,
14
+ picker, config):
15
+ self.connection = connection
16
+ self.ratelimiter = ratelimiter
17
+ self.totalup = totalup
18
+ self.choker = choker
19
+ self.storage = storage
20
+ self.picker = picker
21
+ self.config = config
22
+ self.max_slice_length = config['max_slice_length']
23
+ self.choked = True
24
+ self.cleared = True
25
+ self.interested = False
26
+ self.super_seeding = False
27
+ self.buffer = []
28
+ self.measure = Measure(config['max_rate_period'], config['upload_rate_fudge'])
29
+ self.was_ever_interested = False
30
+ if storage.get_amount_left() == 0:
31
+ if choker.super_seed:
32
+ self.super_seeding = True # flag, and don't send bitfield
33
+ self.seed_have_list = [] # set from piecepicker
34
+ self.skipped_count = 0
35
+ else:
36
+ if config['breakup_seed_bitfield']:
37
+ bitfield, msgs = storage.get_have_list_cloaked()
38
+ connection.send_bitfield(bitfield)
39
+ for have in msgs:
40
+ connection.send_have(have)
41
+ else:
42
+ connection.send_bitfield(storage.get_have_list())
43
+ else:
44
+ if storage.do_I_have_anything():
45
+ connection.send_bitfield(storage.get_have_list())
46
+ self.piecedl = None
47
+ self.piecebuf = None
48
+
49
+ def got_not_interested(self):
50
+ if self.interested:
51
+ self.interested = False
52
+ del self.buffer[:]
53
+ self.piecedl = None
54
+ if self.piecebuf:
55
+ self.piecebuf.release()
56
+ self.piecebuf = None
57
+ self.choker.not_interested(self.connection)
58
+
59
+ def got_interested(self):
60
+ if not self.interested:
61
+ self.interested = True
62
+ self.was_ever_interested = True
63
+ self.choker.interested(self.connection)
64
+
65
+ def get_upload_chunk(self):
66
+ if self.choked or not self.buffer:
67
+ return None
68
+ index, begin, length = self.buffer.pop(0)
69
+ if self.config['buffer_reads']:
70
+ if index != self.piecedl:
71
+ if self.piecebuf:
72
+ self.piecebuf.release()
73
+ self.piecedl = index
74
+ self.piecebuf = self.storage.get_piece(index, 0, -1)
75
+ try:
76
+ piece = self.piecebuf[begin:begin+length]
77
+ assert len(piece) == length
78
+ except: # fails if storage.get_piece returns None or if out of range
79
+ self.connection.close()
80
+ return None
81
+ else:
82
+ if self.piecebuf:
83
+ self.piecebuf.release()
84
+ self.piecedl = None
85
+ piece = self.storage.get_piece(index, begin, length)
86
+ if piece is None:
87
+ self.connection.close()
88
+ return None
89
+ self.measure.update_rate(len(piece))
90
+ self.totalup.update_rate(len(piece))
91
+ return (index, begin, piece)
92
+
93
+ def got_request(self, index, begin, length):
94
+ if ( (self.super_seeding and not index in self.seed_have_list)
95
+ or not self.interested or length > self.max_slice_length ):
96
+ self.connection.close()
97
+ return
98
+ if not self.cleared:
99
+ self.buffer.append((index, begin, length))
100
+ if not self.choked and self.connection.next_upload is None:
101
+ self.ratelimiter.queue(self.connection)
102
+
103
+
104
+ def got_cancel(self, index, begin, length):
105
+ try:
106
+ self.buffer.remove((index, begin, length))
107
+ except ValueError:
108
+ pass
109
+
110
+ def choke(self):
111
+ if not self.choked:
112
+ self.choked = True
113
+ self.connection.send_choke()
114
+ self.piecedl = None
115
+ if self.piecebuf:
116
+ self.piecebuf.release()
117
+ self.piecebuf = None
118
+
119
+ def choke_sent(self):
120
+ del self.buffer[:]
121
+ self.cleared = True
122
+
123
+ def unchoke(self):
124
+ if self.choked:
125
+ self.choked = False
126
+ self.cleared = False
127
+ self.connection.send_unchoke()
128
+
129
+ def disconnected(self):
130
+ if self.piecebuf:
131
+ self.piecebuf.release()
132
+ self.piecebuf = None
133
+
134
+ def is_choked(self):
135
+ return self.choked
136
+
137
+ def is_interested(self):
138
+ return self.interested
139
+
140
+ def has_queries(self):
141
+ return not self.choked and len(self.buffer) > 0
142
+
143
+ def get_rate(self):
144
+ return self.measure.get_rate()
145
+