diff options
| author | Damian Johnson <atagar@torproject.org> | 2019-08-17 13:42:52 -0700 |
|---|---|---|
| committer | Damian Johnson <atagar@torproject.org> | 2019-08-17 13:42:52 -0700 |
| commit | 97c9a58eab40ce32256afbb02a4f8c6c84045bb1 (patch) | |
| tree | 2690a7c640b2b0c4a0ca4a3ea31849d9e141c2d0 | |
| parent | 4357e5480eb8de01337929e3e6f761f0d6de4833 (diff) | |
| parent | 6a44d211342a727b71824825262123aeaf300c99 (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
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 Binary files differindex 5069976c..515e894b 100644 --- a/stem/cached_manual.sqlite +++ b/stem/cached_manual.sqlite 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 Binary files differnew file mode 100644 index 00000000..4f3c8724 --- /dev/null +++ b/test/unit/descriptor/data/collector/bandwidths-2019-05-cropped.tar 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 Binary files differnew file mode 100644 index 00000000..db9f3f7b --- /dev/null +++ b/test/unit/descriptor/data/collector/bridge-extra-infos-2019-03-cropped.tar 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 Binary files differnew file mode 100644 index 00000000..4d0255f3 --- /dev/null +++ b/test/unit/descriptor/data/collector/bridge-server-descriptors-2019-02-cropped.tar 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 Binary files differnew file mode 100644 index 00000000..953d2a86 --- /dev/null +++ b/test/unit/descriptor/data/collector/bridge-statuses-2019-05-cropped.tar diff --git a/test/unit/descriptor/data/collector/certs-cropped.tar b/test/unit/descriptor/data/collector/certs-cropped.tar Binary files differnew file mode 100644 index 00000000..9320b65f --- /dev/null +++ b/test/unit/descriptor/data/collector/certs-cropped.tar diff --git a/test/unit/descriptor/data/collector/consensuses-2018-06-cropped.tar b/test/unit/descriptor/data/collector/consensuses-2018-06-cropped.tar Binary files differnew file mode 100644 index 00000000..0728e595 --- /dev/null +++ b/test/unit/descriptor/data/collector/consensuses-2018-06-cropped.tar 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 Binary files differnew file mode 100644 index 00000000..2f86f8de --- /dev/null +++ b/test/unit/descriptor/data/collector/exit-list-2018-11-cropped.tar 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 Binary files differnew file mode 100644 index 00000000..21227286 --- /dev/null +++ b/test/unit/descriptor/data/collector/extra-infos-2019-04-cropped.tar 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 Binary files differnew file mode 100644 index 00000000..789774c2 --- /dev/null +++ b/test/unit/descriptor/data/collector/microdescs-2019-05-cropped.tar 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 Binary files differnew file mode 100644 index 00000000..4ffe1434 --- /dev/null +++ b/test/unit/descriptor/data/collector/server-descriptors-2005-12-cropped.tar diff --git a/test/unit/descriptor/data/compressed_bz2 b/test/unit/descriptor/data/compressed_bz2 Binary files differnew file mode 100644 index 00000000..4d645a71 --- /dev/null +++ b/test/unit/descriptor/data/compressed_bz2 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') |
