📄 track.py
字号:
# The contents of this file are subject to the BitTorrent Open Source License# Version 1.1 (the License). You may not copy or use this file, in either# source code or executable form, except in compliance with the License. You# may obtain a copy of the License at http://www.bittorrent.com/license/.## Software distributed under the License is distributed on an AS IS basis,# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License# for the specific language governing rights and limitations under the# License.# Written by Bram Cohen and John Hoffmanimport sysimport osimport signalimport reimport cPickleimport loggingimport datetimefrom urlparse import urlparsefrom traceback import print_excfrom time import time, gmtime, strftime, localtimefrom random import shufflefrom types import StringType, IntType, LongType, ListType, DictTypefrom binascii import b2a_hexfrom cStringIO import StringIOfrom BitTorrent.translation import _from BitTorrent.obsoletepythonsupport import *from BitTorrent import platform, BTFailurefrom BitTorrent.platform import decode_from_filesystem, efs2from BitTorrent.defer import DeferredEvent, ThreadedDeferredfrom BitTorrent.yielddefer import wrap_taskfrom BitTorrent.configfile import parse_configuration_and_argsfrom BitTorrent.RawServer_twisted import RawServerfrom BitTorrent.HTTPHandler import HTTPHandlerfrom BitTorrent.parsedir import parsedirfrom BitTorrent.NatCheck import NatCheckfrom BitTorrent.bencode import bencode, bdecode, Bencachedfrom BitTorrent.zurllib import quote, unquotefrom BitTorrent import versionfrom BitTorrent.prefs import Preferencesfrom BitTorrent.defaultargs import get_defaultsfrom BitTorrent.UI import Sizeimport socketimport threadingimport tracebackNOISY = False# code duplication because ow.MAX_INCOMPLETE = 100if os.name == 'nt': from BitTorrent.platform import win_version_num # starting in XP SP2 the incomplete outgoing connection limit was set to 10 if win_version_num >= (2, 5, 1, 2, 0): MAX_INCOMPLETE = 10def statefiletemplate(x): if type(x) != DictType: raise ValueError for cname, cinfo in x.iteritems(): if cname == 'peers': for y in cinfo.itervalues(): # 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 peerid, info in y.iteritems(): # ... of client ids interested in that torrent if (len(peerid) != 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 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.itervalues(): # ... 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'].itervalues()] for y in cinfo.iterkeys(): # 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.itervalues(): # 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]] = 1alas = _("your file may exist elsewhere in the universe\nbut alas, not here\n")def isotime(): #return strftime('%Y-%m-%d %H:%M UTC', gmtime(secs)) return datetime.datetime.utcnow().isoformat()http_via_filter = re.compile(' for ([0-9.]+)\Z')def _get_forwarded_ip(headers): if headers.has_key('http_x_forwarded_for'): header = headers['http_x_forwarded_for'] try: x,y = header.split(',') except: return header if not is_local_ip(x): return x return y if headers.has_key('http_client_ip'): return headers['http_client_ip'] if headers.has_key('http_via'): x = http_via_filter.search(headers['http_via']) try: return x.group(1) except: pass if headers.has_key('http_from'): return headers['http_from'] return Nonedef get_forwarded_ip(headers): x = _get_forwarded_ip(headers) if x is None or not is_valid_ipv4(x) or is_local_ip(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: s = '' except: s = '' # not a valid IP, must be a domain name return sdef is_valid_ipv4(ip): a = ip.split('.') if len(a) != 4: return False try: for x in a: chr(int(x)) return True except: return Falsedef is_local_ip(ip): try: v = [int(x) for x in ip.split('.')] if v[0] == 10 or v[0] == 127 or v[:2] in ([192, 168], [169, 254]): return 1 if v[0] == 172 and v[1] >= 16 and v[1] <= 31: return 1 except ValueError: return 0default_headers = {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}class Tracker(object): def __init__(self, config, rawserver): self.config = config self.response_size = config['response_size'] self.max_give = config['max_give'] self.dfile = efs2(config['dfile']) self.natcheck = config['nat_check'] favicon = config['favicon'] self.favicon = None if favicon: try: h = open(favicon,'r') self.favicon = h.read() h.close() except: errorfunc(logging.WARNING, _("specified favicon file -- %s -- does not exist.") % favicon) self.rawserver = rawserver self.cached = {} # format: infohash: [[time1, l1, s1], [time2, l2, s2], [time3, l3, s3]] self.cached_t = {} # format: infohash: [time, cache] self.times = {} self.state = {} self.seedcount = {} self.save_pending = False self.parse_pending = False 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 os.path.exists(self.dfile): try: h = open(self.dfile, 'rb') ds = h.read() h.close() try: tempstate = cPickle.loads(ds) except: tempstate = bdecode(ds) # backwards-compatibility. if not tempstate.has_key('peers'): tempstate = {'peers': tempstate} statefiletemplate(tempstate) self.state = tempstate except: errorfunc(logging.WARNING, _("statefile %s corrupt; resetting") % self.dfile) self.downloads = self.state.setdefault('peers', {}) self.completed = self.state.setdefault('completed', {}) self.becache = {} # format: infohash: [[l1, s1], [l2, s2], [l3, s3]] for infohash, ds in self.downloads.iteritems(): self.seedcount[infohash] = 0 for x, y in ds.iteritems(): if not y.get('nat', -1): ip = y.get('given_ip') if not (ip and self.allow_local_override(y['ip'], ip)): ip = y['ip'] self.natcheckOK(infohash, x, ip, y['port'], y['left']) if not y['left']: self.seedcount[infohash] += 1 for infohash in self.downloads: self.times[infohash] = {} for peerid in self.downloads[infohash]: self.times[infohash][peerid] = 0 self.reannounce_interval = config['reannounce_interval'] self.save_dfile_interval = config['save_dfile_interval'] self.show_names = config['show_names'] rawserver.add_task(self.save_dfile_interval, self.save_dfile) self.prevtime = time() self.timeout_downloaders_interval = config['timeout_downloaders_interval'] rawserver.add_task(self.timeout_downloaders_interval, self.expire_downloaders) self.logfile = None self.log = None if (config['logfile'] != '') and (config['logfile'] != '-'): try: self.logfile = config['logfile'] self.log = open(self.logfile, 'a') sys.stdout = self.log print _("# Log Started: "), isotime() except: print _("**warning** could not redirect stdout to log file: "), sys.exc_info()[0] if config['hupmonitor']: def huphandler(signum, frame, self = self): try: self.log.close () self.log = open(self.logfile, 'a') sys.stdout = self.log print _("# Log reopened: "), isotime() except: print _("***warning*** could not reopen logfile") signal.signal(signal.SIGHUP, huphandler) self.allow_get = config['allow_get'] if config['allowed_dir'] != '': self.allowed_dir = config['allowed_dir'] self.parse_dir_interval = config['parse_dir_interval'] self.allowed = self.state.setdefault('allowed', {}) self.allowed_dir_files = self.state.setdefault('allowed_dir_files', {}) self.allowed_dir_blocked = {} self.parse_allowed() else: try: del self.state['allowed'] except: pass try: del self.state['allowed_dir_files'] except: pass self.allowed = None self.uq_broken = unquote('+') != ' ' self.keep_dead = config['keep_dead'] def allow_local_override(self, ip, given_ip): return is_valid_ipv4(given_ip) and ( not self.only_local_override_ip or is_local_ip(ip) ) def get_infopage(self): try: if not self.config['show_infopage']: return (404, 'Not Found', default_headers, alas) red = self.config['infopage_redirect'] if red != '':
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -