[tor-commits] [stem/master] Controller methods for querying descriptor info
atagar at torproject.org
atagar at torproject.org
Tue Oct 16 16:06:33 UTC 2012
commit 79612002788e1f9f80733974b17eefa976055d06
Author: Damian Johnson <atagar at torproject.org>
Date: Mon Oct 15 19:12:15 2012 -0700
Controller methods for querying descriptor info
Adding a get_server_descriptor() and get_network_status() method for querying
server descriptors and router status entries.
---
stem/control.py | 54 ++++++++++++++++++++++++
stem/descriptor/server_descriptor.py | 12 +++++
stem/util/tor_tools.py | 7 +++-
test/integ/control/controller.py | 77 ++++++++++++++++++++++++++++++++++
4 files changed, 149 insertions(+), 1 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index a5a7868..6077564 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -32,6 +32,8 @@ interacting at a higher level.
|- repurpose_circuit - change a circuit's purpose
|- map_address - maps one address to another such that connections to the original are replaced with the other
|- get_version - convenience method to get tor version
+ |- get_server_descriptor - querying the server descriptor for a relay
+ |- get_network_status - querying the router status entry for a relay
|- authenticate - convenience method to authenticate the controller
+- protocolinfo - convenience method to get the protocol info
@@ -55,6 +57,8 @@ import threading
import stem.response
import stem.socket
import stem.version
+import stem.descriptor.router_status_entry
+import stem.descriptor.server_descriptor
import stem.util.connection
import stem.util.log as log
@@ -650,6 +654,56 @@ class Controller(BaseController):
return self._request_cache["version"]
+ def get_server_descriptor(self, relay):
+ """
+ Provides the server descriptor for the relay with the given fingerprint or
+ nickname. If the relay identifier could be either a fingerprint *or*
+ nickname then it's queried as a fingerprint.
+
+ :param str relay: fingerprint or nickname of the relay to be queried
+
+ :returns: :class:`stem.descriptor.server_descriptor.RelayDescriptor` for the given relay
+
+ :raises:
+ * :class:`stem.socket.ControllerError` if unable to query the descriptor
+ * ValueError if **relay** doesn't conform with the patter for being a fingerprint or nickname
+ """
+
+ if stem.util.tor_tools.is_valid_fingerprint(relay):
+ query = "desc/id/%s" % relay
+ elif stem.util.tor_tools.is_valid_nickname(relay):
+ query = "desc/name/%s" % relay
+ else:
+ raise ValueError("'%s' isn't a valid fingerprint or nickname" % relay)
+
+ desc_content = self.get_info(query)
+ return stem.descriptor.server_descriptor.RelayDescriptor(desc_content)
+
+ def get_network_status(self, relay):
+ """
+ Provides the router status entry for the relay with the given fingerprint
+ or nickname. If the relay identifier could be either a fingerprint *or*
+ nickname then it's queried as a fingerprint.
+
+ :param str relay: fingerprint or nickname of the relay to be queried
+
+ :returns: :class:`stem.descriptor.router_status_entry.RouterStatusEntryV2` for the given relay
+
+ :raises:
+ * :class:`stem.socket.ControllerError` if unable to query the descriptor
+ * ValueError if **relay** doesn't conform with the patter for being a fingerprint or nickname
+ """
+
+ if stem.util.tor_tools.is_valid_fingerprint(relay):
+ query = "ns/id/%s" % relay
+ elif stem.util.tor_tools.is_valid_nickname(relay):
+ query = "ns/name/%s" % relay
+ else:
+ raise ValueError("'%s' isn't a valid fingerprint or nickname" % relay)
+
+ desc_content = self.get_info(query)
+ return stem.descriptor.router_status_entry.RouterStatusEntryV2(desc_content)
+
def authenticate(self, *args, **kwargs):
"""
A convenience method to authenticate the controller.
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 5e5e04a..b597de9 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -631,6 +631,12 @@ class RelayDescriptor(ServerDescriptor):
del entries["router-signature"]
ServerDescriptor._parse(self, entries, validate)
+
+ def __cmp__(self, other):
+ if not isinstance(other, RelayDescriptor):
+ return 1
+
+ return str(self).strip() > str(other).strip()
class BridgeDescriptor(ServerDescriptor):
"""
@@ -762,4 +768,10 @@ class BridgeDescriptor(ServerDescriptor):
def _last_keyword(self):
return None
+
+ def __cmp__(self, other):
+ if not isinstance(other, BridgeDescriptor):
+ return 1
+
+ return str(self).strip() > str(other).strip()
diff --git a/stem/util/tor_tools.py b/stem/util/tor_tools.py
index 7a980c7..a651445 100644
--- a/stem/util/tor_tools.py
+++ b/stem/util/tor_tools.py
@@ -29,7 +29,9 @@ def is_valid_fingerprint(entry, check_prefix = False):
:returns: True if the string could be a relay fingerprint, False otherwise.
"""
- if check_prefix:
+ if not isinstance(entry, str):
+ return False
+ elif check_prefix:
if not entry or entry[0] != "$": return False
entry = entry[1:]
@@ -44,6 +46,9 @@ def is_valid_nickname(entry):
:returns: True if the string could be a nickname, False otherwise.
"""
+ if not isinstance(entry, str):
+ return False
+
return bool(NICKNAME_PATTERN.match(entry))
def is_hex_digits(entry, count):
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index bd17703..1482c23 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -4,6 +4,7 @@ Integration tests for the stem.control.Controller class.
from __future__ import with_statement
+import os
import re
import shutil
import socket
@@ -16,6 +17,8 @@ import stem.version
import stem.response.protocolinfo
import test.runner
import test.util
+import stem.descriptor.router_status_entry
+import stem.descriptor.reader
class TestController(unittest.TestCase):
def test_from_port(self):
@@ -424,4 +427,78 @@ class TestController(unittest.TestCase):
ip_addr = response[response.find("\r\n\r\n"):].strip()
self.assertTrue(stem.util.connection.is_valid_ip_address(ip_addr))
+
+ def test_get_server_descriptor(self):
+ """
+ Compares get_server_descriptor() against our cached descriptors.
+ """
+
+ runner = test.runner.get_runner()
+ descriptor_path = runner.get_test_dir("cached-descriptors")
+
+ if test.runner.require_control(self): return
+ elif not os.path.exists(descriptor_path):
+ test.runner.skip(self, "(no cached descriptors)")
+ return
+
+ with runner.get_tor_controller() as controller:
+ # we should balk at invalid content
+ self.assertRaises(ValueError, controller.get_server_descriptor, None)
+ self.assertRaises(ValueError, controller.get_server_descriptor, "")
+ self.assertRaises(ValueError, controller.get_server_descriptor, 5)
+ self.assertRaises(ValueError, controller.get_server_descriptor, "z" * 30)
+
+ # try with a relay that doesn't exist
+ self.assertRaises(stem.socket.ControllerError, controller.get_server_descriptor, "blargg")
+ self.assertRaises(stem.socket.ControllerError, controller.get_server_descriptor, "5" * 40)
+
+ first_descriptor = None
+ with stem.descriptor.reader.DescriptorReader([descriptor_path]) as reader:
+ for desc in reader:
+ if desc.nickname != "Unnamed":
+ first_descriptor = desc
+ break
+
+ self.assertEqual(first_descriptor, controller.get_server_descriptor(first_descriptor.fingerprint))
+ self.assertEqual(first_descriptor, controller.get_server_descriptor(first_descriptor.nickname))
+
+ def test_get_network_status(self):
+ """
+ Compares get_network_status() against our cached descriptors.
+ """
+
+ runner = test.runner.get_runner()
+ descriptor_path = runner.get_test_dir("cached-consensus")
+
+ if test.runner.require_control(self): return
+ elif not os.path.exists(descriptor_path):
+ test.runner.skip(self, "(no cached descriptors)")
+ return
+
+ with runner.get_tor_controller() as controller:
+ # we should balk at invalid content
+ self.assertRaises(ValueError, controller.get_network_status, None)
+ self.assertRaises(ValueError, controller.get_network_status, "")
+ self.assertRaises(ValueError, controller.get_network_status, 5)
+ self.assertRaises(ValueError, controller.get_network_status, "z" * 30)
+
+ # try with a relay that doesn't exist
+ self.assertRaises(stem.socket.ControllerError, controller.get_network_status, "blargg")
+ self.assertRaises(stem.socket.ControllerError, controller.get_network_status, "5" * 40)
+
+ # our cached consensus is v3 but the control port can only be queried for
+ # v2 or v1 network status information
+
+ first_descriptor = None
+ with stem.descriptor.reader.DescriptorReader([descriptor_path]) as reader:
+ for desc in reader:
+ if desc.nickname != "Unnamed":
+ # truncate to just the first couple lines and reconstruct as a v2 entry
+ truncated_content = "\n".join(str(desc).split("\n")[:2])
+
+ first_descriptor = stem.descriptor.router_status_entry.RouterStatusEntryV2(truncated_content)
+ break
+
+ self.assertEqual(first_descriptor, controller.get_network_status(first_descriptor.fingerprint))
+ self.assertEqual(first_descriptor, controller.get_network_status(first_descriptor.nickname))
More information about the tor-commits
mailing list