commit 4a878f05489fcf680fb3e390363cc9da6985210a Author: Damian Johnson <ata...@torproject.org> Date: Tue Jun 4 09:51:09 2019 -0700
Integ tests for stem.descriptor.remote hangs Our Serge and tor26 directory authorities intentionally break their DirPort's ability to respond to uncompressed descriptor requests in an effort to mess with DOS attackers. Unfortunately doing so messes with us as well. For example, the following is a curl request I gave up on after 90 seconds... % curl 86.59.21.38:80/tor/status-vote/current/consensus > /tmp/dump % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 33088 0 33088 0 0 364 0 --:--:-- 0:01:30 --:--:-- 0 The trouble is that Python's socket module timeout only takes effect when we stop receiving data. However, because these dirauths dribble a few bits at a time it never takes effect, effectively causing our tests to hang. I adjusted DocTor a while ago to avoid these sand traps, but a couple other issues remained... * One of our integ tests exercised downloading the consensus from each dirauth. Changed it to avoid the problematic dirauths. * When using a DescriptorDownloader it populated a default 'endpoints' variable that caused us to short circuit the code that skipped these dirauths. Fixed that as well. With this 'run_tests.py --integ --target ONLINE' should no longer hang. --- stem/descriptor/remote.py | 24 ++++++++++++++++++------ test/integ/descriptor/remote.py | 3 +++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/stem/descriptor/remote.py b/stem/descriptor/remote.py index ab2fc091..b2f066a3 100644 --- a/stem/descriptor/remote.py +++ b/stem/descriptor/remote.py @@ -140,6 +140,16 @@ SINGLETON_DOWNLOADER = None DETACHED_SIGNATURE_TYPE = 'detached-signature' +# Some authorities intentionally break their DirPort to discourage DOS. In +# particular they throttle the rate to such a degree that requests can take +# hours to complete. Unfortunately Python's socket timeouts only kick in +# when we stop receiving data, so these 'sandtraps' cause our downloads to +# hang pretty much indefinitely. +# +# Best we can do is simply avoid attempting to use them in the first place. + +DIR_PORT_BLACKLIST = ('tor26', 'Serge') + def get_instance(): """ @@ -560,11 +570,12 @@ class Query(object): :param bool use_authority: ignores our endpoints and uses a directory authority instead - :returns: **str** for the url being queried by this request + :returns: :class:`stem.Endpoint` for the location to be downloaded + from by this request """ if use_authority or not self.endpoints: - picked = random.choice([auth for auth in stem.directory.Authority.from_cache().values() if auth.nickname not in ('tor26', 'Serge')]) + picked = random.choice([auth for auth in stem.directory.Authority.from_cache().values() if auth.nickname not in DIR_PORT_BLACKLIST]) return stem.DirPort(picked.address, picked.dir_port) else: return random.choice(self.endpoints) @@ -575,15 +586,17 @@ class Query(object): endpoint = self._pick_endpoint(use_authority = retries == 0 and self.fall_back_to_authority) if isinstance(endpoint, stem.ORPort): + downloaded_from = 'ORPort %s:%s (resource %s)' % (endpoint.address, endpoint.port, self.resource) self.content, self.reply_headers = _download_from_orport(endpoint, self.compression, self.resource) elif isinstance(endpoint, stem.DirPort): self.download_url = 'http://%s:%i/%s' % (endpoint.address, endpoint.port, self.resource.lstrip('/')) + downloaded_from = self.download_url self.content, self.reply_headers = _download_from_dirport(self.download_url, self.compression, timeout) else: raise ValueError("BUG: endpoints can only be ORPorts or DirPorts, '%s' was a %s" % (endpoint, type(endpoint).__name__)) self.runtime = time.time() - self.start_time - log.trace("Descriptors retrieved from '%s' in %0.2fs" % (self.download_url, self.runtime)) + log.trace("Descriptors retrieved from %s in %0.2fs" % (downloaded_from, self.runtime)) except: exc = sys.exc_info()[1] @@ -615,8 +628,7 @@ class DescriptorDownloader(object): def __init__(self, use_mirrors = False, **default_args): self._default_args = default_args - directories = list(stem.directory.Authority.from_cache().values()) - self._endpoints = [(directory.address, directory.dir_port) for directory in directories] + self._endpoints = None if use_mirrors: try: @@ -637,7 +649,7 @@ class DescriptorDownloader(object): :raises: **Exception** if unable to determine the directory mirrors """ - directories = stem.directory.Authority.from_cache().values() + directories = [auth for auth in stem.directory.Authority.from_cache().values() if auth.nickname not in DIR_PORT_BLACKLIST] new_endpoints = set([(directory.address, directory.dir_port) for directory in directories]) consensus = list(self.get_consensus(document_handler = stem.descriptor.DocumentHandler.DOCUMENT).run())[0] diff --git a/test/integ/descriptor/remote.py b/test/integ/descriptor/remote.py index 41fded2c..5788f322 100644 --- a/test/integ/descriptor/remote.py +++ b/test/integ/descriptor/remote.py @@ -102,6 +102,9 @@ class TestDescriptorDownloader(unittest.TestCase): queries = [] for nickname, authority in stem.directory.Authority.from_cache().items(): + if nickname in stem.descriptor.remote.DIR_PORT_BLACKLIST: + continue + queries.append((stem.descriptor.remote.Query( '/tor/server/fp/9695DFC35FFEB861329B9F1AB04C46397020CE31', 'server-descriptor 1.0', _______________________________________________ tor-commits mailing list tor-commits@lists.torproject.org https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits