[tor-commits] [stem/master] Support for consensus 'a' lines

atagar at torproject.org atagar at torproject.org
Mon Dec 31 09:50:07 UTC 2012


commit 683b1ba479f2aff3c277e0ff53bdc8b1f81664af
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Dec 31 01:43:00 2012 -0800

    Support for consensus 'a' lines
    
    Adding support for the 'a' lines in version 3 router status entries. These
    contain IPv6 addresses and port lists. The port lists will make this unweildy
    for users but that's out of our hands.
---
 stem/descriptor/router_status_entry.py      |   46 ++++++++++++++++++++++---
 test/unit/descriptor/router_status_entry.py |   49 +++++++++++++++++++++++++-
 2 files changed, 87 insertions(+), 8 deletions(-)

diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py
index 31458c1..ed01132 100644
--- a/stem/descriptor/router_status_entry.py
+++ b/stem/descriptor/router_status_entry.py
@@ -270,6 +270,8 @@ class RouterStatusEntryV3(RouterStatusEntry):
   Information about an individual router stored within a version 3 network
   status document.
   
+  :var dict addresses_v6: **\*** relay's IPv6 OR addresses, this is a mapping
+    of IPv6 addresses to a listing of [(min port, max port)...] it accepts
   :var str digest: **\*** router's digest
   
   :var int bandwidth: bandwidth claimed by the relay (in kb/s)
@@ -279,14 +281,16 @@ class RouterStatusEntryV3(RouterStatusEntry):
   
   :var stem.exit_policy.MicrodescriptorExitPolicy exit_policy: router's exit policy
   
-  :var list microdescriptor_hashes: tuples of two values, the list of consensus
-    methods for generating a set of digests and the 'algorithm => digest' mappings
+  :var list microdescriptor_hashes: **\*** tuples of two values, the list of
+    consensus methods for generating a set of digests and the 'algorithm =>
+    digest' mappings
   
   **\*** attribute is either required when we're parsed with validation or has
   a default value, others are left as **None** if undefined
   """
   
   def __init__(self, content, validate = True, document = None):
+    self.addresses_v6 = {}
     self.digest = None
     
     self.bandwidth = None
@@ -294,7 +298,7 @@ class RouterStatusEntryV3(RouterStatusEntry):
     self.unrecognized_bandwidth_entries = []
     
     self.exit_policy = None
-    self.microdescriptor_hashes = None
+    self.microdescriptor_hashes = []
     
     super(RouterStatusEntryV3, self).__init__(content, validate, document)
   
@@ -305,6 +309,11 @@ class RouterStatusEntryV3(RouterStatusEntry):
       if keyword == 'r':
         _parse_r_line(self, value, validate, True)
         del entries['r']
+      elif keyword == 'a':
+        for entry, _ in values:
+          _parse_a_line(self, entry, validate)
+        
+        del entries['a']
       elif keyword == 'w':
         _parse_w_line(self, value, validate)
         del entries['w']
@@ -449,6 +458,34 @@ def _parse_r_line(desc, value, validate, include_digest = True):
     if validate:
       raise ValueError("Publication time time wasn't parsable: r %s" % value)
 
+def _parse_a_line(desc, value, validate):
+  # "a" SP address ":" portlist
+  # example: a [2001:888:2133:0:82:94:251:204]:9001
+  
+  if not ':' in value:
+    if not validate: return
+    raise ValueError("%s 'a' line must be of the form '[address]:[ports]': a %s" % (desc._name(), value))
+  
+  address, ports = value.rsplit(':', 1)
+  
+  if validate and not stem.util.connection.is_valid_ipv6_address(address, allow_brackets = True):
+    raise ValueError("%s 'a' line must start with an IPv6 address: a %s" % (desc._name(), value))
+  
+  address = address.lstrip('[').rstrip(']')
+  
+  for port_entry in ports.split(','):
+    if '-' in port_entry:
+      min_port, max_port = port_entry.split('-', 1)
+    else:
+      min_port = max_port = port_entry
+    
+    if not stem.util.connection.is_valid_port(min_port) or \
+       not stem.util.connection.is_valid_port(max_port):
+      if not validate: continue
+      raise ValueError("%s 'a' line had an invalid port range (%s): a %s" % (desc._name(), port_entry, value))
+    
+    desc.addresses_v6.setdefault(address, []).append((int(min_port), int(max_port)))
+
 def _parse_s_line(desc, value, validate):
   # "s" Flags
   # example: s Named Running Stable Valid
@@ -556,9 +593,6 @@ def _parse_m_line(desc, value, validate):
     hash_name, digest = entry.split('=', 1)
     hashes[hash_name] = digest
   
-  if desc.microdescriptor_hashes is None:
-    desc.microdescriptor_hashes = []
-  
   desc.microdescriptor_hashes.append((methods, hashes))
 
 def _decode_fingerprint(identity, validate):
diff --git a/test/unit/descriptor/router_status_entry.py b/test/unit/descriptor/router_status_entry.py
index b0cd3b8..ef03baa 100644
--- a/test/unit/descriptor/router_status_entry.py
+++ b/test/unit/descriptor/router_status_entry.py
@@ -82,7 +82,7 @@ class TestRouterStatusEntry(unittest.TestCase):
     self.assertEqual(None, entry.measured)
     self.assertEqual([], entry.unrecognized_bandwidth_entries)
     self.assertEqual(None, entry.exit_policy)
-    self.assertEqual(None, entry.microdescriptor_hashes)
+    self.assertEqual([], entry.microdescriptor_hashes)
     self.assertEqual([], entry.get_unrecognized_lines())
   
   def test_minimal_micro_v3(self):
@@ -304,6 +304,51 @@ class TestRouterStatusEntry(unittest.TestCase):
           content = get_router_status_entry_v3({'r': r_line}, content = True)
           self._expect_invalid_attr(content, attr, expected)
   
+  def test_ipv6_addresses(self):
+    """
+    Handles a variety of 'a' lines.
+    """
+    
+    test_values = {
+      "[2607:fcd0:daaa:101::602c:bd62]:443": {
+        '2607:fcd0:daaa:101::602c:bd62': [(443, 443)]},
+      "[2607:fcd0:daaa:101::602c:bd62]:80,443": {
+        '2607:fcd0:daaa:101::602c:bd62': [(80, 80), (443, 443)]},
+      "[2607:fcd0:daaa:101::602c:bd62]:443-512": {
+        '2607:fcd0:daaa:101::602c:bd62': [(443, 512)]},
+    }
+    
+    for a_line, expected in test_values.items():
+      entry = get_router_status_entry_v3({'a': a_line})
+      self.assertEquals(expected, entry.addresses_v6)
+    
+    # includes multiple 'a' lines
+    
+    content = get_router_status_entry_v3(content = True)
+    content += "\na [2607:fcd0:daaa:101::602c:bd62]:80,443"
+    content += "\na [2607:fcd0:daaa:101::602c:bd62]:512-600"
+    content += "\na [1148:fcd0:daaa:101::602c:bd62]:80"
+    
+    expected = {
+      '2607:fcd0:daaa:101::602c:bd62': [(80, 80), (443, 443), (512, 600)],
+      '1148:fcd0:daaa:101::602c:bd62': [(80, 80)],
+    }
+    
+    entry = RouterStatusEntryV3(content)
+    self.assertEquals(expected, entry.addresses_v6)
+    
+    # tries some invalid inputs
+    
+    test_values = (
+      "",
+      "127.0.0.1:80",
+      "[1148:fcd0:daaa:101::602c:bd62]:80000",
+    )
+    
+    for a_line in test_values:
+      content = get_router_status_entry_v3({'a': a_line}, content = True)
+      self._expect_invalid_attr(content, expected_value = {})
+  
   def test_flags(self):
     """
     Handles a variety of flag inputs.
@@ -454,7 +499,7 @@ class TestRouterStatusEntry(unittest.TestCase):
     
     # try without a document
     content = get_router_status_entry_v3({'m': "8,9,10,11,12"}, content = True)
-    self._expect_invalid_attr(content, "microdescriptor_hashes")
+    self._expect_invalid_attr(content, "microdescriptor_hashes", expected_value = [])
     
     # tries some invalid inputs
     test_values = (



More information about the tor-commits mailing list