commit 120715e4af8cbaca2b0d1f785aba15a94e4264df Author: Damian Johnson atagar@torproject.org Date: Sat Aug 29 18:24:16 2020 -0700
Support resetting circuit timeouts
According to Mike and Karsten, OnionPerf and Shadow require the ability to reset circuit timeouts when they change tor's guards. Adding this as an argument to drop_guards().
https://github.com/torproject/stem/issues/73 --- docs/change_log.rst | 1 + stem/control.py | 13 ++++++++++++- stem/version.py | 2 ++ test/integ/control/controller.py | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst index f30c3a48..f5a96d7b 100644 --- a/docs/change_log.rst +++ b/docs/change_log.rst @@ -53,6 +53,7 @@ The following are only available within Stem's `git repository * Our API now provides `type hints https://blog.atagar.com/may2020/`_. * Migrated to `asyncio https://docs.python.org/3/library/asyncio.html`_. Stem can now be used by `both synchronous and asynchronous applications https://blog.atagar.com/july2020/`_. * Installation has migrated from distutils to setuptools. + * Added the 'reset_timeouts' argument to :func:`~stem.control.Controller.drop_guards` (:ticket:`73`)
* **Controller**
diff --git a/stem/control.py b/stem/control.py index 79d5881d..5ee9782e 100644 --- a/stem/control.py +++ b/stem/control.py @@ -3894,17 +3894,28 @@ class Controller(BaseController): response = await self.msg('MAPADDRESS %s' % mapaddress_arg) return stem.response._convert_to_mapaddress(response)
- async def drop_guards(self) -> None: + async def drop_guards(self, reset_timeouts: bool = False) -> None: """ Drops our present guard nodes and picks a new set.
.. versionadded:: 1.2.0
+ .. versionchanged:: 2.0.0 + Added the reset_timeouts argument. + + :param reset_timeouts: reset circuit timeout counters + :raises: :class:`stem.ControllerError` if Tor couldn't fulfill the request """
+ if reset_timeouts and (await self.get_version() < stem.version.Requirement.DROPTIMEOUTS): + raise ValueError('DROPTIMEOUTS requires tor %s or higher' % stem.version.Requirement.DROPTIMEOUTS) + await self.msg('DROPGUARDS')
+ if reset_timeouts: + await self.msg('DROPTIMEOUTS') + async def _post_authentication(self) -> None: await super(Controller, self)._post_authentication()
diff --git a/stem/version.py b/stem/version.py index d01e04d9..9ed5fa37 100644 --- a/stem/version.py +++ b/stem/version.py @@ -30,6 +30,7 @@ easily parsed and compared, for instance... Requirement Description ===================== =========== **DORMANT_MODE** **DORMANT** and **ACTIVE** :data:`~stem.Signal` + **DROPTIMEOUTS** **DROPTIMEOUTS** controller command **HSFETCH_V3** HSFETCH for version 3 hidden services ===================== =========== """ @@ -218,5 +219,6 @@ class Version(object):
Requirement = stem.util.enum.Enum( ('DORMANT_MODE', Version('0.4.0.1-alpha')), + ('DROPTIMEOUTS', Version('0.4.5.0-alpha')), ('HSFETCH_V3', Version('0.4.1.1-alpha')), ) diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py index 3a96c688..6f5da0c4 100644 --- a/test/integ/control/controller.py +++ b/test/integ/control/controller.py @@ -1285,6 +1285,42 @@ class TestController(unittest.TestCase): await controller.map_address(dict([(addr, None) for addr in mapped_addresses])) self.assertEquals({}, await address_mappings('control'))
+ @test.require.controller + @test.require.online + @async_test + async def test_drop_guards(self): + async with await test.runner.get_runner().get_tor_controller() as controller: + previous_guards = await controller.get_info('entry-guards') + started_at = time.time() + + await controller.drop_guards() + + while time.time() < (started_at + 5): + if previous_guards != await controller.get_info('entry-guards'): + return # success + + await asyncio.sleep(0.01) + + self.fail('DROPGUARDS failed to change our guards within five seconds') + + @test.require.controller + @test.require.version(stem.version.Requirement.DROPTIMEOUTS) + @async_test + async def test_drop_guards_with_reset(self): + async with await test.runner.get_runner().get_tor_controller() as controller: + events = asyncio.Queue() + + await controller.add_event_listener(lambda event: events.put_nowait(event), stem.control.EventType.BUILDTIMEOUT_SET) + await controller.drop_guards(reset_timeouts = True) + + try: + event = await asyncio.wait_for(events.get(), timeout = 5) + except asyncio.TimeoutError: + self.fail('DROPTIMEOUTS failed to emit a BUILDTIMEOUT_SET event within five seconds') + + self.assertEqual('RESET', event.set_type) + self.assertEqual(0, event.total_times) + @test.require.controller @async_test async def test_mapaddress_mixed_response(self):