📄 track.py
字号:
# Written by Bram Cohen# see LICENSE.txt for license informationfrom BitTornado.parseargs import parseargs, formatDefinitionsfrom BitTornado.RawServer import RawServer, autodetect_ipv6, autodetect_socket_stylefrom BitTornado.HTTPHandler import HTTPHandler, months, weekdaysfrom BitTornado.parsedir import parsedirfrom NatCheck import NatCheck, CHECK_PEER_ID_ENCRYPTEDfrom BitTornado.BTcrypto import CRYPTO_OKfrom T2T import T2TListfrom BitTornado.subnetparse import IP_List, ipv6_to_ipv4, to_ipv4, is_valid_ip, is_ipv4from BitTornado.iprangeparse import IP_List as IP_Range_Listfrom BitTornado.torrentlistparse import parsetorrentlistfrom threading import Event, Threadfrom BitTornado.bencode import bencode, bdecode, Bencachedfrom BitTornado.zurllib import urlopen, quote, unquotefrom Filter import Filterfrom urlparse import urlparsefrom os import rename, getpidfrom os.path import exists, isfilefrom cStringIO import StringIOfrom traceback import print_excfrom time import time, gmtime, strftime, localtimefrom BitTornado.clock import clockfrom random import shuffle, seed, randrangefrom sha import shafrom types import StringType, IntType, LongType, ListType, DictTypefrom binascii import b2a_hex, a2b_hex, a2b_base64from string import lowerimport sys, osimport signalimport reimport BitTornado.__init__from BitTornado.__init__ import version, createPeerIDtry: Trueexcept: True = 1 False = 0 bool = lambda x: not not xdefaults = [ ('port', 80, "Port to listen on."), ('dfile', None, 'file to store recent downloader info in'), ('bind', '', 'comma-separated list of ips/hostnames to bind to locally'),# ('ipv6_enabled', autodetect_ipv6(), ('ipv6_enabled', 0, 'allow the client to connect to peers via IPv6'), ('ipv6_binds_v4', autodetect_socket_style(), 'set if an IPv6 server socket will also field IPv4 connections'), ('socket_timeout', 15, 'timeout for closing connections'), ('save_dfile_interval', 5 * 60, 'seconds between saving dfile'), ('timeout_downloaders_interval', 45 * 60, 'seconds between expiring downloaders'), ('reannounce_interval', 30 * 60, 'seconds downloaders should wait between reannouncements'), ('response_size', 50, 'number of peers to send in an info message'), ('timeout_check_interval', 5, 'time to wait between checking if any connections have timed out'), ('nat_check', 3, "how many times to check if a downloader is behind a NAT (0 = don't check)"), ('log_nat_checks', 0, "whether to add entries to the log for nat-check results"), ('min_time_between_log_flushes', 3.0, 'minimum time it must have been since the last flush to do another one'), ('min_time_between_cache_refreshes', 600.0, 'minimum time in seconds before a cache is considered stale and is flushed'), ('allowed_dir', '', 'only allow downloads for .torrents in this dir'), ('allowed_list', '', 'only allow downloads for hashes in this list (hex format, one per line)'), ('allowed_controls', 0, 'allow special keys in torrents in the allowed_dir to affect tracker access'), ('multitracker_enabled', 0, 'whether to enable multitracker operation'), ('multitracker_allowed', 'autodetect', 'whether to allow incoming tracker announces (can be none, autodetect or all)'), ('multitracker_reannounce_interval', 2 * 60, 'seconds between outgoing tracker announces'), ('multitracker_maxpeers', 20, 'number of peers to get in a tracker announce'), ('aggregate_forward', '', 'format: <url>[,<password>] - if set, forwards all non-multitracker to this url with this optional password'), ('aggregator', '0', 'whether to act as a data aggregator rather than a tracker. If enabled, may be 1, or <password>; ' + 'if password is set, then an incoming password is required for access'), ('hupmonitor', 0, 'whether to reopen the log file upon receipt of HUP signal'), ('http_timeout', 60, 'number of seconds to wait before assuming that an http connection has timed out'), ('parse_dir_interval', 60, 'seconds between reloading of allowed_dir or allowed_file ' + 'and allowed_ips and banned_ips lists'), ('show_infopage', 1, "whether to display an info page when the tracker's root dir is loaded"), ('infopage_redirect', '', 'a URL to redirect the info page to'), ('show_names', 1, 'whether to display names from allowed dir'), ('favicon', '', 'file containing x-icon data to return when browser requests favicon.ico'), ('allowed_ips', '', 'only allow connections from IPs specified in the given file; '+ 'file contains subnet data in the format: aa.bb.cc.dd/len'), ('banned_ips', '', "don't allow connections from IPs specified in the given file; "+ 'file contains IP range data in the format: xxx:xxx:ip1-ip2'), ('only_local_override_ip', 2, "ignore the ip GET parameter from machines which aren't on local network IPs " + "(0 = never, 1 = always, 2 = ignore if NAT checking is not enabled)"), ('logfile', '', 'file to write the tracker logs, use - for stdout (default)'), ('allow_get', 0, 'use with allowed_dir; adds a /file?hash={hash} url that allows users to download the torrent file'), ('keep_dead', 0, 'keep dead torrents after they expire (so they still show up on your /scrape and web page)'), ('scrape_allowed', 'full', 'scrape access allowed (can be none, specific or full)'), ('dedicated_seed_id', '', 'allows tracker to monitor dedicated seed(s) and flag torrents as seeded'), ('compact_reqd', 1, "only allow peers that accept a compact response"), ]def statefiletemplate(x): if type(x) != DictType: raise ValueError for cname, cinfo in x.items(): if cname == 'peers': for y in cinfo.values(): # The 'peers' key is a dictionary of SHA hashes (torrent ids) if type(y) != DictType: # ... for the active torrents, and each is a dictionary raise ValueError for id, info in y.items(): # ... of client ids interested in that torrent if (len(id) != 20): raise ValueError if type(info) != DictType: # ... each of which is also a dictionary raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent if type(info.get('ip', '')) != StringType: raise ValueError port = info.get('port') if type(port) not in (IntType,LongType) or port < 0: raise ValueError left = info.get('left') if type(left) not in (IntType,LongType) or left < 0: raise ValueError if type(info.get('supportcrypto')) not in (IntType,LongType): raise ValueError if type(info.get('requirecrypto')) not in (IntType,LongType): raise ValueError elif cname == 'completed': if (type(cinfo) != DictType): # The 'completed' key is a dictionary of SHA hashes (torrent ids) raise ValueError # ... for keeping track of the total completions per torrent for y in cinfo.values(): # ... each torrent has an integer value if type(y) not in (IntType,LongType): raise ValueError # ... for the number of reported completions for that torrent elif cname == 'allowed': if (type(cinfo) != DictType): # a list of info_hashes and included data raise ValueError if x.has_key('allowed_dir_files'): adlist = [z[1] for z in x['allowed_dir_files'].values()] for y in cinfo.keys(): # and each should have a corresponding key here if not y in adlist: raise ValueError elif cname == 'allowed_dir_files': if (type(cinfo) != DictType): # a list of files, their attributes and info hashes raise ValueError dirkeys = {} for y in cinfo.values(): # each entry should have a corresponding info_hash if not y[1]: continue if not x['allowed'].has_key(y[1]): raise ValueError if dirkeys.has_key(y[1]): # and each should have a unique info_hash raise ValueError dirkeys[y[1]] = 1 alas = 'your file may exist elsewhere in the universe\nbut alas, not here\n'local_IPs = IP_List()local_IPs.set_intranet_addresses()def isotime(secs = None): if secs == None: secs = time() return strftime('%Y-%m-%d %H:%M UTC', gmtime(secs))http_via_filter = re.compile(' for ([0-9.]+)\Z')def _get_forwarded_ip(headers): header = headers.get('x-forwarded-for') if header: try: x,y = header.split(',') except: return header if is_valid_ip(x) and not local_IPs.includes(x): return x return y header = headers.get('client-ip') if header: return header header = headers.get('via') if header: x = http_via_filter.search(header) try: return x.group(1) except: pass header = headers.get('from') #if header: # return header #return None return headerdef get_forwarded_ip(headers): x = _get_forwarded_ip(headers) if not is_valid_ip(x) or local_IPs.includes(x): return None return xdef compact_peer_info(ip, port): try: s = ( ''.join([chr(int(i)) for i in ip.split('.')]) + chr((port & 0xFF00) >> 8) + chr(port & 0xFF) ) if len(s) != 6: raise ValueError except: s = '' # not a valid IP, must be a domain name return sclass Tracker: def __init__(self, config, rawserver): self.config = config self.response_size = config['response_size'] self.dfile = config['dfile'] self.natcheck = config['nat_check'] favicon = config['favicon'] self.parse_dir_interval = config['parse_dir_interval'] self.favicon = None if favicon: try: h = open(favicon,'r') self.favicon = h.read() h.close() except: print "**warning** specified favicon file -- %s -- does not exist." % favicon self.rawserver = rawserver self.cached = {} # format: infohash: [[time1, l1, s1], [time2, l2, s2], ...] self.cached_t = {} # format: infohash: [time, cache] self.times = {} self.state = {} self.seedcount = {} self.allowed_IPs = None self.banned_IPs = None if config['allowed_ips'] or config['banned_ips']: self.allowed_ip_mtime = 0 self.banned_ip_mtime = 0 self.read_ip_lists() self.only_local_override_ip = config['only_local_override_ip'] if self.only_local_override_ip == 2: self.only_local_override_ip = not config['nat_check'] if CHECK_PEER_ID_ENCRYPTED and not CRYPTO_OK: print ('**warning** crypto library not installed,' + ' cannot completely verify encrypted peers') if exists(self.dfile): try: h = open(self.dfile, 'rb') ds = h.read() h.close() tempstate = bdecode(ds) if not tempstate.has_key('peers'): tempstate = {'peers': tempstate} statefiletemplate(tempstate) self.state = tempstate except: print '**warning** statefile '+self.dfile+' corrupt; resetting' self.downloads = self.state.setdefault('peers', {}) self.completed = self.state.setdefault('completed', {}) self.becache = {} ''' format: infohash: [[l0, s0], [l1, s1], ...] l0,s0 = compact, not requirecrypto=1 l1,s1 = compact, only supportcrypto=1 l2,s2 = [compact, crypto_flag], all peers if --compact_reqd 0: l3,s3 = [ip,port,id] l4,l4 = [ip,port] nopeerid ''' if config['compact_reqd']: x = 3 else: x = 5 self.cache_default = [({},{}) for i in xrange(x)] for infohash, ds in self.downloads.items(): self.seedcount[infohash] = 0 for x,y in ds.items(): ip = y['ip'] if ( (self.allowed_IPs and not self.allowed_IPs.includes(ip)) or (self.banned_IPs and self.banned_IPs.includes(ip)) ): del ds[x] continue if not y['left']: self.seedcount[infohash] += 1 if y.get('nat',-1): continue
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -