Commit a7dfc583 authored by Nick Mathewson's avatar Nick Mathewson 👉
Browse files

Add support for the stable flag and for proxy lists.

svn:r15143
parent ac136fe5
......@@ -37,6 +37,15 @@ N_IP_CLUSTERS = 4
# This is a list of (port,minimum) tuples.
FORCE_PORTS = [ (443, 1) ]
# If possible, always give a certain number of answers with a given flag.
# Only "stable" is now supported. This is a list of (flag,minimum) tuples.
FORCE_FLAGS = [ ("Stable", 1) ]
# A list of filenames that contain IP addresses (one per line) of proxies.
# All IP-based distributors that see an incoming connection from a proxy
# will treat them specially.
PROXY_LIST_FILES = [ ]
#==========
# Options related to HTTPS
......
......@@ -106,7 +106,7 @@ class Bridge:
self.nickname = nickname
self.ip = ip
self.orport = orport
self.running = None
self.running = self.stable = None
if id_digest is not None:
assert fingerprint is None
if len(id_digest) != DIGEST_LEN:
......@@ -138,9 +138,11 @@ class Bridge:
assert is_valid_fingerprint(self.fingerprint)
assert 1 <= self.orport <= 65535
def setStatus(self, running=None):
def setStatus(self, running=None, stable=None):
if running is not None:
self.running = running
if stable is not None:
self.stable = stable
def parseDescFile(f, bridge_purpose='bridge'):
......@@ -190,11 +192,7 @@ def parseStatusFile(f):
logging.warn("Unparseable base64 ID %r", line.split()[2])
elif ID and line.startswith("s "):
flags = line.split()
if "Running" in flags:
yield ID, True
else:
yield ID, False
ID = None
yield ID, ("Running" in flags), ("Stable" in flags)
class BridgeHolder:
"""Abstract base class for all classes that hold bridges."""
......@@ -206,15 +204,22 @@ class BridgeHolder:
class BridgeRingParameters:
"""DOCDOC"""
def __init__(self, needPorts=()):
def __init__(self, needPorts=(), needFlags=()):
"""DOCDOC takes list of port, count"""
for port,count in needPorts:
if not (1 <= port <= 65535):
raise TypeError("Port %s out of range."%port)
if count <= 0:
raise TypeError("Count %s out of range."%count)
for flag, count in needFlags:
flag = flag.lower()
if flag not in [ "stable" ]:
raise TypeError("Unsupported flag %s"%flag)
if count <= 0:
raise TypeError("Count %s out of range."%count)
self.needPorts = needPorts[:]
self.needFlags = needFlags[:]
class BridgeRing(BridgeHolder):
"""Arranges bridges in a ring based on an hmac function."""
......@@ -235,19 +240,24 @@ class BridgeRing(BridgeHolder):
answerParameters = BridgeRingParameters()
self.answerParameters = answerParameters
self.portSubrings = [] #DOCDOC
self.subrings = [] #DOCDOC
for port,count in self.answerParameters.needPorts:
#note that we really need to use the same key here, so that
# the mapping is in the same order for all subrings.
self.portSubrings.append( (port,count,BridgeRing(key,None)) )
self.subrings.append( ('port',port,count,BridgeRing(key,None)) )
for flag,count in self.answerParameters.needFlags:
self.subrings.append( ('flag',flag,count,BridgeRing(key,None)) )
self.setName("Ring")
def setName(self, name):
"""DOCDOC"""
self.name = name
for port,_,subring in self.portSubrings:
subring.setName("%s (port-%s subring)"%(name, port))
for tp,val,_,subring in self.subrings:
if tp == 'port':
subring.setName("%s (port-%s subring)"%(name, val))
else:
subring.setname("%s (%s subring)"%(name, val))
def __len__(self):
return len(self.bridges)
......@@ -255,9 +265,14 @@ class BridgeRing(BridgeHolder):
def insert(self, bridge):
"""Add a bridge to the ring. If the bridge is already there,
replace the old one."""
for port,_,subring in self.portSubrings:
if port == bridge.orport:
subring.insert(bridge)
for tp,val,_,subring in self.subrings:
if tp == 'port':
if val == bridge.orport:
subring.insert(bridge)
else:
assert tp == 'flag' and val == 'stable'
if val == 'stable' and bridge.stable:
subring.insert(bridge)
ident = bridge.getID()
pos = self.hmac(ident)
......@@ -293,7 +308,7 @@ class BridgeRing(BridgeHolder):
def getBridges(self, pos, N=1):
"""Return the N bridges appearing in the ring after position pos"""
forced = []
for _,count,subring in self.portSubrings:
for _,_,count,subring in self.subrings:
if len(subring) < count:
count = len.subring
forced.extend(subring._getBridgeKeysAt(pos, count))
......@@ -309,7 +324,7 @@ class BridgeRing(BridgeHolder):
def getBridgeByID(self, fp):
"""Return the bridge whose identity digest is fp, or None if no such
bridge exists."""
for _,_,subring in self.portSubrings:
for _,_,_,subring in self.subrings:
b = subring.getBridgeByID(fp)
if b is not None:
return b
......
......@@ -43,7 +43,9 @@ CONFIG = Conf(
N_IP_CLUSTERS = 4,
MASTER_KEY_FILE = "./secret_key",
REQUIRE_ORPORTS = [(443, 1)],
FORCE_PORTS = [(443, 1)],
FORCE_FLAGS = [("Stable", 1)],
PROXY_LIST_FILES = [ ],
HTTPS_DIST = True,
HTTPS_SHARE=10,
......@@ -127,24 +129,47 @@ def load(cfg, splitter):
status = {}
if hasattr(cfg, "STATUS_FILE"):
f = open(cfg.STATUS_FILE, 'r')
for ID, running in Bridges.parseStatusFile(f):
status[ID] = running
for ID, running, stable in Bridges.parseStatusFile(f):
status[ID] = running, stable
for fname in cfg.BRIDGE_FILES:
f = open(fname, 'r')
for bridge in Bridges.parseDescFile(f, cfg.BRIDGE_PURPOSE):
running = status.get(bridge.getID())
if running is not None:
bridge.setStatus(running=running)
s = status.get(bridge.getID())
if s is not None:
running, stable = s
bridge.setStatus(running=running, stable=stable)
splitter.insert(bridge)
f.close()
def loadProxyList(cfg):
ipset = {}
for fname in cfg.PROXY_LIST_FILES:
f = open(fname, 'r')
for line in f:
line = line.strip()
if Bridges.is_valid_ip(line):
ipset[line] = True
elif line:
logging.info("Skipping line %r in %s: not an IP.",
line, fname)
f.close()
return ipset
_reloadFn = lambda: True
def _handleSIGHUP(*args):
"""Called when we receive a SIGHUP; invokes _reloadFn."""
reactor.callLater(0, _reloadFn)
class ProxyCategory:
def __init__(self):
self.ipset = {}
def contains(self, ip):
return self.ipset.has_key(ip)
def replaceProxyList(self, ipset):
self.ipset = ipset
def startup(cfg):
"""Parse bridges,
"""Parse bridges,
"""
# Expand any ~ characters in paths in the configuration.
cfg.BRIDGE_FILES = [ os.path.expanduser(fn) for fn in cfg.BRIDGE_FILES ]
......@@ -154,6 +179,11 @@ def startup(cfg):
v = getattr(cfg, key, None)
if v:
setattr(cfg, key, os.path.expanduser(v))
if hasattr(cfg, "PROXY_LIST_FILES"):
cfg.PROXY_LIST_FILES = [
os.path.expanduser(v) for v in cfg.PROXY_LIST_FILES ]
else:
cfg.PROXY_LIST_FILES = [ ]
# Change to the directory where we're supposed to run.
if cfg.RUN_IN_DIR:
......@@ -178,6 +208,10 @@ def startup(cfg):
dblogfile = open(cfg.DB_LOG_FILE, "a+", 0)
store = Bridges.LogDB(None, store, dblogfile)
# Get a proxy list.
proxyList = ProxyCategory()
proxyList.replaceProxyList(loadProxyList(cfg))
# Create a BridgeSplitter to assign the bridges to the different
# distributors.
splitter = Bridges.BridgeSplitter(Bridges.get_hmac(key, "Splitter-Key"),
......@@ -185,15 +219,23 @@ def startup(cfg):
# Create ring parameters.
forcePorts = getattr(cfg, "FORCE_PORTS")
ringParams=Bridges.BridgeRingParameters(forcePorts=forcePorts)
forceFlags = getattr(cfg, "FORCE_FLAGS")
if not forcePorts: forcePorts = []
if not forceFlags: forceFlags = []
ringParams=Bridges.BridgeRingParameters(needPorts=forcePorts,
needFlags=forceFlags)
emailDistributor = ipDistributor = None
# As appropriate, create an IP-based distributor.
if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
categories = []
if proxyList.ipset:
categories.append(proxyList)
ipDistributor = Dist.IPBasedDistributor(
Dist.uniformMap,
cfg.N_IP_CLUSTERS,
Bridges.get_hmac(key, "HTTPS-IP-Dist-Key"),
categories,
answerParameters=ringParams)
splitter.addRing(ipDistributor, "https", cfg.HTTPS_SHARE)
webSchedule = Time.IntervalSchedule("day", 2)
......@@ -226,6 +268,7 @@ def startup(cfg):
def reload():
logging.info("Caught SIGHUP")
load(cfg, splitter)
proxyList.replaceProxyList(loadProxyList(cfg))
logging.info("%d bridges loaded", len(splitter))
if emailDistributor:
logging.info("%d for email", len(emailDistributor.ring))
......@@ -233,6 +276,7 @@ def startup(cfg):
logging.info("%d for web:", len(ipDistributor.splitter))
logging.info(" by location set: %s",
" ".join(str(len(r)) for r in ipDistributor.rings))
global _reloadFn
_reloadFn = reload
signal.signal(signal.SIGHUP, _handleSIGHUP)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment