[tor-commits] [bridgedb/master] 4568 - Add class PluggableTransport to Bridges

aagbsn at torproject.org aagbsn at torproject.org
Sat Mar 16 23:46:31 UTC 2013


commit 4300329a30f3b6aa3e390b140193dd50faa6e03f
Author: aagbsn <aagbsn at extc.org>
Date:   Wed Jun 20 04:40:16 2012 -0700

    4568 - Add class PluggableTransport to Bridges
    
    Adds a class PluggableTransport and function parseExtraInfoFile()
    to read pluggable transports from bridge extra-info descriptors.
    Also adds transport support in FilteredBridgeSplitter.dumpAssignments
---
 lib/bridgedb/Bridges.py |  133 +++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 129 insertions(+), 4 deletions(-)

diff --git a/lib/bridgedb/Bridges.py b/lib/bridgedb/Bridges.py
index 5a3949a..3d72d37 100644
--- a/lib/bridgedb/Bridges.py
+++ b/lib/bridgedb/Bridges.py
@@ -105,7 +105,7 @@ 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
@@ -113,8 +113,11 @@ class Bridge:
         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:
@@ -142,13 +145,14 @@ class Bridge:
             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)
@@ -157,6 +161,18 @@ class Bridge:
         # 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())
@@ -179,8 +195,6 @@ class Bridge:
 
     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
@@ -192,6 +206,9 @@ class Bridge:
                     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)
@@ -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
@@ -760,7 +879,9 @@ class FilteredBridgeSplitter(BridgeHolder):
                 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.
@@ -804,6 +925,10 @@ class FilteredBridgeSplitter(BridgeHolder):
                     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()





More information about the tor-commits mailing list