[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [bridgedb/master] 4297 - Update PortSpec parsing to reflect spec



commit 94a5313837b30c07dd313e15715f1a66c7c39feb
Author: aagbsn <aagbsn@xxxxxxxx>
Date:   Wed May 30 19:15:31 2012 -0700

    4297 - Update PortSpec parsing to reflect spec
    
    proposal 186-multiple-orports.txt dropped port range support.
---
 lib/bridgedb/Bridges.py |  222 +++++++++++++++++------------------------------
 lib/bridgedb/Server.py  |   15 ++--
 2 files changed, 85 insertions(+), 152 deletions(-)

diff --git a/lib/bridgedb/Bridges.py b/lib/bridgedb/Bridges.py
index 281e5f2..1e15157 100644
--- a/lib/bridgedb/Bridges.py
+++ b/lib/bridgedb/Bridges.py
@@ -25,6 +25,7 @@ ID_LEN = 20
 DIGESTMOD = sha
 HEX_DIGEST_LEN = 40
 DIGEST_LEN = 20
+PORTSPEC_LEN = 16
 
 def is_valid_ip(ip):
     """Return True if ip is the string encoding of a valid IPv4 address,
@@ -65,17 +66,6 @@ def is_valid_fingerprint(fp):
     else:
         return True
 
-def is_valid_or_address(or_address):
-    """Return true iff or_address is in the right format
-       (ip,frozenset(port)) or (ip, frozenset(port_low,port_high)) for ranges
-    """
-    if len(or_address) != 2: return False
-    ip,port = or_address
-    if not is_valid_ip(ip): return False
-    if type(port) is not int: return False
-    if not (1 <= port <= 65535): return False
-    return True
-
 toHex = binascii.b2a_hex
 fromHex = binascii.a2b_hex
 
@@ -152,29 +142,31 @@ class Bridge:
             self.nickname, self.ip, self.orport, self.fingerprint)
 
     def getConfigLine(self,includeFingerprint=False,
-            selectFromORAddresses=False,
             needIPv4=True, needIPv6=False):
         """Return a line describing this bridge for inclusion in a torrc."""
 
         # select an address:port from or-addresses
-        if selectFromORAddresses and self.or_addresses:
-            filtered_addresses = None
-            # bridges may have both classes. we only return one.
-            if needIPv6:
-                f = lambda x: type(x[0]) is ipaddr.IPv6Address
-                filtered_addresses = filter(f, self.or_addresses.items())
-            elif needIPv4:
-                f = lambda x: type(x[0]) is ipaddr.IPv4Address
-                filtered_addresses = filter(f, self.or_addresses.items())
-            if filtered_addresses:
-                address,portlist = random.choice(filtered_addresses)
-                if type(address) is ipaddr.IPv6Address:
-                    ip = "[%s]"%address
-                else:
-                    ip = "%s"%address
-                orport = portlist.getPort() #magic
+        filtered_addresses = None
+        # bridges may have both classes. we only return one.
+        if needIPv6:
+            f = lambda x: type(x[0]) is ipaddr.IPv6Address
+            filtered_addresses = filter(f, self.or_addresses.items())
+        elif needIPv4:
+            f = lambda x: type(x[0]) is ipaddr.IPv4Address
+            filtered_addresses = filter(f, self.or_addresses.items())
+            # default ip, orport should get a chance at being selected
+            if type(ipaddr.IPAddress(self.ip)) is ipaddr.IPv4Address:
+                filtered_addresses.append((ipaddr.IPAddress(self.ip), set([int(self.orport)])))
+
+        if filtered_addresses:
+            address,portlist = random.choice(filtered_addresses)
+            if type(address) is ipaddr.IPv6Address:
+                ip = "[%s]"%address
+            else:
+                ip = "%s"%address
+            orport = random.choice(list(portlist))
 
-        # default to ip,orport ; ex. when logging
+        # default to ip,orport 
         else:
             ip = self.ip
             orport = self.orport
@@ -204,6 +196,12 @@ class Bridge:
         assert is_valid_ip(self.ip)
         assert is_valid_fingerprint(self.fingerprint)
         assert 1 <= self.orport <= 65535
+        if self.or_addresses:
+            for address, portlist in self.or_addresses.items():
+                assert is_valid_ip(address)
+                for port in portlist:
+                    assert type(port) is int
+                    assert 1 <= port <= 65535
 
     def setStatus(self, running=None, stable=None):
         if running is not None:
@@ -246,7 +244,7 @@ def parseDescFile(f, bridge_purpose='bridge'):
        IPV6ADDR = an ipv6 address, surrounded by square brackets.
        IPV4ADDR = an ipv4 address, represented as a dotted quad.
        PORTLIST = PORTSPEC | PORTSPEC "," PORTLIST
-       PORTSPEC = PORT | PORT "-" PORT
+       PORTSPEC = PORT
        PORT = a number between 1 and 65535 inclusive.
     """
    
@@ -273,10 +271,18 @@ def parseDescFile(f, bridge_purpose='bridge'):
         elif line.startswith("or-address "):
             if num_or_address_lines < 8:
                 line = line[11:]
-                address,portlist = parseORAddressLine(line)
                 try:
-                    or_addresses[address].add(portlist)
+                    address,portlist = parseORAddressLine(line)
+                except ParseORAddressError: 
+                    logging.warn("Invalid or-address line "\
+                            "from bridge with ID %r" %fingerprint)
+                    continue
+                try:
+                    # distinct ports only
+                    portlist.add(or_addresses[address])
                 except KeyError:
+                    pass
+                finally:
                     or_addresses[address] = portlist
             else:
                 logging.warn("Skipping extra or-address line "\
@@ -300,142 +306,70 @@ class PortList:
 
     def __init__(self, *args, **kwargs):
         self.ports = set()
-        self.ranges = [] 
-        self.portdispenser = None
-        if len(args) == 1:
-            if type(args[0]) is str:
-                ports = [p.split('-') for p in args[0].split(',')]
-                # truncate per spec
-                ports = ports[:16]
-                for ps in ports:
-                    try: ps = [int(x) for x in ps]
-                    except ValueError: break
-                    if len(ps) == 1: self.add(ps[0])
-                    elif len(ps) == 2: self.add(ps[0],ps[1])
-            else:
-                self.add(args[0])
-        elif len(args) == 2:
-            l,h = args
-            self.add(l,h)
+        self.add(*args)
 
     def _sanitycheck(self, val):
         #XXX: if debug=False this is disabled. bad!
         assert type(val) is int
-        assert(val > 0)
-        assert(val <= 65535) 
-
-    def __contains__(self, val1, val2=None):
-        self._sanitycheck(val1)
-        if val2: self.sanitycheck(val2)
-
-        # check a single port
-        if not val2 and val1:
-            if val1 in self.ports: return True
-            for start,end in self.ranges:
-                f = lambda x: start <= x <= end
-                if f(val1): return True
-            return False
-
-        if val2 and val1:
-            for start,end in self.ranges:
-                f = lambda x: start <= x <= end
-                if f(val1) and f(val2): return True
-
-        for start,end in self.ranges:
-            f = lambda x: start <= x <= end
-            if f(val): return True
-
-    def add(self, val1, val2=None):
-        self._sanitycheck(val1)
-
-        # add as a single port instead
-        if val2 == val1: val2 = None
-        if val2:
-            self._sanitycheck(val2)
-            start = min(val1,val2)
-            end = max(val1,val2)
-            self.ranges.append((start,end))
-            # reduce to largest continuous ranges
-            self._squash()
-        else:
-            if val1 in self: return
-            self.ports.add(val1)
+        assert(0 < val <= 65535) 
 
-        # reset port dispenser
-        if self.portdispenser:
-            self.portdispenser = None
-
-    def getPort(self):
-        # returns a single valid port
-        if not self.portdispenser:
-            self.portdispenser = self.__iter__()
-        try:
-            return self.portdispenser.next()
-        except StopIteration, AttributeError:
-            self.portdispenser = self.__iter__()
-            return self.portdispenser.next()
-
-    def _squash(self):
-        # merge intersecting ranges
-        if len(self.ranges) > 1:
-            self.ranges.sort(key=lambda x: x[0])
-            squashed = [self.ranges.pop(0)]
-            for r in self.ranges:
-                if (squashed[-1][0] <= r[0] <= squashed[-1][1]):
-                    #intersection, extend r1, drop r2
-                    if r[1] > squashed[-1][1]: 
-                        squashed[-1] = (squashed[-1][0],r[1])
-                    # drop r
-                else:
-                    # keep r
-                    squashed.append(r)
+    def __contains__(self, val1):
+        return val1 in self.ports
 
-            self.ranges = squashed
-
-        # drop enclosed ports
-        ports = self.ports.copy()
-        for p in self.ports:
-            for s,e in self.ranges:
-                if s <= p <= e:
-                    ports.remove(p)
-        self.ports = ports
+    def add(self, *args):
+        for arg in args:
+            try:
+                if type(arg) is str:
+                    ports = set([int(p) for p in arg.split(',')][:PORTSPEC_LEN])
+                    [self._sanitycheck(p) for p in ports]
+                    self.ports.update(ports)
+                if type(arg) is int:
+                    self._sanitycheck(arg)
+                    self.ports.update([arg])
+                if type(arg) is PortList:
+                    self.add(list(arg.ports))
+            except AssertionError: raise ValueError
+            except ValueError: raise
 
     def __iter__(self):
-        for p in self.ports:
-            yield p
-        for l,h in self.ranges:
-            # +1 for inclusive range
-            for rr in xrange(l,h+1):
-                yield rr
+        return self.ports.__iter__()
 
     def __str__(self):
         s = ""
         for p in self.ports:
-            s += "".join(", %s"%p)
-        for l,h in self.ranges:
-            s += ", %s-%s" % (l,h)
-        return s.lstrip(", ")
+            s += "".join(",%s"%p)
+        return s.lstrip(",")
 
     def __repr__(self):
         return "PortList('%s')" % self.__str__()
 
-def parseORAddressLine(line):
-    #XXX should these go somewhere else?
-    re_ipv6 = re.compile("\[([a-fA-F0-9:]+)\]:(.*$)")
-    re_ipv4 = re.compile("((?:\d{1,3}\.?){4}):(.*$)")
+    def __len__(self):
+        return len(self.ports)
 
+class ParseORAddressError(Exception):
+    def __init__(self):
+        msg = "Invalid or-address line"
+        Exception.__init__(self, msg)
+
+re_ipv6 = re.compile("\[([a-fA-F0-9:]+)\]:(.*$)")
+re_ipv4 = re.compile("((?:\d{1,3}\.?){4}):(.*$)")
+
+def parseORAddressLine(line):
     address = None
     portlist = None
     # try regexp to discover ip version
     for regex in [re_ipv4, re_ipv6]:
         m = regex.match(line)
         if m:
+            # get an address and portspec, or raise ParseError
             try:
                 address  = ipaddr.IPAddress(m.group(1))
-                portstring = m.group(2)
-            except IndexError, ValueError: break
-            portlist = PortList(portstring)
-    return address,portlist
+                portlist = PortList(m.group(2))
+            except (IndexError, ValueError): raise ParseORAddressError
+
+    # return a valid address, portlist or raise ParseORAddressError
+    if address and portlist and len(portlist): return address,portlist
+    raise ParseORAddressError
 
 def parseStatusFile(f):
     """DOCDOC"""
@@ -806,7 +740,7 @@ class FilteredBridgeSplitter(BridgeHolder):
     def clear(self):
         #XXX syntax?
         [r.clear() for n,(f,r) in self.filterRings.items()]
-	self.bridges = []
+        self.bridges = []
         #self.filterRings = {}
 
     def insert(self, bridge):
diff --git a/lib/bridgedb/Server.py b/lib/bridgedb/Server.py
index 039e9b7..5dfe888 100644
--- a/lib/bridgedb/Server.py
+++ b/lib/bridgedb/Server.py
@@ -156,7 +156,7 @@ class WebResource(twisted.web.resource.Resource):
         ipv6 = False
         if "ipv6" in request.postpath: ipv6 = True
 
-	rules = []
+        rules = []
 
         if ip:
             if ipv6:
@@ -171,8 +171,7 @@ class WebResource(twisted.web.resource.Resource):
 
         if bridges:
             answer = "".join("%s %s\n" % (
-                b.getConfigLine(self.includeFingerprints,needIPv6=ipv6,
-                                selectFromORAddresses=ipv6),
+                b.getConfigLine(self.includeFingerprints,needIPv6=ipv6),
                 (I18n.BRIDGEDB_TEXT[16] if b.isBlocked(countryCode) else "")
                 ) for b in bridges) 
         else:
@@ -413,9 +412,9 @@ def getMailResponse(lines, ctx):
     try:
         interval = ctx.schedule.getInterval(time.time())
         bridges = ctx.distributor.getBridgesForEmail(clientAddr,
-			interval, ctx.N,
-			countryCode=None,
-			bridgeFilterRules=bridgeFilterRules)
+            interval, ctx.N,
+            countryCode=None,
+            bridgeFilterRules=bridgeFilterRules)
     # Handle rate limited email
     except TooSoonEmail, e:
         logging.info("Got a mail too frequently; warning %r: %s.",
@@ -465,8 +464,8 @@ def getMailResponse(lines, ctx):
 
     if bridges:
         with_fp = ctx.cfg.EMAIL_INCLUDE_FINGERPRINTS
-        answer = "".join("  %s\n" % b.getConfigLine(with_fp, needIPv6=ipv6,\
-                                   selectFromORAddresses=ipv6) for b in bridges)
+        answer = "".join("  %s\n" %b.getConfigLine(with_fp,
+                needIPv6=ipv6) for b in bridges)
     else:
         answer = "(no bridges currently available)"
 



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits