commit b730a0b57d13a7fe12a7b956ac5fc06fabcddf73 Author: Damian Johnson atagar@torproject.org Date: Sat Oct 21 06:43:41 2017 -0700
Cache 'GETINFO fingerprint' when not a relay
Probably the most requested GETINFO parameter is our fingerprint. We cache fingerprint values, but not exceptions causing any request when not a relay to go to tor.
This is unnecessary. We can detect if we become a relay so caching the 'not a relay' response to avoid having callers (like Nyx) accidently hammer tor. --- stem/control.py | 9 +++++++++ test/unit/control/controller.py | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+)
diff --git a/stem/control.py b/stem/control.py index b6d49d22..7b622aff 100644 --- a/stem/control.py +++ b/stem/control.py @@ -1048,6 +1048,7 @@ class Controller(BaseController): self._event_listeners_lock = threading.RLock() self._enabled_features = [] self._is_geoip_unavailable = None + self._last_fingerprint_exc = None
super(Controller, self).__init__(control_socket, is_authenticated)
@@ -1144,6 +1145,8 @@ class Controller(BaseController): for param in params: if param.startswith('ip-to-country/') and param != 'ip-to-country/0.0.0.0' and self.is_geoip_unavailable(): raise stem.ProtocolError('Tor geoip database is unavailable') + elif param == 'fingerprint' and self._last_fingerprint_exc and self.get_conf('ORPort', None) is None: + raise self._last_fingerprint_exc # we already know we're not a relay
# check for cached results
@@ -1189,6 +1192,9 @@ class Controller(BaseController):
self._set_cache(to_cache, 'getinfo')
+ if 'fingerprint' in params: + self._last_fingerprint_exc = None + log.debug('GETINFO %s (runtime: %0.4f)' % (' '.join(params), time.time() - start_time))
if is_multiple: @@ -1196,6 +1202,9 @@ class Controller(BaseController): else: return list(reply.values())[0] except stem.ControllerError as exc: + if 'fingerprint' in params: + self._last_fingerprint_exc = exc + log.debug('GETINFO %s (failed: %s)' % (' '.join(params), exc)) raise
diff --git a/test/unit/control/controller.py b/test/unit/control/controller.py index 8b5d6c8f..c7e412c5 100644 --- a/test/unit/control/controller.py +++ b/test/unit/control/controller.py @@ -45,6 +45,33 @@ class TestControl(unittest.TestCase): for event in stem.control.EventType: self.assertTrue(stem.control.event_description(event) is not None)
+ @patch('stem.control.Controller.msg') + def test_get_get_info(self, msg_mock): + msg_mock.return_value = ControlMessage.from_str('250-hello=hi right back!\r\n250 OK\r\n', 'GETINFO') + self.assertEqual('hi right back!', self.controller.get_info('hello')) + + @patch('stem.control.Controller.msg') + @patch('stem.control.Controller.get_conf') + def test_get_get_info_without_fingerprint(self, get_conf_mock, msg_mock): + msg_mock.return_value = ControlMessage.from_str('551 Not running in server mode\r\n') + get_conf_mock.return_value = None + + self.assertEqual(None, self.controller._last_fingerprint_exc) + self.assertRaisesRegexp(stem.ProtocolError, 'Not running in server mode', self.controller.get_info, 'fingerprint') + self.assertEqual("GETINFO response didn't have an OK status:\nNot running in server mode", str(self.controller._last_fingerprint_exc)) + self.assertEqual(1, msg_mock.call_count) + + # now that we have a cached failure we should provide that back + + self.assertRaisesRegexp(stem.ProtocolError, 'Not running in server mode', self.controller.get_info, 'fingerprint') + self.assertEqual(1, msg_mock.call_count) + + # ... but if we become a relay we'll call it again + + get_conf_mock.return_value = '443' + self.assertRaisesRegexp(stem.ProtocolError, 'Not running in server mode', self.controller.get_info, 'fingerprint') + self.assertEqual(2, msg_mock.call_count) + @patch('stem.control.Controller.get_info') def test_get_version(self, get_info_mock): """