[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