commit c35c8a6cbb3659841f79159512c24a08a5e1d622 Author: Damian Johnson atagar@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
tor-commits@lists.torproject.org