[tor-commits] [stem/master] Handling version 1.1 of bridge extrainfo descriptors

atagar at torproject.org atagar at torproject.org
Fri Jun 29 17:20:28 UTC 2012


commit c35c8a6cbb3659841f79159512c24a08a5e1d622
Author: Damian Johnson <atagar at torproject.org>
Date:   Fri Jun 29 09:59:16 2012 -0700

    Handling version 1.1 of bridge extrainfo descriptors
    
    Version 1.1 of the bridge extrainfo descriptors adds a new 'transport' field.
    I'm not entirely sure yet of its attributes, but recognizing the new descriptor
    version and parsing the field.
    
    I'm also expanding the tests a bit so unit tests cover bridge extrainfo
    descriptors, and parsing the metrics header line rather than just doing string
    matches.
    
    This is to address...
    https://trac.torproject.org/6257
---
 stem/descriptor/__init__.py                  |   27 ++++++++++----
 stem/descriptor/extrainfo_descriptor.py      |    8 ++++-
 test/unit/descriptor/extrainfo_descriptor.py |   50 +++++++++++++++++++++++---
 3 files changed, 71 insertions(+), 14 deletions(-)

diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index 929396c..21dad37 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -65,15 +65,26 @@ def parse_file(path, descriptor_file):
   # Metrics descriptor handling. These contain a single descriptor per file.
   
   first_line, desc = descriptor_file.readline().strip(), None
+  metrics_header_match = re.match("^@type (\S+) (\d+).(\d+)$", first_line)
   
-  if first_line == "@type server-descriptor 1.0":
-    desc = stem.descriptor.server_descriptor.RelayDescriptor(descriptor_file.read())
-  elif first_line == "@type bridge-server-descriptor 1.0":
-    desc = stem.descriptor.server_descriptor.BridgeDescriptor(descriptor_file.read())
-  elif first_line in ("@type extra-info 1.0"):
-    desc = stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor(descriptor_file.read())
-  elif first_line in ("@type bridge-extra-info 1.0"):
-    desc = stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor(descriptor_file.read())
+  if metrics_header_match:
+    # still doesn't necessarily mean that this is a descriptor, check if the
+    # header contents are recognized
+    
+    desc_type, major_version, minor_version = metrics_header_match.groups()
+    major_version, minor_version = int(major_version), int(minor_version)
+    
+    if desc_type == "server-descriptor" and major_version == 1 and minor_version == 0:
+      desc = stem.descriptor.server_descriptor.RelayDescriptor(descriptor_file.read())
+    elif desc_type == "bridge-server-descriptor" and major_version == 1 and minor_version == 0:
+      desc = stem.descriptor.server_descriptor.BridgeDescriptor(descriptor_file.read())
+    elif desc_type == "extra-info" and major_version == 1 and minor_version == 0:
+      desc = stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor(descriptor_file.read())
+    elif desc_type == "bridge-extra-info" and major_version == 1 and minor_version in (0, 1):
+      # version 1.1 introduced a 'transport' field...
+      # https://trac.torproject.org/6257
+      
+      desc = stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor(descriptor_file.read())
   
   if desc:
     desc._set_path(path)
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py
index 04f061a..979c679 100644
--- a/stem/descriptor/extrainfo_descriptor.py
+++ b/stem/descriptor/extrainfo_descriptor.py
@@ -742,10 +742,13 @@ class RelayExtraInfoDescriptor(ExtraInfoDescriptor):
 class BridgeExtraInfoDescriptor(ExtraInfoDescriptor):
   """
   Bridge extra-info descriptor (`specification <https://metrics.torproject.org/formats.html#bridgedesc>`_)
+  
+  :var str transport: transport method recognized by the bridge (ex. obfs3)
   """
   
   def __init__(self, raw_contents, validate = True):
     self._digest = None
+    self.transport = None
     
     ExtraInfoDescriptor.__init__(self, raw_contents, validate)
   
@@ -760,7 +763,10 @@ class BridgeExtraInfoDescriptor(ExtraInfoDescriptor):
       value, _ = values[0]
       line = "%s %s" % (keyword, value) # original line
       
-      if keyword == "router-digest":
+      if keyword == "transport":
+        self.transport = value
+        del entries["transport"]
+      elif keyword == "router-digest":
         if validate and not stem.util.tor_tools.is_hex_digits(value, 40):
           raise ValueError("Router digest line had an invalid sha1 digest: %s" % line)
         
diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py
index 888180c..e9b06a0 100644
--- a/test/unit/descriptor/extrainfo_descriptor.py
+++ b/test/unit/descriptor/extrainfo_descriptor.py
@@ -4,7 +4,7 @@ Unit tests for stem.descriptor.extrainfo_descriptor.
 
 import datetime
 import unittest
-from stem.descriptor.extrainfo_descriptor import RelayExtraInfoDescriptor, DirResponses, DirStats
+from stem.descriptor.extrainfo_descriptor import RelayExtraInfoDescriptor, BridgeExtraInfoDescriptor, DirResponses, DirStats
 
 CRYPTO_BLOB = """
 K5FSywk7qvw/boA4DQcqkls6Ize5vcBYfhQ8JnOeRQC9+uDxbnpm3qaYN9jZ8myj
@@ -12,13 +12,19 @@ k0d2aofcVbHr4fPQOSST0LXDrhFl5Fqo5um296zpJGvRUeO6S44U/EfJAGShtqWw
 7LZqklu+gVvhMKREpchVqlAwXkWR44VENm24Hs+mT3M=
 """
 
-EXTRAINFO_DESCRIPTOR_ATTR = (
+RELAY_EXTRAINFO_ATTR = (
   ("extra-info", "ninja B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48"),
   ("published", "2012-05-05 17:03:50"),
   ("router-signature", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB),
 )
 
-def _make_descriptor(attr = None, exclude = None):
+BRIDGE_EXTRAINFO_ATTR = (
+  ("extra-info", "ec2bridgereaac65a3 1EC248422B57D9C0BD751892FE787585407479A4"),
+  ("published", "2012-05-05 17:03:50"),
+  ("router-digest", "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"),
+)
+
+def _make_descriptor(attr = None, exclude = None, is_bridge = False):
   """
   Constructs a minimal extrainfo descriptor with the given attributes.
   
@@ -31,21 +37,28 @@ def _make_descriptor(attr = None, exclude = None):
   descriptor_lines = []
   if attr is None: attr = {}
   if exclude is None: exclude = []
+  desc_attr = BRIDGE_EXTRAINFO_ATTR if is_bridge else RELAY_EXTRAINFO_ATTR
   attr = dict(attr) # shallow copy since we're destructive
   
-  for keyword, value in EXTRAINFO_DESCRIPTOR_ATTR:
+  for keyword, value in desc_attr:
     if keyword in exclude: continue
     elif keyword in attr:
       value = attr[keyword]
       del attr[keyword]
     
     # if this is the last entry then we should dump in any unused attributes
-    if keyword == "router-signature":
+    if not is_bridge and keyword == "router-signature":
       for attr_keyword, attr_value in attr.items():
         descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
     
     descriptor_lines.append("%s %s" % (keyword, value))
   
+  # bridges don't have a router-signature so simply append any extra attributes
+  # to the end
+  if is_bridge:
+    for attr_keyword, attr_value in attr.items():
+      descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
+  
   return "\n".join(descriptor_lines)
 
 class TestExtraInfoDescriptor(unittest.TestCase):
@@ -492,6 +505,33 @@ class TestExtraInfoDescriptor(unittest.TestCase):
         desc_text = _make_descriptor({keyword: entry})
         self._expect_invalid_attr(desc_text, attr, {})
   
+  def test_minimal_bridge_descriptor(self):
+    """
+    Basic sanity check that we can parse a descriptor with minimal attributes.
+    """
+    
+    desc_text = _make_descriptor(is_bridge = True)
+    desc = BridgeExtraInfoDescriptor(desc_text)
+    
+    self.assertEquals("ec2bridgereaac65a3", desc.nickname)
+    self.assertEquals("1EC248422B57D9C0BD751892FE787585407479A4", desc.fingerprint)
+    self.assertEquals("006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4", desc.digest())
+    self.assertEquals([], desc.get_unrecognized_lines())
+    
+    # check that we don't have crypto fields
+    self.assertRaises(AttributeError, getattr, desc, "signature")
+  
+  def test_transport_line(self):
+    """
+    Basic exercise of the bridge descriptor's transport entry.
+    attributes.
+    """
+    
+    desc_text = _make_descriptor({"transport": "obfs3"}, is_bridge = True)
+    desc = BridgeExtraInfoDescriptor(desc_text)
+    self.assertEquals("obfs3", desc.transport)
+    self.assertEquals([], desc.get_unrecognized_lines())
+  
   def _expect_invalid_attr(self, desc_text, attr = None, expected_value = None):
     """
     Asserts that construction will fail due to desc_text having a malformed



More information about the tor-commits mailing list