~ubuntu-branches/ubuntu/quantal/bittornado/quantal

« back to all changes in this revision

Viewing changes to BitTornado/BT1/track.py

  • Committer: Bazaar Package Importer
  • Author(s): Cameron Dale
  • Date: 2008-07-19 16:08:44 UTC
  • mfrom: (0.1.14 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080719160844-nnmp02ar9pri55v1
Tags: 0.3.18-7
* Refresh all the quilt patches (Closes: #485320)
* Update standards version to 3.8.0
  - Add a README.source file pointing to the quilt documentation
* medium urgency to get it into lenny

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
from BitTornado.RawServer import RawServer, autodetect_ipv6, autodetect_socket_style
6
6
from BitTornado.HTTPHandler import HTTPHandler, months, weekdays
7
7
from BitTornado.parsedir import parsedir
8
 
from NatCheck import NatCheck
 
8
from NatCheck import NatCheck, CHECK_PEER_ID_ENCRYPTED
 
9
from BitTornado.BTcrypto import CRYPTO_OK
9
10
from T2T import T2TList
10
11
from BitTornado.subnetparse import IP_List, ipv6_to_ipv4, to_ipv4, is_valid_ip, is_ipv4
11
12
from BitTornado.iprangeparse import IP_List as IP_Range_List
36
37
except:
37
38
    True = 1
38
39
    False = 0
 
40
    bool = lambda x: not not x
39
41
 
40
42
defaults = [
41
43
    ('port', 80, "Port to listen on."),
91
93
    ('keep_dead', 0, 'keep dead torrents after they expire (so they still show up on your /scrape and web page)'),
92
94
    ('scrape_allowed', 'full', 'scrape access allowed (can be none, specific or full)'),
93
95
    ('dedicated_seed_id', '', 'allows tracker to monitor dedicated seed(s) and flag torrents as seeded'),
 
96
    ('compact_reqd', 1, "only allow peers that accept a compact response"),
94
97
  ]
95
98
 
96
99
def statefiletemplate(x):
99
102
    for cname, cinfo in x.items():
100
103
        if cname == 'peers':
101
104
            for y in cinfo.values():      # The 'peers' key is a dictionary of SHA hashes (torrent ids)
102
 
                 if type(y) != DictType:   # ... for the active torrents, and each is a dictionary
103
 
                     raise ValueError
104
 
                 for id, info in y.items(): # ... of client ids interested in that torrent
105
 
                     if (len(id) != 20):
106
 
                         raise ValueError
107
 
                     if type(info) != DictType:  # ... each of which is also a dictionary
108
 
                         raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent
109
 
                     if type(info.get('ip', '')) != StringType:
110
 
                         raise ValueError
111
 
                     port = info.get('port')
112
 
                     if type(port) not in (IntType,LongType) or port < 0:
113
 
                         raise ValueError
114
 
                     left = info.get('left')
115
 
                     if type(left) not in (IntType,LongType) or left < 0:
116
 
                         raise ValueError
 
105
                if type(y) != DictType:   # ... for the active torrents, and each is a dictionary
 
106
                    raise ValueError
 
107
                for id, info in y.items(): # ... of client ids interested in that torrent
 
108
                    if (len(id) != 20):
 
109
                        raise ValueError
 
110
                    if type(info) != DictType:  # ... each of which is also a dictionary
 
111
                        raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent
 
112
                    if type(info.get('ip', '')) != StringType:
 
113
                        raise ValueError
 
114
                    port = info.get('port')
 
115
                    if type(port) not in (IntType,LongType) or port < 0:
 
116
                        raise ValueError
 
117
                    left = info.get('left')
 
118
                    if type(left) not in (IntType,LongType) or left < 0:
 
119
                        raise ValueError
 
120
                    if type(info.get('supportcrypto')) not in (IntType,LongType):
 
121
                        raise ValueError
 
122
                    if type(info.get('requirecrypto')) not in (IntType,LongType):
 
123
                        raise ValueError
117
124
        elif cname == 'completed':
118
125
            if (type(cinfo) != DictType): # The 'completed' key is a dictionary of SHA hashes (torrent ids)
119
126
                raise ValueError          # ... for keeping track of the total completions per torrent
214
221
            except:
215
222
                print "**warning** specified favicon file -- %s -- does not exist." % favicon
216
223
        self.rawserver = rawserver
217
 
        self.cached = {}    # format: infohash: [[time1, l1, s1], [time2, l2, s2], [time3, l3, s3]]
 
224
        self.cached = {}    # format: infohash: [[time1, l1, s1], [time2, l2, s2], ...]
218
225
        self.cached_t = {}  # format: infohash: [time, cache]
219
226
        self.times = {}
220
227
        self.state = {}
231
238
        if self.only_local_override_ip == 2:
232
239
            self.only_local_override_ip = not config['nat_check']
233
240
 
 
241
        if CHECK_PEER_ID_ENCRYPTED and not CRYPTO_OK:
 
242
            print ('**warning** crypto library not installed,' +
 
243
                   ' cannot completely verify encrypted peers')
 
244
 
234
245
        if exists(self.dfile):
235
246
            try:
236
247
                h = open(self.dfile, 'rb')
246
257
        self.downloads = self.state.setdefault('peers', {})
247
258
        self.completed = self.state.setdefault('completed', {})
248
259
 
249
 
        self.becache = {}   # format: infohash: [[l1, s1], [l2, s2], [l3, s3]]
 
260
        self.becache = {}
 
261
        ''' format: infohash: [[l0, s0], [l1, s1], ...]
 
262
                l0,s0 = compact, not requirecrypto=1
 
263
                l1,s1 = compact, only supportcrypto=1
 
264
                l2,s2 = [compact, crypto_flag], all peers
 
265
            if --compact_reqd 0:
 
266
                l3,s3 = [ip,port,id]
 
267
                l4,l4 = [ip,port] nopeerid
 
268
        '''
 
269
        if config['compact_reqd']:
 
270
            x = 3
 
271
        else:
 
272
            x = 5
 
273
        self.cache_default = [({},{}) for i in xrange(x)]
250
274
        for infohash, ds in self.downloads.items():
251
275
            self.seedcount[infohash] = 0
252
276
            for x,y in ds.items():
263
287
                if is_valid_ip(gip) and (
264
288
                    not self.only_local_override_ip or local_IPs.includes(ip) ):
265
289
                    ip = gip
266
 
                self.natcheckOK(infohash,x,ip,y['port'],y['left'])
 
290
                self.natcheckOK(infohash,x,ip,y['port'],y)
267
291
            
268
292
        for x in self.downloads.keys():
269
293
            self.times[x] = {}
459
483
                    else:
460
484
                        s.write('<tr><td><code>%s</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td></tr>\n' \
461
485
                            % (b2a_hex(hash), c, d, n))
462
 
                ttn = 0
463
 
                for i in self.completed.values():
464
 
                    ttn = ttn + i
465
486
                if self.config['allowed_dir'] and self.show_names:
466
 
                    s.write('<tr><td align="right" colspan="2">%i files</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i/%i</td><td align="right">%s</td></tr>\n'
467
 
                            % (nf, size_format(ts), tc, td, tn, ttn, size_format(tt)))
 
487
                    s.write('<tr><td align="right" colspan="2">%i files</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n'
 
488
                            % (nf, size_format(ts), tc, td, tn, size_format(tt)))
468
489
                else:
469
 
                    s.write('<tr><td align="right">%i files</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i/%i</td></tr>\n'
470
 
                            % (nf, tc, td, tn, ttn))
 
490
                    s.write('<tr><td align="right">%i files</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td></tr>\n'
 
491
                            % (nf, tc, td, tn))
471
492
                s.write('</table>\n' \
472
493
                    '<ul>\n' \
473
494
                    '<li><em>info hash:</em> SHA1 hash of the "info" section of the metainfo (*.torrent)</li>\n' \
474
495
                    '<li><em>complete:</em> number of connected clients with the complete file</li>\n' \
475
496
                    '<li><em>downloading:</em> number of connected clients still downloading</li>\n' \
476
 
                    '<li><em>downloaded:</em> reported complete downloads (total: current/all)</li>\n' \
 
497
                    '<li><em>downloaded:</em> reported complete downloads</li>\n' \
477
498
                    '<li><em>transferred:</em> torrent size * total downloaded (does not include partial transfers)</li>\n' \
478
499
                    '</ul>\n')
479
500
 
586
607
            raise ValueError, 'id not of length 20'
587
608
        if event not in ['started', 'completed', 'stopped', 'snooped', None]:
588
609
            raise ValueError, 'invalid event'
589
 
        port = long(params('port',''))
 
610
        port = params('cryptoport')
 
611
        if port is None:
 
612
            port = params('port','')
 
613
        port = long(port)
590
614
        if port < 0 or port > 65535:
591
615
            raise ValueError, 'invalid port'
592
616
        left = long(params('left',''))
594
618
            raise ValueError, 'invalid amount left'
595
619
        uploaded = long(params('uploaded',''))
596
620
        downloaded = long(params('downloaded',''))
 
621
        if params('supportcrypto'):
 
622
            supportcrypto = 1
 
623
            try:
 
624
                s = int(params['requirecrypto'])
 
625
                chr(s)
 
626
            except:
 
627
                s = 0
 
628
            requirecrypto = s
 
629
        else:
 
630
            supportcrypto = 0
 
631
            requirecrypto = 0
597
632
 
598
633
        peer = peers.get(myid)
599
634
        islocal = local_IPs.includes(ip)
619
654
        
620
655
        elif not peer:
621
656
            ts[myid] = clock()
622
 
            peer = {'ip': ip, 'port': port, 'left': left}
 
657
            peer = { 'ip': ip, 'port': port, 'left': left,
 
658
                     'supportcrypto': supportcrypto,
 
659
                     'requirecrypto': requirecrypto }
623
660
            if mykey:
624
661
                peer['key'] = mykey
625
662
            if gip:
627
664
            if port:
628
665
                if not self.natcheck or islocal:
629
666
                    peer['nat'] = 0
630
 
                    self.natcheckOK(infohash,myid,ip1,port,left)
 
667
                    self.natcheckOK(infohash,myid,ip1,port,peer)
631
668
                else:
632
 
                    NatCheck(self.connectback_result,infohash,myid,ip1,port,self.rawserver)
 
669
                    NatCheck(self.connectback_result,infohash,myid,ip1,port,
 
670
                             self.rawserver,encrypted=requirecrypto)
633
671
            else:
634
672
                peer['nat'] = 2**30
635
673
            if event == 'completed':
636
674
                self.completed[infohash] += 1
637
675
            if not left:
638
676
                self.seedcount[infohash] += 1
639
 
 
 
677
                
640
678
            peers[myid] = peer
641
679
 
642
680
        else:
687
725
                if recheck:
688
726
                    if not self.natcheck or islocal:
689
727
                        peer['nat'] = 0
690
 
                        self.natcheckOK(infohash,myid,ip1,port,left)
 
728
                        self.natcheckOK(infohash,myid,ip1,port,peer)
691
729
                    else:
692
 
                        NatCheck(self.connectback_result,infohash,myid,ip1,port,self.rawserver)
 
730
                        NatCheck(self.connectback_result,infohash,myid,ip1,port,
 
731
                                 self.rawserver,encrypted=requirecrypto)
693
732
 
694
733
        return rsize
695
734
 
696
735
 
697
 
    def peerlist(self, infohash, stopped, tracker, is_seed, return_type, rsize):
 
736
    def peerlist(self, infohash, stopped, tracker, is_seed,
 
737
                 return_type, rsize, supportcrypto):
698
738
        data = {}    # return data
699
739
        seeds = self.seedcount[infohash]
700
740
        data['complete'] = seeds
711
751
            cache = self.cached_t.setdefault(infohash, None)
712
752
            if ( not cache or len(cache[1]) < rsize
713
753
                 or cache[0] + self.config['min_time_between_cache_refreshes'] < clock() ):
714
 
                bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]])
 
754
                bc = self.becache.setdefault(infohash,self.cache_default)
715
755
                cache = [ clock(), bc[0][0].values() + bc[0][1].values() ]
716
756
                self.cached_t[infohash] = cache
717
757
                shuffle(cache[1])
726
766
            data['peers'] = []
727
767
            return data
728
768
 
729
 
        bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]])
730
 
        len_l = len(bc[0][0])
731
 
        len_s = len(bc[0][1])
 
769
        bc = self.becache.setdefault(infohash,self.cache_default)
 
770
        len_l = len(bc[2][0])
 
771
        len_s = len(bc[2][1])
732
772
        if not (len_l+len_s):   # caches are empty!
733
773
            data['peers'] = []
734
774
            return data
741
781
            cache = None
742
782
        if not cache:
743
783
            peers = self.downloads[infohash]
744
 
            vv = [[],[],[]]
 
784
            if self.config['compact_reqd']:
 
785
                vv = ([],[],[])
 
786
            else:
 
787
                vv = ([],[],[],[],[])
745
788
            for key, ip, port in self.t2tlist.harvest(infohash):   # empty if disabled
746
789
                if not peers.has_key(key):
747
 
                    vv[0].append({'ip': ip, 'port': port, 'peer id': key})
748
 
                    vv[1].append({'ip': ip, 'port': port})
749
 
                    vv[2].append(compact_peer_info(ip, port))
 
790
                    cp = compact_peer_info(ip, port)
 
791
                    vv[0].append(cp)
 
792
                    vv[2].append((cp,'\x00'))
 
793
                    if not self.config['compact_reqd']:
 
794
                        vv[3].append({'ip': ip, 'port': port, 'peer id': key})
 
795
                        vv[4].append({'ip': ip, 'port': port})
750
796
            cache = [ self.cachetime,
751
797
                      bc[return_type][0].values()+vv[return_type],
752
798
                      bc[return_type][1].values() ]
775
821
            if rsize:
776
822
                peerdata.extend(cache[1][-rsize:])
777
823
                del cache[1][-rsize:]
778
 
        if return_type == 2:
779
 
            peerdata = ''.join(peerdata)
780
 
        data['peers'] = peerdata
 
824
        if return_type == 0:
 
825
            data['peers'] = ''.join(peerdata)
 
826
        elif return_type == 1:
 
827
            data['crypto_flags'] = "0x01"*len(peerdata)
 
828
            data['peers'] = ''.join(peerdata)
 
829
        elif return_type == 2:
 
830
            data['crypto_flags'] = ''.join([p[1] for p in peerdata])
 
831
            data['peers'] = ''.join([p[0] for p in peerdata])
 
832
        else:
 
833
            data['peers'] = peerdata
781
834
        return data
782
835
 
783
836
 
873
926
                    bencode({'response': 'OK'}))
874
927
 
875
928
        if params('compact') and ipv4:
876
 
            return_type = 2
 
929
            if params('requirecrypto'):
 
930
                return_type = 1
 
931
            elif params('supportcrypto'):
 
932
                return_type = 2
 
933
            else:
 
934
                return_type = 0
 
935
        elif self.config['compact_reqd'] and ipv4:
 
936
            return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 
 
937
                'your client is outdated, please upgrade')
877
938
        elif params('no_peer_id'):
878
 
            return_type = 1
 
939
            return_type = 4
879
940
        else:
880
 
            return_type = 0
 
941
            return_type = 3
881
942
            
882
943
        data = self.peerlist(infohash, event=='stopped',
883
944
                             params('tracker'), not params('left'),
884
 
                             return_type, rsize)
 
945
                             return_type, rsize, params('supportcrypto'))
885
946
 
886
947
        if paramslist.has_key('scrape'):    # deprecated
887
948
            data['scrape'] = self.scrapedata(infohash, False)
895
956
        return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode(data))
896
957
 
897
958
 
898
 
    def natcheckOK(self, infohash, peerid, ip, port, not_seed):
899
 
        bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]])
900
 
        bc[0][not not_seed][peerid] = Bencached(bencode({'ip': ip, 'port': port,
901
 
                                              'peer id': peerid}))
902
 
        bc[1][not not_seed][peerid] = Bencached(bencode({'ip': ip, 'port': port}))
903
 
        bc[2][not not_seed][peerid] = compact_peer_info(ip, port)
 
959
    def natcheckOK(self, infohash, peerid, ip, port, peer):
 
960
        seed = not peer['left']
 
961
        bc = self.becache.setdefault(infohash,self.cache_default)
 
962
        cp = compact_peer_info(ip, port)
 
963
        reqc = peer['requirecrypto']
 
964
        bc[2][seed][peerid] = (cp,chr(reqc))
 
965
        if peer['supportcrypto']:
 
966
            bc[1][seed][peerid] = cp
 
967
        if not reqc:
 
968
            bc[0][seed][peerid] = cp
 
969
            if not self.config['compact_reqd']:
 
970
                bc[3][seed][peerid] = Bencached(bencode({'ip': ip, 'port': port,
 
971
                                                         'peer id': peerid}))
 
972
                bc[4][seed][peerid] = Bencached(bencode({'ip': ip, 'port': port}))
904
973
 
905
974
 
906
975
    def natchecklog(self, peerid, ip, port, result):
910
979
            ip, port, result)
911
980
 
912
981
    def connectback_result(self, result, downloadid, peerid, ip, port):
913
 
        record = self.downloads.get(downloadid, {}).get(peerid)
 
982
        record = self.downloads.get(downloadid,{}).get(peerid)
914
983
        if ( record is None 
915
984
                 or (record['ip'] != ip and record.get('given ip') != ip)
916
985
                 or record['port'] != port ):
926
995
        if not record.has_key('nat'):
927
996
            record['nat'] = int(not result)
928
997
            if result:
929
 
                self.natcheckOK(downloadid,peerid,ip,port,record['left'])
 
998
                self.natcheckOK(downloadid,peerid,ip,port,record)
930
999
        elif result and record['nat']:
931
1000
            record['nat'] = 0
932
 
            self.natcheckOK(downloadid,peerid,ip,port,record['left'])
 
1001
            self.natcheckOK(downloadid,peerid,ip,port,record)
933
1002
        elif not result:
934
1003
            record['nat'] += 1
935
1004
 
1013
1082
            l = self.becache[infohash]
1014
1083
            y = not peer['left']
1015
1084
            for x in l:
1016
 
                del x[y][peerid]
 
1085
                if x[y].has_key(peerid):
 
1086
                    del x[y][peerid]
1017
1087
        del self.times[infohash][peerid]
1018
1088
        del dls[peerid]
1019
1089