[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Fallback directory v2 parsing
commit ea55eaa28c1bf6a3c97cf678e7856f3bd8f23be8
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Sat Dec 23 13:10:24 2017 -0800
Fallback directory v2 parsing
Adding support for the proposed v2 fallback directory format...
https://lists.torproject.org/pipermail/tor-dev/2017-December/012721.html
This isn't live yet and I won't be checking persistance support until it is,
but good starting point. Remaining thoughs are...
1. The 'extrainfo=' line is optional but we're using it as a delimiter, so
this won't work if it's omitted.
2. It would be nice if the document explicitly said its format version.
3. I suspect reading persisted has_extrainfo attributes mistakenly provide a
str rather than bool. As mentioned above I'll be checking persistence once
it's live.
---
stem/descriptor/remote.py | 98 ++++++++++++++++++++++++++++++++++++++++--
test/unit/descriptor/remote.py | 32 ++++++++++++--
2 files changed, 124 insertions(+), 6 deletions(-)
diff --git a/stem/descriptor/remote.py b/stem/descriptor/remote.py
index dbba6857..9124a45f 100644
--- a/stem/descriptor/remote.py
+++ b/stem/descriptor/remote.py
@@ -939,12 +939,25 @@ class FallbackDirectory(Directory):
.. versionadded:: 1.5.0
+ .. versionchanged:: 1.7.0
+ Added the nickname and has_extrainfo attributes.
+
+ .. versionchanged:: 1.7.0
+ Support for parsing `second version of the fallback directories
+ <https://lists.torproject.org/pipermail/tor-dev/2017-December/012721.html>`_.
+
+ :var str nickname: relay nickname
+ :var bool has_extrainfo: **True** if the relay should be able to provide
+ extrainfo descriptors, **False** otherwise.
:var str orport_v6: **(address, port)** tuple for the directory's IPv6
ORPort, or **None** if it doesn't have one
"""
- def __init__(self, address = None, or_port = None, dir_port = None, fingerprint = None, orport_v6 = None):
+ def __init__(self, address = None, or_port = None, dir_port = None, fingerprint = None, nickname = None, has_extrainfo = False, orport_v6 = None):
super(FallbackDirectory, self).__init__(address, or_port, dir_port, fingerprint)
+
+ self.nickname = nickname
+ self.has_extrainfo = has_extrainfo
self.orport_v6 = orport_v6
@staticmethod
@@ -971,11 +984,11 @@ class FallbackDirectory(Directory):
attr = {}
- for attr_name in ('address', 'or_port', 'dir_port', 'orport6_address', 'orport6_port'):
+ for attr_name in ('address', 'or_port', 'dir_port', 'nickname', 'has_extrainfo', 'orport6_address', 'orport6_port'):
key = '%s.%s' % (fingerprint, attr_name)
attr[attr_name] = conf.get(key)
- if not attr[attr_name] and not attr_name.startswith('orport6_'):
+ if not attr[attr_name] and attr_name not in ('nickname', 'has_extrainfo', 'orport6_address', 'orport6_port'):
raise IOError("'%s' is missing from %s" % (key, CACHE_PATH))
if not connection.is_valid_ipv4_address(attr['address']):
@@ -984,6 +997,8 @@ class FallbackDirectory(Directory):
raise IOError("'%s.or_port' was an invalid port (%s)" % (fingerprint, attr['or_port']))
elif not connection.is_valid_port(attr['dir_port']):
raise IOError("'%s.dir_port' was an invalid port (%s)" % (fingerprint, attr['dir_port']))
+ elif attr['nickname'] and not connection.is_valid_nickname(attr['nickname']):
+ raise IOError("'%s.nickname' was an invalid nickname (%s)" % (fingerprint, attr['nickname']))
elif attr['orport6_address'] and not connection.is_valid_ipv6_address(attr['orport6_address']):
raise IOError("'%s.orport6_address' was an invalid IPv6 address (%s)" % (fingerprint, attr['orport6_address']))
elif attr['orport6_port'] and not connection.is_valid_port(attr['orport6_port']):
@@ -1034,12 +1049,21 @@ class FallbackDirectory(Directory):
exc = sys.exc_info()[1]
raise IOError("Unable to download tor's fallback directories from %s: %s" % (GITWEB_FALLBACK_DIR_URL, exc))
+ if '/* nickname=' in fallback_dir_page:
+ return FallbackDirectory._parse_v2(fallback_dir_page)
+ else:
+ return FallbackDirectory._parse_v1(fallback_dir_page)
+
+ @staticmethod
+ def _parse_v1(fallback_dir_page):
# Example of an entry...
#
# "5.175.233.86:80 orport=443 id=5525D0429BFE5DC4F1B0E9DE47A4CFA169661E33"
# " ipv6=[2a03:b0c0:0:1010::a4:b001]:9001"
# " weight=43680",
+ # TODO: this method can be removed once gitweb provides a v2 formatted document
+
results, attr = {}, {}
for line in fallback_dir_page.splitlines():
@@ -1087,6 +1111,74 @@ class FallbackDirectory(Directory):
return results
+ @staticmethod
+ def _parse_v2(fallback_dir_page):
+ # Example of an entry...
+ #
+ # "5.9.110.236:9030 orport=9001 id=0756B7CD4DFC8182BE23143FAC0642F515182CEB"
+ # " ipv6=[2a01:4f8:162:51e2::2]:9001"
+ # /* nickname=rueckgrat */
+ # /* extrainfo=1 */
+
+ results, attr = {}, {}
+
+ for line in fallback_dir_page.splitlines():
+ addr_line_match = re.match('"([\d\.]+):(\d+) orport=(\d+) id=([\dA-F]{40}).*', line)
+ nickname_match = re.match('/\* nickname=(\S+) \*/', line)
+ has_extrainfo_match = re.match('/\* extrainfo=([0-1]) \*/', line)
+ ipv6_line_match = re.match('" ipv6=\[([\da-f:]+)\]:(\d+)"', line)
+
+ if addr_line_match:
+ address, dir_port, or_port, fingerprint = addr_line_match.groups()
+
+ if not connection.is_valid_ipv4_address(address):
+ raise IOError('%s has an invalid IPv4 address: %s' % (fingerprint, address))
+ elif not connection.is_valid_port(or_port):
+ raise IOError('%s has an invalid or_port: %s' % (fingerprint, or_port))
+ elif not connection.is_valid_port(dir_port):
+ raise IOError('%s has an invalid dir_port: %s' % (fingerprint, dir_port))
+ elif not tor_tools.is_valid_fingerprint(fingerprint):
+ raise IOError('%s has an invalid fingerprint: %s' % (fingerprint, fingerprint))
+
+ attr = {
+ 'address': address,
+ 'or_port': int(or_port),
+ 'dir_port': int(dir_port),
+ 'fingerprint': fingerprint,
+ }
+ elif ipv6_line_match:
+ address, port = ipv6_line_match.groups()
+
+ if not connection.is_valid_ipv6_address(address):
+ raise IOError('%s has an invalid IPv6 address: %s' % (fingerprint, address))
+ elif not connection.is_valid_port(port):
+ raise IOError('%s has an invalid ORPort for its IPv6 endpoint: %s' % (fingerprint, port))
+
+ attr['orport_v6'] = (address, int(port))
+ elif nickname_match:
+ nickname = nickname_match.group(1)
+
+ if not tor_tools.is_valid_nickname(nickname):
+ raise IOError('%s has an invalid nickname: %s' % (fingerprint, nickname))
+
+ attr['nickname'] = nickname
+ elif has_extrainfo_match:
+ attr['has_extrainfo'] = has_extrainfo_match.group(1) == '1'
+
+ results[attr.get('fingerprint')] = FallbackDirectory(
+ address = attr.get('address'),
+ or_port = attr.get('or_port'),
+ dir_port = attr.get('dir_port'),
+ fingerprint = attr.get('fingerprint'),
+ nickname = attr.get('nickname'),
+ has_extrainfo = attr.get('has_extrainfo', False),
+ orport_v6 = attr.get('orport_v6'),
+ )
+
+ attr = {}
+
+ return results
+
def __hash__(self):
return _hash_attr(self, 'orport_v6', parent = Directory)
diff --git a/test/unit/descriptor/remote.py b/test/unit/descriptor/remote.py
index 1b818b67..69ffd8e3 100644
--- a/test/unit/descriptor/remote.py
+++ b/test/unit/descriptor/remote.py
@@ -58,7 +58,7 @@ iO3EUE0AEYah2W9gdz8t+i3Dtr0zgqLS841GC/TyDKCm+MKmN8d098qnwK0NGF9q
-----END SIGNATURE-----
"""
-FALLBACK_DIR_CONTENT = b"""\
+FALLBACK_DIR_CONTENT_V1 = b"""\
/* Trial fallbacks for 0.2.8.1-alpha with ADDRESS_AND_PORT_STABLE_DAYS = 30
* This works around an issue where relays post a descriptor without a DirPort
* when restarted. If these relays stay up, they will have been up for 120 days
@@ -70,6 +70,13 @@ FALLBACK_DIR_CONTENT = b"""\
" weight=43680",
"""
+FALLBACK_DIR_CONTENT_V2 = b"""\
+"5.9.110.236:9030 orport=9001 id=0756B7CD4DFC8182BE23143FAC0642F515182CEB"
+" ipv6=[2a01:4f8:162:51e2::2]:9001"
+/* nickname=rueckgrat */
+/* extrainfo=1 */
+"""
+
class TestDescriptorDownloader(unittest.TestCase):
@patch(URL_OPEN)
@@ -178,8 +185,8 @@ class TestDescriptorDownloader(unittest.TestCase):
self.assertEqual('5.39.92.199', fallback_directories['0BEA4A88D069753218EAAAD6D22EA87B9A1319D6'].address)
@patch(URL_OPEN)
- def test_fallback_directories_from_remote(self, urlopen_mock):
- urlopen_mock.return_value = io.BytesIO(FALLBACK_DIR_CONTENT)
+ def test_fallback_directories_from_remote_v1(self, urlopen_mock):
+ urlopen_mock.return_value = io.BytesIO(FALLBACK_DIR_CONTENT_V1)
fallback_directories = stem.descriptor.remote.FallbackDirectory.from_remote()
expected = {
@@ -199,3 +206,22 @@ class TestDescriptorDownloader(unittest.TestCase):
}
self.assertEqual(expected, fallback_directories)
+
+ @patch(URL_OPEN)
+ def test_fallback_directories_from_remote_v2(self, urlopen_mock):
+ urlopen_mock.return_value = io.BytesIO(FALLBACK_DIR_CONTENT_V2)
+ fallback_directories = stem.descriptor.remote.FallbackDirectory.from_remote()
+
+ expected = {
+ '0756B7CD4DFC8182BE23143FAC0642F515182CEB': stem.descriptor.remote.FallbackDirectory(
+ address = '5.9.110.236',
+ or_port = 9001,
+ dir_port = 9030,
+ fingerprint = '0756B7CD4DFC8182BE23143FAC0642F515182CEB',
+ nickname = 'rueckgrat',
+ has_extrainfo = True,
+ orport_v6 = ('2a01:4f8:162:51e2::2', 9001),
+ ),
+ }
+
+ self.assertEqual(expected, fallback_directories)
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits