summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamian Johnson <atagar@torproject.org>2019-08-17 13:42:52 -0700
committerDamian Johnson <atagar@torproject.org>2019-08-17 13:42:52 -0700
commit97c9a58eab40ce32256afbb02a4f8c6c84045bb1 (patch)
tree2690a7c640b2b0c4a0ca4a3ea31849d9e141c2d0
parent4357e5480eb8de01337929e3e6f761f0d6de4833 (diff)
parent6a44d211342a727b71824825262123aeaf300c99 (diff)
CollecTor module
Brand new module that makes it simple to browse tor's network topology at prior points in time. https://trac.torproject.org/projects/tor/ticket/17979
-rw-r--r--docs/_static/example/collector_caching.py18
-rw-r--r--docs/_static/example/collector_reading.py10
-rw-r--r--docs/_static/example/past_descriptors.py5
-rw-r--r--docs/api.rst1
-rw-r--r--docs/api/descriptor/collector.rst5
-rw-r--r--docs/change_log.rst3
-rw-r--r--docs/contents.rst1
-rw-r--r--docs/tutorials/mirror_mirror_on_the_wall.rst13
-rwxr-xr-xrun_tests.py15
-rw-r--r--stem/__init__.py67
-rw-r--r--stem/cached_fallbacks.cfg775
-rw-r--r--stem/cached_manual.sqlitebin248832 -> 249856 bytes
-rw-r--r--stem/descriptor/__init__.py90
-rw-r--r--stem/descriptor/collector.py727
-rw-r--r--stem/descriptor/hidden_service_descriptor.py3
-rw-r--r--stem/descriptor/networkstatus.py7
-rw-r--r--stem/descriptor/remote.py125
-rw-r--r--stem/descriptor/router_status_entry.py19
-rw-r--r--stem/descriptor/server_descriptor.py17
-rw-r--r--stem/directory.py27
-rw-r--r--stem/manual.py6
-rw-r--r--stem/settings.cfg1
-rw-r--r--stem/util/connection.py52
-rw-r--r--test/arguments.py21
-rw-r--r--test/integ/descriptor/__init__.py1
-rw-r--r--test/integ/descriptor/collector.py98
-rw-r--r--test/integ/manual.py6
-rw-r--r--test/integ/util/connection.py34
-rw-r--r--test/settings.cfg3
-rw-r--r--test/unit/descriptor/__init__.py2
-rw-r--r--test/unit/descriptor/collector.py373
-rw-r--r--test/unit/descriptor/compression.py39
-rw-r--r--test/unit/descriptor/data/__init__.py7
-rw-r--r--test/unit/descriptor/data/collector/__init__.py7
-rw-r--r--test/unit/descriptor/data/collector/bandwidths-2019-05-cropped.tarbin0 -> 32768 bytes
-rw-r--r--test/unit/descriptor/data/collector/bridge-extra-infos-2019-03-cropped.tarbin0 -> 15872 bytes
-rw-r--r--test/unit/descriptor/data/collector/bridge-server-descriptors-2019-02-cropped.tarbin0 -> 9216 bytes
-rw-r--r--test/unit/descriptor/data/collector/bridge-statuses-2019-05-cropped.tarbin0 -> 467456 bytes
-rw-r--r--test/unit/descriptor/data/collector/certs-cropped.tarbin0 -> 14336 bytes
-rw-r--r--test/unit/descriptor/data/collector/consensuses-2018-06-cropped.tarbin0 -> 100864 bytes
-rw-r--r--test/unit/descriptor/data/collector/exit-list-2018-11-cropped.tarbin0 -> 590336 bytes
-rw-r--r--test/unit/descriptor/data/collector/extra-infos-2019-04-cropped.tarbin0 -> 22528 bytes
-rw-r--r--test/unit/descriptor/data/collector/index.json1
-rw-r--r--test/unit/descriptor/data/collector/index.py530
-rw-r--r--test/unit/descriptor/data/collector/microdescs-2019-05-cropped.tarbin0 -> 193024 bytes
-rw-r--r--test/unit/descriptor/data/collector/server-descriptors-2005-12-cropped.tarbin0 -> 22528 bytes
-rw-r--r--test/unit/descriptor/data/compressed_bz2bin0 -> 1691 bytes
-rw-r--r--test/unit/descriptor/remote.py13
-rw-r--r--test/unit/directory/authority.py3
-rw-r--r--test/unit/directory/fallback.py5
-rw-r--r--test/unit/tutorial.py18
-rw-r--r--test/unit/util/connection.py44
52 files changed, 2656 insertions, 536 deletions
diff --git a/docs/_static/example/collector_caching.py b/docs/_static/example/collector_caching.py
new file mode 100644
index 00000000..bff63c47
--- /dev/null
+++ b/docs/_static/example/collector_caching.py
@@ -0,0 +1,18 @@
+import datetime
+import stem.descriptor
+import stem.descriptor.collector
+
+yesterday = datetime.datetime.utcnow() - datetime.timedelta(days = 1)
+cache_dir = '~/descriptor_cache/server_desc_today'
+
+collector = stem.descriptor.collector.CollecTor()
+
+for f in collector.files('server-descriptor', start = yesterday):
+ f.download(cache_dir)
+
+# then later...
+
+for f in collector.files('server-descriptor', start = yesterday):
+ for desc in f.read(cache_dir):
+ if desc.exit_policy.is_exiting_allowed():
+ print(' %s (%s)' % (desc.nickname, desc.fingerprint))
diff --git a/docs/_static/example/collector_reading.py b/docs/_static/example/collector_reading.py
new file mode 100644
index 00000000..06cc913a
--- /dev/null
+++ b/docs/_static/example/collector_reading.py
@@ -0,0 +1,10 @@
+import datetime
+import stem.descriptor.collector
+
+yesterday = datetime.datetime.utcnow() - datetime.timedelta(days = 1)
+
+# provide yesterday's exits
+
+for desc in stem.descriptor.collector.get_server_descriptors(start = yesterday):
+ if desc.exit_policy.is_exiting_allowed():
+ print(' %s (%s)' % (desc.nickname, desc.fingerprint))
diff --git a/docs/_static/example/past_descriptors.py b/docs/_static/example/past_descriptors.py
deleted file mode 100644
index 41004845..00000000
--- a/docs/_static/example/past_descriptors.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from stem.descriptor.reader import DescriptorReader
-
-with DescriptorReader(["/home/atagar/server-descriptors-2013-03.tar"]) as reader:
- for desc in reader:
- print("found relay %s (%s)" % (desc.nickname, desc.fingerprint))
diff --git a/docs/api.rst b/docs/api.rst
index 2e2f9fae..a8ba7e24 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -43,6 +43,7 @@ remotely like Tor does.
* `stem.directory <api/directory.html>`_ - Directory authority and fallback directory information.
* `stem.descriptor.reader <api/descriptor/reader.html>`_ - Reads and parses descriptor files from disk.
* `stem.descriptor.remote <api/descriptor/remote.html>`_ - Downloads descriptors from directory mirrors and authorities.
+* `stem.descriptor.collector <api/descriptor/collector.html>`_ - Downloads past descriptors from `CollecTor <https://metrics.torproject.org/collector.html>`_.
* `stem.descriptor.export <api/descriptor/export.html>`_ - Exports descriptors to other formats.
Utilities
diff --git a/docs/api/descriptor/collector.rst b/docs/api/descriptor/collector.rst
new file mode 100644
index 00000000..e699d0e7
--- /dev/null
+++ b/docs/api/descriptor/collector.rst
@@ -0,0 +1,5 @@
+CollecTor
+=========
+
+.. automodule:: stem.descriptor.collector
+
diff --git a/docs/change_log.rst b/docs/change_log.rst
index fec98f5b..a2337e60 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -56,6 +56,7 @@ The following are only available within Stem's `git repository
* **Descriptors**
+ * Added the `stem.descriptor.collector <api/descriptor/collector.html>`_ module.
* `Bandwidth file support <api/descriptor/bandwidth_file.html>`_ (:trac:`29056`)
* Ed25519 validity checks are now done though the cryptography module rather than PyNaCl (:trac:`22022`)
* Download compressed descriptors by default (:trac:`29186`)
@@ -134,7 +135,7 @@ and the `stem.directory module <api/directory.html>`_.
* Added the *orport_v6* attribute to the :class:`~stem.directory.Authority` class
* Added server descriptor's new is_hidden_service_dir attribute
* Added the network status vote's new bandwidth_file_headers attribute (:spec:`84591df`)
- * Added the microdescriptor router status entry's new or_addresses attribute (:trac:`26405`, :spec:`fdc8f3e8`)
+ * Added the microdescriptor router status entry's new or_addresses attribute (:trac:`26405`, :spec:`fdc8f3e`)
* Don't retry downloading descriptors when we've timed out
* Don't download from tor26, an authority that frequently timeout
* Replaced Bifroest bridge authority with Serge (:trac:`26771`)
diff --git a/docs/contents.rst b/docs/contents.rst
index fb4d6b24..267979e0 100644
--- a/docs/contents.rst
+++ b/docs/contents.rst
@@ -43,6 +43,7 @@ Contents
api/descriptor/bandwidth_file
api/descriptor/certificate
+ api/descriptor/collector
api/descriptor/descriptor
api/descriptor/server_descriptor
api/descriptor/extrainfo_descriptor
diff --git a/docs/tutorials/mirror_mirror_on_the_wall.rst b/docs/tutorials/mirror_mirror_on_the_wall.rst
index eed4c65c..04cc86de 100644
--- a/docs/tutorials/mirror_mirror_on_the_wall.rst
+++ b/docs/tutorials/mirror_mirror_on_the_wall.rst
@@ -117,10 +117,17 @@ Where can I get past descriptors?
---------------------------------
Descriptor archives are available from `CollecTor
-<https://collector.torproject.org/>`_. These archives can be read with
-the `DescriptorReader <../api/descriptor/reader.html>`_...
+<https://metrics.torproject.org/collector.html>`_. If you need Tor's topology
+at a prior point in time this is the place to go!
-.. literalinclude:: /_static/example/past_descriptors.py
+With CollecTor you can either read descriptors directly...
+
+.. literalinclude:: /_static/example/collector_reading.py
+ :language: python
+
+... or download the descriptors to disk and read them later.
+
+.. literalinclude:: /_static/example/collector_caching.py
:language: python
.. _can-i-get-descriptors-from-the-tor-process:
diff --git a/run_tests.py b/run_tests.py
index 77db73b5..a3a90d04 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -146,18 +146,7 @@ def _get_tests(modules, module_prefixes):
if not module_prefixes:
yield import_name
else:
- # Example import_name: test.unit.util.conf.TestConf
- #
- # Our '--test' argument doesn't include the prefix, so excluding it from
- # the names we look for.
-
- if import_name.startswith('test.unit.'):
- cropped_name = import_name[10:]
- elif import_name.startswith('test.integ.'):
- cropped_name = import_name[11:]
- else:
- cropped_name = import_name
-
+ cropped_name = test.arguments.crop_module_name(import_name)
cropped_name = cropped_name.rsplit('.', 1)[0] # exclude the class name
for prefix in module_prefixes:
@@ -370,7 +359,7 @@ def main():
println('\nYou can re-run just these tests with:\n', ERROR, STDERR)
for module in error_modules:
- println(' %s --test %s' % (' '.join(sys.argv), module), ERROR, STDERR)
+ println(' %s --test %s' % (' '.join(sys.argv), test.arguments.crop_module_name(module)), ERROR, STDERR)
else:
if skipped_tests > 0:
println('%i TESTS WERE SKIPPED' % skipped_tests, STATUS)
diff --git a/stem/__init__.py b/stem/__init__.py
index 70870e8e..293b2b94 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -28,6 +28,9 @@ Library for working with the tor process.
+- SocketError - Communication with the socket failed.
+- SocketClosed - Socket has been shut down.
+ DownloadFailed - Inability to download a resource.
+ +- DownloadTimeout - Download timeout reached.
+
.. data:: Runlevel (enum)
Rating of importance used for event logging.
@@ -499,6 +502,8 @@ Library for working with the tor process.
================= ===========
"""
+import traceback
+
import stem.util
import stem.util.enum
@@ -532,6 +537,8 @@ __all__ = [
'InvalidArguments',
'SocketError',
'SocketClosed',
+ 'DownloadFailed',
+ 'DownloadTimeout',
'Runlevel',
'Signal',
'Flag',
@@ -712,6 +719,66 @@ class SocketClosed(SocketError):
'Control socket was closed before completing the message.'
+class DownloadFailed(IOError):
+ """
+ Inability to download a resource. Python's urllib module raises
+ a wide variety of undocumented exceptions (urllib2.URLError,
+ socket.timeout, and others).
+
+ This wraps lower level failures in a common exception type that
+ retains their exception and `stacktrace
+ <https://docs.python.org/3/library/traceback.html>`_.
+
+ .. versionadded:: 1.8.0
+
+ :var str url: url we failed to download from
+ :var Exception error: original urllib exception
+ :var traceback stacktrace: original stacktrace
+ :var str stacktrace_str: string representation of the stacktrace
+ """
+
+ def __init__(self, url, error, stacktrace, message = None):
+ if message is None:
+ # The string representation of exceptions can reside in several places.
+ # urllib.URLError use a 'reason' attribute that in turn may referrence
+ # low level structures such as socket.gaierror. Whereas most exceptions
+ # use a 'message' attribute.
+
+ reason = str(error)
+
+ all_str_repr = (
+ getattr(getattr(error, 'reason', None), 'strerror', None),
+ getattr(error, 'reason', None),
+ getattr(error, 'message', None),
+ )
+
+ for str_repr in all_str_repr:
+ if str_repr and isinstance(str_repr, str):
+ reason = str_repr
+ break
+
+ message = 'Failed to download from %s (%s): %s' % (url, type(error).__name__, reason)
+
+ super(DownloadFailed, self).__init__(message)
+
+ self.url = url
+ self.error = error
+ self.stacktrace = stacktrace
+ self.stacktrace_str = ''.join(traceback.format_tb(stacktrace))
+
+
+class DownloadTimeout(DownloadFailed):
+ """
+ Timeout reached while downloading this resource.
+
+ .. versionadded:: 1.8.0
+ """
+
+ def __init__(self, url, error, stacktrace, timeout):
+ message = 'Failed to download from %s: %0.1f second timeout reached' % (url, timeout)
+ super(DownloadTimeout, self).__init__(url, error, stacktrace, message)
+
+
Runlevel = stem.util.enum.UppercaseEnum(
'DEBUG',
'INFO',
diff --git a/stem/cached_fallbacks.cfg b/stem/cached_fallbacks.cfg
index 5a725ae6..90f5db5b 100644
--- a/stem/cached_fallbacks.cfg
+++ b/stem/cached_fallbacks.cfg
@@ -1,23 +1,28 @@
-tor_commit 63b4ea22af8e8314dd718f02046de5f4b91edf9d
-stem_commit 206b50519dbdf2881cf72cf9f0cbae453db08ede
-header.timestamp 20181207055710
+tor_commit 1dd95278970f9f32d83a31fe73e0258a30523539
+stem_commit ec67e06398d6bbbcefdc14b56d2e91bd49f47539
+header.timestamp 20190625114911
+header.source whitelist
header.version 2.0.0
-header.timestamp2 20181207195255
-header.timestamp0 20181207055710
-header.timestamp1 20181207193756
+header.timestamp0 20190625114911
+header.timestamp1 20190628085927
header.type fallback
-0111BA9B604669E636FFD5B503F382A4B7AD6E80.address 176.10.104.240
-0111BA9B604669E636FFD5B503F382A4B7AD6E80.or_port 443
-0111BA9B604669E636FFD5B503F382A4B7AD6E80.dir_port 80
-0111BA9B604669E636FFD5B503F382A4B7AD6E80.nickname DigiGesTor1e1
-0111BA9B604669E636FFD5B503F382A4B7AD6E80.has_extrainfo false
-01A9258A46E97FF8B2CAC7910577862C14F2C524.address 193.171.202.146
-01A9258A46E97FF8B2CAC7910577862C14F2C524.or_port 9001
-01A9258A46E97FF8B2CAC7910577862C14F2C524.dir_port 9030
-01A9258A46E97FF8B2CAC7910577862C14F2C524.nickname ins0
-01A9258A46E97FF8B2CAC7910577862C14F2C524.has_extrainfo false
-01A9258A46E97FF8B2CAC7910577862C14F2C524.orport6_address 2001:628:200a:f001:20::146
-01A9258A46E97FF8B2CAC7910577862C14F2C524.orport6_port 9001
+001524DD403D729F08F7E5D77813EF12756CFA8D.address 185.13.39.197
+001524DD403D729F08F7E5D77813EF12756CFA8D.or_port 443
+001524DD403D729F08F7E5D77813EF12756CFA8D.dir_port 80
+001524DD403D729F08F7E5D77813EF12756CFA8D.nickname Neldoreth
+001524DD403D729F08F7E5D77813EF12756CFA8D.has_extrainfo false
+025B66CEBC070FCB0519D206CF0CF4965C20C96E.address 185.100.85.61
+025B66CEBC070FCB0519D206CF0CF4965C20C96E.or_port 443
+025B66CEBC070FCB0519D206CF0CF4965C20C96E.dir_port 80
+025B66CEBC070FCB0519D206CF0CF4965C20C96E.nickname nibbana
+025B66CEBC070FCB0519D206CF0CF4965C20C96E.has_extrainfo false
+0338F9F55111FE8E3570E7DE117EF3AF999CC1D7.address 185.225.17.3
+0338F9F55111FE8E3570E7DE117EF3AF999CC1D7.or_port 443
+0338F9F55111FE8E3570E7DE117EF3AF999CC1D7.dir_port 80
+0338F9F55111FE8E3570E7DE117EF3AF999CC1D7.nickname Nebuchadnezzar
+0338F9F55111FE8E3570E7DE117EF3AF999CC1D7.has_extrainfo false
+0338F9F55111FE8E3570E7DE117EF3AF999CC1D7.orport6_address 2a0a:c800:1:5::3
+0338F9F55111FE8E3570E7DE117EF3AF999CC1D7.orport6_port 443
0B85617241252517E8ECF2CFC7F4C1A32DCD153F.address 163.172.149.155
0B85617241252517E8ECF2CFC7F4C1A32DCD153F.or_port 443
0B85617241252517E8ECF2CFC7F4C1A32DCD153F.dir_port 80
@@ -28,18 +33,6 @@ header.type fallback
0C039F35C2E40DCB71CD8A07E97C7FD7787D42D6.dir_port 80
0C039F35C2E40DCB71CD8A07E97C7FD7787D42D6.nickname libel
0C039F35C2E40DCB71CD8A07E97C7FD7787D42D6.has_extrainfo false
-0C2C599AFCB26F5CFC2C7592435924C1D63D9484.address 5.196.88.122
-0C2C599AFCB26F5CFC2C7592435924C1D63D9484.or_port 9001
-0C2C599AFCB26F5CFC2C7592435924C1D63D9484.dir_port 9030
-0C2C599AFCB26F5CFC2C7592435924C1D63D9484.nickname ATo
-0C2C599AFCB26F5CFC2C7592435924C1D63D9484.has_extrainfo false
-0C2C599AFCB26F5CFC2C7592435924C1D63D9484.orport6_address 2001:41d0:a:fb7a::1
-0C2C599AFCB26F5CFC2C7592435924C1D63D9484.orport6_port 9001
-0E8C0C8315B66DB5F703804B3889A1DD66C67CE0.address 185.100.86.100
-0E8C0C8315B66DB5F703804B3889A1DD66C67CE0.or_port 443
-0E8C0C8315B66DB5F703804B3889A1DD66C67CE0.dir_port 80
-0E8C0C8315B66DB5F703804B3889A1DD66C67CE0.nickname saveyourprivacyex1
-0E8C0C8315B66DB5F703804B3889A1DD66C67CE0.has_extrainfo false
113143469021882C3A4B82F084F8125B08EE471E.address 37.252.185.182
113143469021882C3A4B82F084F8125B08EE471E.or_port 8080
113143469021882C3A4B82F084F8125B08EE471E.dir_port 9030
@@ -54,6 +47,11 @@ header.type fallback
11DF0017A43AF1F08825CD5D973297F81AB00FF3.has_extrainfo false
11DF0017A43AF1F08825CD5D973297F81AB00FF3.orport6_address 2a03:4000:6:724c:df98:15f9:b34d:443
11DF0017A43AF1F08825CD5D973297F81AB00FF3.orport6_port 443
+1211AC1BBB8A1AF7CBA86BCE8689AA3146B86423.address 95.85.8.226
+1211AC1BBB8A1AF7CBA86BCE8689AA3146B86423.or_port 443
+1211AC1BBB8A1AF7CBA86BCE8689AA3146B86423.dir_port 80
+1211AC1BBB8A1AF7CBA86BCE8689AA3146B86423.nickname ccrelaycc
+1211AC1BBB8A1AF7CBA86BCE8689AA3146B86423.has_extrainfo false
12AD30E5D25AA67F519780E2111E611A455FDC89.address 193.11.114.43
12AD30E5D25AA67F519780E2111E611A455FDC89.or_port 9001
12AD30E5D25AA67F519780E2111E611A455FDC89.dir_port 9030
@@ -61,42 +59,23 @@ header.type fallback
12AD30E5D25AA67F519780E2111E611A455FDC89.has_extrainfo false
12AD30E5D25AA67F519780E2111E611A455FDC89.orport6_address 2001:6b0:30:1000::99
12AD30E5D25AA67F519780E2111E611A455FDC89.orport6_port 9050
-136F9299A5009A4E0E96494E723BDB556FB0A26B.address 193.234.15.59
-136F9299A5009A4E0E96494E723BDB556FB0A26B.or_port 443
-136F9299A5009A4E0E96494E723BDB556FB0A26B.dir_port 80
-136F9299A5009A4E0E96494E723BDB556FB0A26B.nickname bakunin2
-136F9299A5009A4E0E96494E723BDB556FB0A26B.has_extrainfo false
-136F9299A5009A4E0E96494E723BDB556FB0A26B.orport6_address 2a00:1c20:4089:1234:bff6:e1bb:1ce3:8dc6
-136F9299A5009A4E0E96494E723BDB556FB0A26B.orport6_port 443
-14419131033443AE6E21DA82B0D307F7CAE42BDB.address 144.76.14.145
-14419131033443AE6E21DA82B0D307F7CAE42BDB.or_port 143
-14419131033443AE6E21DA82B0D307F7CAE42BDB.dir_port 110
-14419131033443AE6E21DA82B0D307F7CAE42BDB.nickname PedicaboMundi
-14419131033443AE6E21DA82B0D307F7CAE42BDB.has_extrainfo false
-14419131033443AE6E21DA82B0D307F7CAE42BDB.orport6_address 2a01:4f8:190:9490::dead
-14419131033443AE6E21DA82B0D307F7CAE42BDB.orport6_port 443
-14877C6384A9E793F422C8D1DDA447CACA4F7C4B.address 185.220.101.9
-14877C6384A9E793F422C8D1DDA447CACA4F7C4B.or_port 20009
-14877C6384A9E793F422C8D1DDA447CACA4F7C4B.dir_port 10009
-14877C6384A9E793F422C8D1DDA447CACA4F7C4B.nickname niftywoodmouse
-14877C6384A9E793F422C8D1DDA447CACA4F7C4B.has_extrainfo false
-1576BE143D8727745BB2BCDDF183291B3C3EFEFC.address 54.37.138.138
-1576BE143D8727745BB2BCDDF183291B3C3EFEFC.or_port 993
-1576BE143D8727745BB2BCDDF183291B3C3EFEFC.dir_port 8080
-1576BE143D8727745BB2BCDDF183291B3C3EFEFC.nickname anotherone
-1576BE143D8727745BB2BCDDF183291B3C3EFEFC.has_extrainfo false
-15BE17C99FACE24470D40AF782D6A9C692AB36D6.address 51.15.78.0
-15BE17C99FACE24470D40AF782D6A9C692AB36D6.or_port 9001
-15BE17C99FACE24470D40AF782D6A9C692AB36D6.dir_port 9030
-15BE17C99FACE24470D40AF782D6A9C692AB36D6.nickname rofltor07
-15BE17C99FACE24470D40AF782D6A9C692AB36D6.has_extrainfo false
-15BE17C99FACE24470D40AF782D6A9C692AB36D6.orport6_address 2001:bc8:4700:2300::16:c0b
-15BE17C99FACE24470D40AF782D6A9C692AB36D6.orport6_port 9001
-185F2A57B0C4620582602761097D17DB81654F70.address 204.11.50.131
-185F2A57B0C4620582602761097D17DB81654F70.or_port 9001
-185F2A57B0C4620582602761097D17DB81654F70.dir_port 9030
-185F2A57B0C4620582602761097D17DB81654F70.nickname BoingBoing
-185F2A57B0C4620582602761097D17DB81654F70.has_extrainfo false
+12FD624EE73CEF37137C90D38B2406A66F68FAA2.address 37.157.195.87
+12FD624EE73CEF37137C90D38B2406A66F68FAA2.or_port 443
+12FD624EE73CEF37137C90D38B2406A66F68FAA2.dir_port 8030
+12FD624EE73CEF37137C90D38B2406A66F68FAA2.nickname thanatosCZ
+12FD624EE73CEF37137C90D38B2406A66F68FAA2.has_extrainfo false
+183005F78229D94EE51CE7795A42280070A48D0D.address 217.182.51.248
+183005F78229D94EE51CE7795A42280070A48D0D.or_port 443
+183005F78229D94EE51CE7795A42280070A48D0D.dir_port 80
+183005F78229D94EE51CE7795A42280070A48D0D.nickname Cosworth02
+183005F78229D94EE51CE7795A42280070A48D0D.has_extrainfo false
+185663B7C12777F052B2C2D23D7A239D8DA88A0F.address 171.25.193.25
+185663B7C12777F052B2C2D23D7A239D8DA88A0F.or_port 443
+185663B7C12777F052B2C2D23D7A239D8DA88A0F.dir_port 80
+185663B7C12777F052B2C2D23D7A239D8DA88A0F.nickname DFRI5
+185663B7C12777F052B2C2D23D7A239D8DA88A0F.has_extrainfo false
+185663B7C12777F052B2C2D23D7A239D8DA88A0F.orport6_address 2001:67c:289c::25
+185663B7C12777F052B2C2D23D7A239D8DA88A0F.orport6_port 443
1938EBACBB1A7BFA888D9623C90061130E63BB3F.address 149.56.141.138
1938EBACBB1A7BFA888D9623C90061130E63BB3F.or_port 9001
1938EBACBB1A7BFA888D9623C90061130E63BB3F.dir_port 9030
@@ -107,13 +86,13 @@ header.type fallback
1AE039EE0B11DB79E4B4B29CBA9F752864A0259E.dir_port 9001
1AE039EE0B11DB79E4B4B29CBA9F752864A0259E.nickname Ichotolot60
1AE039EE0B11DB79E4B4B29CBA9F752864A0259E.has_extrainfo true
-1C90D3AEADFF3BCD079810632C8B85637924A58E.address 163.172.53.84
-1C90D3AEADFF3BCD079810632C8B85637924A58E.or_port 21
-1C90D3AEADFF3BCD079810632C8B85637924A58E.dir_port 143
-1C90D3AEADFF3BCD079810632C8B85637924A58E.nickname Multivac
-1C90D3AEADFF3BCD079810632C8B85637924A58E.has_extrainfo false
-1C90D3AEADFF3BCD079810632C8B85637924A58E.orport6_address 2001:bc8:24f8::
-1C90D3AEADFF3BCD079810632C8B85637924A58E.orport6_port 21
+1CD17CB202063C51C7DAD3BACEF87ECE81C2350F.address 50.7.74.171
+1CD17CB202063C51C7DAD3BACEF87ECE81C2350F.or_port 9001
+1CD17CB202063C51C7DAD3BACEF87ECE81C2350F.dir_port 9030
+1CD17CB202063C51C7DAD3BACEF87ECE81C2350F.nickname theia1
+1CD17CB202063C51C7DAD3BACEF87ECE81C2350F.has_extrainfo false
+1CD17CB202063C51C7DAD3BACEF87ECE81C2350F.orport6_address 2001:49f0:d002:2::51
+1CD17CB202063C51C7DAD3BACEF87ECE81C2350F.orport6_port 443
1F6ABD086F40B890A33C93CC4606EE68B31C9556.address 199.184.246.250
1F6ABD086F40B890A33C93CC4606EE68B31C9556.or_port 443
1F6ABD086F40B890A33C93CC4606EE68B31C9556.dir_port 80
@@ -128,45 +107,33 @@ header.type fallback
20462CBA5DA4C2D963567D17D0B7249718114A68.has_extrainfo false
20462CBA5DA4C2D963567D17D0B7249718114A68.orport6_address 2001:bc8:4400:2100::f03
20462CBA5DA4C2D963567D17D0B7249718114A68.orport6_port 9001
+204DFD2A2C6A0DC1FA0EACB495218E0B661704FD.address 77.247.181.164
+204DFD2A2C6A0DC1FA0EACB495218E0B661704FD.or_port 443
+204DFD2A2C6A0DC1FA0EACB495218E0B661704FD.dir_port 80
+204DFD2A2C6A0DC1FA0EACB495218E0B661704FD.nickname HaveHeart
+204DFD2A2C6A0DC1FA0EACB495218E0B661704FD.has_extrainfo false
230A8B2A8BA861210D9B4BA97745AEC217A94207.address 163.172.176.167
230A8B2A8BA861210D9B4BA97745AEC217A94207.or_port 443
230A8B2A8BA861210D9B4BA97745AEC217A94207.dir_port 80
230A8B2A8BA861210D9B4BA97745AEC217A94207.nickname niij01
230A8B2A8BA861210D9B4BA97745AEC217A94207.has_extrainfo false
-24E91955D969AEA1D80413C64FE106FAE7FD2EA9.address 185.220.101.8
-24E91955D969AEA1D80413C64FE106FAE7FD2EA9.or_port 20008
-24E91955D969AEA1D80413C64FE106FAE7FD2EA9.dir_port 10008
-24E91955D969AEA1D80413C64FE106FAE7FD2EA9.nickname niftymouse
-24E91955D969AEA1D80413C64FE106FAE7FD2EA9.has_extrainfo false
-2BA2C8E96B2590E1072AECE2BDB5C48921BF8510.address 138.201.250.33
-2BA2C8E96B2590E1072AECE2BDB5C48921BF8510.or_port 9011
-2BA2C8E96B2590E1072AECE2BDB5C48921BF8510.dir_port 9012
-2BA2C8E96B2590E1072AECE2BDB5C48921BF8510.nickname storm
-2BA2C8E96B2590E1072AECE2BDB5C48921BF8510.has_extrainfo false
-2CDCFED0142B28B002E89D305CBA2E26063FADE2.address 193.234.15.56
-2CDCFED0142B28B002E89D305CBA2E26063FADE2.or_port 443
-2CDCFED0142B28B002E89D305CBA2E26063FADE2.dir_port 80
-2CDCFED0142B28B002E89D305CBA2E26063FADE2.nickname jaures
-2CDCFED0142B28B002E89D305CBA2E26063FADE2.has_extrainfo false
-2CDCFED0142B28B002E89D305CBA2E26063FADE2.orport6_address 2a00:1c20:4089:1234:cd49:b58a:9ebe:67ec
-2CDCFED0142B28B002E89D305CBA2E26063FADE2.orport6_port 443
2F0F32AB1E5B943CA7D062C03F18960C86E70D94.address 97.74.237.196
2F0F32AB1E5B943CA7D062C03F18960C86E70D94.or_port 9001
2F0F32AB1E5B943CA7D062C03F18960C86E70D94.dir_port 9030
2F0F32AB1E5B943CA7D062C03F18960C86E70D94.nickname Minotaur
2F0F32AB1E5B943CA7D062C03F18960C86E70D94.has_extrainfo false
-311A4533F7A2415F42346A6C8FA77E6FD279594C.address 94.230.208.147
-311A4533F7A2415F42346A6C8FA77E6FD279594C.or_port 8443
-311A4533F7A2415F42346A6C8FA77E6FD279594C.dir_port 8080
-311A4533F7A2415F42346A6C8FA77E6FD279594C.nickname DigiGesTor3e2
-311A4533F7A2415F42346A6C8FA77E6FD279594C.has_extrainfo false
-311A4533F7A2415F42346A6C8FA77E6FD279594C.orport6_address 2a02:418:6017::147
-311A4533F7A2415F42346A6C8FA77E6FD279594C.orport6_port 8443
322C6E3A973BC10FC36DE3037AD27BC89F14723B.address 212.83.154.33
322C6E3A973BC10FC36DE3037AD27BC89F14723B.or_port 8443
322C6E3A973BC10FC36DE3037AD27BC89F14723B.dir_port 8080
322C6E3A973BC10FC36DE3037AD27BC89F14723B.nickname bauruine204
322C6E3A973BC10FC36DE3037AD27BC89F14723B.has_extrainfo false
+32EE911D968BE3E016ECA572BB1ED0A9EE43FC2F.address 109.105.109.162
+32EE911D968BE3E016ECA572BB1ED0A9EE43FC2F.or_port 60784
+32EE911D968BE3E016ECA572BB1ED0A9EE43FC2F.dir_port 52860
+32EE911D968BE3E016ECA572BB1ED0A9EE43FC2F.nickname ndnr1
+32EE911D968BE3E016ECA572BB1ED0A9EE43FC2F.has_extrainfo false
+32EE911D968BE3E016ECA572BB1ED0A9EE43FC2F.orport6_address 2001:948:7:2::163
+32EE911D968BE3E016ECA572BB1ED0A9EE43FC2F.orport6_port 5001
330CD3DB6AD266DC70CDB512B036957D03D9BC59.address 185.100.84.212
330CD3DB6AD266DC70CDB512B036957D03D9BC59.or_port 443
330CD3DB6AD266DC70CDB512B036957D03D9BC59.dir_port 80
@@ -174,40 +141,49 @@ header.type fallback
330CD3DB6AD266DC70CDB512B036957D03D9BC59.has_extrainfo false
330CD3DB6AD266DC70CDB512B036957D03D9BC59.orport6_address 2a06:1700:0:7::1
330CD3DB6AD266DC70CDB512B036957D03D9BC59.orport6_port 443
-360CBA08D1E24F513162047BDB54A1015E531534.address 54.37.17.235
-360CBA08D1E24F513162047BDB54A1015E531534.or_port 9001
-360CBA08D1E24F513162047BDB54A1015E531534.dir_port 9030
-360CBA08D1E24F513162047BDB54A1015E531534.nickname Aerodynamik06
-360CBA08D1E24F513162047BDB54A1015E531534.has_extrainfo false
361D33C96D0F161275EE67E2C91EE10B276E778B.address 37.157.255.35
361D33C96D0F161275EE67E2C91EE10B276E778B.or_port 9090
361D33C96D0F161275EE67E2C91EE10B276E778B.dir_port 9030
361D33C96D0F161275EE67E2C91EE10B276E778B.nickname cxx4freedom
361D33C96D0F161275EE67E2C91EE10B276E778B.has_extrainfo false
-36B9E7AC1E36B62A9D6F330ABEB6012BA7F0D400.address 37.187.22.87
-36B9E7AC1E36B62A9D6F330ABEB6012BA7F0D400.or_port 9001
-36B9E7AC1E36B62A9D6F330ABEB6012BA7F0D400.dir_port 9030
-36B9E7AC1E36B62A9D6F330ABEB6012BA7F0D400.nickname kimsufi321
-36B9E7AC1E36B62A9D6F330ABEB6012BA7F0D400.has_extrainfo false
-36B9E7AC1E36B62A9D6F330ABEB6012BA7F0D400.orport6_address 2001:41d0:a:1657::1
-36B9E7AC1E36B62A9D6F330ABEB6012BA7F0D400.orport6_port 9001
375DCBB2DBD94E5263BC0C015F0C9E756669617E.address 64.79.152.132
375DCBB2DBD94E5263BC0C015F0C9E756669617E.or_port 443
375DCBB2DBD94E5263BC0C015F0C9E756669617E.dir_port 80
375DCBB2DBD94E5263BC0C015F0C9E756669617E.nickname ebola
375DCBB2DBD94E5263BC0C015F0C9E756669617E.has_extrainfo false
-387B065A38E4DAA16D9D41C2964ECBC4B31D30FF.address 62.210.92.11
-387B065A38E4DAA16D9D41C2964ECBC4B31D30FF.or_port 9101
-387B065A38E4DAA16D9D41C2964ECBC4B31D30FF.dir_port 9130
-387B065A38E4DAA16D9D41C2964ECBC4B31D30FF.nickname redjohn1
-387B065A38E4DAA16D9D41C2964ECBC4B31D30FF.has_extrainfo false
-387B065A38E4DAA16D9D41C2964ECBC4B31D30FF.orport6_address 2001:bc8:338c::1
-387B065A38E4DAA16D9D41C2964ECBC4B31D30FF.orport6_port 9101
-39F096961ED2576975C866D450373A9913AFDC92.address 198.50.191.95
-39F096961ED2576975C866D450373A9913AFDC92.or_port 443
-39F096961ED2576975C866D450373A9913AFDC92.dir_port 80
-39F096961ED2576975C866D450373A9913AFDC92.nickname thomas
-39F096961ED2576975C866D450373A9913AFDC92.has_extrainfo false
+39F91959416763AFD34DBEEC05474411B964B2DC.address 213.183.60.21
+39F91959416763AFD34DBEEC05474411B964B2DC.or_port 443
+39F91959416763AFD34DBEEC05474411B964B2DC.dir_port 9030
+39F91959416763AFD34DBEEC05474411B964B2DC.nickname angeltest11
+39F91959416763AFD34DBEEC05474411B964B2DC.has_extrainfo false
+3AFDAAD91A15B4C6A7686A53AA8627CA871FF491.address 50.7.74.174
+3AFDAAD91A15B4C6A7686A53AA8627CA871FF491.or_port 9001
+3AFDAAD91A15B4C6A7686A53AA8627CA871FF491.dir_port 9030
+3AFDAAD91A15B4C6A7686A53AA8627CA871FF491.nickname theia7
+3AFDAAD91A15B4C6A7686A53AA8627CA871FF491.has_extrainfo false
+3AFDAAD91A15B4C6A7686A53AA8627CA871FF491.orport6_address 2001:49f0:d002:2::57
+3AFDAAD91A15B4C6A7686A53AA8627CA871FF491.orport6_port 443
+3CA0D15567024D2E0B557DC0CF3E962B37999A79.address 199.249.230.83
+3CA0D15567024D2E0B557DC0CF3E962B37999A79.or_port 443
+3CA0D15567024D2E0B557DC0CF3E962B37999A79.dir_port 80
+3CA0D15567024D2E0B557DC0CF3E962B37999A79.nickname QuintexAirVPN30
+3CA0D15567024D2E0B557DC0CF3E962B37999A79.has_extrainfo false
+3CA0D15567024D2E0B557DC0CF3E962B37999A79.orport6_address 2620:7:6001::ffff:c759:e653
+3CA0D15567024D2E0B557DC0CF3E962B37999A79.orport6_port 80
+3CB4193EF4E239FCEDC4DC43468E0B0D6B67ACC3.address 51.38.65.160
+3CB4193EF4E239FCEDC4DC43468E0B0D6B67ACC3.or_port 9001
+3CB4193EF4E239FCEDC4DC43468E0B0D6B67ACC3.dir_port 9030
+3CB4193EF4E239FCEDC4DC43468E0B0D6B67ACC3.nickname rofltor10
+3CB4193EF4E239FCEDC4DC43468E0B0D6B67ACC3.has_extrainfo false
+3CB4193EF4E239FCEDC4DC43468E0B0D6B67ACC3.orport6_address 2001:41d0:801:2000::f6e
+3CB4193EF4E239FCEDC4DC43468E0B0D6B67ACC3.orport6_port 9001
+3E53D3979DB07EFD736661C934A1DED14127B684.address 217.79.179.177
+3E53D3979DB07EFD736661C934A1DED14127B684.or_port 9001
+3E53D3979DB07EFD736661C934A1DED14127B684.dir_port 9030
+3E53D3979DB07EFD736661C934A1DED14127B684.nickname Unnamed
+3E53D3979DB07EFD736661C934A1DED14127B684.has_extrainfo false
+3E53D3979DB07EFD736661C934A1DED14127B684.orport6_address 2001:4ba0:fff9:131:6c4f::90d3
+3E53D3979DB07EFD736661C934A1DED14127B684.orport6_port 9001
3F092986E9B87D3FDA09B71FA3A602378285C77A.address 66.111.2.16
3F092986E9B87D3FDA09B71FA3A602378285C77A.or_port 9001
3F092986E9B87D3FDA09B71FA3A602378285C77A.dir_port 9030
@@ -220,57 +196,33 @@ header.type fallback
4061C553CA88021B8302F0814365070AAE617270.dir_port 9030
4061C553CA88021B8302F0814365070AAE617270.nickname TorExitRomania
4061C553CA88021B8302F0814365070AAE617270.has_extrainfo false
-41A3C16269C7B63DB6EB741DBDDB4E1F586B1592.address 195.191.81.7
-41A3C16269C7B63DB6EB741DBDDB4E1F586B1592.or_port 9001
-41A3C16269C7B63DB6EB741DBDDB4E1F586B1592.dir_port 9030
-41A3C16269C7B63DB6EB741DBDDB4E1F586B1592.nickname rofltor02
-41A3C16269C7B63DB6EB741DBDDB4E1F586B1592.has_extrainfo false
-41A3C16269C7B63DB6EB741DBDDB4E1F586B1592.orport6_address 2a00:1908:fffc:ffff:c0a6:ccff:fe62:e1a1
-41A3C16269C7B63DB6EB741DBDDB4E1F586B1592.orport6_port 9001
-41C59606AFE1D1AA6EC6EF6719690B856F0B6587.address 178.17.170.156
-41C59606AFE1D1AA6EC6EF6719690B856F0B6587.or_port 9001
-41C59606AFE1D1AA6EC6EF6719690B856F0B6587.dir_port 9030
-41C59606AFE1D1AA6EC6EF6719690B856F0B6587.nickname TorExitMoldova2
-41C59606AFE1D1AA6EC6EF6719690B856F0B6587.has_extrainfo false
-41C59606AFE1D1AA6EC6EF6719690B856F0B6587.orport6_address 2a00:1dc0:caff:48::9257
-41C59606AFE1D1AA6EC6EF6719690B856F0B6587.orport6_port 9001
-45362E8ECD651CCAC521156FFBD2FF7F27FA8E88.address 81.7.10.251
-45362E8ECD651CCAC521156FFBD2FF7F27FA8E88.or_port 443
-45362E8ECD651CCAC521156FFBD2FF7F27FA8E88.dir_port 80
-45362E8ECD651CCAC521156FFBD2FF7F27FA8E88.nickname torpidsDEisppro2
-45362E8ECD651CCAC521156FFBD2FF7F27FA8E88.has_extrainfo false
-45362E8ECD651CCAC521156FFBD2FF7F27FA8E88.orport6_address 2a02:180:1:1::517:afb
-45362E8ECD651CCAC521156FFBD2FF7F27FA8E88.orport6_port 443
4623A9EC53BFD83155929E56D6F7B55B5E718C24.address 163.172.157.213
4623A9EC53BFD83155929E56D6F7B55B5E718C24.or_port 443
4623A9EC53BFD83155929E56D6F7B55B5E718C24.dir_port 8080
4623A9EC53BFD83155929E56D6F7B55B5E718C24.nickname Cotopaxi
4623A9EC53BFD83155929E56D6F7B55B5E718C24.has_extrainfo false
-4661DE96D3F8E923994B05218F23760C8D7935A4.address 132.248.241.5
-4661DE96D3F8E923994B05218F23760C8D7935A4.or_port 9001
-4661DE96D3F8E923994B05218F23760C8D7935A4.dir_port 9030
-4661DE96D3F8E923994B05218F23760C8D7935A4.nickname toritounam
-4661DE96D3F8E923994B05218F23760C8D7935A4.has_extrainfo false
+465D17C6FC297E3857B5C6F152006A1E212944EA.address 195.123.245.141
+465D17C6FC297E3857B5C6F152006A1E212944EA.or_port 443
+465D17C6FC297E3857B5C6F152006A1E212944EA.dir_port 9030
+465D17C6FC297E3857B5C6F152006A1E212944EA.nickname angeltest14
+465D17C6FC297E3857B5C6F152006A1E212944EA.has_extrainfo false
46791D156C9B6C255C2665D4D8393EC7DBAA7798.address 31.31.78.49
46791D156C9B6C255C2665D4D8393EC7DBAA7798.or_port 443
46791D156C9B6C255C2665D4D8393EC7DBAA7798.dir_port 80
46791D156C9B6C255C2665D4D8393EC7DBAA7798.nickname KrigHaBandolo
46791D156C9B6C255C2665D4D8393EC7DBAA7798.has_extrainfo false
-47C42E2094EE482E7C9B586B10BABFB67557030B.address 185.220.101.34
-47C42E2094EE482E7C9B586B10BABFB67557030B.or_port 20034
-47C42E2094EE482E7C9B586B10BABFB67557030B.dir_port 10034
-47C42E2094EE482E7C9B586B10BABFB67557030B.nickname niftysugarglider
-47C42E2094EE482E7C9B586B10BABFB67557030B.has_extrainfo false
484A10BA2B8D48A5F0216674C8DD50EF27BC32F3.address 193.70.43.76
484A10BA2B8D48A5F0216674C8DD50EF27BC32F3.or_port 9001
484A10BA2B8D48A5F0216674C8DD50EF27BC32F3.dir_port 9030
484A10BA2B8D48A5F0216674C8DD50EF27BC32F3.nickname Aerodynamik03
484A10BA2B8D48A5F0216674C8DD50EF27BC32F3.has_extrainfo false
-4CC9CC9195EC38645B699A33307058624F660CCF.address 51.254.101.242
-4CC9CC9195EC38645B699A33307058624F660CCF.or_port 9001
-4CC9CC9195EC38645B699A33307058624F660CCF.dir_port 9002
-4CC9CC9195EC38645B699A33307058624F660CCF.nickname devsum
-4CC9CC9195EC38645B699A33307058624F660CCF.has_extrainfo false
+489D94333DF66D57FFE34D9D59CC2D97E2CB0053.address 37.187.102.186
+489D94333DF66D57FFE34D9D59CC2D97E2CB0053.or_port 9001
+489D94333DF66D57FFE34D9D59CC2D97E2CB0053.dir_port 9030
+489D94333DF66D57FFE34D9D59CC2D97E2CB0053.nickname txtfileTorNode65536
+489D94333DF66D57FFE34D9D59CC2D97E2CB0053.has_extrainfo false
+489D94333DF66D57FFE34D9D59CC2D97E2CB0053.orport6_address 2001:41d0:a:26ba::1
+489D94333DF66D57FFE34D9D59CC2D97E2CB0053.orport6_port 9001
4EB55679FA91363B97372554F8DC7C63F4E5B101.address 81.7.13.84
4EB55679FA91363B97372554F8DC7C63F4E5B101.or_port 443
4EB55679FA91363B97372554F8DC7C63F4E5B101.dir_port 80
@@ -278,11 +230,18 @@ header.type fallback
4EB55679FA91363B97372554F8DC7C63F4E5B101.has_extrainfo false
4EB55679FA91363B97372554F8DC7C63F4E5B101.orport6_address 2a02:180:1:1::5b8f:538c
4EB55679FA91363B97372554F8DC7C63F4E5B101.orport6_port 443
-50586E25BE067FD1F739998550EDDCB1A14CA5B2.address 212.51.134.123
-50586E25BE067FD1F739998550EDDCB1A14CA5B2.or_port 9001
-50586E25BE067FD1F739998550EDDCB1A14CA5B2.dir_port 9030
-50586E25BE067FD1F739998550EDDCB1A14CA5B2.nickname Jans
-50586E25BE067FD1F739998550EDDCB1A14CA5B2.has_extrainfo false
+4F0DB7E687FC7C0AE55C8F243DA8B0EB27FBF1F2.address 108.53.208.157
+4F0DB7E687FC7C0AE55C8F243DA8B0EB27FBF1F2.or_port 443
+4F0DB7E687FC7C0AE55C8F243DA8B0EB27FBF1F2.dir_port 80
+4F0DB7E687FC7C0AE55C8F243DA8B0EB27FBF1F2.nickname Binnacle
+4F0DB7E687FC7C0AE55C8F243DA8B0EB27FBF1F2.has_extrainfo true
+509EAB4C5D10C9A9A24B4EA0CE402C047A2D64E6.address 5.9.158.75
+509EAB4C5D10C9A9A24B4EA0CE402C047A2D64E6.or_port 9001
+509EAB4C5D10C9A9A24B4EA0CE402C047A2D64E6.dir_port 9030
+509EAB4C5D10C9A9A24B4EA0CE402C047A2D64E6.nickname zwiebeltoralf2
+509EAB4C5D10C9A9A24B4EA0CE402C047A2D64E6.has_extrainfo true
+509EAB4C5D10C9A9A24B4EA0CE402C047A2D64E6.orport6_address 2a01:4f8:190:514a::2
+509EAB4C5D10C9A9A24B4EA0CE402C047A2D64E6.orport6_port 9001
51E1CF613FD6F9F11FE24743C91D6F9981807D82.address 81.7.16.182
51E1CF613FD6F9F11FE24743C91D6F9981807D82.or_port 443
51E1CF613FD6F9F11FE24743C91D6F9981807D82.dir_port 80
@@ -290,11 +249,6 @@ header.type fallback
51E1CF613FD6F9F11FE24743C91D6F9981807D82.has_extrainfo false
51E1CF613FD6F9F11FE24743C91D6F9981807D82.orport6_address 2a02:180:1:1::517:10b6
51E1CF613FD6F9F11FE24743C91D6F9981807D82.orport6_port 993
-52BFADA8BEAA01BA46C8F767F83C18E2FE50C1B9.address 85.25.159.65
-52BFADA8BEAA01BA46C8F767F83C18E2FE50C1B9.or_port 80
-52BFADA8BEAA01BA46C8F767F83C18E2FE50C1B9.dir_port 995
-52BFADA8BEAA01BA46C8F767F83C18E2FE50C1B9.nickname BeastieJoy63
-52BFADA8BEAA01BA46C8F767F83C18E2FE50C1B9.has_extrainfo false
547DA56F6B88B6C596B3E3086803CDA4F0EF8F21.address 192.160.102.166
547DA56F6B88B6C596B3E3086803CDA4F0EF8F21.or_port 9001
547DA56F6B88B6C596B3E3086803CDA4F0EF8F21.dir_port 80
@@ -309,40 +263,37 @@ header.type fallback
557ACEC850F54EEE65839F83CACE2B0825BE811E.has_extrainfo false
557ACEC850F54EEE65839F83CACE2B0825BE811E.orport6_address 2620:132:300c:c01d::a
557ACEC850F54EEE65839F83CACE2B0825BE811E.orport6_port 9002
-587E0A9552E4274B251F29B5B2673D38442EE4BF.address 95.130.12.119
-587E0A9552E4274B251F29B5B2673D38442EE4BF.or_port 443
-587E0A9552E4274B251F29B5B2673D38442EE4BF.dir_port 80
-587E0A9552E4274B251F29B5B2673D38442EE4BF.nickname Nuath
-587E0A9552E4274B251F29B5B2673D38442EE4BF.has_extrainfo false
-58ED9C9C35E433EE58764D62892B4FFD518A3CD0.address 185.21.100.50
-58ED9C9C35E433EE58764D62892B4FFD518A3CD0.or_port 9001
-58ED9C9C35E433EE58764D62892B4FFD518A3CD0.dir_port 9030
-58ED9C9C35E433EE58764D62892B4FFD518A3CD0.nickname SamAAdams2
-58ED9C9C35E433EE58764D62892B4FFD518A3CD0.has_extrainfo false
-58ED9C9C35E433EE58764D62892B4FFD518A3CD0.orport6_address 2a00:1158:2:cd00:0:74:6f:72
-58ED9C9C35E433EE58764D62892B4FFD518A3CD0.orport6_port 443
-5CF8AFA5E4B0BB88942A44A3F3AAE08C3BDFD60B.address 193.234.15.62
-5CF8AFA5E4B0BB88942A44A3F3AAE08C3BDFD60B.or_port 443
-5CF8AFA5E4B0BB88942A44A3F3AAE08C3BDFD60B.dir_port 80
-5CF8AFA5E4B0BB88942A44A3F3AAE08C3BDFD60B.nickname jaures4
-5CF8AFA5E4B0BB88942A44A3F3AAE08C3BDFD60B.has_extrainfo false
-5CF8AFA5E4B0BB88942A44A3F3AAE08C3BDFD60B.orport6_address 2a00:1c20:4089:1234:a6a4:2926:d0af:dfee
-5CF8AFA5E4B0BB88942A44A3F3AAE08C3BDFD60B.orport6_port 443
+5BF17163CBE73D8CD9FDBE030C944EA05707DA93.address 50.7.74.170
+5BF17163CBE73D8CD9FDBE030C944EA05707DA93.or_port 443
+5BF17163CBE73D8CD9FDBE030C944EA05707DA93.dir_port 80
+5BF17163CBE73D8CD9FDBE030C944EA05707DA93.nickname theia8
+5BF17163CBE73D8CD9FDBE030C944EA05707DA93.has_extrainfo false
+5BF17163CBE73D8CD9FDBE030C944EA05707DA93.orport6_address 2001:49f0:d002:2::58
+5BF17163CBE73D8CD9FDBE030C944EA05707DA93.orport6_port 443
5E56738E7F97AA81DEEF59AF28494293DFBFCCDF.address 172.98.193.43
5E56738E7F97AA81DEEF59AF28494293DFBFCCDF.or_port 443
5E56738E7F97AA81DEEF59AF28494293DFBFCCDF.dir_port 80
5E56738E7F97AA81DEEF59AF28494293DFBFCCDF.nickname Backplane
5E56738E7F97AA81DEEF59AF28494293DFBFCCDF.has_extrainfo false
-609E598FB6A00BCF7872906B602B705B64541C50.address 185.220.101.28
-609E598FB6A00BCF7872906B602B705B64541C50.or_port 20028
-609E598FB6A00BCF7872906B602B705B64541C50.dir_port 10028
-609E598FB6A00BCF7872906B602B705B64541C50.nickname niftychipmunk
-609E598FB6A00BCF7872906B602B705B64541C50.has_extrainfo false
+616081EC829593AF4232550DE6FFAA1D75B37A90.address 95.128.43.164
+616081EC829593AF4232550DE6FFAA1D75B37A90.or_port 443
+616081EC829593AF4232550DE6FFAA1D75B37A90.dir_port 80
+616081EC829593AF4232550DE6FFAA1D75B37A90.nickname AquaRayTerminus
+616081EC829593AF4232550DE6FFAA1D75B37A90.has_extrainfo false
+616081EC829593AF4232550DE6FFAA1D75B37A90.orport6_address 2a02:ec0:209:10::4
+616081EC829593AF4232550DE6FFAA1D75B37A90.orport6_port 443
68F175CCABE727AA2D2309BCD8789499CEE36ED7.address 163.172.139.104
68F175CCABE727AA2D2309BCD8789499CEE36ED7.or_port 443
68F175CCABE727AA2D2309BCD8789499CEE36ED7.dir_port 8080
68F175CCABE727AA2D2309BCD8789499CEE36ED7.nickname Pichincha
68F175CCABE727AA2D2309BCD8789499CEE36ED7.has_extrainfo false
+6A7551EEE18F78A9813096E82BF84F740D32B911.address 94.130.186.5
+6A7551EEE18F78A9813096E82BF84F740D32B911.or_port 443
+6A7551EEE18F78A9813096E82BF84F740D32B911.dir_port 80
+6A7551EEE18F78A9813096E82BF84F740D32B911.nickname TorMachine
+6A7551EEE18F78A9813096E82BF84F740D32B911.has_extrainfo false
+6A7551EEE18F78A9813096E82BF84F740D32B911.orport6_address 2a01:4f8:1c0c:45f7::1
+6A7551EEE18F78A9813096E82BF84F740D32B911.orport6_port 443
6EF897645B79B6CB35E853B32506375014DE3621.address 80.127.137.19
6EF897645B79B6CB35E853B32506375014DE3621.or_port 443
6EF897645B79B6CB35E853B32506375014DE3621.dir_port 80
@@ -362,36 +313,59 @@ header.type fallback
70C55A114C0EF3DC5784A4FAEE64388434A3398F.dir_port 80
70C55A114C0EF3DC5784A4FAEE64388434A3398F.nickname torpidsFRplusserver
70C55A114C0EF3DC5784A4FAEE64388434A3398F.has_extrainfo false
-71CFDEB4D9E00CCC3E31EC4E8A29E109BBC1FB36.address 185.220.101.30
-71CFDEB4D9E00CCC3E31EC4E8A29E109BBC1FB36.or_port 20030
-71CFDEB4D9E00CCC3E31EC4E8A29E109BBC1FB36.dir_port 10030
-71CFDEB4D9E00CCC3E31EC4E8A29E109BBC1FB36.nickname niftypedetidae
-71CFDEB4D9E00CCC3E31EC4E8A29E109BBC1FB36.has_extrainfo false
72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE.address 85.235.250.88
72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE.or_port 443
72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE.dir_port 80
72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE.nickname TykRelay01
72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE.has_extrainfo false
-7600680249A22080ECC6173FBBF64D6FCF330A61.address 81.7.14.31
-7600680249A22080ECC6173FBBF64D6FCF330A61.or_port 443
-7600680249A22080ECC6173FBBF64D6FCF330A61.dir_port 9001
-7600680249A22080ECC6173FBBF64D6FCF330A61.nickname Ichotolot62
-7600680249A22080ECC6173FBBF64D6FCF330A61.has_extrainfo true
+72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE.orport6_address 2a01:3a0:1:1900:85:235:250:88
+72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE.orport6_port 443
+742C45F2D9004AADE0077E528A4418A6A81BC2BA.address 178.17.170.23
+742C45F2D9004AADE0077E528A4418A6A81BC2BA.or_port 9001
+742C45F2D9004AADE0077E528A4418A6A81BC2BA.dir_port 9030
+742C45F2D9004AADE0077E528A4418A6A81BC2BA.nickname TorExitMoldova2
+742C45F2D9004AADE0077E528A4418A6A81BC2BA.has_extrainfo false
+742C45F2D9004AADE0077E528A4418A6A81BC2BA.orport6_address 2a00:1dc0:caff:7d::8254
+742C45F2D9004AADE0077E528A4418A6A81BC2BA.orport6_port 9001
+745369332749021C6FAF100D327BC3BF1DF4707B.address 50.7.74.173
+745369332749021C6FAF100D327BC3BF1DF4707B.or_port 9001
+745369332749021C6FAF100D327BC3BF1DF4707B.dir_port 9030
+745369332749021C6FAF100D327BC3BF1DF4707B.nickname theia5
+745369332749021C6FAF100D327BC3BF1DF4707B.has_extrainfo false
+745369332749021C6FAF100D327BC3BF1DF4707B.orport6_address 2001:49f0:d002:2::55
+745369332749021C6FAF100D327BC3BF1DF4707B.orport6_port 443
+77131D7E2EC1CA9B8D737502256DA9103599CE51.address 77.247.181.166
+77131D7E2EC1CA9B8D737502256DA9103599CE51.or_port 443
+77131D7E2EC1CA9B8D737502256DA9103599CE51.dir_port 80
+77131D7E2EC1CA9B8D737502256DA9103599CE51.nickname CriticalMass
+77131D7E2EC1CA9B8D737502256DA9103599CE51.has_extrainfo false
775B0FAFDE71AADC23FFC8782B7BEB1D5A92733E.address 5.196.23.64
775B0FAFDE71AADC23FFC8782B7BEB1D5A92733E.or_port 9001
775B0FAFDE71AADC23FFC8782B7BEB1D5A92733E.dir_port 9030
775B0FAFDE71AADC23FFC8782B7BEB1D5A92733E.nickname Aerodynamik01
775B0FAFDE71AADC23FFC8782B7BEB1D5A92733E.has_extrainfo false
+79509683AB4C8DDAF90A120C69A4179C6CD5A387.address 185.244.193.141
+79509683AB4C8DDAF90A120C69A4179C6CD5A387.or_port 9001
+79509683AB4C8DDAF90A120C69A4179C6CD5A387.dir_port 9030
+79509683AB4C8DDAF90A120C69A4179C6CD5A387.nickname DerDickeReloaded
+79509683AB4C8DDAF90A120C69A4179C6CD5A387.has_extrainfo false
+79509683AB4C8DDAF90A120C69A4179C6CD5A387.orport6_address 2a03:4000:27:192:24:12:1984:4
+79509683AB4C8DDAF90A120C69A4179C6CD5A387.orport6_port 9001
7BB70F8585DFC27E75D692970C0EEB0F22983A63.address 51.254.136.195
7BB70F8585DFC27E75D692970C0EEB0F22983A63.or_port 443
7BB70F8585DFC27E75D692970C0EEB0F22983A63.dir_port 80
7BB70F8585DFC27E75D692970C0EEB0F22983A63.nickname torproxy02
7BB70F8585DFC27E75D692970C0EEB0F22983A63.has_extrainfo false
-7D05A38E39FC5D29AFE6BE487B9B4DC9E635D09E.address 185.100.84.82
-7D05A38E39FC5D29AFE6BE487B9B4DC9E635D09E.or_port 443
-7D05A38E39FC5D29AFE6BE487B9B4DC9E635D09E.dir_port 80
-7D05A38E39FC5D29AFE6BE487B9B4DC9E635D09E.nickname saveyourprivacyexit
-7D05A38E39FC5D29AFE6BE487B9B4DC9E635D09E.has_extrainfo false
+7BFB908A3AA5B491DA4CA72CCBEE0E1F2A939B55.address 77.247.181.162
+7BFB908A3AA5B491DA4CA72CCBEE0E1F2A939B55.or_port 443
+7BFB908A3AA5B491DA4CA72CCBEE0E1F2A939B55.dir_port 80
+7BFB908A3AA5B491DA4CA72CCBEE0E1F2A939B55.nickname sofia
+7BFB908A3AA5B491DA4CA72CCBEE0E1F2A939B55.has_extrainfo false
+7E281CD2C315C4F7A84BC7C8721C3BC974DDBFA3.address 185.220.101.48
+7E281CD2C315C4F7A84BC7C8721C3BC974DDBFA3.or_port 20048
+7E281CD2C315C4F7A84BC7C8721C3BC974DDBFA3.dir_port 10048
+7E281CD2C315C4F7A84BC7C8721C3BC974DDBFA3.nickname niftyporcupine
+7E281CD2C315C4F7A84BC7C8721C3BC974DDBFA3.has_extrainfo false
80AAF8D5956A43C197104CEF2550CD42D165C6FB.address 193.11.114.45
80AAF8D5956A43C197104CEF2550CD42D165C6FB.or_port 9002
80AAF8D5956A43C197104CEF2550CD42D165C6FB.dir_port 9031
@@ -404,13 +378,6 @@ header.type fallback
8101421BEFCCF4C271D5483C5AABCAAD245BBB9D.has_extrainfo false
8101421BEFCCF4C271D5483C5AABCAAD245BBB9D.orport6_address 2001:41d0:401:3100::30dc
8101421BEFCCF4C271D5483C5AABCAAD245BBB9D.orport6_port 9001
-81AFA888F8F8F4A024AB58ECA0ADDEBB93FF01DA.address 217.12.199.190
-81AFA888F8F8F4A024AB58ECA0ADDEBB93FF01DA.or_port 443
-81AFA888F8F8F4A024AB58ECA0ADDEBB93FF01DA.dir_port 80
-81AFA888F8F8F4A024AB58ECA0ADDEBB93FF01DA.nickname torpidsUAitlas
-81AFA888F8F8F4A024AB58ECA0ADDEBB93FF01DA.has_extrainfo false
-81AFA888F8F8F4A024AB58ECA0ADDEBB93FF01DA.orport6_address 2a02:27a8:0:2::486
-81AFA888F8F8F4A024AB58ECA0ADDEBB93FF01DA.orport6_port 993
81B75D534F91BFB7C57AB67DA10BCEF622582AE8.address 192.42.116.16
81B75D534F91BFB7C57AB67DA10BCEF622582AE8.or_port 443
81B75D534F91BFB7C57AB67DA10BCEF622582AE8.dir_port 80
@@ -435,23 +402,23 @@ header.type fallback
8456DFA94161CDD99E480C2A2992C366C6564410.dir_port 80
8456DFA94161CDD99E480C2A2992C366C6564410.nickname turingmachine
8456DFA94161CDD99E480C2A2992C366C6564410.has_extrainfo false
+855BC2DABE24C861CD887DB9B2E950424B49FC34.address 85.230.178.139
+855BC2DABE24C861CD887DB9B2E950424B49FC34.or_port 443
+855BC2DABE24C861CD887DB9B2E950424B49FC34.dir_port 9030
+855BC2DABE24C861CD887DB9B2E950424B49FC34.nickname Logforme
+855BC2DABE24C861CD887DB9B2E950424B49FC34.has_extrainfo false
+85A885433E50B1874F11CEC9BE98451E24660976.address 178.254.7.88
+85A885433E50B1874F11CEC9BE98451E24660976.or_port 8443
+85A885433E50B1874F11CEC9BE98451E24660976.dir_port 8080
+85A885433E50B1874F11CEC9BE98451E24660976.nickname wr3ck3d0ni0n01
+85A885433E50B1874F11CEC9BE98451E24660976.has_extrainfo false
86C281AD135058238D7A337D546C902BE8505DDE.address 185.96.88.29
86C281AD135058238D7A337D546C902BE8505DDE.or_port 443
86C281AD135058238D7A337D546C902BE8505DDE.dir_port 80
86C281AD135058238D7A337D546C902BE8505DDE.nickname TykRelay05
86C281AD135058238D7A337D546C902BE8505DDE.has_extrainfo false
-8844D87E9B038BE3270938F05AF797E1D3C74C0F.address 93.180.156.84
-8844D87E9B038BE3270938F05AF797E1D3C74C0F.or_port 9001
-8844D87E9B038BE3270938F05AF797E1D3C74C0F.dir_port 9030
-8844D87E9B038BE3270938F05AF797E1D3C74C0F.nickname BARACUDA
-8844D87E9B038BE3270938F05AF797E1D3C74C0F.has_extrainfo false
-8B6556601612F1E2AFCE2A12FFFAF8482A76DD1F.address 51.15.205.214
-8B6556601612F1E2AFCE2A12FFFAF8482A76DD1F.or_port 9001
-8B6556601612F1E2AFCE2A12FFFAF8482A76DD1F.dir_port 9030
-8B6556601612F1E2AFCE2A12FFFAF8482A76DD1F.nickname titania1
-8B6556601612F1E2AFCE2A12FFFAF8482A76DD1F.has_extrainfo false
-8B6556601612F1E2AFCE2A12FFFAF8482A76DD1F.orport6_address 2001:bc8:4400:2500::5:b07
-8B6556601612F1E2AFCE2A12FFFAF8482A76DD1F.orport6_port 9001
+86C281AD135058238D7A337D546C902BE8505DDE.orport6_address 2a00:4020::185:96:88:29
+86C281AD135058238D7A337D546C902BE8505DDE.orport6_port 443
8C00FA7369A7A308F6A137600F0FA07990D9D451.address 163.172.194.53
8C00FA7369A7A308F6A137600F0FA07990D9D451.or_port 9001
8C00FA7369A7A308F6A137600F0FA07990D9D451.dir_port 9030
@@ -459,11 +426,23 @@ header.type fallback
8C00FA7369A7A308F6A137600F0FA07990D9D451.has_extrainfo false
8C00FA7369A7A308F6A137600F0FA07990D9D451.orport6_address 2001:bc8:225f:142:6c69:7461:7669:73
8C00FA7369A7A308F6A137600F0FA07990D9D451.orport6_port 9001
+8D79F73DCD91FC4F5017422FAC70074D6DB8DD81.address 5.189.169.190
+8D79F73DCD91FC4F5017422FAC70074D6DB8DD81.or_port 8080
+8D79F73DCD91FC4F5017422FAC70074D6DB8DD81.dir_port 8030
+8D79F73DCD91FC4F5017422FAC70074D6DB8DD81.nickname thanatosDE
+8D79F73DCD91FC4F5017422FAC70074D6DB8DD81.has_extrainfo false
8FA37B93397015B2BC5A525C908485260BE9F422.address 81.7.11.96
8FA37B93397015B2BC5A525C908485260BE9F422.or_port 9001
8FA37B93397015B2BC5A525C908485260BE9F422.dir_port 9030
8FA37B93397015B2BC5A525C908485260BE9F422.nickname Doedel22
8FA37B93397015B2BC5A525C908485260BE9F422.has_extrainfo false
+90A5D1355C4B5840E950EB61E673863A6AE3ACA1.address 54.37.139.118
+90A5D1355C4B5840E950EB61E673863A6AE3ACA1.or_port 9001
+90A5D1355C4B5840E950EB61E673863A6AE3ACA1.dir_port 9030
+90A5D1355C4B5840E950EB61E673863A6AE3ACA1.nickname rofltor09
+90A5D1355C4B5840E950EB61E673863A6AE3ACA1.has_extrainfo false
+90A5D1355C4B5840E950EB61E673863A6AE3ACA1.orport6_address 2001:41d0:601:1100::1b8
+90A5D1355C4B5840E950EB61E673863A6AE3ACA1.orport6_port 9001
91D23D8A539B83D2FB56AA67ECD4D75CC093AC55.address 37.187.20.59
91D23D8A539B83D2FB56AA67ECD4D75CC093AC55.or_port 443
91D23D8A539B83D2FB56AA67ECD4D75CC093AC55.dir_port 80
@@ -471,28 +450,28 @@ header.type fallback
91D23D8A539B83D2FB56AA67ECD4D75CC093AC55.has_extrainfo false
91D23D8A539B83D2FB56AA67ECD4D75CC093AC55.orport6_address 2001:41d0:a:143b::1
91D23D8A539B83D2FB56AA67ECD4D75CC093AC55.orport6_port 993
-9231DF741915AA1630031A93026D88726877E93A.address 51.255.41.65
-9231DF741915AA1630031A93026D88726877E93A.or_port 9001
-9231DF741915AA1630031A93026D88726877E93A.dir_port 9030
-9231DF741915AA1630031A93026D88726877E93A.nickname PrisnCellRelayFR1
-9231DF741915AA1630031A93026D88726877E93A.has_extrainfo false
-92412EA1B9AA887D462B51D816777002F4D58907.address 54.37.73.111
-92412EA1B9AA887D462B51D816777002F4D58907.or_port 9001
-92412EA1B9AA887D462B51D816777002F4D58907.dir_port 9030
-92412EA1B9AA887D462B51D816777002F4D58907.nickname Aerodynamik05
-92412EA1B9AA887D462B51D816777002F4D58907.has_extrainfo false
+91E4015E1F82DAF0121D62267E54A1F661AB6DC7.address 173.255.245.116
+91E4015E1F82DAF0121D62267E54A1F661AB6DC7.or_port 9001
+91E4015E1F82DAF0121D62267E54A1F661AB6DC7.dir_port 9030
+91E4015E1F82DAF0121D62267E54A1F661AB6DC7.nickname IWorshipHisShadow
+91E4015E1F82DAF0121D62267E54A1F661AB6DC7.has_extrainfo false
924B24AFA7F075D059E8EEB284CC400B33D3D036.address 96.253.78.108
924B24AFA7F075D059E8EEB284CC400B33D3D036.or_port 443
924B24AFA7F075D059E8EEB284CC400B33D3D036.dir_port 80
924B24AFA7F075D059E8EEB284CC400B33D3D036.nickname NSDFreedom
924B24AFA7F075D059E8EEB284CC400B33D3D036.has_extrainfo false
-92CFD9565B24646CAC2D172D3DB503D69E777B8A.address 193.234.15.57
-92CFD9565B24646CAC2D172D3DB503D69E777B8A.or_port 443
-92CFD9565B24646CAC2D172D3DB503D69E777B8A.dir_port 80
-92CFD9565B24646CAC2D172D3DB503D69E777B8A.nickname bakunin
-92CFD9565B24646CAC2D172D3DB503D69E777B8A.has_extrainfo false
-92CFD9565B24646CAC2D172D3DB503D69E777B8A.orport6_address 2a00:1c20:4089:1234:7825:2c5d:1ecd:c66f
-92CFD9565B24646CAC2D172D3DB503D69E777B8A.orport6_port 443
+9288B75B5FF8861EFF32A6BE8825CC38A4F9F8C2.address 92.38.163.21
+9288B75B5FF8861EFF32A6BE8825CC38A4F9F8C2.or_port 443
+9288B75B5FF8861EFF32A6BE8825CC38A4F9F8C2.dir_port 9030
+9288B75B5FF8861EFF32A6BE8825CC38A4F9F8C2.nickname angeltest9
+9288B75B5FF8861EFF32A6BE8825CC38A4F9F8C2.has_extrainfo false
+935F589545B8A271A722E330445BB99F67DBB058.address 163.172.53.84
+935F589545B8A271A722E330445BB99F67DBB058.or_port 443
+935F589545B8A271A722E330445BB99F67DBB058.dir_port 80
+935F589545B8A271A722E330445BB99F67DBB058.nickname Multivac0
+935F589545B8A271A722E330445BB99F67DBB058.has_extrainfo false
+935F589545B8A271A722E330445BB99F67DBB058.orport6_address 2001:bc8:24f8::
+935F589545B8A271A722E330445BB99F67DBB058.orport6_port 443
94C4B7B8C50C86A92B6A20107539EE2678CF9A28.address 204.8.156.142
94C4B7B8C50C86A92B6A20107539EE2678CF9A28.or_port 443
94C4B7B8C50C86A92B6A20107539EE2678CF9A28.dir_port 80
@@ -508,18 +487,6 @@ header.type fallback
99E246DB480B313A3012BC3363093CC26CD209C7.dir_port 31336
99E246DB480B313A3012BC3363093CC26CD209C7.nickname ViDiSrv
99E246DB480B313A3012BC3363093CC26CD209C7.has_extrainfo false
-9A0D54D3A6D2E0767596BF1515E6162A75B3293F.address 91.229.20.27
-9A0D54D3A6D2E0767596BF1515E6162A75B3293F.or_port 9001
-9A0D54D3A6D2E0767596BF1515E6162A75B3293F.dir_port 9030
-9A0D54D3A6D2E0767596BF1515E6162A75B3293F.nickname gordonkeybag
-9A0D54D3A6D2E0767596BF1515E6162A75B3293F.has_extrainfo false
-9A68B85A02318F4E7E87F2828039FBD5D75B0142.address 66.111.2.20
-9A68B85A02318F4E7E87F2828039FBD5D75B0142.or_port 9001
-9A68B85A02318F4E7E87F2828039FBD5D75B0142.dir_port 9030
-9A68B85A02318F4E7E87F2828039FBD5D75B0142.nickname NYCBUG0
-9A68B85A02318F4E7E87F2828039FBD5D75B0142.has_extrainfo false
-9A68B85A02318F4E7E87F2828039FBD5D75B0142.orport6_address 2610:1c0:0:5::20
-9A68B85A02318F4E7E87F2828039FBD5D75B0142.orport6_port 9001
9B31F1F1C1554F9FFB3455911F82E818EF7C7883.address 185.100.86.128
9B31F1F1C1554F9FFB3455911F82E818EF7C7883.or_port 9001
9B31F1F1C1554F9FFB3455911F82E818EF7C7883.dir_port 9030
@@ -527,6 +494,11 @@ header.type fallback
9B31F1F1C1554F9FFB3455911F82E818EF7C7883.has_extrainfo false
9B31F1F1C1554F9FFB3455911F82E818EF7C7883.orport6_address 2a06:1700:1::11
9B31F1F1C1554F9FFB3455911F82E818EF7C7883.orport6_port 9001
+9B816A5B3EB20B8E4E9B9D1FBA299BD3F40F0320.address 185.220.101.49
+9B816A5B3EB20B8E4E9B9D1FBA299BD3F40F0320.or_port 20049
+9B816A5B3EB20B8E4E9B9D1FBA299BD3F40F0320.dir_port 10049
+9B816A5B3EB20B8E4E9B9D1FBA299BD3F40F0320.nickname niftypygmyjerboa
+9B816A5B3EB20B8E4E9B9D1FBA299BD3F40F0320.has_extrainfo false
9C900A7F6F5DD034CFFD192DAEC9CCAA813DB022.address 86.105.212.130
9C900A7F6F5DD034CFFD192DAEC9CCAA813DB022.or_port 443
9C900A7F6F5DD034CFFD192DAEC9CCAA813DB022.dir_port 9030
@@ -547,63 +519,31 @@ A0F06C2FADF88D3A39AA3072B406F09D7095AC9E.or_port 443
A0F06C2FADF88D3A39AA3072B406F09D7095AC9E.dir_port 80
A0F06C2FADF88D3A39AA3072B406F09D7095AC9E.nickname Dhalgren
A0F06C2FADF88D3A39AA3072B406F09D7095AC9E.has_extrainfo true
-A10C4F666D27364036B562823E5830BC448E046A.address 171.25.193.77
-A10C4F666D27364036B562823E5830BC448E046A.or_port 443
-A10C4F666D27364036B562823E5830BC448E046A.dir_port 80
-A10C4F666D27364036B562823E5830BC448E046A.nickname DFRI1
-A10C4F666D27364036B562823E5830BC448E046A.has_extrainfo false
-A10C4F666D27364036B562823E5830BC448E046A.orport6_address 2001:67c:289c:3::77
-A10C4F666D27364036B562823E5830BC448E046A.orport6_port 443
-A2A6616723B511D8E068BB71705191763191F6B2.address 87.118.122.120
-A2A6616723B511D8E068BB71705191763191F6B2.or_port 443
-A2A6616723B511D8E068BB71705191763191F6B2.dir_port 80
-A2A6616723B511D8E068BB71705191763191F6B2.nickname otheontelth
-A2A6616723B511D8E068BB71705191763191F6B2.has_extrainfo false
A2E6BB5C391CD46B38C55B4329C35304540771F1.address 81.7.3.67
A2E6BB5C391CD46B38C55B4329C35304540771F1.or_port 443
A2E6BB5C391CD46B38C55B4329C35304540771F1.dir_port 993
A2E6BB5C391CD46B38C55B4329C35304540771F1.nickname BeastieJoy62
A2E6BB5C391CD46B38C55B4329C35304540771F1.has_extrainfo true
-A478E421F83194C114F41E94F95999672AED51FE.address 171.25.193.78
-A478E421F83194C114F41E94F95999672AED51FE.or_port 443
-A478E421F83194C114F41E94F95999672AED51FE.dir_port 80
-A478E421F83194C114F41E94F95999672AED51FE.nickname DFRI4
-A478E421F83194C114F41E94F95999672AED51FE.has_extrainfo false
-A478E421F83194C114F41E94F95999672AED51FE.orport6_address 2001:67c:289c:3::78
-A478E421F83194C114F41E94F95999672AED51FE.orport6_port 443
-A4C98CEA3F34E05299417E9F885A642C88EF6029.address 193.234.15.58
-A4C98CEA3F34E05299417E9F885A642C88EF6029.or_port 443
-A4C98CEA3F34E05299417E9F885A642C88EF6029.dir_port 80
-A4C98CEA3F34E05299417E9F885A642C88EF6029.nickname jaures2
-A4C98CEA3F34E05299417E9F885A642C88EF6029.has_extrainfo false
-A4C98CEA3F34E05299417E9F885A642C88EF6029.orport6_address 2a00:1c20:4089:1234:cdae:1b3e:cc38:3d45
-A4C98CEA3F34E05299417E9F885A642C88EF6029.orport6_port 443
A53C46F5B157DD83366D45A8E99A244934A14C46.address 128.31.0.13
A53C46F5B157DD83366D45A8E99A244934A14C46.or_port 443
A53C46F5B157DD83366D45A8E99A244934A14C46.dir_port 80
A53C46F5B157DD83366D45A8E99A244934A14C46.nickname csailmitexit
A53C46F5B157DD83366D45A8E99A244934A14C46.has_extrainfo false
-AA0D167E03E298F9A8CD50F448B81FBD7FA80D56.address 94.142.242.84
-AA0D167E03E298F9A8CD50F448B81FBD7FA80D56.or_port 443
-AA0D167E03E298F9A8CD50F448B81FBD7FA80D56.dir_port 80
-AA0D167E03E298F9A8CD50F448B81FBD7FA80D56.nickname rejozenger
-AA0D167E03E298F9A8CD50F448B81FBD7FA80D56.has_extrainfo false
-AA0D167E03E298F9A8CD50F448B81FBD7FA80D56.orport6_address 2a02:898:24:84::1
-AA0D167E03E298F9A8CD50F448B81FBD7FA80D56.orport6_port 443
-AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C.address 195.154.164.243
-AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C.or_port 443
-AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C.dir_port 80
-AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C.nickname torpidsFRonline3
-AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C.has_extrainfo false
-AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C.orport6_address 2001:bc8:399f:f000::1
-AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C.orport6_port 993
-ACD889D86E02EDDAB1AFD81F598C0936238DC6D0.address 86.59.119.88
-ACD889D86E02EDDAB1AFD81F598C0936238DC6D0.or_port 443
-ACD889D86E02EDDAB1AFD81F598C0936238DC6D0.dir_port 80
-ACD889D86E02EDDAB1AFD81F598C0936238DC6D0.nickname ph3x
-ACD889D86E02EDDAB1AFD81F598C0936238DC6D0.has_extrainfo false
-ACD889D86E02EDDAB1AFD81F598C0936238DC6D0.orport6_address 2001:858:2:30:86:59:119:88
-ACD889D86E02EDDAB1AFD81F598C0936238DC6D0.orport6_port 443
+A86EC24F5B8B964F67AC7C27CE92842025983274.address 185.246.152.22
+A86EC24F5B8B964F67AC7C27CE92842025983274.or_port 443
+A86EC24F5B8B964F67AC7C27CE92842025983274.dir_port 9030
+A86EC24F5B8B964F67AC7C27CE92842025983274.nickname angeltest19
+A86EC24F5B8B964F67AC7C27CE92842025983274.has_extrainfo false
+A9406A006D6E7B5DA30F2C6D4E42A338B5E340B2.address 163.172.149.122
+A9406A006D6E7B5DA30F2C6D4E42A338B5E340B2.or_port 443
+A9406A006D6E7B5DA30F2C6D4E42A338B5E340B2.dir_port 80
+A9406A006D6E7B5DA30F2C6D4E42A338B5E340B2.nickname niij03
+A9406A006D6E7B5DA30F2C6D4E42A338B5E340B2.has_extrainfo false
+AC2BEDD0BAC72838EA7E6F113F856C4E8018ACDB.address 176.10.107.180
+AC2BEDD0BAC72838EA7E6F113F856C4E8018ACDB.or_port 9001
+AC2BEDD0BAC72838EA7E6F113F856C4E8018ACDB.dir_port 9030
+AC2BEDD0BAC72838EA7E6F113F856C4E8018ACDB.nickname schokomilch
+AC2BEDD0BAC72838EA7E6F113F856C4E8018ACDB.has_extrainfo false
ACDD9E85A05B127BA010466C13C8C47212E8A38F.address 185.129.62.62
ACDD9E85A05B127BA010466C13C8C47212E8A38F.or_port 9001
ACDD9E85A05B127BA010466C13C8C47212E8A38F.dir_port 9030
@@ -611,13 +551,6 @@ ACDD9E85A05B127BA010466C13C8C47212E8A38F.nickname kramse
ACDD9E85A05B127BA010466C13C8C47212E8A38F.has_extrainfo false
ACDD9E85A05B127BA010466C13C8C47212E8A38F.orport6_address 2a06:d380:0:3700::62
ACDD9E85A05B127BA010466C13C8C47212E8A38F.orport6_port 9001
-AD19490C7DBB26D3A68EFC824F67E69B0A96E601.address 188.40.128.246
-AD19490C7DBB26D3A68EFC824F67E69B0A96E601.or_port 9001
-AD19490C7DBB26D3A68EFC824F67E69B0A96E601.dir_port 9030
-AD19490C7DBB26D3A68EFC824F67E69B0A96E601.nickname sputnik
-AD19490C7DBB26D3A68EFC824F67E69B0A96E601.has_extrainfo false
-AD19490C7DBB26D3A68EFC824F67E69B0A96E601.orport6_address 2a01:4f8:221:1ac1:dead:beef:7005:9001
-AD19490C7DBB26D3A68EFC824F67E69B0A96E601.orport6_port 9001
ADB2C26629643DBB9F8FE0096E7D16F9414B4F8D.address 31.185.104.20
ADB2C26629643DBB9F8FE0096E7D16F9414B4F8D.or_port 443
ADB2C26629643DBB9F8FE0096E7D16F9414B4F8D.dir_port 80
@@ -649,35 +582,33 @@ B143D439B72D239A419F8DCE07B8A8EB1B486FA7.or_port 443
B143D439B72D239A419F8DCE07B8A8EB1B486FA7.dir_port 80
B143D439B72D239A419F8DCE07B8A8EB1B486FA7.nickname wardsback
B143D439B72D239A419F8DCE07B8A8EB1B486FA7.has_extrainfo false
+B2197C23A4FF5D1C49EE45BA7688BA8BCCD89A0B.address 199.249.230.64
+B2197C23A4FF5D1C49EE45BA7688BA8BCCD89A0B.or_port 443
+B2197C23A4FF5D1C49EE45BA7688BA8BCCD89A0B.dir_port 80
+B2197C23A4FF5D1C49EE45BA7688BA8BCCD89A0B.nickname Quintex41
+B2197C23A4FF5D1C49EE45BA7688BA8BCCD89A0B.has_extrainfo false
+B2197C23A4FF5D1C49EE45BA7688BA8BCCD89A0B.orport6_address 2620:7:6001::ffff:c759:e640
+B2197C23A4FF5D1C49EE45BA7688BA8BCCD89A0B.orport6_port 80
B291D30517D23299AD7CEE3E60DFE60D0E3A4664.address 136.243.214.137
B291D30517D23299AD7CEE3E60DFE60D0E3A4664.or_port 443
B291D30517D23299AD7CEE3E60DFE60D0E3A4664.dir_port 80
B291D30517D23299AD7CEE3E60DFE60D0E3A4664.nickname TorKIT
B291D30517D23299AD7CEE3E60DFE60D0E3A4664.has_extrainfo false
-B44FBE5366AD98B46D829754FA4AC599BAE41A6A.address 193.234.15.60
-B44FBE5366AD98B46D829754FA4AC599BAE41A6A.or_port 443
-B44FBE5366AD98B46D829754FA4AC599BAE41A6A.dir_port 80
-B44FBE5366AD98B46D829754FA4AC599BAE41A6A.nickname jaures3
-B44FBE5366AD98B46D829754FA4AC599BAE41A6A.has_extrainfo false
-B44FBE5366AD98B46D829754FA4AC599BAE41A6A.orport6_address 2a00:1c20:4089:1234:67bc:79f3:61c0:6e49
-B44FBE5366AD98B46D829754FA4AC599BAE41A6A.orport6_port 443
+B4CAFD9CBFB34EC5DAAC146920DC7DFAFE91EA20.address 212.47.233.86
+B4CAFD9CBFB34EC5DAAC146920DC7DFAFE91EA20.or_port 9001
+B4CAFD9CBFB34EC5DAAC146920DC7DFAFE91EA20.dir_port 9030
+B4CAFD9CBFB34EC5DAAC146920DC7DFAFE91EA20.nickname netimanmu
+B4CAFD9CBFB34EC5DAAC146920DC7DFAFE91EA20.has_extrainfo false
B5212DB685A2A0FCFBAE425738E478D12361710D.address 93.115.97.242
B5212DB685A2A0FCFBAE425738E478D12361710D.or_port 9001
B5212DB685A2A0FCFBAE425738E478D12361710D.dir_port 9030
B5212DB685A2A0FCFBAE425738E478D12361710D.nickname firstor
B5212DB685A2A0FCFBAE425738E478D12361710D.has_extrainfo false
-B6904ADD4C0D10CDA7179E051962350A69A63243.address 81.2.209.10
-B6904ADD4C0D10CDA7179E051962350A69A63243.or_port 80
-B6904ADD4C0D10CDA7179E051962350A69A63243.dir_port 443
-B6904ADD4C0D10CDA7179E051962350A69A63243.nickname torzabehlice
-B6904ADD4C0D10CDA7179E051962350A69A63243.has_extrainfo false
-B6904ADD4C0D10CDA7179E051962350A69A63243.orport6_address 2001:15e8:201:1::d10a
-B6904ADD4C0D10CDA7179E051962350A69A63243.orport6_port 80
-B771AA877687F88E6F1CA5354756DF6C8A7B6B24.address 185.220.101.32
-B771AA877687F88E6F1CA5354756DF6C8A7B6B24.or_port 20032
-B771AA877687F88E6F1CA5354756DF6C8A7B6B24.dir_port 10032
-B771AA877687F88E6F1CA5354756DF6C8A7B6B24.nickname niftypika
-B771AA877687F88E6F1CA5354756DF6C8A7B6B24.has_extrainfo false
+B57A87009FA838471FB2227DDE68165AB2A2FCC4.address 51.38.134.104
+B57A87009FA838471FB2227DDE68165AB2A2FCC4.or_port 443
+B57A87009FA838471FB2227DDE68165AB2A2FCC4.dir_port 9030
+B57A87009FA838471FB2227DDE68165AB2A2FCC4.nickname angeltest5
+B57A87009FA838471FB2227DDE68165AB2A2FCC4.has_extrainfo false
B83DC1558F0D34353BB992EF93AFEAFDB226A73E.address 193.11.114.46
B83DC1558F0D34353BB992EF93AFEAFDB226A73E.or_port 9003
B83DC1558F0D34353BB992EF93AFEAFDB226A73E.dir_port 9032
@@ -695,6 +626,23 @@ B86137AE9681701901C6720E55C16805B46BD8E3.or_port 443
B86137AE9681701901C6720E55C16805B46BD8E3.dir_port 1080
B86137AE9681701901C6720E55C16805B46BD8E3.nickname BeastieJoy60
B86137AE9681701901C6720E55C16805B46BD8E3.has_extrainfo true
+BB60F5BA113A0B8B44B7B37DE3567FE561E92F78.address 51.15.179.153
+BB60F5BA113A0B8B44B7B37DE3567FE561E92F78.or_port 995
+BB60F5BA113A0B8B44B7B37DE3567FE561E92F78.dir_port 110
+BB60F5BA113A0B8B44B7B37DE3567FE561E92F78.nickname Casper04
+BB60F5BA113A0B8B44B7B37DE3567FE561E92F78.has_extrainfo false
+BCEDF6C193AA687AE471B8A22EBF6BC57C2D285E.address 198.96.155.3
+BCEDF6C193AA687AE471B8A22EBF6BC57C2D285E.or_port 5001
+BCEDF6C193AA687AE471B8A22EBF6BC57C2D285E.dir_port 8080
+BCEDF6C193AA687AE471B8A22EBF6BC57C2D285E.nickname gurgle
+BCEDF6C193AA687AE471B8A22EBF6BC57C2D285E.has_extrainfo false
+BCEF908195805E03E92CCFE669C48738E556B9C5.address 128.199.55.207
+BCEF908195805E03E92CCFE669C48738E556B9C5.or_port 9001
+BCEF908195805E03E92CCFE669C48738E556B9C5.dir_port 9030
+BCEF908195805E03E92CCFE669C48738E556B9C5.nickname EldritchReaper
+BCEF908195805E03E92CCFE669C48738E556B9C5.has_extrainfo false
+BCEF908195805E03E92CCFE669C48738E556B9C5.orport6_address 2a03:b0c0:2:d0::158:3001
+BCEF908195805E03E92CCFE669C48738E556B9C5.orport6_port 9001
BD552C165E2ED2887D3F1CCE9CFF155DDA2D86E6.address 213.141.138.174
BD552C165E2ED2887D3F1CCE9CFF155DDA2D86E6.or_port 9001
BD552C165E2ED2887D3F1CCE9CFF155DDA2D86E6.dir_port 9030
@@ -707,11 +655,13 @@ BF0FB582E37F738CD33C3651125F2772705BB8E8.nickname quadhead
BF0FB582E37F738CD33C3651125F2772705BB8E8.has_extrainfo false
BF0FB582E37F738CD33C3651125F2772705BB8E8.orport6_address 2a01:4f8:211:c68::2
BF0FB582E37F738CD33C3651125F2772705BB8E8.orport6_port 9010
-BF735F669481EE1CCC348F0731551C933D1E2278.address 104.192.5.248
-BF735F669481EE1CCC348F0731551C933D1E2278.or_port 443
+BF735F669481EE1CCC348F0731551C933D1E2278.address 212.47.233.250
+BF735F669481EE1CCC348F0731551C933D1E2278.or_port 9001
BF735F669481EE1CCC348F0731551C933D1E2278.dir_port 9030
-BF735F669481EE1CCC348F0731551C933D1E2278.nickname Freeway1a1
+BF735F669481EE1CCC348F0731551C933D1E2278.nickname FreewaySca
BF735F669481EE1CCC348F0731551C933D1E2278.has_extrainfo false
+BF735F669481EE1CCC348F0731551C933D1E2278.orport6_address 2001:bc8:4400:2b00::1c:629
+BF735F669481EE1CCC348F0731551C933D1E2278.orport6_port 9001
C0192FF43E777250084175F4E59AC1BA2290CE38.address 192.160.102.169
C0192FF43E777250084175F4E59AC1BA2290CE38.or_port 9001
C0192FF43E777250084175F4E59AC1BA2290CE38.dir_port 80
@@ -719,35 +669,23 @@ C0192FF43E777250084175F4E59AC1BA2290CE38.nickname manipogo
C0192FF43E777250084175F4E59AC1BA2290CE38.has_extrainfo false
C0192FF43E777250084175F4E59AC1BA2290CE38.orport6_address 2620:132:300c:c01d::9
C0192FF43E777250084175F4E59AC1BA2290CE38.orport6_port 9002
-C08DE49658E5B3CFC6F2A952B453C4B608C9A16A.address 185.220.101.6
-C08DE49658E5B3CFC6F2A952B453C4B608C9A16A.or_port 20006
-C08DE49658E5B3CFC6F2A952B453C4B608C9A16A.dir_port 10006
-C08DE49658E5B3CFC6F2A952B453C4B608C9A16A.nickname niftyvolcanorabbit
-C08DE49658E5B3CFC6F2A952B453C4B608C9A16A.has_extrainfo false
+C0C4F339046EB824999F711D178472FDF53BE7F5.address 132.248.241.5
+C0C4F339046EB824999F711D178472FDF53BE7F5.or_port 9101
+C0C4F339046EB824999F711D178472FDF53BE7F5.dir_port 9130
+C0C4F339046EB824999F711D178472FDF53BE7F5.nickname toritounam2
+C0C4F339046EB824999F711D178472FDF53BE7F5.has_extrainfo false
C2AAB088555850FC434E68943F551072042B85F1.address 31.185.104.21
C2AAB088555850FC434E68943F551072042B85F1.or_port 443
C2AAB088555850FC434E68943F551072042B85F1.dir_port 80
C2AAB088555850FC434E68943F551072042B85F1.nickname Digitalcourage3ip3
C2AAB088555850FC434E68943F551072042B85F1.has_extrainfo false
-C37BC191AC389179674578C3E6944E925FE186C2.address 213.239.217.18
-C37BC191AC389179674578C3E6944E925FE186C2.or_port 1337
-C37BC191AC389179674578C3E6944E925FE186C2.dir_port 1338
-C37BC191AC389179674578C3E6944E925FE186C2.nickname xzdsb
-C37BC191AC389179674578C3E6944E925FE186C2.has_extrainfo false
-C37BC191AC389179674578C3E6944E925FE186C2.orport6_address 2a01:4f8:a0:746a:101:1:1:1
-C37BC191AC389179674578C3E6944E925FE186C2.orport6_port 1337
-C414F28FD2BEC1553024299B31D4E726BEB8E788.address 188.138.112.60
-C414F28FD2BEC1553024299B31D4E726BEB8E788.or_port 1521
-C414F28FD2BEC1553024299B31D4E726BEB8E788.dir_port 1433
-C414F28FD2BEC1553024299B31D4E726BEB8E788.nickname zebra620
-C414F28FD2BEC1553024299B31D4E726BEB8E788.has_extrainfo false
-C4AEA05CF380BAD2230F193E083B8869B4A29937.address 193.234.15.55
-C4AEA05CF380BAD2230F193E083B8869B4A29937.or_port 443
-C4AEA05CF380BAD2230F193E083B8869B4A29937.dir_port 80
-C4AEA05CF380BAD2230F193E083B8869B4A29937.nickname bakunin4
-C4AEA05CF380BAD2230F193E083B8869B4A29937.has_extrainfo false
-C4AEA05CF380BAD2230F193E083B8869B4A29937.orport6_address 2a00:1c20:4089:1234:7b2c:11c5:5221:903e
-C4AEA05CF380BAD2230F193E083B8869B4A29937.orport6_port 443
+C36A434DB54C66E1A97A5653858CE36024352C4D.address 50.7.74.170
+C36A434DB54C66E1A97A5653858CE36024352C4D.or_port 9001
+C36A434DB54C66E1A97A5653858CE36024352C4D.dir_port 9030
+C36A434DB54C66E1A97A5653858CE36024352C4D.nickname theia9
+C36A434DB54C66E1A97A5653858CE36024352C4D.has_extrainfo false
+C36A434DB54C66E1A97A5653858CE36024352C4D.orport6_address 2001:49f0:d002:2::59
+C36A434DB54C66E1A97A5653858CE36024352C4D.orport6_port 443
C793AB88565DDD3C9E4C6F15CCB9D8C7EF964CE9.address 85.248.227.163
C793AB88565DDD3C9E4C6F15CCB9D8C7EF964CE9.or_port 9001
C793AB88565DDD3C9E4C6F15CCB9D8C7EF964CE9.dir_port 443
@@ -767,16 +705,25 @@ CBD0D1BD110EC52963082D839AC6A89D0AE243E7.or_port 9001
CBD0D1BD110EC52963082D839AC6A89D0AE243E7.dir_port 9030
CBD0D1BD110EC52963082D839AC6A89D0AE243E7.nickname UV74S7mjxRcYVrGsAMw
CBD0D1BD110EC52963082D839AC6A89D0AE243E7.has_extrainfo false
-CE47F0356D86CF0A1A2008D97623216D560FB0A8.address 85.25.213.211
-CE47F0356D86CF0A1A2008D97623216D560FB0A8.or_port 80
-CE47F0356D86CF0A1A2008D97623216D560FB0A8.dir_port 465
-CE47F0356D86CF0A1A2008D97623216D560FB0A8.nickname BeastieJoy61
-CE47F0356D86CF0A1A2008D97623216D560FB0A8.has_extrainfo false
-D30E9D4D639068611D6D96861C95C2099140B805.address 46.38.237.221
-D30E9D4D639068611D6D96861C95C2099140B805.or_port 9001
-D30E9D4D639068611D6D96861C95C2099140B805.dir_port 9030
-D30E9D4D639068611D6D96861C95C2099140B805.nickname mine
-D30E9D4D639068611D6D96861C95C2099140B805.has_extrainfo false
+D15AFF44BE641368B958A32FB6B071AC2136B8B1.address 51.254.147.57
+D15AFF44BE641368B958A32FB6B071AC2136B8B1.or_port 443
+D15AFF44BE641368B958A32FB6B071AC2136B8B1.dir_port 80
+D15AFF44BE641368B958A32FB6B071AC2136B8B1.nickname Cosworth01
+D15AFF44BE641368B958A32FB6B071AC2136B8B1.has_extrainfo false
+D1AFBF3117B308B6D1A7AA762B1315FD86A6B8AF.address 50.7.74.172
+D1AFBF3117B308B6D1A7AA762B1315FD86A6B8AF.or_port 443
+D1AFBF3117B308B6D1A7AA762B1315FD86A6B8AF.dir_port 80
+D1AFBF3117B308B6D1A7AA762B1315FD86A6B8AF.nickname theia2
+D1AFBF3117B308B6D1A7AA762B1315FD86A6B8AF.has_extrainfo false
+D1AFBF3117B308B6D1A7AA762B1315FD86A6B8AF.orport6_address 2001:49f0:d002:2::52
+D1AFBF3117B308B6D1A7AA762B1315FD86A6B8AF.orport6_port 443
+D379A1CB8285748FFF64AE94296CA89878F25B22.address 62.141.38.69
+D379A1CB8285748FFF64AE94296CA89878F25B22.or_port 443
+D379A1CB8285748FFF64AE94296CA89878F25B22.dir_port 9030
+D379A1CB8285748FFF64AE94296CA89878F25B22.nickname angeltest3
+D379A1CB8285748FFF64AE94296CA89878F25B22.has_extrainfo false
+D379A1CB8285748FFF64AE94296CA89878F25B22.orport6_address 2001:4ba0:cafe:ac5::1
+D379A1CB8285748FFF64AE94296CA89878F25B22.orport6_port 443
D405FCCF06ADEDF898DF2F29C9348DCB623031BA.address 5.45.111.149
D405FCCF06ADEDF898DF2F29C9348DCB623031BA.or_port 443
D405FCCF06ADEDF898DF2F29C9348DCB623031BA.dir_port 80
@@ -784,21 +731,18 @@ D405FCCF06ADEDF898DF2F29C9348DCB623031BA.nickname gGDHjdcC6zAlM8k08lY
D405FCCF06ADEDF898DF2F29C9348DCB623031BA.has_extrainfo false
D405FCCF06ADEDF898DF2F29C9348DCB623031BA.orport6_address 2a03:4000:6:2388:df98:15f9:b34d:443
D405FCCF06ADEDF898DF2F29C9348DCB623031BA.orport6_port 443
+D50101A2ABD09DC245F7E96C0818D003CDD62351.address 50.7.74.174
+D50101A2ABD09DC245F7E96C0818D003CDD62351.or_port 443
+D50101A2ABD09DC245F7E96C0818D003CDD62351.dir_port 80
+D50101A2ABD09DC245F7E96C0818D003CDD62351.nickname theia6
+D50101A2ABD09DC245F7E96C0818D003CDD62351.has_extrainfo false
+D50101A2ABD09DC245F7E96C0818D003CDD62351.orport6_address 2001:49f0:d002:2::56
+D50101A2ABD09DC245F7E96C0818D003CDD62351.orport6_port 443
D5039E1EBFD96D9A3F9846BF99EC9F75EDDE902A.address 37.187.115.157
D5039E1EBFD96D9A3F9846BF99EC9F75EDDE902A.or_port 9001
D5039E1EBFD96D9A3F9846BF99EC9F75EDDE902A.dir_port 9030
D5039E1EBFD96D9A3F9846BF99EC9F75EDDE902A.nickname Janky328891
D5039E1EBFD96D9A3F9846BF99EC9F75EDDE902A.has_extrainfo false
-D6BA940D3255AB40DC5EE5B0B285FA143E1F9865.address 217.182.51.248
-D6BA940D3255AB40DC5EE5B0B285FA143E1F9865.or_port 443
-D6BA940D3255AB40DC5EE5B0B285FA143E1F9865.dir_port 80
-D6BA940D3255AB40DC5EE5B0B285FA143E1F9865.nickname Cosworth02
-D6BA940D3255AB40DC5EE5B0B285FA143E1F9865.has_extrainfo false
-D71B1CA1C9DC7E8CA64158E106AD770A21160FEE.address 185.34.33.2
-D71B1CA1C9DC7E8CA64158E106AD770A21160FEE.or_port 31415
-D71B1CA1C9DC7E8CA64158E106AD770A21160FEE.dir_port 9265
-D71B1CA1C9DC7E8CA64158E106AD770A21160FEE.nickname lqdn
-D71B1CA1C9DC7E8CA64158E106AD770A21160FEE.has_extrainfo false
D8B7A3A6542AA54D0946B9DC0257C53B6C376679.address 85.10.201.47
D8B7A3A6542AA54D0946B9DC0257C53B6C376679.or_port 9001
D8B7A3A6542AA54D0946B9DC0257C53B6C376679.dir_port 9030
@@ -816,6 +760,11 @@ DB2682153AC0CCAECD2BD1E9EBE99C6815807A1E.or_port 443
DB2682153AC0CCAECD2BD1E9EBE99C6815807A1E.dir_port 80
DB2682153AC0CCAECD2BD1E9EBE99C6815807A1E.nickname GermanCraft2
DB2682153AC0CCAECD2BD1E9EBE99C6815807A1E.has_extrainfo false
+DC163DDEF4B6F0C6BC226F9F6656A5A30C5C5686.address 176.158.236.102
+DC163DDEF4B6F0C6BC226F9F6656A5A30C5C5686.or_port 9001
+DC163DDEF4B6F0C6BC226F9F6656A5A30C5C5686.dir_port 9030
+DC163DDEF4B6F0C6BC226F9F6656A5A30C5C5686.nickname Underworld
+DC163DDEF4B6F0C6BC226F9F6656A5A30C5C5686.has_extrainfo false
DD823AFB415380A802DCAEB9461AE637604107FB.address 178.33.183.251
DD823AFB415380A802DCAEB9461AE637604107FB.or_port 443
DD823AFB415380A802DCAEB9461AE637604107FB.dir_port 80
@@ -830,13 +779,6 @@ DD8BD7307017407FCC36F8D04A688F74A0774C02.nickname DFRI0
DD8BD7307017407FCC36F8D04A688F74A0774C02.has_extrainfo false
DD8BD7307017407FCC36F8D04A688F74A0774C02.orport6_address 2001:67c:289c::20
DD8BD7307017407FCC36F8D04A688F74A0774C02.orport6_port 443
-DDBB2A38252ADDA53E4492DDF982CA6CC6E10EC0.address 83.212.99.68
-DDBB2A38252ADDA53E4492DDF982CA6CC6E10EC0.or_port 443
-DDBB2A38252ADDA53E4492DDF982CA6CC6E10EC0.dir_port 80
-DDBB2A38252ADDA53E4492DDF982CA6CC6E10EC0.nickname zouzounella
-DDBB2A38252ADDA53E4492DDF982CA6CC6E10EC0.has_extrainfo false
-DDBB2A38252ADDA53E4492DDF982CA6CC6E10EC0.orport6_address 2001:648:2ffc:1225:a800:bff:fe3d:67b5
-DDBB2A38252ADDA53E4492DDF982CA6CC6E10EC0.orport6_port 443
DED6892FF89DBD737BA689698A171B2392EB3E82.address 92.222.38.67
DED6892FF89DBD737BA689698A171B2392EB3E82.or_port 443
DED6892FF89DBD737BA689698A171B2392EB3E82.dir_port 80
@@ -844,6 +786,11 @@ DED6892FF89DBD737BA689698A171B2392EB3E82.nickname ThorExit
DED6892FF89DBD737BA689698A171B2392EB3E82.has_extrainfo false
DED6892FF89DBD737BA689698A171B2392EB3E82.orport6_address 2001:41d0:52:100::112a
DED6892FF89DBD737BA689698A171B2392EB3E82.orport6_port 443
+E41B16F7DDF52EBB1DB4268AB2FE340B37AD8904.address 166.70.207.2
+E41B16F7DDF52EBB1DB4268AB2FE340B37AD8904.or_port 9101
+E41B16F7DDF52EBB1DB4268AB2FE340B37AD8904.dir_port 9130
+E41B16F7DDF52EBB1DB4268AB2FE340B37AD8904.nickname xmission1
+E41B16F7DDF52EBB1DB4268AB2FE340B37AD8904.has_extrainfo false
E51620B90DCB310138ED89EDEDD0A5C361AAE24E.address 185.100.86.182
E51620B90DCB310138ED89EDEDD0A5C361AAE24E.or_port 8080
E51620B90DCB310138ED89EDEDD0A5C361AAE24E.dir_port 9030
@@ -854,11 +801,20 @@ E81EF60A73B3809F8964F73766B01BAA0A171E20.or_port 443
E81EF60A73B3809F8964F73766B01BAA0A171E20.dir_port 8080
E81EF60A73B3809F8964F73766B01BAA0A171E20.nickname Chimborazo
E81EF60A73B3809F8964F73766B01BAA0A171E20.has_extrainfo false
-EB80A8D52F07238B576C42CEAB98ADD084EE075E.address 51.254.147.57
-EB80A8D52F07238B576C42CEAB98ADD084EE075E.or_port 443
-EB80A8D52F07238B576C42CEAB98ADD084EE075E.dir_port 80
-EB80A8D52F07238B576C42CEAB98ADD084EE075E.nickname Cosworth01
-EB80A8D52F07238B576C42CEAB98ADD084EE075E.has_extrainfo false
+E8D114B3C78D8E6E7FEB1004650DD632C2143C9E.address 185.4.132.148
+E8D114B3C78D8E6E7FEB1004650DD632C2143C9E.or_port 443
+E8D114B3C78D8E6E7FEB1004650DD632C2143C9E.dir_port 80
+E8D114B3C78D8E6E7FEB1004650DD632C2143C9E.nickname libreonion1
+E8D114B3C78D8E6E7FEB1004650DD632C2143C9E.has_extrainfo false
+E8D114B3C78D8E6E7FEB1004650DD632C2143C9E.orport6_address 2a02:c500:2:f0::5492
+E8D114B3C78D8E6E7FEB1004650DD632C2143C9E.orport6_port 443
+EBE718E1A49EE229071702964F8DB1F318075FF8.address 131.188.40.188
+EBE718E1A49EE229071702964F8DB1F318075FF8.or_port 80
+EBE718E1A49EE229071702964F8DB1F318075FF8.dir_port 1443
+EBE718E1A49EE229071702964F8DB1F318075FF8.nickname fluxe4
+EBE718E1A49EE229071702964F8DB1F318075FF8.has_extrainfo true
+EBE718E1A49EE229071702964F8DB1F318075FF8.orport6_address 2001:638:a000:4140::ffff:188
+EBE718E1A49EE229071702964F8DB1F318075FF8.orport6_port 80
ED2338CAC2711B3E331392E1ED2831219B794024.address 192.87.28.28
ED2338CAC2711B3E331392E1ED2831219B794024.or_port 9001
ED2338CAC2711B3E331392E1ED2831219B794024.dir_port 9030
@@ -866,6 +822,13 @@ ED2338CAC2711B3E331392E1ED2831219B794024.nickname SEC6xFreeBSD64
ED2338CAC2711B3E331392E1ED2831219B794024.has_extrainfo false
ED2338CAC2711B3E331392E1ED2831219B794024.orport6_address 2001:678:230:3028:192:87:28:28
ED2338CAC2711B3E331392E1ED2831219B794024.orport6_port 9001
+EE4AF632058F0734C1426B1AD689F47445CA2056.address 37.252.187.111
+EE4AF632058F0734C1426B1AD689F47445CA2056.or_port 443
+EE4AF632058F0734C1426B1AD689F47445CA2056.dir_port 9030
+EE4AF632058F0734C1426B1AD689F47445CA2056.nickname angeltest7
+EE4AF632058F0734C1426B1AD689F47445CA2056.has_extrainfo false
+EE4AF632058F0734C1426B1AD689F47445CA2056.orport6_address 2a00:63c1:c:111::2
+EE4AF632058F0734C1426B1AD689F47445CA2056.orport6_port 443
EFEACD781604EB80FBC025EDEDEA2D523AEAAA2F.address 217.182.75.181
EFEACD781604EB80FBC025EDEDEA2D523AEAAA2F.or_port 9001
EFEACD781604EB80FBC025EDEDEA2D523AEAAA2F.dir_port 9030
@@ -876,13 +839,6 @@ F10BDE279AE71515DDCCCC61DC19AC8765F8A3CC.or_port 443
F10BDE279AE71515DDCCCC61DC19AC8765F8A3CC.dir_port 80
F10BDE279AE71515DDCCCC61DC19AC8765F8A3CC.nickname ParkBenchInd001
F10BDE279AE71515DDCCCC61DC19AC8765F8A3CC.has_extrainfo false
-F2DFE5FA1E4CF54F8E761A6D304B9B4EC69BDAE8.address 129.13.131.140
-F2DFE5FA1E4CF54F8E761A6D304B9B4EC69BDAE8.or_port 443
-F2DFE5FA1E4CF54F8E761A6D304B9B4EC69BDAE8.dir_port 80
-F2DFE5FA1E4CF54F8E761A6D304B9B4EC69BDAE8.nickname AlleKochenKaffee
-F2DFE5FA1E4CF54F8E761A6D304B9B4EC69BDAE8.has_extrainfo false
-F2DFE5FA1E4CF54F8E761A6D304B9B4EC69BDAE8.orport6_address 2a00:1398:5:f604:cafe:cafe:cafe:9001
-F2DFE5FA1E4CF54F8E761A6D304B9B4EC69BDAE8.orport6_port 443
F4263275CF54A6836EE7BD527B1328836A6F06E1.address 37.187.102.108
F4263275CF54A6836EE7BD527B1328836A6F06E1.or_port 443
F4263275CF54A6836EE7BD527B1328836A6F06E1.dir_port 80
@@ -890,6 +846,11 @@ F4263275CF54A6836EE7BD527B1328836A6F06E1.nickname EvilMoe
F4263275CF54A6836EE7BD527B1328836A6F06E1.has_extrainfo false
F4263275CF54A6836EE7BD527B1328836A6F06E1.orport6_address 2001:41d0:a:266c::1
F4263275CF54A6836EE7BD527B1328836A6F06E1.orport6_port 443
+F4C0EDAA0BF0F7EC138746F8FEF1CE26C7860265.address 5.199.142.236
+F4C0EDAA0BF0F7EC138746F8FEF1CE26C7860265.or_port 9001
+F4C0EDAA0BF0F7EC138746F8FEF1CE26C7860265.dir_port 9030
+F4C0EDAA0BF0F7EC138746F8FEF1CE26C7860265.nickname tornodenumber9004
+F4C0EDAA0BF0F7EC138746F8FEF1CE26C7860265.has_extrainfo false
F6A358DD367B3282D6EF5824C9D45E1A19C7E815.address 192.160.102.168
F6A358DD367B3282D6EF5824C9D45E1A19C7E815.or_port 9001
F6A358DD367B3282D6EF5824C9D45E1A19C7E815.dir_port 80
@@ -897,13 +858,6 @@ F6A358DD367B3282D6EF5824C9D45E1A19C7E815.nickname prawksi
F6A358DD367B3282D6EF5824C9D45E1A19C7E815.has_extrainfo false
F6A358DD367B3282D6EF5824C9D45E1A19C7E815.orport6_address 2620:132:300c:c01d::8
F6A358DD367B3282D6EF5824C9D45E1A19C7E815.orport6_port 9002
-F741E5124CB12700DA946B78C9B2DD175D6CD2A1.address 163.172.154.162
-F741E5124CB12700DA946B78C9B2DD175D6CD2A1.or_port 9001
-F741E5124CB12700DA946B78C9B2DD175D6CD2A1.dir_port 9030
-F741E5124CB12700DA946B78C9B2DD175D6CD2A1.nickname rofltor06
-F741E5124CB12700DA946B78C9B2DD175D6CD2A1.has_extrainfo false
-F741E5124CB12700DA946B78C9B2DD175D6CD2A1.orport6_address 2001:bc8:4400:2100::17:419
-F741E5124CB12700DA946B78C9B2DD175D6CD2A1.orport6_port 9001
F8D27B163B9247B232A2EEE68DD8B698695C28DE.address 78.47.18.110
F8D27B163B9247B232A2EEE68DD8B698695C28DE.or_port 80
F8D27B163B9247B232A2EEE68DD8B698695C28DE.dir_port 443
@@ -911,23 +865,13 @@ F8D27B163B9247B232A2EEE68DD8B698695C28DE.nickname fluxe3
F8D27B163B9247B232A2EEE68DD8B698695C28DE.has_extrainfo true
F8D27B163B9247B232A2EEE68DD8B698695C28DE.orport6_address 2a01:4f8:120:4023::110
F8D27B163B9247B232A2EEE68DD8B698695C28DE.orport6_port 80
-F9246DEF2B653807236DA134F2AEAB103D58ABFE.address 178.254.19.101
-F9246DEF2B653807236DA134F2AEAB103D58ABFE.or_port 443
-F9246DEF2B653807236DA134F2AEAB103D58ABFE.dir_port 80
-F9246DEF2B653807236DA134F2AEAB103D58ABFE.nickname Freebird31
-F9246DEF2B653807236DA134F2AEAB103D58ABFE.has_extrainfo true
F93D8F37E35C390BCAD9F9069E13085B745EC216.address 185.96.180.29
F93D8F37E35C390BCAD9F9069E13085B745EC216.or_port 443
F93D8F37E35C390BCAD9F9069E13085B745EC216.dir_port 80
F93D8F37E35C390BCAD9F9069E13085B745EC216.nickname TykRelay06
F93D8F37E35C390BCAD9F9069E13085B745EC216.has_extrainfo false
-FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B.address 86.59.119.83
-FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B.or_port 443
-FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B.dir_port 80
-FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B.nickname ph3x
-FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B.has_extrainfo false
-FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B.orport6_address 2001:858:2:30:86:59:119:83
-FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B.orport6_port 443
+F93D8F37E35C390BCAD9F9069E13085B745EC216.orport6_address 2a00:4820::185:96:180:29
+F93D8F37E35C390BCAD9F9069E13085B745EC216.orport6_port 443
FE296180018833AF03A8EACD5894A614623D3F76.address 149.56.45.200
FE296180018833AF03A8EACD5894A614623D3F76.or_port 9001
FE296180018833AF03A8EACD5894A614623D3F76.dir_port 9030
@@ -935,3 +879,10 @@ FE296180018833AF03A8EACD5894A614623D3F76.nickname PyotrTorpotkinOne
FE296180018833AF03A8EACD5894A614623D3F76.has_extrainfo false
FE296180018833AF03A8EACD5894A614623D3F76.orport6_address 2607:5300:201:3000::17d3
FE296180018833AF03A8EACD5894A614623D3F76.orport6_port 9002
+FFA72BD683BC2FCF988356E6BEC1E490F313FB07.address 193.11.164.243
+FFA72BD683BC2FCF988356E6BEC1E490F313FB07.or_port 9001
+FFA72BD683BC2FCF988356E6BEC1E490F313FB07.dir_port 9030
+FFA72BD683BC2FCF988356E6BEC1E490F313FB07.nickname Lule
+FFA72BD683BC2FCF988356E6BEC1E490F313FB07.has_extrainfo false
+FFA72BD683BC2FCF988356E6BEC1E490F313FB07.orport6_address 2001:6b0:7:125::243
+FFA72BD683BC2FCF988356E6BEC1E490F313FB07.orport6_port 9001
diff --git a/stem/cached_manual.sqlite b/stem/cached_manual.sqlite
index 5069976c..515e894b 100644
--- a/stem/cached_manual.sqlite
+++ b/stem/cached_manual.sqlite
Binary files differ
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index ef6530ed..c099ca86 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -11,6 +11,8 @@ Package for parsing and processing descriptor data.
parse_file - Parses the descriptors in a file.
create_signing_key - Cretes a signing key that can be used for creating descriptors.
+ Compression - method of descriptor decompression
+
Descriptor - Common parent for all descriptor file types.
| |- content - creates the text of a new descriptor
| |- create - creates a new descriptor
@@ -113,6 +115,7 @@ except ImportError:
__all__ = [
'bandwidth_file',
'certificate',
+ 'collector',
'export',
'extrainfo_descriptor',
'hidden_service_descriptor',
@@ -171,6 +174,91 @@ DocumentHandler = stem.util.enum.UppercaseEnum(
)
+class _Compression(object):
+ """
+ Compression method supported by CollecTor.
+
+ :var bool available: **True** if this method of decryption is available,
+ **False** otherwise
+ :var str encoding: `http 'Accept-Encoding' parameter <https://en.wikipedia.org/wiki/HTTP_compression#Content-Encoding_tokens>`_
+ :var str extension: file extension of this compression
+
+ .. versionadded:: 1.8.0
+ """
+
+ def __init__(self, name, module, encoding, extension, decompression_func):
+ if module is None:
+ self._module = None
+ self.available = True
+ else:
+ # Compression modules are optional. Usually gzip and bz2 are available,
+ # but they might be missing if compiling python yourself. As for lzma it
+ # was added in python 3.3.
+
+ try:
+ self._module = __import__(module)
+ self.available = True
+ except ImportError:
+ self._module = None
+ self.available = False
+
+ self.extension = extension
+ self.encoding = encoding
+
+ self._name = name
+ self._module_name = module
+ self._decompression_func = decompression_func
+
+ def decompress(self, content):
+ """
+ Decompresses the given content via this method.
+
+ :param bytes content: content to be decompressed
+
+ :returns: **bytes** with the decompressed content
+
+ :raises:
+ If unable to decompress this provide...
+
+ * **IOError** if content isn't compressed with this
+ * **ImportError** if this method if decompression is unavalable
+ """
+
+ if not self.available:
+ if self._name == 'zstd':
+ raise ImportError('Decompressing zstd data requires https://pypi.org/project/zstandard/')
+ elif self._name == 'lzma':
+ raise ImportError('Decompressing lzma data requires https://docs.python.org/3/library/lzma.html')
+ else:
+ raise ImportError("'%s' decompression module is unavailable" % self._module_name)
+
+ try:
+ return self._decompression_func(self._module, content)
+ except Exception as exc:
+ raise IOError('Failed to decompress as %s: %s' % (self, exc))
+
+ def __str__(self):
+ return self._name
+
+
+def _zstd_decompress(module, content):
+ output_buffer = io.BytesIO()
+
+ with module.ZstdDecompressor().write_to(output_buffer) as decompressor:
+ decompressor.write(content)
+
+ return output_buffer.getvalue()
+
+
+Compression = stem.util.enum.Enum(
+ ('PLAINTEXT', _Compression('plaintext', None, 'identity', '.txt', lambda module, content: content)),
+ ('GZIP', _Compression('gzip', 'zlib', 'gzip', '.gz', lambda module, content: module.decompress(content, module.MAX_WBITS | 32))),
+ ('BZ2', _Compression('bzip2', 'bz2', 'bzip2', '.bz2', lambda module, content: module.decompress(content))),
+ ('LZMA', _Compression('lzma', 'lzma', 'x-tor-lzma', '.xz', lambda module, content: module.decompress(content))),
+ ('ZSTD', _Compression('zstd', 'zstd', 'x-zstd', '.zst', _zstd_decompress)),
+)
+
+
class TypeAnnotation(collections.namedtuple('TypeAnnotation', ['name', 'major_version', 'minor_version'])):
"""
`Tor metrics type annotation
@@ -832,6 +920,8 @@ class Descriptor(object):
:returns: :class:`~stem.descriptor.TypeAnnotation` with our type information
"""
+ # TODO: populate this from the archive instead if available (so we have correct version numbers)
+
if self.TYPE_ANNOTATION_NAME is not None:
return TypeAnnotation(self.TYPE_ANNOTATION_NAME, 1, 0)
else:
diff --git a/stem/descriptor/collector.py b/stem/descriptor/collector.py
new file mode 100644
index 00000000..28fcbd49
--- /dev/null
+++ b/stem/descriptor/collector.py
@@ -0,0 +1,727 @@
+# Copyright 2019, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Descriptor archives are available from `CollecTor
+<https://metrics.torproject.org/collector.html>`_. If you need Tor's topology
+at a prior point in time this is the place to go!
+
+With CollecTor you can either read descriptors directly...
+
+.. literalinclude:: /_static/example/collector_reading.py
+ :language: python
+
+... or download the descriptors to disk and read them later.
+
+.. literalinclude:: /_static/example/collector_caching.py
+ :language: python
+
+::
+
+ get_instance - Provides a singleton CollecTor used for...
+ |- get_server_descriptors - published server descriptors
+ |- get_extrainfo_descriptors - published extrainfo descriptors
+ |- get_microdescriptors - published microdescriptors
+ |- get_consensus - published router status entries
+ |
+ |- get_key_certificates - authority key certificates
+ |- get_bandwidth_files - bandwidth authority heuristics
+ +- get_exit_lists - TorDNSEL exit list
+
+ File - Individual file residing within CollecTor
+ |- read - provides descriptors from this file
+ +- download - download this file to disk
+
+ CollecTor - Downloader for descriptors from CollecTor
+ |- get_server_descriptors - published server descriptors
+ |- get_extrainfo_descriptors - published extrainfo descriptors
+ |- get_microdescriptors - published microdescriptors
+ |- get_consensus - published router status entries
+ |
+ |- get_key_certificates - authority key certificates
+ |- get_bandwidth_files - bandwidth authority heuristics
+ |- get_exit_lists - TorDNSEL exit list
+ |
+ |- index - metadata for content available from CollecTor
+ +- files - files available from CollecTor
+
+.. versionadded:: 1.8.0
+"""
+
+import datetime
+import json
+import os
+import re
+import shutil
+import tempfile
+import time
+
+import stem.descriptor
+import stem.util.connection
+import stem.util.str_tools
+
+from stem.descriptor import Compression, DocumentHandler
+
+COLLECTOR_URL = 'https://collector.torproject.org/'
+REFRESH_INDEX_RATE = 3600 # get new index if cached copy is an hour old
+SINGLETON_COLLECTOR = None
+
+YEAR_DATE = re.compile('-(\\d{4})-(\\d{2})\\.')
+SEC_DATE = re.compile('(\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2})')
+
+# distant future date so we can sort files without a timestamp at the end
+
+FUTURE = datetime.datetime(9999, 1, 1)
+
+# mapping of path prefixes to their descriptor type (sampled 7/11/19)
+
+COLLECTOR_DESC_TYPES = {
+ 'archive/bridge-descriptors/server-descriptors/': 'bridge-server-descriptor 1.2',
+ 'archive/bridge-descriptors/extra-infos/': 'bridge-extra-info 1.3',
+ 'archive/bridge-descriptors/statuses/': 'bridge-network-status 1.1',
+ 'archive/bridge-pool-assignments/': 'bridge-pool-assignment 1.0',
+ 'archive/exit-lists/': 'tordnsel 1.0',
+ 'archive/relay-descriptors/bandwidths/': 'bandwidth-file 1.0',
+ 'archive/relay-descriptors/certs': 'dir-key-certificate-3 1.0',
+ 'archive/relay-descriptors/consensuses/': 'network-status-consensus-3 1.0',
+ 'archive/relay-descriptors/extra-infos/': 'extra-info 1.0',
+ 'archive/relay-descriptors/microdescs/': ('network-status-microdesc-consensus-3 1.0', 'microdescriptor 1.0'),
+ 'archive/relay-descriptors/server-descriptors/': 'server-descriptor 1.0',
+ 'archive/relay-descriptors/statuses/': 'network-status-2 1.0',
+ 'archive/relay-descriptors/tor/': 'directory 1.0',
+ 'archive/relay-descriptors/votes/': 'network-status-vote-3 1.0',
+ 'archive/torperf/': 'torperf 1.0',
+ 'archive/webstats/': (),
+ 'recent/bridge-descriptors/extra-infos/': 'bridge-extra-info 1.3',
+ 'recent/bridge-descriptors/server-descriptors/': 'bridge-server-descriptor 1.2',
+ 'recent/bridge-descriptors/statuses/': 'bridge-network-status 1.2',
+ 'recent/exit-lists/': 'tordnsel 1.0',
+ 'recent/relay-descriptors/bandwidths/': 'bandwidth-file 1.0',
+ 'recent/relay-descriptors/consensuses/': 'network-status-consensus-3 1.0',
+ 'recent/relay-descriptors/extra-infos/': 'extra-info 1.0',
+ 'recent/relay-descriptors/microdescs/consensus-microdesc/': 'network-status-microdesc-consensus-3 1.0',
+ 'recent/relay-descriptors/microdescs/micro/': 'microdescriptor 1.0',
+ 'recent/relay-descriptors/server-descriptors/': 'server-descriptor 1.0',
+ 'recent/relay-descriptors/votes/': 'network-status-vote-3 1.0',
+ 'recent/torperf/': 'torperf 1.1',
+ 'recent/webstats/': (),
+}
+
+
+def get_instance():
+ """
+ Provides the singleton :class:`~stem.descriptor.collector.CollecTor`
+ used for this module's shorthand functions.
+
+ :returns: singleton :class:`~stem.descriptor.collector.CollecTor` instance
+ """
+
+ global SINGLETON_COLLECTOR
+
+ if SINGLETON_COLLECTOR is None:
+ SINGLETON_COLLECTOR = CollecTor()
+
+ return SINGLETON_COLLECTOR
+
+
+def get_server_descriptors(start = None, end = None, cache_to = None, bridge = False, timeout = None, retries = 3):
+ """
+ Shorthand for
+ :func:`~stem.descriptor.collector.CollecTor.get_server_descriptors`
+ on our singleton instance.
+ """
+
+ for desc in get_instance().get_server_descriptors(start, end, cache_to, bridge, timeout, retries):
+ yield desc
+
+
+def get_extrainfo_descriptors(start = None, end = None, cache_to = None, bridge = False, timeout = None, retries = 3):
+ """
+ Shorthand for
+ :func:`~stem.descriptor.collector.CollecTor.get_extrainfo_descriptors`
+ on our singleton instance.
+ """
+
+ for desc in get_instance().get_extrainfo_descriptors(start, end, cache_to, bridge, timeout, retries):
+ yield desc
+
+
+def get_microdescriptors(start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ """
+ Shorthand for
+ :func:`~stem.descriptor.collector.CollecTor.get_microdescriptors`
+ on our singleton instance.
+ """
+
+ for desc in get_instance().get_microdescriptors(start, end, cache_to, timeout, retries):
+ yield desc
+
+
+def get_consensus(start = None, end = None, cache_to = None, document_handler = DocumentHandler.ENTRIES, version = 3, microdescriptor = False, bridge = False, timeout = None, retries = 3):
+ """
+ Shorthand for
+ :func:`~stem.descriptor.collector.CollecTor.get_consensus`
+ on our singleton instance.
+ """
+
+ for desc in get_instance().get_consensus(start, end, cache_to, document_handler, version, microdescriptor, bridge, timeout, retries):
+ yield desc
+
+
+def get_key_certificates(start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ """
+ Shorthand for
+ :func:`~stem.descriptor.collector.CollecTor.get_key_certificates`
+ on our singleton instance.
+ """
+
+ for desc in get_instance().get_key_certificates(start, end, cache_to, timeout, retries):
+ yield desc
+
+
+def get_bandwidth_files(start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ """
+ Shorthand for
+ :func:`~stem.descriptor.collector.CollecTor.get_bandwidth_files`
+ on our singleton instance.
+ """
+
+ for desc in get_instance().get_bandwidth_files(start, end, cache_to, timeout, retries):
+ yield desc
+
+
+def get_exit_lists(start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ """
+ Shorthand for
+ :func:`~stem.descriptor.collector.CollecTor.get_exit_lists`
+ on our singleton instance.
+ """
+
+ for desc in get_instance().get_exit_lists(start, end, cache_to, timeout, retries):
+ yield desc
+
+
+class File(object):
+ """
+ File within CollecTor.
+
+ :var str path: file path within collector
+ :var stem.descriptor.Compression compression: file compression, **None** if
+ this cannot be determined
+ :var int size: size of the file
+
+ :var datetime start: beginning of the time range descriptors are for,
+ **None** if this cannot be determined
+ :var datetime end: ending of the time range descriptors are for,
+ **None** if this cannot be determined
+ :var datetime last_modified: when the file was last modified
+ """
+
+ def __init__(self, path, size, last_modified):
+ self.path = path
+ self.compression = File._guess_compression(path)
+ self.size = size
+
+ self.start, self.end = File._guess_time_range(path)
+ self.last_modified = datetime.datetime.strptime(last_modified, '%Y-%m-%d %H:%M')
+
+ self._guessed_type = File._guess_descriptor_types(path)
+ self._downloaded_to = None # location we last downloaded to
+
+ def read(self, directory = None, descriptor_type = None, document_handler = DocumentHandler.ENTRIES, timeout = None, retries = 3):
+ """
+ Provides descriptors from this archive. Descriptors are downloaded or read
+ from disk as follows...
+
+ * If this file has already been downloaded through
+ :func:`~stem.descriptor.collector.CollecTor.download' these descriptors
+ are read from disk.
+
+ * If a **directory** argument is provided and the file is already present
+ these descriptors are read from disk.
+
+ * If a **directory** argument is provided and the file is not present the
+ file is downloaded this location then read.
+
+ * If the file has neither been downloaded and no **directory** argument
+ is provided then the file is downloaded to a temporary directory that's
+ deleted after it is read.
+
+ :param str directory: destination to download into
+ :param str descriptor_type: `descriptor type
+ <https://metrics.torproject.org/collector.html#data-formats>`_, this is
+ guessed if not provided
+ :param stem.descriptor.__init__.DocumentHandler document_handler: method in
+ which to parse a :class:`~stem.descriptor.networkstatus.NetworkStatusDocument`
+ :param int timeout: timeout when connection becomes idle, no timeout
+ applied if **None**
+ :param int retries: maximum attempts to impose
+
+ :returns: iterator for :class:`~stem.descriptor.__init__.Descriptor`
+ instances in the file
+
+ :raises:
+ * **ValueError** if unable to determine the descirptor type
+ * **TypeError** if we cannot parse this descriptor type
+ * :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ if descriptor_type is None:
+ if not self._guessed_type:
+ raise ValueError("Unable to determine this file's descriptor type")
+ elif len(self._guessed_type) > 1:
+ raise ValueError("Unable to determine disambiguate file's descriptor type from %s" % ', '.join(self._guessed_type))
+
+ descriptor_type = self._guessed_type[0]
+
+ if directory is None:
+ if self._downloaded_to and os.path.exists(self._downloaded_to):
+ directory = os.path.dirname(self._downloaded_to)
+ else:
+ # TODO: The following can be replaced with simpler usage of
+ # tempfile.TemporaryDirectory when we drop python 2.x support.
+
+ tmp_directory = tempfile.mkdtemp()
+
+ for desc in self.read(tmp_directory, descriptor_type, document_handler, timeout, retries):
+ yield desc
+
+ shutil.rmtree(tmp_directory)
+
+ return
+
+ path = self.download(directory, True, timeout, retries)
+
+ # Archives can contain multiple descriptor types, so parsing everything and
+ # filtering to what we're after.
+
+ for desc in stem.descriptor.parse_file(path, document_handler = document_handler):
+ if descriptor_type is None or descriptor_type.startswith(desc.type_annotation().name):
+ yield desc
+
+ def download(self, directory, decompress = True, timeout = None, retries = 3):
+ """
+ Downloads this file to the given location. If a file already exists this is
+ a no-op.
+
+ :param str directory: destination to download into
+ :param bool decompress: decompress written file
+ :param int timeout: timeout when connection becomes idle, no timeout
+ applied if **None**
+ :param int retries: maximum attempts to impose
+
+ :returns: **str** with the path we downloaded to
+
+ :raises: :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ # TODO: If checksums get added to the index we should replace
+ # the path check below to verify that...
+ #
+ # https://trac.torproject.org/projects/tor/ticket/31204
+
+ filename = self.path.split('/')[-1]
+
+ if self.compression != Compression.PLAINTEXT and decompress:
+ filename = filename.rsplit('.', 1)[0]
+
+ directory = os.path.expanduser(directory)
+
+ path = os.path.join(directory, filename)
+
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ elif os.path.exists(path):
+ return path # file already exists
+
+ response = stem.util.connection.download(COLLECTOR_URL + self.path, timeout, retries)
+
+ if decompress:
+ response = self.compression.decompress(response)
+
+ with open(path, 'wb') as output_file:
+ output_file.write(response)
+
+ self._downloaded_to = path
+ return path
+
+ @staticmethod
+ def _guess_descriptor_types(path):
+ """
+ Descriptor @type this file is expected to have based on its path. If unable
+ to determine any this tuple is empty.
+
+ Hopefully this will be replaced with an explicit value in the future:
+
+ https://trac.torproject.org/projects/tor/ticket/31204
+
+ :returns: **tuple** with the descriptor types this file is expected to have
+ """
+
+ for path_prefix, types in COLLECTOR_DESC_TYPES.items():
+ if path.startswith(path_prefix):
+ return (types,) if isinstance(types, str) else types
+
+ return ()
+
+ @staticmethod
+ def _guess_compression(path):
+ """
+ Determine file comprssion from CollecTor's filename.
+ """
+
+ for compression in (Compression.LZMA, Compression.BZ2, Compression.GZIP):
+ if path.endswith(compression.extension):
+ return compression
+
+ return Compression.PLAINTEXT
+
+ @staticmethod
+ def _guess_time_range(path):
+ """
+ Attemt to determine the (start, end) time range from CollecTor's filename.
+ This provides (None, None) if this cannot be determined.
+ """
+
+ year_match = YEAR_DATE.search(path)
+
+ if year_match:
+ year, month = map(int, year_match.groups())
+ start = datetime.datetime(year, month, 1)
+
+ if month < 12:
+ return (start, datetime.datetime(year, month + 1, 1))
+ else:
+ return (start, datetime.datetime(year + 1, 1, 1))
+
+ sec_match = SEC_DATE.search(path)
+
+ if sec_match:
+ # Descriptors in the 'recent/*' section have filenames with second level
+ # granularity. Not quite sure why, but since consensus documents are
+ # published hourly we'll use that as the delta here.
+
+ start = datetime.datetime.strptime(sec_match.group(1), '%Y-%m-%d-%H-%M-%S')
+ return (start, start + datetime.timedelta(seconds = 3600))
+
+ return (None, None)
+
+
+class CollecTor(object):
+ """
+ Downloader for descriptors from CollecTor. The contents of CollecTor are
+ provided in `an index <https://collector.torproject.org/index/index.json>`_
+ that's fetched as required.
+
+ :var int retries: number of times to attempt the request if downloading it
+ fails
+ :var float timeout: duration before we'll time out our request
+ """
+
+ def __init__(self, retries = 2, timeout = None):
+ self.retries = retries
+ self.timeout = timeout
+
+ self._cached_index = None
+ self._cached_files = None
+ self._cached_index_at = 0
+
+ def get_server_descriptors(self, start = None, end = None, cache_to = None, bridge = False, timeout = None, retries = 3):
+ """
+ Provides server descriptors published during the given time range, sorted
+ oldest to newest.
+
+ :param datetime.datetime start: time range to begin with
+ :param datetime.datetime end: time range to end with
+ :param str cache_to: directory to cache archives into, if an archive is
+ available here it is not downloaded
+ :param bool bridge: standard descriptors if **False**, bridge if **True**
+ :param int timeout: timeout for downloading each individual archive when
+ the connection becomes idle, no timeout applied if **None**
+ :param int retries: maximum attempts to impose on a per-archive basis
+
+ :returns: **iterator** of
+ :class:`~stem.descriptor.server_descriptor.ServerDescriptor` for the
+ given time range
+
+ :raises: :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ desc_type = 'server-descriptor' if not bridge else 'bridge-server-descriptor'
+
+ for f in self.files(desc_type, start, end):
+ for desc in f.read(cache_to, desc_type, timeout = timeout, retries = retries):
+ yield desc
+
+ def get_extrainfo_descriptors(self, start = None, end = None, cache_to = None, bridge = False, timeout = None, retries = 3):
+ """
+ Provides extrainfo descriptors published during the given time range,
+ sorted oldest to newest.
+
+ :param datetime.datetime start: time range to begin with
+ :param datetime.datetime end: time range to end with
+ :param str cache_to: directory to cache archives into, if an archive is
+ available here it is not downloaded
+ :param bool bridge: standard descriptors if **False**, bridge if **True**
+ :param int timeout: timeout for downloading each individual archive when
+ the connection becomes idle, no timeout applied if **None**
+ :param int retries: maximum attempts to impose on a per-archive basis
+
+ :returns: **iterator** of
+ :class:`~stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`
+ for the given time range
+
+ :raises: :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ desc_type = 'extra-info' if not bridge else 'bridge-extra-info'
+
+ for f in self.files(desc_type, start, end):
+ for desc in f.read(cache_to, desc_type, timeout = timeout, retries = retries):
+ yield desc
+
+ def get_microdescriptors(self, start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ """
+ Provides microdescriptors published during the given time range,
+ sorted oldest to newest. Unlike server/extrainfo descriptors,
+ microdescriptors change very infrequently...
+
+ ::
+
+ "Microdescriptors are expected to be relatively static and only change
+ about once per week." -dir-spec section 3.3
+
+ CollecTor archives only contain microdescriptors that *change*, so hourly
+ tarballs often contain very few.
+
+ :param datetime.datetime start: time range to begin with
+ :param datetime.datetime end: time range to end with
+ :param str cache_to: directory to cache archives into, if an archive is
+ available here it is not downloaded
+ :param int timeout: timeout for downloading each individual archive when
+ the connection becomes idle, no timeout applied if **None**
+ :param int retries: maximum attempts to impose on a per-archive basis
+
+ :returns: **iterator** of
+ :class:`~stem.descriptor.microdescriptor.Microdescriptor
+ for the given time range
+
+ :raises: :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ for f in self.files('microdescriptor', start, end):
+ for desc in f.read(cache_to, 'microdescriptor', timeout = timeout, retries = retries):
+ yield desc
+
+ def get_consensus(self, start = None, end = None, cache_to = None, document_handler = DocumentHandler.ENTRIES, version = 3, microdescriptor = False, bridge = False, timeout = None, retries = 3):
+ """
+ Provides consensus router status entries published during the given time
+ range, sorted oldest to newest.
+
+ :param datetime.datetime start: time range to begin with
+ :param datetime.datetime end: time range to end with
+ :param str cache_to: directory to cache archives into, if an archive is
+ available here it is not downloaded
+ :param stem.descriptor.__init__.DocumentHandler document_handler: method in
+ which to parse a :class:`~stem.descriptor.networkstatus.NetworkStatusDocument`
+ :param int version: consensus variant to retrieve (versions 2 or 3)
+ :param bool microdescriptor: provides the microdescriptor consensus if
+ **True**, standard consensus otherwise
+ :param bool bridge: standard descriptors if **False**, bridge if **True**
+ :param int timeout: timeout for downloading each individual archive when
+ the connection becomes idle, no timeout applied if **None**
+ :param int retries: maximum attempts to impose on a per-archive basis
+
+ :returns: **iterator** of
+ :class:`~stem.descriptor.router_status_entry.RouterStatusEntry`
+ for the given time range
+
+ :raises: :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ if version == 3 and not microdescriptor and not bridge:
+ desc_type = 'network-status-consensus-3'
+ elif version == 3 and microdescriptor and not bridge:
+ desc_type = 'network-status-microdesc-consensus-3'
+ elif version == 2 and not microdescriptor and not bridge:
+ desc_type = 'network-status-2'
+ elif bridge:
+ desc_type = 'bridge-network-status'
+ else:
+ if microdescriptor and version != 3:
+ raise ValueError('Only v3 microdescriptors are available (not version %s)' % version)
+ else:
+ raise ValueError('Only v2 and v3 router status entries are available (not version %s)' % version)
+
+ for f in self.files(desc_type, start, end):
+ for desc in f.read(cache_to, desc_type, document_handler, timeout = timeout, retries = retries):
+ yield desc
+
+ def get_key_certificates(self, start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ """
+ Directory authority key certificates for the given time range,
+ sorted oldest to newest.
+
+ :param datetime.datetime start: time range to begin with
+ :param datetime.datetime end: time range to end with
+ :param str cache_to: directory to cache archives into, if an archive is
+ available here it is not downloaded
+ :param int timeout: timeout for downloading each individual archive when
+ the connection becomes idle, no timeout applied if **None**
+ :param int retries: maximum attempts to impose on a per-archive basis
+
+ :returns: **iterator** of
+ :class:`~stem.descriptor.networkstatus.KeyCertificate
+ for the given time range
+
+ :raises: :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ for f in self.files('dir-key-certificate-3', start, end):
+ for desc in f.read(cache_to, 'dir-key-certificate-3', timeout = timeout, retries = retries):
+ yield desc
+
+ def get_bandwidth_files(self, start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ """
+ Bandwidth authority heuristics for the given time range, sorted oldest to
+ newest.
+
+ :param datetime.datetime start: time range to begin with
+ :param datetime.datetime end: time range to end with
+ :param str cache_to: directory to cache archives into, if an archive is
+ available here it is not downloaded
+ :param int timeout: timeout for downloading each individual archive when
+ the connection becomes idle, no timeout applied if **None**
+ :param int retries: maximum attempts to impose on a per-archive basis
+
+ :returns: **iterator** of
+ :class:`~stem.descriptor.bandwidth_file.BandwidthFile
+ for the given time range
+
+ :raises: :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ for f in self.files('bandwidth-file', start, end):
+ for desc in f.read(cache_to, 'bandwidth-file', timeout = timeout, retries = retries):
+ yield desc
+
+ def get_exit_lists(self, start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ """
+ `TorDNSEL exit lists <https://www.torproject.org/projects/tordnsel.html.en>`_
+ for the given time range, sorted oldest to newest.
+
+ :param datetime.datetime start: time range to begin with
+ :param datetime.datetime end: time range to end with
+ :param str cache_to: directory to cache archives into, if an archive is
+ available here it is not downloaded
+ :param int timeout: timeout for downloading each individual archive when
+ the connection becomes idle, no timeout applied if **None**
+ :param int retries: maximum attempts to impose on a per-archive basis
+
+ :returns: **iterator** of
+ :class:`~stem.descriptor.tordnsel.TorDNSEL
+ for the given time range
+
+ :raises: :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ for f in self.files('tordnsel', start, end):
+ for desc in f.read(cache_to, 'tordnsel', timeout = timeout, retries = retries):
+ yield desc
+
+ def index(self, compression = 'best'):
+ """
+ Provides the archives available in CollecTor.
+
+ :param descriptor.Compression compression: compression type to
+ download from, if undefiled we'll use the best decompression available
+
+ :returns: :class:`~stem.descriptor.collector.Index` with the archive
+ contents
+
+ :raises:
+ If unable to retrieve the index this provide...
+
+ * **ValueError** if json is malformed
+ * **IOError** if unable to decompress
+ * :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ if not self._cached_index or time.time() - self._cached_index_at >= REFRESH_INDEX_RATE:
+ if compression == 'best':
+ for option in (Compression.LZMA, Compression.BZ2, Compression.GZIP, Compression.PLAINTEXT):
+ if option.available:
+ compression = option
+ break
+ elif compression is None:
+ compression = Compression.PLAINTEXT
+
+ extension = compression.extension if compression != Compression.PLAINTEXT else ''
+ url = COLLECTOR_URL + 'index/index.json' + extension
+ response = compression.decompress(stem.util.connection.download(url, self.timeout, self.retries))
+
+ self._cached_index = json.loads(stem.util.str_tools._to_unicode(response))
+ self._cached_index_at = time.time()
+
+ return self._cached_index
+
+ def files(self, descriptor_type = None, start = None, end = None):
+ """
+ Provides files CollecTor presently has, sorted oldest to newest.
+
+ :param str descriptor_type: descriptor type or prefix to retrieve
+ :param datetime.datetime start: time range to begin with
+ :param datetime.datetime end: time range to end with
+
+ :returns: **list** of :class:`~stem.descriptor.collector.File`
+
+ :raises:
+ If unable to retrieve the index this provide...
+
+ * **ValueError** if json is malformed
+ * **IOError** if unable to decompress
+ * :class:`~stem.DownloadFailed` if the download fails
+ """
+
+ if not self._cached_files or time.time() - self._cached_index_at >= REFRESH_INDEX_RATE:
+ self._cached_files = sorted(CollecTor._files(self.index(), []), key = lambda x: x.start if x.start else FUTURE)
+
+ matches = []
+
+ for f in self._cached_files:
+ if start and (f.start is None or f.start < start):
+ continue
+ elif end and (f.end is None or f.end > end):
+ continue
+
+ if descriptor_type is None or any([desc_type.startswith(descriptor_type) for desc_type in f._guessed_type]):
+ matches.append(f)
+
+ return matches
+
+ @staticmethod
+ def _files(val, path):
+ """
+ Recursively provies files within the index.
+
+ :param dict val: index hash
+ :param list path: path we've transversed into
+
+ :returns: **list** of :class:`~stem.descriptor.collector.File`
+ """
+
+ if not isinstance(val, dict):
+ return [] # leaf node without any files
+
+ files = []
+
+ for k, v in val.items():
+ if k == 'files':
+ for attr in v:
+ file_path = '/'.join(path + [attr.get('path')])
+ files.append(File(file_path, attr.get('size'), attr.get('last_modified')))
+ elif k == 'directories':
+ for attr in v:
+ files.extend(CollecTor._files(attr, path + [attr.get('path')]))
+
+ return files
diff --git a/stem/descriptor/hidden_service_descriptor.py b/stem/descriptor/hidden_service_descriptor.py
index 1b7f2cc3..99d6414e 100644
--- a/stem/descriptor/hidden_service_descriptor.py
+++ b/stem/descriptor/hidden_service_descriptor.py
@@ -21,6 +21,9 @@ These are only available through the Controller's
.. versionadded:: 1.4.0
"""
+# TODO: In stem 2.x rename this module to 'hidden_service' (ie, drop the
+# redundant '_descriptor' suffix).
+
import base64
import binascii
import collections
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index b0589f2a..5f542c2f 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -93,6 +93,7 @@ from stem.descriptor import (
from stem.descriptor.router_status_entry import (
RouterStatusEntryV2,
+ RouterStatusEntryBridgeV2,
RouterStatusEntryV3,
RouterStatusEntryMicroV3,
)
@@ -322,7 +323,7 @@ def _parse_file(document_file, document_type = None, validate = False, is_microd
elif document_type == NetworkStatusDocumentV3:
router_type = RouterStatusEntryMicroV3 if is_microdescriptor else RouterStatusEntryV3
elif document_type == BridgeNetworkStatusDocument:
- document_type, router_type = BridgeNetworkStatusDocument, RouterStatusEntryV2
+ document_type, router_type = BridgeNetworkStatusDocument, RouterStatusEntryBridgeV2
elif document_type == DetachedSignature:
yield document_type(document_file.read(), validate, **kwargs)
return
@@ -1228,7 +1229,9 @@ class NetworkStatusDocumentV3(NetworkStatusDocument):
self._footer(document_file, validate)
def type_annotation(self):
- if not self.is_microdescriptor:
+ if isinstance(self, BridgeNetworkStatusDocument):
+ return TypeAnnotation('bridge-network-status', 1, 0)
+ elif not self.is_microdescriptor:
return TypeAnnotation('network-status-consensus-3' if not self.is_vote else 'network-status-vote-3', 1, 0)
else:
# Directory authorities do not issue a 'microdescriptor consensus' vote,
diff --git a/stem/descriptor/remote.py b/stem/descriptor/remote.py
index 15f46070..82634513 100644
--- a/stem/descriptor/remote.py
+++ b/stem/descriptor/remote.py
@@ -48,7 +48,10 @@ content. For example...
|- their_server_descriptor - provides the server descriptor of the relay we download from
|- get_server_descriptors - provides present server descriptors
|- get_extrainfo_descriptors - provides present extrainfo descriptors
- +- get_consensus - provides the present consensus or router status entries
+ |- get_microdescriptors - provides present microdescriptors with the given digests
+ |- get_consensus - provides the present consensus or router status entries
+ |- get_bandwidth_file - provides bandwidth heuristics used to make the next consensus
+ +- get_detached_signatures - authority signatures used to make the next consensus
Query - Asynchronous request to download tor descriptors
|- start - issues the query if it isn't already running
@@ -63,7 +66,7 @@ content. For example...
|- get_consensus - provides the present consensus or router status entries
|- get_vote - provides an authority's vote for the next consensus
|- get_key_certificates - provides present authority key certificates
- |- get_bandwidth_file - provies bandwidth heuristics used to make the next consensus
+ |- get_bandwidth_file - provides bandwidth heuristics used to make the next consensus
|- get_detached_signatures - authority signatures used to make the next consensus
+- query - request an arbitrary descriptor resource
@@ -97,10 +100,10 @@ content. For example...
import io
import random
+import socket
import sys
import threading
import time
-import zlib
import stem
import stem.client
@@ -119,6 +122,8 @@ try:
except ImportError:
import urllib2 as urllib
+# TODO: remove in stem 2.x, replaced with stem.descriptor.Compression
+
Compression = stem.util.enum.Enum(
('PLAINTEXT', 'identity'),
('GZIP', 'gzip'), # can also be 'deflate'
@@ -126,6 +131,13 @@ Compression = stem.util.enum.Enum(
('LZMA', 'x-tor-lzma'),
)
+COMPRESSION_MIGRATION = {
+ 'identity': stem.descriptor.Compression.PLAINTEXT,
+ 'gzip': stem.descriptor.Compression.GZIP,
+ 'x-zstd': stem.descriptor.Compression.ZSTD,
+ 'x-tor-lzma': stem.descriptor.Compression.LZMA,
+}
+
# Tor has a limited number of descriptors we can fetch explicitly by their
# fingerprint or hashes due to a limit on the url length by squid proxies.
@@ -156,11 +168,7 @@ DIR_PORT_BLACKLIST = ('tor26', 'Serge')
def get_instance():
"""
Provides the singleton :class:`~stem.descriptor.remote.DescriptorDownloader`
- used for the following functions...
-
- * :func:`stem.descriptor.remote.get_server_descriptors`
- * :func:`stem.descriptor.remote.get_extrainfo_descriptors`
- * :func:`stem.descriptor.remote.get_consensus`
+ used for this module's shorthand functions.
.. versionadded:: 1.5.0
@@ -364,6 +372,11 @@ class Query(object):
.. versionchanged:: 1.8.0
Defaulting to gzip compression rather than plaintext downloads.
+ .. versionchanged:: 1.8.0
+ Using :class:`~stem.descriptor.__init__.Compression` for our compression
+ argument, usage of strings or this module's Compression enum is deprecated
+ and will be removed in stem 2.x.
+
:var str resource: resource being fetched, such as '/tor/server/all'
:var str descriptor_type: type of descriptors being fetched (for options see
:func:`~stem.descriptor.__init__.parse_file`), this is guessed from the
@@ -371,7 +384,7 @@ class Query(object):
:var list endpoints: :class:`~stem.DirPort` or :class:`~stem.ORPort` of the
authority or mirror we're querying, this uses authorities if undefined
- :var list compression: list of :data:`stem.descriptor.remote.Compression`
+ :var list compression: list of :data:`stem.descriptor.Compression`
we're willing to accept, when none are mutually supported downloads fall
back to Compression.PLAINTEXT
:var int retries: number of times to attempt the request if downloading it
@@ -429,6 +442,19 @@ class Query(object):
if not compression:
compression = [Compression.PLAINTEXT]
+ # TODO: Normalize from our old compression enum to
+ # stem.descriptor.Compression. This will get removed in Stem 2.x.
+
+ new_compression = []
+
+ for legacy_compression in compression:
+ if isinstance(legacy_compression, stem.descriptor._Compression):
+ new_compression.append(legacy_compression)
+ elif legacy_compression in COMPRESSION_MIGRATION:
+ new_compression.append(COMPRESSION_MIGRATION[legacy_compression])
+ else:
+ raise ValueError("'%s' (%s) is not a recognized type of compression" % (legacy_compression, type(legacy_compression).__name__))
+
if descriptor_type:
self.descriptor_type = descriptor_type
else:
@@ -446,13 +472,12 @@ class Query(object):
raise ValueError("Endpoints must be an stem.ORPort, stem.DirPort, or two value tuple. '%s' is a %s." % (endpoint, type(endpoint).__name__))
self.resource = resource
- self.compression = compression
+ self.compression = new_compression
self.retries = retries
self.fall_back_to_authority = fall_back_to_authority
self.content = None
- self.error = None # TODO: maybe remove in favor of error_attr in stem 2.x
- self._error_attr = None
+ self.error = None
self.is_done = False
self.download_url = None
@@ -504,11 +529,8 @@ class Query(object):
**False**...
* **ValueError** if the descriptor contents is malformed
- * **socket.timeout** if our request timed out
- * **urllib2.URLError** for most request failures
-
- Note that the urllib2 module may fail with other exception types, in
- which case we'll pass it along.
+ * :class:`~stem.DownloadTimeout` if our request timed out
+ * :class:`~stem.DownloadFailed` if our request fails
"""
return list(self._run(suppress))
@@ -930,11 +952,11 @@ class DescriptorDownloader(object):
Traceback (most recent call last):
File "demo.py", line 3, in
detached_sigs = stem.descriptor.remote.get_detached_signatures().run()[0]
- File "/home/atagar/Desktop/stem/stem/descriptor/remote.py", line 476, in run
+ File "/home/atagar/Desktop/stem/stem/descriptor/remote.py", line 533, in run
return list(self._run(suppress))
- File "/home/atagar/Desktop/stem/stem/descriptor/remote.py", line 487, in _run
+ File "/home/atagar/Desktop/stem/stem/descriptor/remote.py", line 544, in _run
raise self.error
- urllib2.HTTPError: HTTP Error 404: Not found
+ stem.DownloadFailed: Failed to download from http://154.35.175.225:80/tor/status-vote/next/consensus-signatures (HTTPError): Not found
.. versionadded:: 1.8.0
@@ -1009,7 +1031,7 @@ def _download_from_orport(endpoint, compression, resource):
with relay.create_circuit() as circ:
request = '\r\n'.join((
'GET %s HTTP/1.0' % resource,
- 'Accept-Encoding: %s' % ', '.join(compression),
+ 'Accept-Encoding: %s' % ', '.join(map(lambda c: c.encoding, compression)),
'User-Agent: %s' % stem.USER_AGENT,
)) + '\r\n\r\n'
@@ -1043,20 +1065,26 @@ def _download_from_dirport(url, compression, timeout):
:returns: two value tuple of the form (data, reply_headers)
:raises:
- * **socket.timeout** if our request timed out
- * **urllib2.URLError** for most request failures
+ * :class:`~stem.DownloadTimeout` if our request timed out
+ * :class:`~stem.DownloadFailed` if our request fails
"""
- response = urllib.urlopen(
- urllib.Request(
- url,
- headers = {
- 'Accept-Encoding': ', '.join(compression),
- 'User-Agent': stem.USER_AGENT,
- }
- ),
- timeout = timeout,
- )
+ try:
+ response = urllib.urlopen(
+ urllib.Request(
+ url,
+ headers = {
+ 'Accept-Encoding': ', '.join(map(lambda c: c.encoding, compression)),
+ 'User-Agent': stem.USER_AGENT,
+ }
+ ),
+ timeout = timeout,
+ )
+ except socket.timeout as exc:
+ raise stem.DownloadTimeout(url, exc, sys.exc_info()[2], timeout)
+ except:
+ exc, stacktrace = sys.exc_info()[1:3]
+ raise stem.DownloadFailed(url, exc, stacktrace)
return _decompress(response.read(), response.headers.get('Content-Encoding')), response.headers
@@ -1080,29 +1108,14 @@ def _decompress(data, encoding):
* **ImportError** if missing the decompression module
"""
- if encoding == Compression.PLAINTEXT:
- return data
- elif encoding in (Compression.GZIP, 'deflate'):
- return zlib.decompress(data, zlib.MAX_WBITS | 32)
- elif encoding == Compression.ZSTD:
- if not stem.prereq.is_zstd_available():
- raise ImportError('Decompressing zstd data requires https://pypi.org/project/zstandard/')
-
- import zstd
- output_buffer = io.BytesIO()
-
- with zstd.ZstdDecompressor().write_to(output_buffer) as decompressor:
- decompressor.write(data)
-
- return output_buffer.getvalue()
- elif encoding == Compression.LZMA:
- if not stem.prereq.is_lzma_available():
- raise ImportError('Decompressing lzma data requires https://docs.python.org/3/library/lzma.html')
-
- import lzma
- return lzma.decompress(data)
- else:
- raise ValueError("'%s' isn't a recognized type of encoding" % encoding)
+ if encoding == 'deflate':
+ return stem.descriptor.Compression.GZIP.decompress(data)
+
+ for compression in stem.descriptor.Compression:
+ if encoding == compression.encoding:
+ return compression.decompress(data)
+
+ raise ValueError("'%s' isn't a recognized type of encoding" % encoding)
def _guess_descriptor_type(resource):
diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py
index 344c5697..ce662b40 100644
--- a/stem/descriptor/router_status_entry.py
+++ b/stem/descriptor/router_status_entry.py
@@ -15,6 +15,8 @@ sources...
RouterStatusEntry - Common parent for router status entries
|- RouterStatusEntryV2 - Entry for a network status v2 document
+ | +- RouterStatusEntryBridgeV2 - Entry for a bridge flavored v2 document
+ |
|- RouterStatusEntryV3 - Entry for a network status v3 document
+- RouterStatusEntryMicroV3 - Entry for a microdescriptor flavored v3 document
"""
@@ -515,6 +517,8 @@ class RouterStatusEntryV2(RouterStatusEntry):
a default value, others are left as **None** if undefined
"""
+ TYPE_ANNOTATION_NAME = 'network-status-consensus-2'
+
ATTRIBUTES = dict(RouterStatusEntry.ATTRIBUTES, **{
'digest': (None, _parse_r_line),
})
@@ -538,6 +542,17 @@ class RouterStatusEntryV2(RouterStatusEntry):
return ('r', 's', 'v')
+class RouterStatusEntryBridgeV2(RouterStatusEntryV2):
+ """
+ Information about an individual router stored within a bridge flavored
+ version 2 network status document.
+
+ .. versionadded:: 1.8.0
+ """
+
+ TYPE_ANNOTATION_NAME = 'bridge-network-status'
+
+
class RouterStatusEntryV3(RouterStatusEntry):
"""
Information about an individual router stored within a version 3 network
@@ -575,6 +590,8 @@ class RouterStatusEntryV3(RouterStatusEntry):
Added the protocols attribute.
"""
+ TYPE_ANNOTATION_NAME = 'network-status-consensus-3'
+
ATTRIBUTES = dict(RouterStatusEntry.ATTRIBUTES, **{
'digest': (None, _parse_r_line),
'or_addresses': ([], _parse_a_line),
@@ -652,6 +669,8 @@ class RouterStatusEntryMicroV3(RouterStatusEntry):
a default value, others are left as **None** if undefined
"""
+ TYPE_ANNOTATION_NAME = 'network-status-microdesc-consensus-3'
+
ATTRIBUTES = dict(RouterStatusEntry.ATTRIBUTES, **{
'or_addresses': ([], _parse_a_line),
'bandwidth': (None, _parse_w_line),
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 2d9133f5..85e35f57 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -869,21 +869,26 @@ class RelayDescriptor(ServerDescriptor):
self.certificate.validate(self)
@classmethod
- def content(cls, attr = None, exclude = (), sign = False, signing_key = None):
+ def content(cls, attr = None, exclude = (), sign = False, signing_key = None, exit_policy = None):
if signing_key:
sign = True
if attr is None:
attr = {}
- base_header = (
+ if exit_policy is None:
+ exit_policy = REJECT_ALL_POLICY
+
+ base_header = [
('router', '%s %s 9001 0 0' % (_random_nickname(), _random_ipv4_address())),
('published', _random_date()),
('bandwidth', '153600 256000 104590'),
- ('reject', '*:*'),
+ ] + [
+ tuple(line.split(' ', 1)) for line in str(exit_policy).splitlines()
+ ] + [
('onion-key', _random_crypto_blob('RSA PUBLIC KEY')),
('signing-key', _random_crypto_blob('RSA PUBLIC KEY')),
- )
+ ]
if sign:
if attr and 'signing-key' in attr:
@@ -909,8 +914,8 @@ class RelayDescriptor(ServerDescriptor):
))
@classmethod
- def create(cls, attr = None, exclude = (), validate = True, sign = False, signing_key = None):
- return cls(cls.content(attr, exclude, sign, signing_key), validate = validate, skip_crypto_validation = not sign)
+ def create(cls, attr = None, exclude = (), validate = True, sign = False, signing_key = None, exit_policy = None):
+ return cls(cls.content(attr, exclude, sign, signing_key, exit_policy), validate = validate, skip_crypto_validation = not sign)
@lru_cache()
def digest(self, hash_type = DigestHash.SHA1, encoding = DigestEncoding.HEX):
diff --git a/stem/directory.py b/stem/directory.py
index 01eca70b..9e397d88 100644
--- a/stem/directory.py
+++ b/stem/directory.py
@@ -40,7 +40,9 @@ as follows...
import os
import re
+import sys
+import stem
import stem.util
import stem.util.conf
@@ -264,11 +266,13 @@ class Authority(Directory):
def from_remote(timeout = 60):
try:
lines = str_tools._to_unicode(urllib.urlopen(GITWEB_AUTHORITY_URL, timeout = timeout).read()).splitlines()
- except Exception as exc:
- raise IOError("Unable to download tor's directory authorities from %s: %s" % (GITWEB_AUTHORITY_URL, exc))
- if not lines:
- raise IOError('%s did not have any content' % GITWEB_AUTHORITY_URL)
+ if not lines:
+ raise IOError('no content')
+ except:
+ exc, stacktrace = sys.exc_info()[1:3]
+ message = "Unable to download tor's directory authorities from %s: %s" % (GITWEB_AUTHORITY_URL, exc)
+ raise stem.DownloadFailed(GITWEB_AUTHORITY_URL, exc, stacktrace, message)
# Entries look like...
#
@@ -410,16 +414,19 @@ class Fallback(Directory):
def from_remote(timeout = 60):
try:
lines = str_tools._to_unicode(urllib.urlopen(GITWEB_FALLBACK_URL, timeout = timeout).read()).splitlines()
- except Exception as exc:
- raise IOError("Unable to download tor's fallback directories from %s: %s" % (GITWEB_FALLBACK_URL, exc))
- if not lines:
- raise IOError('%s did not have any content' % GITWEB_FALLBACK_URL)
- elif lines[0] != '/* type=fallback */':
- raise IOError('%s does not have a type field indicating it is fallback directory metadata' % GITWEB_FALLBACK_URL)
+ if not lines:
+ raise IOError('no content')
+ except:
+ exc, stacktrace = sys.exc_info()[1:3]
+ message = "Unable to download tor's fallback directories from %s: %s" % (GITWEB_FALLBACK_URL, exc)
+ raise stem.DownloadFailed(GITWEB_FALLBACK_URL, exc, stacktrace, message)
# header metadata
+ if lines[0] != '/* type=fallback */':
+ raise IOError('%s does not have a type field indicating it is fallback directory metadata' % GITWEB_FALLBACK_URL)
+
header = {}
for line in Fallback._pop_section(lines):
diff --git a/stem/manual.py b/stem/manual.py
index 0aef8a48..24596851 100644
--- a/stem/manual.py
+++ b/stem/manual.py
@@ -53,6 +53,7 @@ import shutil
import sys
import tempfile
+import stem
import stem.prereq
import stem.util
import stem.util.conf
@@ -311,8 +312,9 @@ def download_man_page(path = None, file_handle = None, url = GITWEB_MANUAL_URL,
request = urllib.urlopen(url, timeout = timeout)
shutil.copyfileobj(request, asciidoc_file)
except:
- exc = sys.exc_info()[1]
- raise IOError("Unable to download tor's manual from %s to %s: %s" % (url, asciidoc_path, exc))
+ exc, stacktrace = sys.exc_info()[1:3]
+ message = "Unable to download tor's manual from %s to %s: %s" % (url, asciidoc_path, exc)
+ raise stem.DownloadFailed(url, exc, stacktrace, message)
try:
stem.util.system.call('a2x -f manpage %s' % asciidoc_path)
diff --git a/stem/settings.cfg b/stem/settings.cfg
index a2fa2820..b7be1ee1 100644
--- a/stem/settings.cfg
+++ b/stem/settings.cfg
@@ -287,7 +287,6 @@ manual.summary.AuthoritativeDirectory Act as a directory authority
manual.summary.V3AuthoritativeDirectory Generates a version 3 consensus
manual.summary.VersioningAuthoritativeDirectory Provides opinions on recommended versions of tor
manual.summary.RecommendedVersions Suggested versions of tor
-manual.summary.RecommendedPackages Suggested versions of applications other than tor
manual.summary.RecommendedClientVersions Tor versions believed to be safe for clients
manual.summary.BridgeAuthoritativeDir Acts as a bridge authority
manual.summary.MinUptimeHidServDirectoryV2 Required uptime before accepting hidden service directory
diff --git a/stem/util/connection.py b/stem/util/connection.py
index c23d74e7..421eb9a3 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -8,6 +8,7 @@ Connection and networking based utility functions.
::
+ download - download from a given url
get_connections - quieries the connections belonging to a given process
system_resolvers - provides connection resolution methods that are likely to be available
port_usage - brief description of the common usage for a port
@@ -58,13 +59,23 @@ import collections
import os
import platform
import re
+import socket
+import sys
+import time
+import stem
import stem.util
import stem.util.proc
import stem.util.system
from stem.util import conf, enum, log, str_tools
+try:
+ # account for urllib's change between python 2.x and 3.x
+ import urllib.request as urllib
+except ImportError:
+ import urllib2 as urllib
+
# Connection resolution is risky to log about since it's highly likely to
# contain sensitive information. That said, it's also difficult to get right in
# a platform independent fashion. To opt into the logging requried to
@@ -162,6 +173,47 @@ class Connection(collections.namedtuple('Connection', ['local_address', 'local_p
"""
+def download(url, timeout = None, retries = None):
+ """
+ Download from the given url.
+
+ .. versionadded:: 1.8.0
+
+ :param str url: uncompressed url to download from
+ :param int timeout: timeout when connection becomes idle, no timeout applied
+ if **None**
+ :param int retires: maximum attempts to impose
+
+ :returns: **bytes** content of the given url
+
+ :raises:
+ * :class:`~stem.DownloadTimeout` if our request timed out
+ * :class:`~stem.DownloadFailed` if our request fails
+ """
+
+ if retries is None:
+ retries = 0
+
+ start_time = time.time()
+
+ try:
+ return urllib.urlopen(url, timeout = timeout).read()
+ except socket.timeout as exc:
+ raise stem.DownloadTimeout(url, exc, sys.exc_info()[2], timeout)
+ except:
+ exc, stacktrace = sys.exc_info()[1:3]
+
+ if timeout is not None:
+ timeout -= time.time() - start_time
+
+ if retries > 0 and (timeout is None or timeout > 0):
+ log.debug('Failed to download from %s (%i retries remaining): %s' % (url, retries, exc))
+ return download(url, timeout, retries - 1)
+ else:
+ log.debug('Failed to download from %s: %s' % (url, exc))
+ raise stem.DownloadFailed(url, exc, stacktrace)
+
+
def get_connections(resolver = None, process_pid = None, process_name = None):
"""
Retrieves a list of the current connections for a given process. This
diff --git a/test/arguments.py b/test/arguments.py
index 0fa492d8..a871b4e5 100644
--- a/test/arguments.py
+++ b/test/arguments.py
@@ -104,7 +104,7 @@ def parse(argv):
args['attribute_targets'] = attribute_targets
elif opt == '--test':
- args['specific_test'].append(arg)
+ args['specific_test'].append(crop_module_name(arg))
elif opt in ('-l', '--log'):
arg = arg.upper()
@@ -149,3 +149,22 @@ def get_help():
help_msg += '\n'
return help_msg
+
+
+def crop_module_name(name):
+ """
+ Test modules have a 'test.unit.' or 'test.integ.' prefix which can
+ be omitted from our '--test' argument. Cropping this so we can do
+ normalized comparisons.
+
+ :param str name: module name to crop
+
+ :returns: **str** with the cropped module name
+ """
+
+ if name.startswith('test.unit.'):
+ return name[10:]
+ elif name.startswith('test.integ.'):
+ return name[11:]
+ else:
+ return name
diff --git a/test/integ/descriptor/__init__.py b/test/integ/descriptor/__init__.py
index 331316a2..2ed1feef 100644
--- a/test/integ/descriptor/__init__.py
+++ b/test/integ/descriptor/__init__.py
@@ -3,6 +3,7 @@ Integration tests for stem.descriptor.* contents.
"""
__all__ = [
+ 'collector',
'extrainfo_descriptor',
'microdescriptor',
'networkstatus',
diff --git a/test/integ/descriptor/collector.py b/test/integ/descriptor/collector.py
new file mode 100644
index 00000000..f1336c53
--- /dev/null
+++ b/test/integ/descriptor/collector.py
@@ -0,0 +1,98 @@
+"""
+Integration tests for stem.descriptor.collector.
+"""
+
+import datetime
+import re
+import unittest
+
+import test.require
+
+import stem.descriptor.collector
+
+from stem.descriptor import Compression
+
+RECENT = datetime.datetime.utcnow() - datetime.timedelta(minutes = 60)
+
+
+class TestCollector(unittest.TestCase):
+ @test.require.only_run_once
+ @test.require.online
+ def test_index_plaintext(self):
+ self._test_index(None)
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_index_gzip(self):
+ self._test_index(Compression.GZIP)
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_index_bz2(self):
+ self._test_index(Compression.BZ2)
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_index_lzma(self):
+ self._test_index(Compression.LZMA)
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_downloading_server_descriptors(self):
+ recent_descriptors = list(stem.descriptor.collector.get_server_descriptors(start = RECENT))
+
+ if not (300 < len(recent_descriptors) < 800):
+ self.fail('Downloaded %i descriptors, expected 300-800' % len(recent_descriptors)) # 584 on 8/5/19
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_downloading_extrainfo_descriptors(self):
+ recent_descriptors = list(stem.descriptor.collector.get_extrainfo_descriptors(start = RECENT))
+
+ if not (300 < len(recent_descriptors) < 800):
+ self.fail('Downloaded %i descriptors, expected 300-800' % len(recent_descriptors)) # 583 on 8/7/19
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_downloading_microdescriptors(self):
+ recent_descriptors = list(stem.descriptor.collector.get_microdescriptors(start = RECENT))
+
+ if not (10 < len(recent_descriptors) < 100):
+ self.fail('Downloaded %i descriptors, expected 10-100' % len(recent_descriptors)) # 23 on 8/7/19
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_downloading_consensus_v3(self):
+ recent_descriptors = list(stem.descriptor.collector.get_consensus(start = RECENT))
+
+ if not (3000 < len(recent_descriptors) < 10000):
+ self.fail('Downloaded %i descriptors, expected 3000-10000' % len(recent_descriptors)) # 6554 on 8/10/19
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_downloading_consensus_micro(self):
+ recent_descriptors = list(stem.descriptor.collector.get_consensus(start = RECENT, microdescriptor = True))
+
+ if not (3000 < len(recent_descriptors) < 10000):
+ self.fail('Downloaded %i descriptors, expected 3000-10000' % len(recent_descriptors))
+
+ def test_downloading_consensus_invalid_type(self):
+ test_values = (
+ ({'version': 2, 'microdescriptor': True}, 'Only v3 microdescriptors are available (not version 2)'),
+ ({'version': 1}, 'Only v2 and v3 router status entries are available (not version 1)'),
+ ({'version': 4}, 'Only v2 and v3 router status entries are available (not version 4)'),
+ )
+
+ for args, expected_msg in test_values:
+ self.assertRaisesRegexp(ValueError, re.escape(expected_msg), list, stem.descriptor.collector.get_consensus(**args))
+
+ def _test_index(self, compression):
+ if compression and not compression.available:
+ self.skipTest('(%s unavailable)' % compression)
+ return
+
+ collector = stem.descriptor.collector.CollecTor()
+ index = collector.index(compression = compression)
+
+ self.assertEqual('https://collector.torproject.org', index['path'])
+ self.assertEqual(['archive', 'contrib', 'recent'], [entry['path'] for entry in index['directories']])
diff --git a/test/integ/manual.py b/test/integ/manual.py
index a5cc1be5..de86f695 100644
--- a/test/integ/manual.py
+++ b/test/integ/manual.py
@@ -38,7 +38,7 @@ EXPECTED_CATEGORIES = set([
'AUTHORS',
])
-EXPECTED_CLI_OPTIONS = set(['-f FILE', '--hash-password PASSWORD', '--ignore-missing-torrc', '--defaults-torrc FILE', '--key-expiration [purpose]', '--list-fingerprint', '--list-deprecated-options', '--allow-missing-torrc', '--nt-service', '--verify-config', '--service remove|start|stop', '--passphrase-fd FILEDES', '--keygen [--newpass]', '--list-torrc-options', '--service install [--options command-line options]', '--list-modules', '--quiet|--hush', '--version', '-h, -help'])
+EXPECTED_CLI_OPTIONS = set(['-f FILE', '--hash-password PASSWORD', '--ignore-missing-torrc', '--defaults-torrc FILE', '--key-expiration [purpose]', '--list-fingerprint', '--list-deprecated-options', '--allow-missing-torrc', '--nt-service', '--verify-config', '--service remove|start|stop', '--passphrase-fd FILEDES', '--keygen [--newpass]', '--list-torrc-options', '--service install [--options command-line options]', '--list-modules', '--quiet|--hush', '--version', '-h, --help'])
EXPECTED_SIGNALS = set(['SIGTERM', 'SIGINT', 'SIGHUP', 'SIGUSR1', 'SIGUSR2', 'SIGCHLD', 'SIGPIPE', 'SIGXFSZ'])
EXPECTED_DESCRIPTION = """
@@ -162,7 +162,7 @@ class TestManual(unittest.TestCase):
def assert_equal(category, expected, actual):
if expected != actual:
- self.fail("Changed tor's man page? The %s changed as follows...\n\nexpected: %s\n\nactual: %s" % (category, expected, actual))
+ self.fail("Changed tor's man page? The %s changed as follows...\n\nexpected: %s\n\nactual: %s" % (category, sorted(expected), sorted(actual)))
manual = stem.manual.Manual.from_man(self.man_path)
@@ -171,7 +171,7 @@ class TestManual(unittest.TestCase):
assert_equal('description', EXPECTED_DESCRIPTION, manual.description)
assert_equal('commandline options', EXPECTED_CLI_OPTIONS, set(manual.commandline_options.keys()))
- assert_equal('help option', 'Display a short help message and exit.', manual.commandline_options['-h, -help'])
+ assert_equal('help option', 'Display a short help message and exit.', manual.commandline_options['-h, --help'])
assert_equal('file option', EXPECTED_FILE_DESCRIPTION, manual.commandline_options['-f FILE'])
assert_equal('signals', EXPECTED_SIGNALS, set(manual.signals.keys()))
diff --git a/test/integ/util/connection.py b/test/integ/util/connection.py
index 12ce8ac4..ed55ad89 100644
--- a/test/integ/util/connection.py
+++ b/test/integ/util/connection.py
@@ -5,11 +5,19 @@ that we're running.
import unittest
+import stem
+import stem.util.connection
import stem.util.system
import test.require
import test.runner
-from stem.util.connection import RESOLVER_COMMAND, Resolver, get_connections, system_resolvers
+from stem.util.connection import Resolver
+
+try:
+ # account for urllib's change between python 2.x and 3.x
+ import urllib.request as urllib
+except ImportError:
+ import urllib2 as urllib
class TestConnection(unittest.TestCase):
@@ -20,22 +28,40 @@ class TestConnection(unittest.TestCase):
if test.runner.Torrc.PORT not in runner.get_options():
self.skipTest('(no control port)')
return
- elif resolver not in system_resolvers():
+ elif resolver not in stem.util.connection.system_resolvers():
self.skipTest('(resolver unavailable on this platform)')
return
with runner.get_tor_socket():
- connections = get_connections(resolver, process_pid = runner.get_pid())
+ connections = stem.util.connection.get_connections(resolver, process_pid = runner.get_pid())
for conn in connections:
if conn.local_address == '127.0.0.1' and conn.local_port == test.runner.CONTROL_PORT:
return
- resolver_command = RESOLVER_COMMAND[resolver].format(pid = runner.get_pid())
+ resolver_command = stem.util.connection.RESOLVER_COMMAND[resolver].format(pid = runner.get_pid())
resolver_output = stem.util.system.call(resolver_command)
self.fail('Unable to find our controller connection with %s (%s). Connections found were...\n\n%s\n\nCommand output was...\n\n%s' % (resolver, resolver_command, '\n'.join(map(str, connections)), resolver_output))
+ @test.require.only_run_once
+ @test.require.online
+ def test_download(self):
+ response = stem.util.connection.download('https://collector.torproject.org/index/index.json')
+ self.assertTrue(b'"path":"https://collector.torproject.org"' in response)
+
+ @test.require.only_run_once
+ @test.require.online
+ def test_download_failure(self):
+ try:
+ stem.util.connection.download('https://no.such.testing.url')
+ self.fail('expected a stem.DownloadFailed to be raised')
+ except stem.DownloadFailed as exc:
+ self.assertEqual('Failed to download from https://no.such.testing.url (URLError): Name or service not known', str(exc))
+ self.assertEqual('https://no.such.testing.url', exc.url)
+ self.assertEqual('Name or service not known', exc.error.reason.strerror)
+ self.assertEqual(urllib.URLError, type(exc.error))
+
def test_connections_by_proc(self):
self.check_resolver(Resolver.PROC)
diff --git a/test/settings.cfg b/test/settings.cfg
index d422ffa8..1bdb1a0a 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -240,8 +240,10 @@ test.unit_tests
|test.unit.util.__init__.TestBaseUtil
|test.unit.installation.TestInstallation
|test.unit.descriptor.descriptor.TestDescriptor
+|test.unit.descriptor.compression.TestCompression
|test.unit.descriptor.export.TestExport
|test.unit.descriptor.reader.TestDescriptorReader
+|test.unit.descriptor.collector.TestCollector
|test.unit.descriptor.remote.TestDescriptorDownloader
|test.unit.descriptor.server_descriptor.TestServerDescriptor
|test.unit.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor
@@ -309,6 +311,7 @@ test.integ_tests
|test.integ.connection.connect.TestConnect
|test.integ.control.base_controller.TestBaseController
|test.integ.control.controller.TestController
+|test.integ.descriptor.collector.TestCollector
|test.integ.descriptor.remote.TestDescriptorDownloader
|test.integ.descriptor.server_descriptor.TestServerDescriptor
|test.integ.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor
diff --git a/test/unit/descriptor/__init__.py b/test/unit/descriptor/__init__.py
index a2c03f1d..867ed0a4 100644
--- a/test/unit/descriptor/__init__.py
+++ b/test/unit/descriptor/__init__.py
@@ -6,6 +6,8 @@ import os
__all__ = [
'bandwidth_file',
+ 'collector',
+ 'data',
'export',
'extrainfo_descriptor',
'microdescriptor',
diff --git a/test/unit/descriptor/collector.py b/test/unit/descriptor/collector.py
new file mode 100644
index 00000000..44893fab
--- /dev/null
+++ b/test/unit/descriptor/collector.py
@@ -0,0 +1,373 @@
+"""
+Unit tests for stem.descriptor.collector.
+"""
+
+import datetime
+import io
+import unittest
+
+import stem.prereq
+
+from stem.descriptor import Compression, DocumentHandler
+from stem.descriptor.collector import CollecTor, File
+from test.unit.descriptor import get_resource
+from test.unit.descriptor.data.collector.index import EXAMPLE_INDEX
+
+try:
+ # added in python 3.3
+ from unittest.mock import Mock, patch
+except ImportError:
+ from mock import Mock, patch
+
+URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
+
+
+with open(get_resource('collector/index.json'), 'rb') as index_file:
+ EXAMPLE_INDEX_JSON = index_file.read()
+
+
+class TestCollector(unittest.TestCase):
+ # tests for the File class
+
+ def test_file_guess_descriptor_types(self):
+ test_values = {
+ 'archive/bridge-descriptors/extra-infos/bridge-extra-infos-2008-05.tar.xz': ('bridge-extra-info 1.3',),
+ 'archive/relay-descriptors/microdescs/microdescs-2014-01.tar.xz': ('network-status-microdesc-consensus-3 1.0', 'microdescriptor 1.0'),
+ 'archive/webstats/webstats-2015-03.tar': (),
+ 'archive/no_such_file.tar': (),
+ }
+
+ for path, expected in test_values.items():
+ self.assertEqual(expected, File._guess_descriptor_types(path))
+
+ def test_file_guess_compression(self):
+ test_values = {
+ 'archive/relay-descriptors/microdescs/microdescs-2014-01.tar.xz': Compression.LZMA,
+ 'archive/webstats/webstats-2015-03.tar': Compression.PLAINTEXT,
+ 'recent/relay-descriptors/extra-infos/2019-07-03-02-05-00-extra-infos': Compression.PLAINTEXT,
+ }
+
+ for path, expected in test_values.items():
+ self.assertEqual(expected, File._guess_compression(path))
+
+ def test_file_guess_time_range(self):
+ test_values = {
+ 'archive/relay-descriptors/microdescs/microdescs-2014-01.tar.xz':
+ (datetime.datetime(2014, 1, 1), datetime.datetime(2014, 2, 1)),
+ 'recent/relay-descriptors/extra-infos/2019-07-03-02-05-00-extra-infos':
+ (datetime.datetime(2019, 7, 3, 2, 5, 0), datetime.datetime(2019, 7, 3, 3, 5, 0)),
+ 'archive/relay-descriptors/certs.tar.xz':
+ (None, None),
+ 'archive/relay-descriptors/microdescs/microdescs-2014-12.tar.xz':
+ (datetime.datetime(2014, 12, 1), datetime.datetime(2015, 1, 1)),
+ 'recent/relay-descriptors/extra-infos/2019-07-03-23-05-00-extra-infos':
+ (datetime.datetime(2019, 7, 3, 23, 5, 0), datetime.datetime(2019, 7, 4, 0, 5, 0))
+ }
+
+ for path, (expected_start, expected_end) in test_values.items():
+ f = File(path, 7515396, '2014-02-07 03:59')
+ self.assertEqual(expected_start, f.start)
+ self.assertEqual(expected_end, f.end)
+
+ # tests for the CollecTor class
+
+ @patch(URL_OPEN)
+ def test_index_plaintext(self, urlopen_mock):
+ urlopen_mock.return_value = io.BytesIO(EXAMPLE_INDEX_JSON)
+
+ collector = CollecTor()
+ self.assertEqual(EXAMPLE_INDEX, collector.index(Compression.PLAINTEXT))
+ urlopen_mock.assert_called_with('https://collector.torproject.org/index/index.json', timeout = None)
+
+ @patch(URL_OPEN)
+ def test_index_gzip(self, urlopen_mock):
+ if not Compression.GZIP.available:
+ self.skipTest('(gzip compression unavailable)')
+ return
+
+ import zlib
+ urlopen_mock.return_value = io.BytesIO(zlib.compress(EXAMPLE_INDEX_JSON))
+
+ collector = CollecTor()
+ self.assertEqual(EXAMPLE_INDEX, collector.index(Compression.GZIP))
+ urlopen_mock.assert_called_with('https://collector.torproject.org/index/index.json.gz', timeout = None)
+
+ @patch(URL_OPEN)
+ def test_index_bz2(self, urlopen_mock):
+ if not Compression.BZ2.available:
+ self.skipTest('(bz2 compression unavailable)')
+ return
+
+ import bz2
+ urlopen_mock.return_value = io.BytesIO(bz2.compress(EXAMPLE_INDEX_JSON))
+
+ collector = CollecTor()
+ self.assertEqual(EXAMPLE_INDEX, collector.index(Compression.BZ2))
+ urlopen_mock.assert_called_with('https://collector.torproject.org/index/index.json.bz2', timeout = None)
+
+ @patch(URL_OPEN)
+ def test_index_lzma(self, urlopen_mock):
+ if not Compression.LZMA.available:
+ self.skipTest('(lzma compression unavailable)')
+ return
+
+ import lzma
+ urlopen_mock.return_value = io.BytesIO(lzma.compress(EXAMPLE_INDEX_JSON))
+
+ collector = CollecTor()
+ self.assertEqual(EXAMPLE_INDEX, collector.index(Compression.LZMA))
+ urlopen_mock.assert_called_with('https://collector.torproject.org/index/index.json.xz', timeout = None)
+
+ @patch(URL_OPEN)
+ def test_index_retries(self, urlopen_mock):
+ urlopen_mock.side_effect = IOError('boom')
+
+ collector = CollecTor(retries = 0)
+ self.assertRaisesRegexp(IOError, 'boom', collector.index)
+ self.assertEqual(1, urlopen_mock.call_count)
+
+ urlopen_mock.reset_mock()
+
+ collector = CollecTor(retries = 4)
+ self.assertRaisesRegexp(IOError, 'boom', collector.index)
+ self.assertEqual(5, urlopen_mock.call_count)
+
+ @patch(URL_OPEN, Mock(return_value = io.BytesIO(b'not json')))
+ def test_index_malformed_json(self):
+ collector = CollecTor()
+
+ if stem.prereq.is_python_3():
+ self.assertRaisesRegexp(ValueError, 'Expecting value: line 1 column 1', collector.index, Compression.PLAINTEXT)
+ else:
+ self.assertRaisesRegexp(ValueError, 'No JSON object could be decoded', collector.index, Compression.PLAINTEXT)
+
+ def test_index_malformed_compression(self):
+ for compression in (Compression.GZIP, Compression.BZ2, Compression.LZMA):
+ if not compression.available:
+ continue
+
+ with patch(URL_OPEN, Mock(return_value = io.BytesIO(b'not compressed'))):
+ collector = CollecTor()
+ self.assertRaisesRegexp(IOError, 'Failed to decompress as %s' % compression, collector.index, compression)
+
+ @patch('stem.descriptor.collector.CollecTor.index', Mock(return_value = EXAMPLE_INDEX))
+ def test_files(self):
+ collector = CollecTor()
+ files = collector.files()
+ self.assertEqual(85, len(files))
+
+ extrainfo_file = list(filter(lambda x: x.path.endswith('extra-infos-2007-09.tar.xz'), files))[0]
+ self.assertEqual('archive/relay-descriptors/extra-infos/extra-infos-2007-09.tar.xz', extrainfo_file.path)
+ self.assertEqual(Compression.LZMA, extrainfo_file.compression)
+ self.assertEqual(6459884, extrainfo_file.size)
+ self.assertEqual(datetime.datetime(2016, 6, 23, 9, 54), extrainfo_file.last_modified)
+
+ @patch('stem.descriptor.collector.CollecTor.index', Mock(return_value = EXAMPLE_INDEX))
+ def test_files_by_descriptor_type(self):
+ collector = CollecTor()
+
+ self.assertEqual([
+ 'archive/relay-descriptors/server-descriptors/server-descriptors-2005-12.tar.xz',
+ 'archive/relay-descriptors/server-descriptors/server-descriptors-2006-02.tar.xz',
+ 'archive/relay-descriptors/server-descriptors/server-descriptors-2006-03.tar.xz',
+ 'recent/relay-descriptors/server-descriptors/2019-07-03-02-05-00-server-descriptors',
+ 'recent/relay-descriptors/server-descriptors/2019-07-03-03-05-00-server-descriptors',
+ 'recent/relay-descriptors/server-descriptors/2019-07-03-04-05-00-server-descriptors',
+ ], [f.path for f in collector.files(descriptor_type = 'server-descriptor')])
+
+ @patch('stem.descriptor.collector.CollecTor.index', Mock(return_value = EXAMPLE_INDEX))
+ def test_file_by_date(self):
+ collector = CollecTor()
+
+ self.assertEqual([
+ 'recent/relay-descriptors/server-descriptors/2019-07-03-02-05-00-server-descriptors',
+ 'recent/relay-descriptors/server-descriptors/2019-07-03-03-05-00-server-descriptors',
+ 'recent/relay-descriptors/server-descriptors/2019-07-03-04-05-00-server-descriptors',
+ ], [f.path for f in collector.files(descriptor_type = 'server-descriptor', start = datetime.datetime(2007, 1, 1))])
+
+ self.assertEqual([
+ 'archive/relay-descriptors/server-descriptors/server-descriptors-2005-12.tar.xz',
+ 'archive/relay-descriptors/server-descriptors/server-descriptors-2006-02.tar.xz',
+ 'archive/relay-descriptors/server-descriptors/server-descriptors-2006-03.tar.xz',
+ ], [f.path for f in collector.files(descriptor_type = 'server-descriptor', end = datetime.datetime(2007, 1, 1))])
+
+ self.assertEqual([
+ 'archive/relay-descriptors/server-descriptors/server-descriptors-2006-03.tar.xz',
+ ], [f.path for f in collector.files(descriptor_type = 'server-descriptor', start = datetime.datetime(2006, 2, 10), end = datetime.datetime(2007, 1, 1))])
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_server_descriptors(self, files_mock, download_mock):
+ with open(get_resource('collector/server-descriptors-2005-12-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/relay-descriptors/server-descriptors/server-descriptors-2005-12.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_server_descriptors())
+ self.assertEqual(5, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('RelayDescriptor', type(f).__name__)
+ self.assertEqual('3E2F63E2356F52318B536A12B6445373808A5D6C', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_bridge_server_descriptors(self, files_mock, download_mock):
+ with open(get_resource('collector/bridge-server-descriptors-2019-02-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/bridge-descriptors/server-descriptors/bridge-server-descriptors-2019-02.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_server_descriptors(bridge = True))
+ self.assertEqual(4, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('BridgeDescriptor', type(f).__name__)
+ self.assertEqual('E90D1DE12B930DEC3F3E1127AAA25E47430CD3F4', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_extrainfo_descriptors(self, files_mock, download_mock):
+ with open(get_resource('collector/extra-infos-2019-04-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/relay-descriptors/extra-infos/extra-infos-2019-04.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_extrainfo_descriptors())
+ self.assertEqual(7, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('RelayExtraInfoDescriptor', type(f).__name__)
+ self.assertEqual('170EF19C0FA0491DFCEA6E1FB0941670B80506E1', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_bridge_extrainfo_descriptors(self, files_mock, download_mock):
+ with open(get_resource('collector/bridge-extra-infos-2019-03-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/bridge-descriptors/extra-infos/bridge-extra-infos-2019-03.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_extrainfo_descriptors(bridge = True))
+ self.assertEqual(6, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('BridgeExtraInfoDescriptor', type(f).__name__)
+ self.assertEqual('A0187027648A392C6AC413B66F7CD25DD001BF76', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_microdescriptors(self, files_mock, download_mock):
+ with open(get_resource('collector/microdescs-2019-05-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/relay-descriptors/microdescs/microdescs-2019-05.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_microdescriptors())
+ self.assertEqual(3, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('Microdescriptor', type(f).__name__)
+ self.assertEqual(['ed25519'], list(f.identifiers.keys()))
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_consensus(self, files_mock, download_mock):
+ with open(get_resource('collector/consensuses-2018-06-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/relay-descriptors/consensuses/consensuses-2018-06.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_consensus())
+ self.assertEqual(243, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('RouterStatusEntryV3', type(f).__name__)
+ self.assertEqual('000A10D43011EA4928A35F610405F92B4433B4DC', f.fingerprint)
+
+ descriptors = list(stem.descriptor.collector.get_consensus(document_handler = DocumentHandler.DOCUMENT))
+ self.assertEqual(2, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('NetworkStatusDocumentV3', type(f).__name__)
+ self.assertEqual(35, len(f.routers))
+
+ # this archive shouldn't have any v2 or microdescriptor consensus data
+
+ self.assertEqual(0, len(list(stem.descriptor.collector.get_consensus(version = 2))))
+ self.assertEqual(0, len(list(stem.descriptor.collector.get_consensus(microdescriptor = True))))
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_microdescriptor_consensus(self, files_mock, download_mock):
+ with open(get_resource('collector/microdescs-2019-05-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/relay-descriptors/microdescs/microdescs-2019-05.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_consensus(microdescriptor = True))
+ self.assertEqual(556, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('RouterStatusEntryMicroV3', type(f).__name__)
+ self.assertEqual('000A10D43011EA4928A35F610405F92B4433B4DC', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_bridge_consensus(self, files_mock, download_mock):
+ with open(get_resource('collector/bridge-statuses-2019-05-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/bridge-descriptors/microdescs/bridge-statuses-2019-05.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_consensus(bridge = True))
+ self.assertEqual(2593, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('RouterStatusEntryBridgeV2', type(f).__name__)
+ self.assertEqual('0035EA2A61E28D395F080ACA2244539490E70950', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_key_certificates(self, files_mock, download_mock):
+ with open(get_resource('collector/certs-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/relay-descriptors/certs.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_key_certificates())
+ self.assertEqual(5, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('KeyCertificate', type(f).__name__)
+ self.assertEqual('14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_bandwidth_files(self, files_mock, download_mock):
+ with open(get_resource('collector/bandwidths-2019-05-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/relay-descriptors/bandwidths/bandwidths-2019-05.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_bandwidth_files())
+ self.assertEqual(2, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('BandwidthFile', type(f).__name__)
+ self.assertEqual(22, len(f.measurements))
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_exit_lists(self, files_mock, download_mock):
+ with open(get_resource('collector/exit-list-2018-11-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/exit-lists/exit-list-2018-11.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_exit_lists())
+ self.assertEqual(3713, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('TorDNSEL', type(f).__name__)
+ self.assertEqual('0011BD2485AD45D984EC4159C88FC066E5E3300E', f.fingerprint)
diff --git a/test/unit/descriptor/compression.py b/test/unit/descriptor/compression.py
new file mode 100644
index 00000000..3945bc9c
--- /dev/null
+++ b/test/unit/descriptor/compression.py
@@ -0,0 +1,39 @@
+"""
+Unit tests for stem.descriptor.Compression.
+"""
+
+import unittest
+
+from stem.descriptor import Compression
+
+from test.unit.descriptor import get_resource
+
+
+class TestCompression(unittest.TestCase):
+ def test_decompress_plaintext(self):
+ self._check_file(Compression.PLAINTEXT, 'compressed_identity')
+
+ def test_decompress_gzip(self):
+ self._check_file(Compression.GZIP, 'compressed_gzip')
+
+ def test_decompress_bz2(self):
+ self._check_file(Compression.BZ2, 'compressed_bz2')
+
+ def test_decompress_lzma(self):
+ self._check_file(Compression.LZMA, 'compressed_lzma')
+
+ def test_decompress_zstd(self):
+ self._check_file(Compression.ZSTD, 'compressed_zstd')
+
+ def _check_file(self, compression, filename):
+ """
+ Decompress one of our 'compressed_*' server descriptors.
+ """
+
+ if not compression.available:
+ self.skipTest('(%s unavailable)' % compression)
+ return
+
+ with open(get_resource(filename), 'rb') as compressed_file:
+ content = compression.decompress(compressed_file.read())
+ self.assertTrue(content.startswith(b'router moria1 128.31.0.34 9101 0 9131'))
diff --git a/test/unit/descriptor/data/__init__.py b/test/unit/descriptor/data/__init__.py
new file mode 100644
index 00000000..8bc640c6
--- /dev/null
+++ b/test/unit/descriptor/data/__init__.py
@@ -0,0 +1,7 @@
+"""
+Test data for test.unit.descriptor tests.
+"""
+
+__all__ = [
+ 'collector',
+]
diff --git a/test/unit/descriptor/data/collector/__init__.py b/test/unit/descriptor/data/collector/__init__.py
new file mode 100644
index 00000000..fe90c9fb
--- /dev/null
+++ b/test/unit/descriptor/data/collector/__init__.py
@@ -0,0 +1,7 @@
+"""
+Test data for test.unit.descriptor's collector tests.
+"""
+
+__all__ = [
+ 'index',
+]
diff --git a/test/unit/descriptor/data/collector/bandwidths-2019-05-cropped.tar b/test/unit/descriptor/data/collector/bandwidths-2019-05-cropped.tar
new file mode 100644
index 00000000..4f3c8724
--- /dev/null
+++ b/test/unit/descriptor/data/collector/bandwidths-2019-05-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/bridge-extra-infos-2019-03-cropped.tar b/test/unit/descriptor/data/collector/bridge-extra-infos-2019-03-cropped.tar
new file mode 100644
index 00000000..db9f3f7b
--- /dev/null
+++ b/test/unit/descriptor/data/collector/bridge-extra-infos-2019-03-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/bridge-server-descriptors-2019-02-cropped.tar b/test/unit/descriptor/data/collector/bridge-server-descriptors-2019-02-cropped.tar
new file mode 100644
index 00000000..4d0255f3
--- /dev/null
+++ b/test/unit/descriptor/data/collector/bridge-server-descriptors-2019-02-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/bridge-statuses-2019-05-cropped.tar b/test/unit/descriptor/data/collector/bridge-statuses-2019-05-cropped.tar
new file mode 100644
index 00000000..953d2a86
--- /dev/null
+++ b/test/unit/descriptor/data/collector/bridge-statuses-2019-05-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/certs-cropped.tar b/test/unit/descriptor/data/collector/certs-cropped.tar
new file mode 100644
index 00000000..9320b65f
--- /dev/null
+++ b/test/unit/descriptor/data/collector/certs-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/consensuses-2018-06-cropped.tar b/test/unit/descriptor/data/collector/consensuses-2018-06-cropped.tar
new file mode 100644
index 00000000..0728e595
--- /dev/null
+++ b/test/unit/descriptor/data/collector/consensuses-2018-06-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/exit-list-2018-11-cropped.tar b/test/unit/descriptor/data/collector/exit-list-2018-11-cropped.tar
new file mode 100644
index 00000000..2f86f8de
--- /dev/null
+++ b/test/unit/descriptor/data/collector/exit-list-2018-11-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/extra-infos-2019-04-cropped.tar b/test/unit/descriptor/data/collector/extra-infos-2019-04-cropped.tar
new file mode 100644
index 00000000..21227286
--- /dev/null
+++ b/test/unit/descriptor/data/collector/extra-infos-2019-04-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/index.json b/test/unit/descriptor/data/collector/index.json
new file mode 100644
index 00000000..c6528f23
--- /dev/null
+++ b/test/unit/descriptor/data/collector/index.json
@@ -0,0 +1 @@
+{"index_created":"2019-07-06 01:54","build_revision":"df98ac79","path":"https://collector.torproject.org","directories":[{"path":"archive","directories":[{"path":"bridge-descriptors","directories":[{"path":"extra-infos","files":[{"path":"bridge-extra-infos-2008-05.tar.xz","size":377644,"last_modified":"2016-09-04 09:21"},{"path":"bridge-extra-infos-2008-06.tar.xz","size":600484,"last_modified":"2016-09-04 09:21"},{"path":"bridge-extra-infos-2008-07.tar.xz","size":716320,"last_modified":"2016-09-04 09:21"}]},{"path":"server-descriptors","files":[{"path":"bridge-server-descriptors-2008-05.tar.xz","size":205348,"last_modified":"2016-09-09 14:13"},{"path":"bridge-server-descriptors-2008-06.tar.xz","size":342828,"last_modified":"2016-09-09 14:13"},{"path":"bridge-server-descriptors-2008-07.tar.xz","size":374848,"last_modified":"2016-09-09 14:13"}]},{"path":"statuses","files":[{"path":"bridge-statuses-2008-05.tar.xz","size":74792,"last_modified":"2016-09-14 21:11"},{"path":"bridge-statuses-2008-06.tar.xz","size":123488,"last_modified":"2016-09-14 21:11"},{"path":"bridge-statuses-2008-07.tar.xz","size":143836,"last_modified":"2016-09-14 21:11"}]}]},{"path":"bridge-pool-assignments","files":[{"path":"bridge-pool-assignments-2010-09.tar.xz","size":32804,"last_modified":"2012-05-31 10:21"},{"path":"bridge-pool-assignments-2010-10.tar.xz","size":304684,"last_modified":"2012-05-31 10:21"},{"path":"bridge-pool-assignments-2010-11.tar.xz","size":292228,"last_modified":"2012-05-31 10:21"}]},{"path":"exit-lists","files":[{"path":"exit-list-2010-02.tar.xz","size":272008,"last_modified":"2012-05-31 18:57"},{"path":"exit-list-2010-03.tar.xz","size":1247484,"last_modified":"2012-05-31 18:57"},{"path":"exit-list-2010-04.tar.xz","size":1139896,"last_modified":"2012-05-31 18:57"}]},{"path":"relay-descriptors","files":[{"path":"certs.tar.xz","size":144696,"last_modified":"2019-07-03 03:29"}],"directories":[{"path":"bandwidths","files":[{"path":"bandwidths-2019-05.tar.xz","size":50385816,"last_modified":"2019-06-07 08:05"},{"path":"bandwidths-2019-06.tar.xz","size":105881156,"last_modified":"2019-07-03 07:30"},{"path":"bandwidths-2019-07.tar.xz","size":11374436,"last_modified":"2019-07-03 07:12"}]},{"path":"consensuses","files":[{"path":"consensuses-2007-10.tar.xz","size":1061648,"last_modified":"2012-05-15 14:35"},{"path":"consensuses-2007-11.tar.xz","size":6810308,"last_modified":"2012-05-15 14:35"},{"path":"consensuses-2007-12.tar.xz","size":8106968,"last_modified":"2012-05-15 14:35"}]},{"path":"extra-infos","files":[{"path":"extra-infos-2007-08.tar.xz","size":3016916,"last_modified":"2016-06-23 09:53"},{"path":"extra-infos-2007-09.tar.xz","size":6459884,"last_modified":"2016-06-23 09:54"},{"path":"extra-infos-2007-10.tar.xz","size":7326564,"last_modified":"2016-06-23 09:54"}]},{"path":"microdescs","files":[{"path":"microdescs-2014-01.tar.xz","size":7515396,"last_modified":"2014-02-07 03:59"},{"path":"microdescs-2014-02.tar.xz","size":21822944,"last_modified":"2014-03-07 04:54"},{"path":"microdescs-2014-03.tar.xz","size":24201436,"last_modified":"2014-04-07 03:54"}]},{"path":"server-descriptors","files":[{"path":"server-descriptors-2005-12.tar.xz","size":1348620,"last_modified":"2016-06-24 08:12"},{"path":"server-descriptors-2006-02.tar.xz","size":28625408,"last_modified":"2016-06-24 08:14"},{"path":"server-descriptors-2006-03.tar.xz","size":49548736,"last_modified":"2016-06-24 08:17"}]},{"path":"statuses","files":[{"path":"statuses-2005-12.tar.xz","size":1468844,"last_modified":"2016-06-25 11:50"},{"path":"statuses-2006-01.tar.xz","size":3344280,"last_modified":"2016-06-25 11:52"},{"path":"statuses-2006-02.tar.xz","size":4006336,"last_modified":"2016-06-25 11:54"}]},{"path":"tor","files":[{"path":"tor-2004-05.tar.xz","size":386672,"last_modified":"2012-05-18 14:26"},{"path":"tor-2004-06.tar.xz","size":1087980,"last_modified":"2012-05-18 14:26"},{"path":"tor-2004-07.tar.xz","size":1366568,"last_modified":"2012-05-18 14:26"}]},{"path":"votes","files":[{"path":"votes-2007-10.tar.xz","size":1356504,"last_modified":"2012-05-15 14:51"},{"path":"votes-2007-11.tar.xz","size":10641492,"last_modified":"2012-05-15 14:51"},{"path":"votes-2007-12.tar.xz","size":14712136,"last_modified":"2012-05-15 14:52"}]}]},{"path":"torperf","files":[{"path":"torperf-2009-07.tar.xz","size":182712,"last_modified":"2012-05-30 07:23"},{"path":"torperf-2009-08.tar.xz","size":203236,"last_modified":"2012-05-30 07:23"},{"path":"torperf-2009-09.tar.xz","size":193832,"last_modified":"2012-05-30 07:23"}]},{"path":"webstats","files":[{"path":"webstats-2015-01.tar","size":30720,"last_modified":"2018-03-19 16:07"},{"path":"webstats-2015-02.tar","size":20480,"last_modified":"2018-03-19 16:07"},{"path":"webstats-2015-03.tar","size":20480,"last_modified":"2018-03-19 16:07"}]}]},{"path":"contrib"},{"path":"recent","directories":[{"path":"bridge-descriptors","directories":[{"path":"extra-infos","files":[{"path":"2019-07-03-02-09-00-extra-infos","size":507816,"last_modified":"2019-07-03 02:09"},{"path":"2019-07-03-03-09-00-extra-infos","size":558790,"last_modified":"2019-07-03 03:09"},{"path":"2019-07-03-04-09-05-extra-infos","size":592279,"last_modified":"2019-07-03 04:09"}]},{"path":"server-descriptors","files":[{"path":"2019-07-03-02-09-00-server-descriptors","size":274355,"last_modified":"2019-07-03 02:09"},{"path":"2019-07-03-03-09-00-server-descriptors","size":295671,"last_modified":"2019-07-03 03:09"},{"path":"2019-07-03-04-09-05-server-descriptors","size":312822,"last_modified":"2019-07-03 04:09"}]},{"path":"statuses","files":[{"path":"20190703-011231-BA44A889E64B93FAA2B114E02C2A279A8555C533","size":238340,"last_modified":"2019-07-03 02:09"},{"path":"20190703-014231-BA44A889E64B93FAA2B114E02C2A279A8555C533","size":238147,"last_modified":"2019-07-03 02:09"},{"path":"20190703-021231-BA44A889E64B93FAA2B114E02C2A279A8555C533","size":238231,"last_modified":"2019-07-03 03:09"}]}]},{"path":"exit-lists","files":[{"path":"2019-07-03-02-02-00","size":158516,"last_modified":"2019-07-03 02:02"},{"path":"2019-07-03-03-02-00","size":158830,"last_modified":"2019-07-03 03:02"},{"path":"2019-07-03-04-02-00","size":158831,"last_modified":"2019-07-03 04:02"}]},{"path":"relay-descriptors","directories":[{"path":"bandwidths","files":[{"path":"2019-07-03-00-30-57-bandwidth-616DF2278EE2C90F475E4EA2562E2C00EB9F10E517FB901229F331AEB70B8AD7","size":2580044,"last_modified":"2019-07-03 02:35"},{"path":"2019-07-03-01-35-02-bandwidth-2A5B679E9AA2D5C903CD16C00E16FEFA3E21E38AB3DE338941AE4AC13FD505DE","size":4210146,"last_modified":"2019-07-03 02:35"},{"path":"2019-07-03-01-35-03-bandwidth-3B06DAD9EDE8E1D08CC494E856AFD9D49256ECC2D68456799DD1EC7ED3436875","size":4249699,"last_modified":"2019-07-03 02:35"}]},{"path":"consensuses","files":[{"path":"2019-07-03-02-00-00-consensus","size":2211265,"last_modified":"2019-07-03 02:05"},{"path":"2019-07-03-03-00-00-consensus","size":2204478,"last_modified":"2019-07-03 03:05"},{"path":"2019-07-03-04-00-00-consensus","size":2202554,"last_modified":"2019-07-03 04:05"}]},{"path":"extra-infos","files":[{"path":"2019-07-03-02-05-00-extra-infos","size":1162899,"last_modified":"2019-07-03 02:05"},{"path":"2019-07-03-03-05-00-extra-infos","size":1133425,"last_modified":"2019-07-03 03:05"},{"path":"2019-07-03-04-05-00-extra-infos","size":1153793,"last_modified":"2019-07-03 04:05"}]},{"path":"microdescs","directories":[{"path":"consensus-microdesc","files":[{"path":"2019-07-03-02-00-00-consensus-microdesc","size":2028994,"last_modified":"2019-07-03 02:05"},{"path":"2019-07-03-03-00-00-consensus-microdesc","size":2022808,"last_modified":"2019-07-03 03:05"},{"path":"2019-07-03-04-00-00-consensus-microdesc","size":2020751,"last_modified":"2019-07-03 04:05"}]},{"path":"micro","files":[{"path":"2019-07-02-23-05-00-micro","size":19934,"last_modified":"2019-07-02 23:05"},{"path":"2019-07-03-00-05-00-micro","size":12030,"last_modified":"2019-07-03 00:05"},{"path":"2019-07-03-01-05-00-micro","size":14850,"last_modified":"2019-07-03 01:05"}]}]},{"path":"server-descriptors","files":[{"path":"2019-07-03-02-05-00-server-descriptors","size":1374587,"last_modified":"2019-07-03 02:05"},{"path":"2019-07-03-03-05-00-server-descriptors","size":1353286,"last_modified":"2019-07-03 03:05"},{"path":"2019-07-03-04-05-00-server-descriptors","size":1336125,"last_modified":"2019-07-03 04:05"}]},{"path":"votes","files":[{"path":"2019-07-03-02-00-00-vote-0232AF901C31A04EE9848595AF9BB7620D4C5B2E-D9B6923A3AF9FB5DB3CD30191BCBD29A7A936D86","size":3449707,"last_modified":"2019-07-03 02:05"},{"path":"2019-07-03-02-00-00-vote-14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4-39429F0789A9EC07A2A8754C4C449CCABAB787CC","size":3435482,"last_modified":"2019-07-03 02:05"},{"path":"2019-07-03-02-00-00-vote-23D15D965BC35114467363C165C4F724B64B4F66-4337842B697467123B5351E1E8EB64ED497C0EDA","size":3571106,"last_modified":"2019-07-03 02:05"}]}]},{"path":"torperf","files":[{"path":"op-ab-1048576-2019-07-03.tpf","size":39152,"last_modified":"2019-07-04 00:01"},{"path":"op-ab-51200-2019-07-03.tpf","size":248173,"last_modified":"2019-07-04 00:01"},{"path":"op-ab-5242880-2019-07-03.tpf","size":15900,"last_modified":"2019-07-04 00:01"}]},{"path":"webstats","files":[{"path":"2019.www.torproject.org_hetzner-hel1-03.torproject.org_access.log_20190620.xz","size":8872,"last_modified":"2019-07-03 04:21"},{"path":"2019.www.torproject.org_hetzner-hel1-03.torproject.org_access.log_20190621.xz","size":9312,"last_modified":"2019-07-04 04:21"},{"path":"2019.www.torproject.org_hetzner-hel1-03.torproject.org_access.log_20190622.xz","size":9836,"last_modified":"2019-07-05 04:21"}]}]}]}
diff --git a/test/unit/descriptor/data/collector/index.py b/test/unit/descriptor/data/collector/index.py
new file mode 100644
index 00000000..41ef6cbd
--- /dev/null
+++ b/test/unit/descriptor/data/collector/index.py
@@ -0,0 +1,530 @@
+# far more readable python hash of collector_index.json
+
+EXAMPLE_INDEX = {
+ 'index_created': '2019-07-06 01:54',
+ 'build_revision': 'df98ac79',
+ 'path': 'https://collector.torproject.org',
+ 'directories': [
+ {
+ 'path': 'archive',
+ 'directories': [
+ {
+ 'path': 'bridge-descriptors',
+ 'directories': [
+ {
+ 'path': 'extra-infos',
+ 'files': [
+ {
+ 'path': 'bridge-extra-infos-2008-05.tar.xz',
+ 'size': 377644,
+ 'last_modified': '2016-09-04 09:21'
+ }, {
+ 'path': 'bridge-extra-infos-2008-06.tar.xz',
+ 'size': 600484,
+ 'last_modified': '2016-09-04 09:21'
+ }, {
+ 'path': 'bridge-extra-infos-2008-07.tar.xz',
+ 'size': 716320,
+ 'last_modified': '2016-09-04 09:21'
+ }
+ ]
+ }, {
+ 'path': 'server-descriptors',
+ 'files': [
+ {
+ 'path': 'bridge-server-descriptors-2008-05.tar.xz',
+ 'size': 205348,
+ 'last_modified': '2016-09-09 14:13'
+ }, {
+ 'path': 'bridge-server-descriptors-2008-06.tar.xz',
+ 'size': 342828,
+ 'last_modified': '2016-09-09 14:13'
+ }, {
+ 'path': 'bridge-server-descriptors-2008-07.tar.xz',
+ 'size': 374848,
+ 'last_modified': '2016-09-09 14:13'
+ }
+ ]
+ }, {
+ 'path': 'statuses',
+ 'files': [
+ {
+ 'path': 'bridge-statuses-2008-05.tar.xz',
+ 'size': 74792,
+ 'last_modified': '2016-09-14 21:11'
+ }, {
+ 'path': 'bridge-statuses-2008-06.tar.xz',
+ 'size': 123488,
+ 'last_modified': '2016-09-14 21:11'
+ }, {
+ 'path': 'bridge-statuses-2008-07.tar.xz',
+ 'size': 143836,
+ 'last_modified': '2016-09-14 21:11'
+ }
+ ]
+ }
+ ]
+ }, {
+ 'path': 'bridge-pool-assignments',
+ 'files': [
+ {
+ 'path': 'bridge-pool-assignments-2010-09.tar.xz',
+ 'size': 32804,
+ 'last_modified': '2012-05-31 10:21'
+ }, {
+ 'path': 'bridge-pool-assignments-2010-10.tar.xz',
+ 'size': 304684,
+ 'last_modified': '2012-05-31 10:21'
+ }, {
+ 'path': 'bridge-pool-assignments-2010-11.tar.xz',
+ 'size': 292228,
+ 'last_modified': '2012-05-31 10:21'
+ }
+ ]
+ }, {
+ 'path': 'exit-lists',
+ 'files': [
+ {
+ 'path': 'exit-list-2010-02.tar.xz',
+ 'size': 272008,
+ 'last_modified': '2012-05-31 18:57'
+ }, {
+ 'path': 'exit-list-2010-03.tar.xz',
+ 'size': 1247484,
+ 'last_modified': '2012-05-31 18:57'
+ }, {
+ 'path': 'exit-list-2010-04.tar.xz',
+ 'size': 1139896,
+ 'last_modified': '2012-05-31 18:57'
+ }
+ ]
+ }, {
+ 'path': 'relay-descriptors',
+ 'files': [
+ {
+ 'path': 'certs.tar.xz',
+ 'size': 144696,
+ 'last_modified': '2019-07-03 03:29'
+ }
+ ],
+ 'directories': [
+ {
+ 'path': 'bandwidths',
+ 'files': [
+ {
+ 'path': 'bandwidths-2019-05.tar.xz',
+ 'size': 50385816,
+ 'last_modified': '2019-06-07 08:05'
+ }, {
+ 'path': 'bandwidths-2019-06.tar.xz',
+ 'size': 105881156,
+ 'last_modified': '2019-07-03 07:30'
+ }, {
+ 'path': 'bandwidths-2019-07.tar.xz',
+ 'size': 11374436,
+ 'last_modified': '2019-07-03 07:12'
+ }
+ ]
+ }, {
+ 'path': 'consensuses',
+ 'files': [
+ {
+ 'path': 'consensuses-2007-10.tar.xz',
+ 'size': 1061648,
+ 'last_modified': '2012-05-15 14:35'
+ }, {
+ 'path': 'consensuses-2007-11.tar.xz',
+ 'size': 6810308,
+ 'last_modified': '2012-05-15 14:35'
+ }, {
+ 'path': 'consensuses-2007-12.tar.xz',
+ 'size': 8106968,
+ 'last_modified': '2012-05-15 14:35'
+ }
+ ]
+ }, {
+ 'path': 'extra-infos',
+ 'files': [
+ {
+ 'path': 'extra-infos-2007-08.tar.xz',
+ 'size': 3016916,
+ 'last_modified': '2016-06-23 09:53'
+ }, {
+ 'path': 'extra-infos-2007-09.tar.xz',
+ 'size': 6459884,
+ 'last_modified': '2016-06-23 09:54'
+ }, {
+ 'path': 'extra-infos-2007-10.tar.xz',
+ 'size': 7326564,
+ 'last_modified': '2016-06-23 09:54'
+ }
+ ]
+ }, {
+ 'path': 'microdescs',
+ 'files': [
+ {
+ 'path': 'microdescs-2014-01.tar.xz',
+ 'size': 7515396,
+ 'last_modified': '2014-02-07 03:59'
+ }, {
+ 'path': 'microdescs-2014-02.tar.xz',
+ 'size': 21822944,
+ 'last_modified': '2014-03-07 04:54'
+ }, {
+ 'path': 'microdescs-2014-03.tar.xz',
+ 'size': 24201436,
+ 'last_modified': '2014-04-07 03:54'
+ }
+ ]
+ }, {
+ 'path': 'server-descriptors',
+ 'files': [
+ {
+ 'path': 'server-descriptors-2005-12.tar.xz',
+ 'size': 1348620,
+ 'last_modified': '2016-06-24 08:12'
+ }, {
+ 'path': 'server-descriptors-2006-02.tar.xz',
+ 'size': 28625408,
+ 'last_modified': '2016-06-24 08:14'
+ }, {
+ 'path': 'server-descriptors-2006-03.tar.xz',
+ 'size': 49548736,
+ 'last_modified': '2016-06-24 08:17'
+ }
+ ]
+ }, {
+ 'path': 'statuses',
+ 'files': [
+ {
+ 'path': 'statuses-2005-12.tar.xz',
+ 'size': 1468844,
+ 'last_modified': '2016-06-25 11:50'
+ }, {
+ 'path': 'statuses-2006-01.tar.xz',
+ 'size': 3344280,
+ 'last_modified': '2016-06-25 11:52'
+ }, {
+ 'path': 'statuses-2006-02.tar.xz',
+ 'size': 4006336,
+ 'last_modified': '2016-06-25 11:54'
+ }
+ ]
+ }, {
+ 'path': 'tor',
+ 'files': [
+ {
+ 'path': 'tor-2004-05.tar.xz',
+ 'size': 386672,
+ 'last_modified': '2012-05-18 14:26'
+ }, {
+ 'path': 'tor-2004-06.tar.xz',
+ 'size': 1087980,
+ 'last_modified': '2012-05-18 14:26'
+ }, {
+ 'path': 'tor-2004-07.tar.xz',
+ 'size': 1366568,
+ 'last_modified': '2012-05-18 14:26'
+ }
+ ]
+ }, {
+ 'path': 'votes',
+ 'files': [
+ {
+ 'path': 'votes-2007-10.tar.xz',
+ 'size': 1356504,
+ 'last_modified': '2012-05-15 14:51'
+ }, {
+ 'path': 'votes-2007-11.tar.xz',
+ 'size': 10641492,
+ 'last_modified': '2012-05-15 14:51'
+ }, {
+ 'path': 'votes-2007-12.tar.xz',
+ 'size': 14712136,
+ 'last_modified': '2012-05-15 14:52'
+ }
+ ]
+ }
+ ]
+ }, {
+ 'path': 'torperf',
+ 'files': [
+ {
+ 'path': 'torperf-2009-07.tar.xz',
+ 'size': 182712,
+ 'last_modified': '2012-05-30 07:23'
+ }, {
+ 'path': 'torperf-2009-08.tar.xz',
+ 'size': 203236,
+ 'last_modified': '2012-05-30 07:23'
+ }, {
+ 'path': 'torperf-2009-09.tar.xz',
+ 'size': 193832,
+ 'last_modified': '2012-05-30 07:23'
+ }
+ ]
+ }, {
+ 'path': 'webstats',
+ 'files': [
+ {
+ 'path': 'webstats-2015-01.tar',
+ 'size': 30720,
+ 'last_modified': '2018-03-19 16:07'
+ }, {
+ 'path': 'webstats-2015-02.tar',
+ 'size': 20480,
+ 'last_modified': '2018-03-19 16:07'
+ }, {
+ 'path': 'webstats-2015-03.tar',
+ 'size': 20480,
+ 'last_modified': '2018-03-19 16:07'
+ }
+ ]
+ }
+ ]
+ }, {
+ 'path': 'contrib'
+ }, {
+ 'path': 'recent',
+ 'directories': [
+ {
+ 'path': 'bridge-descriptors',
+ 'directories': [
+ {
+ 'path': 'extra-infos',
+ 'files': [
+ {
+ 'path': '2019-07-03-02-09-00-extra-infos',
+ 'size': 507816,
+ 'last_modified': '2019-07-03 02:09'
+ }, {
+ 'path': '2019-07-03-03-09-00-extra-infos',
+ 'size': 558790,
+ 'last_modified': '2019-07-03 03:09'
+ }, {
+ 'path': '2019-07-03-04-09-05-extra-infos',
+ 'size': 592279,
+ 'last_modified': '2019-07-03 04:09'
+ }
+ ]
+ }, {
+ 'path': 'server-descriptors',
+ 'files': [
+ {
+ 'path': '2019-07-03-02-09-00-server-descriptors',
+ 'size': 274355,
+ 'last_modified': '2019-07-03 02:09'
+ }, {
+ 'path': '2019-07-03-03-09-00-server-descriptors',
+ 'size': 295671,
+ 'last_modified': '2019-07-03 03:09'
+ }, {
+ 'path': '2019-07-03-04-09-05-server-descriptors',
+ 'size': 312822,
+ 'last_modified': '2019-07-03 04:09'
+ }
+ ]
+ }, {
+ 'path': 'statuses',
+ 'files': [
+ {
+ 'path': '20190703-011231-BA44A889E64B93FAA2B114E02C2A279A8555C533',
+ 'size': 238340,
+ 'last_modified': '2019-07-03 02:09'
+ }, {
+ 'path': '20190703-014231-BA44A889E64B93FAA2B114E02C2A279A8555C533',
+ 'size': 238147,
+ 'last_modified': '2019-07-03 02:09'
+ }, {
+ 'path': '20190703-021231-BA44A889E64B93FAA2B114E02C2A279A8555C533',
+ 'size': 238231,
+ 'last_modified': '2019-07-03 03:09'
+ }
+ ]
+ }
+ ]
+ }, {
+ 'path': 'exit-lists',
+ 'files': [
+ {
+ 'path': '2019-07-03-02-02-00',
+ 'size': 158516,
+ 'last_modified': '2019-07-03 02:02'
+ }, {
+ 'path': '2019-07-03-03-02-00',
+ 'size': 158830,
+ 'last_modified': '2019-07-03 03:02'
+ }, {
+ 'path': '2019-07-03-04-02-00',
+ 'size': 158831,
+ 'last_modified': '2019-07-03 04:02'
+ }
+ ]
+ }, {
+ 'path': 'relay-descriptors',
+ 'directories': [
+ {
+ 'path': 'bandwidths',
+ 'files': [
+ {
+ 'path': '2019-07-03-00-30-57-bandwidth-616DF2278EE2C90F475E4EA2562E2C00EB9F10E517FB901229F331AEB70B8AD7',
+ 'size': 2580044,
+ 'last_modified': '2019-07-03 02:35'
+ }, {
+ 'path': '2019-07-03-01-35-02-bandwidth-2A5B679E9AA2D5C903CD16C00E16FEFA3E21E38AB3DE338941AE4AC13FD505DE',
+ 'size': 4210146,
+ 'last_modified': '2019-07-03 02:35'
+ }, {
+ 'path': '2019-07-03-01-35-03-bandwidth-3B06DAD9EDE8E1D08CC494E856AFD9D49256ECC2D68456799DD1EC7ED3436875',
+ 'size': 4249699,
+ 'last_modified': '2019-07-03 02:35'
+ }
+ ]
+ }, {
+ 'path': 'consensuses',
+ 'files': [
+ {
+ 'path': '2019-07-03-02-00-00-consensus',
+ 'size': 2211265,
+ 'last_modified': '2019-07-03 02:05'
+ }, {
+ 'path': '2019-07-03-03-00-00-consensus',
+ 'size': 2204478,
+ 'last_modified': '2019-07-03 03:05'
+ }, {
+ 'path': '2019-07-03-04-00-00-consensus',
+ 'size': 2202554,
+ 'last_modified': '2019-07-03 04:05'
+ }
+ ]
+ }, {
+ 'path': 'extra-infos',
+ 'files': [
+ {
+ 'path': '2019-07-03-02-05-00-extra-infos',
+ 'size': 1162899,
+ 'last_modified': '2019-07-03 02:05'
+ }, {
+ 'path': '2019-07-03-03-05-00-extra-infos',
+ 'size': 1133425,
+ 'last_modified': '2019-07-03 03:05'
+ }, {
+ 'path': '2019-07-03-04-05-00-extra-infos',
+ 'size': 1153793,
+ 'last_modified': '2019-07-03 04:05'
+ }
+ ]
+ }, {
+ 'path': 'microdescs',
+ 'directories': [
+ {
+ 'path': 'consensus-microdesc',
+ 'files': [
+ {
+ 'path': '2019-07-03-02-00-00-consensus-microdesc',
+ 'size': 2028994,
+ 'last_modified': '2019-07-03 02:05'
+ }, {
+ 'path': '2019-07-03-03-00-00-consensus-microdesc',
+ 'size': 2022808,
+ 'last_modified': '2019-07-03 03:05'
+ }, {
+ 'path': '2019-07-03-04-00-00-consensus-microdesc',
+ 'size': 2020751,
+ 'last_modified': '2019-07-03 04:05'
+ }
+ ]
+ }, {
+ 'path': 'micro',
+ 'files': [
+ {
+ 'path': '2019-07-02-23-05-00-micro',
+ 'size': 19934,
+ 'last_modified': '2019-07-02 23:05'
+ }, {
+ 'path': '2019-07-03-00-05-00-micro',
+ 'size': 12030,
+ 'last_modified': '2019-07-03 00:05'
+ }, {
+ 'path': '2019-07-03-01-05-00-micro',
+ 'size': 14850,
+ 'last_modified': '2019-07-03 01:05'
+ }
+ ]
+ }
+ ]
+ }, {
+ 'path': 'server-descriptors',
+ 'files': [
+ {
+ 'path': '2019-07-03-02-05-00-server-descriptors',
+ 'size': 1374587,
+ 'last_modified': '2019-07-03 02:05'
+ }, {
+ 'path': '2019-07-03-03-05-00-server-descriptors',
+ 'size': 1353286,
+ 'last_modified': '2019-07-03 03:05'
+ }, {
+ 'path': '2019-07-03-04-05-00-server-descriptors',
+ 'size': 1336125,
+ 'last_modified': '2019-07-03 04:05'
+ }
+ ]
+ }, {
+ 'path': 'votes',
+ 'files': [
+ {
+ 'path': '2019-07-03-02-00-00-vote-0232AF901C31A04EE9848595AF9BB7620D4C5B2E-D9B6923A3AF9FB5DB3CD30191BCBD29A7A936D86',
+ 'size': 3449707,
+ 'last_modified': '2019-07-03 02:05'
+ }, {
+ 'path': '2019-07-03-02-00-00-vote-14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4-39429F0789A9EC07A2A8754C4C449CCABAB787CC',
+ 'size': 3435482,
+ 'last_modified': '2019-07-03 02:05'
+ }, {
+ 'path': '2019-07-03-02-00-00-vote-23D15D965BC35114467363C165C4F724B64B4F66-4337842B697467123B5351E1E8EB64ED497C0EDA',
+ 'size': 3571106,
+ 'last_modified': '2019-07-03 02:05'
+ }
+ ]
+ }
+ ]
+ }, {
+ 'path': 'torperf',
+ 'files': [
+ {
+ 'path': 'op-ab-1048576-2019-07-03.tpf',
+ 'size': 39152,
+ 'last_modified': '2019-07-04 00:01'
+ }, {
+ 'path': 'op-ab-51200-2019-07-03.tpf',
+ 'size': 248173,
+ 'last_modified': '2019-07-04 00:01'
+ }, {
+ 'path': 'op-ab-5242880-2019-07-03.tpf',
+ 'size': 15900,
+ 'last_modified': '2019-07-04 00:01'
+ }
+ ]
+ }, {
+ 'path': 'webstats',
+ 'files': [
+ {
+ 'path': '2019.www.torproject.org_hetzner-hel1-03.torproject.org_access.log_20190620.xz',
+ 'size': 8872,
+ 'last_modified': '2019-07-03 04:21'
+ }, {
+ 'path': '2019.www.torproject.org_hetzner-hel1-03.torproject.org_access.log_20190621.xz',
+ 'size': 9312,
+ 'last_modified': '2019-07-04 04:21'
+ }, {
+ 'path': '2019.www.torproject.org_hetzner-hel1-03.torproject.org_access.log_20190622.xz',
+ 'size': 9836,
+ 'last_modified': '2019-07-05 04:21'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/unit/descriptor/data/collector/microdescs-2019-05-cropped.tar b/test/unit/descriptor/data/collector/microdescs-2019-05-cropped.tar
new file mode 100644
index 00000000..789774c2
--- /dev/null
+++ b/test/unit/descriptor/data/collector/microdescs-2019-05-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/collector/server-descriptors-2005-12-cropped.tar b/test/unit/descriptor/data/collector/server-descriptors-2005-12-cropped.tar
new file mode 100644
index 00000000..4ffe1434
--- /dev/null
+++ b/test/unit/descriptor/data/collector/server-descriptors-2005-12-cropped.tar
Binary files differ
diff --git a/test/unit/descriptor/data/compressed_bz2 b/test/unit/descriptor/data/compressed_bz2
new file mode 100644
index 00000000..4d645a71
--- /dev/null
+++ b/test/unit/descriptor/data/compressed_bz2
Binary files differ
diff --git a/test/unit/descriptor/remote.py b/test/unit/descriptor/remote.py
index 8c5e835b..8a35216d 100644
--- a/test/unit/descriptor/remote.py
+++ b/test/unit/descriptor/remote.py
@@ -8,6 +8,7 @@ import time
import unittest
import stem
+import stem.descriptor
import stem.descriptor.remote
import stem.prereq
import stem.util.str_tools
@@ -181,26 +182,26 @@ class TestDescriptorDownloader(unittest.TestCase):
def test_gzip_url_override(self):
query = stem.descriptor.remote.Query(TEST_RESOURCE + '.z', compression = Compression.PLAINTEXT, start = False)
- self.assertEqual([Compression.GZIP], query.compression)
+ self.assertEqual([stem.descriptor.Compression.GZIP], query.compression)
self.assertEqual(TEST_RESOURCE, query.resource)
def test_zstd_support_check(self):
with patch('stem.prereq.is_zstd_available', Mock(return_value = True)):
query = stem.descriptor.remote.Query(TEST_RESOURCE, compression = Compression.ZSTD, start = False)
- self.assertEqual([Compression.ZSTD], query.compression)
+ self.assertEqual([stem.descriptor.Compression.ZSTD], query.compression)
with patch('stem.prereq.is_zstd_available', Mock(return_value = False)):
query = stem.descriptor.remote.Query(TEST_RESOURCE, compression = Compression.ZSTD, start = False)
- self.assertEqual([Compression.PLAINTEXT], query.compression)
+ self.assertEqual([stem.descriptor.Compression.PLAINTEXT], query.compression)
def test_lzma_support_check(self):
with patch('stem.prereq.is_lzma_available', Mock(return_value = True)):
query = stem.descriptor.remote.Query(TEST_RESOURCE, compression = Compression.LZMA, start = False)
- self.assertEqual([Compression.LZMA], query.compression)
+ self.assertEqual([stem.descriptor.Compression.LZMA], query.compression)
with patch('stem.prereq.is_lzma_available', Mock(return_value = False)):
query = stem.descriptor.remote.Query(TEST_RESOURCE, compression = Compression.LZMA, start = False)
- self.assertEqual([Compression.PLAINTEXT], query.compression)
+ self.assertEqual([stem.descriptor.Compression.PLAINTEXT], query.compression)
@patch(URL_OPEN, _dirport_mock(read_resource('compressed_identity'), encoding = 'identity'))
def test_compression_plaintext(self):
@@ -382,7 +383,7 @@ class TestDescriptorDownloader(unittest.TestCase):
# After two requests we'll have reached our total permissable timeout.
# Check that we don't make a third.
- self.assertRaises(socket.timeout, query.run)
+ self.assertRaises(stem.DownloadTimeout, query.run)
self.assertEqual(2, dirport_mock.call_count)
def test_query_with_invalid_endpoints(self):
diff --git a/test/unit/directory/authority.py b/test/unit/directory/authority.py
index 56bd6ec8..1c2a86b4 100644
--- a/test/unit/directory/authority.py
+++ b/test/unit/directory/authority.py
@@ -5,6 +5,7 @@ Unit tests for stem.directory.Authority.
import io
import unittest
+import stem
import stem.directory
import stem.prereq
@@ -78,4 +79,4 @@ class TestAuthority(unittest.TestCase):
@patch(URL_OPEN, Mock(return_value = io.BytesIO(b'')))
def test_from_remote_empty(self):
- self.assertRaisesRegexp(IOError, 'did not have any content', stem.directory.Authority.from_remote)
+ self.assertRaisesRegexp(stem.DownloadFailed, 'no content', stem.directory.Authority.from_remote)
diff --git a/test/unit/directory/fallback.py b/test/unit/directory/fallback.py
index 3f5ceca3..06b4510d 100644
--- a/test/unit/directory/fallback.py
+++ b/test/unit/directory/fallback.py
@@ -7,6 +7,7 @@ import re
import tempfile
import unittest
+import stem
import stem.directory
import stem.util.conf
@@ -92,7 +93,7 @@ class TestFallback(unittest.TestCase):
def test_from_cache(self):
fallbacks = stem.directory.Fallback.from_cache()
self.assertTrue(len(fallbacks) > 10)
- self.assertEqual('193.171.202.146', fallbacks['01A9258A46E97FF8B2CAC7910577862C14F2C524'].address)
+ self.assertEqual('185.13.39.197', fallbacks['001524DD403D729F08F7E5D77813EF12756CFA8D'].address)
@patch(URL_OPEN, Mock(return_value = io.BytesIO(FALLBACK_GITWEB_CONTENT)))
def test_from_remote(self):
@@ -123,7 +124,7 @@ class TestFallback(unittest.TestCase):
@patch(URL_OPEN, Mock(return_value = io.BytesIO(b'')))
def test_from_remote_empty(self):
- self.assertRaisesRegexp(IOError, 'did not have any content', stem.directory.Fallback.from_remote)
+ self.assertRaisesRegexp(stem.DownloadFailed, 'no content', stem.directory.Fallback.from_remote)
@patch(URL_OPEN, Mock(return_value = io.BytesIO(b'\n'.join(FALLBACK_GITWEB_CONTENT.splitlines()[1:]))))
def test_from_remote_no_header(self):
diff --git a/test/unit/tutorial.py b/test/unit/tutorial.py
index c74b1912..9d70a5cf 100644
--- a/test/unit/tutorial.py
+++ b/test/unit/tutorial.py
@@ -8,10 +8,10 @@ import unittest
import stem.descriptor.remote
from stem.control import Controller
-from stem.descriptor.reader import DescriptorReader
from stem.descriptor.router_status_entry import RouterStatusEntryV2, RouterStatusEntryV3
from stem.descriptor.networkstatus import NetworkStatusDocumentV3
from stem.descriptor.server_descriptor import RelayDescriptor
+from stem.exit_policy import ExitPolicy
from test.unit import exec_documentation_example
try:
@@ -165,13 +165,15 @@ class TestTutorial(unittest.TestCase):
self.assertEqual('found relay caerSidi (A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB)\n', stdout_mock.getvalue())
@patch('sys.stdout', new_callable = StringIO)
- @patch('stem.descriptor.reader.DescriptorReader', spec = DescriptorReader)
- def test_mirror_mirror_on_the_wall_4(self, reader_mock, stdout_mock):
- reader = reader_mock().__enter__()
- reader.__iter__.return_value = iter([RelayDescriptor.create({'router': 'caerSidi 71.35.133.197 9001 0 0'})])
-
- exec_documentation_example('past_descriptors.py')
- self.assertEqual('found relay caerSidi (None)\n', stdout_mock.getvalue())
+ @patch('stem.descriptor.collector.get_server_descriptors')
+ def test_mirror_mirror_on_the_wall_4(self, get_desc_mock, stdout_mock):
+ get_desc_mock.return_value = iter([RelayDescriptor.create({
+ 'router': 'caerSidi 71.35.133.197 9001 0 0',
+ 'fingerprint': '2C3C 4662 5698 B6D6 7DF3 2BC1 918A D3EE 1F99 06B1',
+ }, exit_policy = ExitPolicy('accept *:*'), validate = False)])
+
+ exec_documentation_example('collector_reading.py')
+ self.assertEqual(' caerSidi (2C3C46625698B6D67DF32BC1918AD3EE1F9906B1)\n', stdout_mock.getvalue())
@patch('sys.stdout', new_callable = StringIO)
@patch('stem.descriptor.remote.DescriptorDownloader')
diff --git a/test/unit/util/connection.py b/test/unit/util/connection.py
index a2162029..8721ff6d 100644
--- a/test/unit/util/connection.py
+++ b/test/unit/util/connection.py
@@ -2,19 +2,30 @@
Unit tests for the stem.util.connection functions.
"""
+import io
import platform
import unittest
+import stem
import stem.util.connection
from stem.util.connection import Resolver, Connection
try:
+ # account for urllib's change between python 2.x and 3.x
+ import urllib.request as urllib
+except ImportError:
+ import urllib2 as urllib
+
+try:
# added in python 3.3
from unittest.mock import Mock, patch
except ImportError:
from mock import Mock, patch
+URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
+URL = 'https://example.unit.test.url'
+
NETSTAT_OUTPUT = """\
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
@@ -166,6 +177,39 @@ _tor tor 15843 20* internet stream tcp 0x0 192.168.1.100:36174 -->
class TestConnection(unittest.TestCase):
+ @patch(URL_OPEN)
+ def test_download(self, urlopen_mock):
+ urlopen_mock.return_value = io.BytesIO(b'hello')
+
+ self.assertEqual(b'hello', stem.util.connection.download(URL))
+ urlopen_mock.assert_called_with(URL, timeout = None)
+
+ @patch(URL_OPEN)
+ def test_download_failure(self, urlopen_mock):
+ urlopen_mock.side_effect = urllib.URLError('boom')
+
+ try:
+ stem.util.connection.download(URL)
+ self.fail('expected a stem.DownloadFailed to be raised')
+ except stem.DownloadFailed as exc:
+ self.assertEqual('Failed to download from https://example.unit.test.url (URLError): boom', str(exc))
+ self.assertEqual(URL, exc.url)
+ self.assertEqual('boom', exc.error.reason)
+ self.assertEqual(urllib.URLError, type(exc.error))
+ self.assertTrue('return urllib.urlopen(url, timeout = timeout).read()' in exc.stacktrace_str)
+
+ @patch(URL_OPEN)
+ def test_download_retries(self, urlopen_mock):
+ urlopen_mock.side_effect = urllib.URLError('boom')
+
+ self.assertRaisesRegexp(IOError, 'boom', stem.util.connection.download, URL)
+ self.assertEqual(1, urlopen_mock.call_count)
+
+ urlopen_mock.reset_mock()
+
+ self.assertRaisesRegexp(IOError, 'boom', stem.util.connection.download, URL, retries = 4)
+ self.assertEqual(5, urlopen_mock.call_count)
+
@patch('os.access')
@patch('stem.util.system.is_available')
@patch('stem.util.proc.is_available')