Skip to content

Commit

Permalink
4568 - Add class PluggableTransport to Bridges
Browse files Browse the repository at this point in the history
Adds a class PluggableTransport and function parseExtraInfoFile()
to read pluggable transports from bridge extra-info descriptors.
Also adds transport support in FilteredBridgeSplitter.dumpAssignments
  • Loading branch information
aagbsn committed Jun 20, 2012
1 parent d6605fa commit 4300329
Showing 1 changed file with 129 additions and 4 deletions.
133 changes: 129 additions & 4 deletions lib/bridgedb/Bridges.py
Expand Up @@ -105,16 +105,19 @@ class Bridge:
## running,stable -- DOCDOC
## blockingCountries -- list of country codes blocking this bridge
def __init__(self, nickname, ip, orport, fingerprint=None, id_digest=None,
or_addresses=None):
or_addresses=None, transports=None):
"""Create a new Bridge. One of fingerprint and id_digest must be
set."""
self.nickname = nickname
self.ip = ip
self.orport = orport
if not or_addresses: or_addresses = {}
self.or_addresses = or_addresses
if not transports: transports = []
self.transports = transports
self.running = self.stable = None
self.blockingCountries = None

if id_digest is not None:
assert fingerprint is None
if len(id_digest) != DIGEST_LEN:
Expand Down Expand Up @@ -142,13 +145,14 @@ def __repr__(self):
self.nickname, self.ip, self.orport, self.fingerprint)

def getConfigLine(self, includeFingerprint=False, addressClass=None,
request=None):
request=None, transport=None):
"""Returns a valid bridge line for inclusion in a torrc"""
#arguments:
# includeFingerprint
# addressClass - type of address to choose
# request - a string unique to this request
# e.g. email-address or uniformMap(ip)
# transport - a pluggable transport method name

if not request: request = 'default'
digest = get_hmac_fn('Order-Or-Addresses')(request)
Expand All @@ -157,6 +161,18 @@ def getConfigLine(self, includeFingerprint=False, addressClass=None,
# default address type
if not addressClass: addressClass = ipaddr.IPv4Address

# pluggable transports
if transport:
# filter by 'methodname'
transports = filter(lambda x: transport == x.methodname,
self.transports)
# filter by 'addressClass'
transports = filter(lambda x: isinstance(x.address, addressClass),
transports)
if transports:
pt = transports[pos % len(transports)]
return pt.getTransportLine(includeFingerprint)

# filter addresses by address class
addresses = filter(lambda x: isinstance(x[0], addressClass),
self.or_addresses.items())
Expand All @@ -179,8 +195,6 @@ def getConfigLine(self, includeFingerprint=False, addressClass=None,

def getAllConfigLines(self,includeFingerprint=False):
"""Generator. Iterate over all valid config lines for this bridge."""
# warning: a bridge with large port ranges may generate thousands
# of lines of output
for address,portlist in self.or_addresses.items():
if type(address) is ipaddr.IPv6Address:
ip = "[%s]" % address
Expand All @@ -192,6 +206,9 @@ def getAllConfigLines(self,includeFingerprint=False):
yield "bridge %s:%d %s" % (ip,orport,self.fingerprint)
else:
yield "bridge %s:%d" % (ip,orport)
for pt in self.transports:
yield pt.getTransportLine(includeFingerprints)


def assertOK(self):
assert is_valid_ip(self.ip)
Expand Down Expand Up @@ -352,6 +369,108 @@ def parseORAddressLine(line):
if address and portlist and len(portlist): return address,portlist
raise ParseORAddressError

class PluggableTransport:
"""
an object that represents a pluggable-transport method
and a reference to the relevant bridge
"""
def __init__(self, bridge, methodname, address, port, argdict=None):

#XXX: assert are disabled with python -O
assert isinstance(bridge, Bridge)
assert type(address) in (ipaddr.IPv4Address, ipaddr.IPv6Address)
assert type(port) is int
assert (0 < port < 65536)
assert type(methodname) is str

self.bridge = bridge
self.address = address
self.port = port
self.methodname = methodname
if type(argdict) is dict:
self.argdict = argdict
else: self.argdict = {}

def getTransportLine(self, includeFingerprint=False):
"""
returns a torrc bridge line for this transport
"""
if isinstance(self.address,ipaddr.IPv6Address):
address = "[%s]" % self.address
else: address = self.address
host = "bridge %s %s:%d" % (self.methodname, address, self.port)
fp = ''
if includeFingerprint: fp = "keyid=%s" % self.bridge.fingerprint
args = " ".join(["%s=%s"%(k,v) for k,v in self.argdict.items()]).strip()
return "%s %s %s" % (host, fp, args)

def parseExtraInfoFile(f):
"""
parses lines in Bridges extra-info documents.
returns an object whose type corresponds to the
relevant set of extra-info lines.
presently supported lines and the accompanying type are:
{ 'transport': PluggableTransport, }
'transport' lines (torspec.git/proposals/180-pluggable-transport.txt)
Bridges put the 'transport' lines in their extra-info documents.
the format is:
transport SP <methodname> SP <address:port> [SP arglist] NL
"""

ID = None
for line in f:
line = line.strip()

argdict = {}

# do we need to skip 'opt' here?
# if line.startswith("opt "):
# line = line[4:]

# get the bridge ID ?
if line.startswith("extra-info "): #XXX: get the router ID
line = line[11:]
(nickname, ID) = line.split()
if is_valid_fingerprint(ID):
ID = fromHex(ID)

# get the transport line
if ID and line.startswith("transport "):
fields = line[10:].split()
# [ arglist ] field, optional
if len(fields) >= 3:
arglist = fields[2:]
# parse arglist [k=v,...k=v] as argdict {k:v,...,k:v}
argdict = {}
for arg in arglist:
try: k,v = arg.split('=')
except ValueError: continue
argdict[k] = v

# get the required fields, method name and address
if len(fields) >= 2:
# get the method name
# Method names must be C identifiers
for regex in [re_ipv4, re_ipv6]:
try:
method_name = re.match('[_a-zA-Z][_a-zA-Z0-9]*',fields[0]).group()
m = regex.match(fields[1])
address = ipaddr.IPAddress(m.group(1))
port = int(m.group(2))
yield ID, method_name, address, port, argdict
except (IndexError, ValueError, AttributeError):
# skip this line
continue

# end of descriptor is defined how?
if ID and line.startswith("router-signature"):
ID = None

def parseStatusFile(f):
"""DOCDOC"""
ID = None
Expand Down Expand Up @@ -760,7 +879,9 @@ def insert(self, bridge):
logging.debug("insert bridge into %s" % n)

#XXX db.insertBridgeAndGetRing ??
# already 'assigned' by the FixedBridgeSplitter
#XXX persisent mapping?
# the filters rebuild

def addRing(self, ring, ringname, filterFn, populate_from=None):
"""Add a ring to this splitter.
Expand Down Expand Up @@ -804,6 +925,10 @@ def dumpAssignments(self, f, description=""):
except TypeError:
desc.append(g.description)

# add transports
for transport in b.transports:
desc.append("transport=%s"%(transport.methodname))

# dedupe and group
desc = set(desc)
grouped = dict()
Expand Down

0 comments on commit 4300329

Please sign in to comment.