commit b730a0b57d13a7fe12a7b956ac5fc06fabcddf73
Author: Damian Johnson <atagar(a)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):
"""