📄 track.py
字号:
# The contents of this file are subject to the BitTorrent Open Source License# Version 1.0 (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 refrom threading import Eventfrom 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.obsoletepythonsupport import *from BitTorrent.parseargs import parseargs, formatDefinitionsfrom BitTorrent.RawServer import RawServerfrom BitTorrent.HTTPHandler import HTTPHandler, months, weekdaysfrom BitTorrent.parsedir import parsedirfrom BitTorrent.NatCheck import NatCheckfrom BitTorrent.bencode import bencode, bdecode, Bencachedfrom BitTorrent.zurllib import quote, unquotefrom BitTorrent import versiondefaults = [ ('port', 80, "Port to listen on."), ('dfile', None, 'file to store recent downloader info in'), ('bind', '', 'ip to bind to locally'), ('socket_timeout', 15, 'timeout for closing connections'), ('close_with_rst', 0, 'close connections with RST and avoid the TCP TIME_WAIT state'), ('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, 'default number of peers to send in an info message if the client does not specify a number'), ('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 (and recursively in subdirectories of directories that have no .torrent files themselves). If set, torrents in this directory show up on infopage/scrape whether they have peers or not'), ('parse_dir_interval', 60, 'how often to rescan the torrent directory, in seconds'), ('allowed_controls', 0, 'allow special keys in torrents in the allowed_dir to affect tracker access'), ('hupmonitor', 0, 'whether to reopen the log file upon receipt of HUP signal'), ('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'), ('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). HTTP proxy headers giving address of original client are treated the same as --ip."), ('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). Only matters if allowed_dir is not set'), ('scrape_allowed', 'full', 'scrape access allowed (can be none, specific or full)'), ('max_give', 200, 'maximum number of peers to give with any one request'), ]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 peerid, info in y.items(): # ... 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.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]] = 1alas = 'your file may exist elsewhere in the universe\nbut alas, not here\n'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): 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 0class Tracker(object): def __init__(self, config, rawserver): self.config = config self.response_size = config['response_size'] self.max_give = config['max_give'] self.dfile = 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: print "**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.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() 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: [[l1, s1], [l2, s2], [l3, s3]] for infohash, ds in self.downloads.items(): self.seedcount[infohash] = 0 for x,y in ds.items(): 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, self.save_dfile_interval) self.prevtime = time() self.timeout_downloaders_interval = config['timeout_downloaders_interval'] rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval) 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"
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -