[tor-commits] [stem/master] Function to query PROTOCOLINFO via a control port

atagar at torproject.org atagar at torproject.org
Mon Nov 21 18:15:07 UTC 2011


commit 7f89ff58d2ad6ecbde7f8225e7b2c02ec400be1b
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Nov 21 08:36:20 2011 -0800

    Function to query PROTOCOLINFO via a control port
    
    Implementation and integ testing to query a PROTOCOLINFO response via a control
    port. Next is to do the same for control sockets.
---
 run_tests.py                          |    2 +-
 stem/connection.py                    |   74 ++++++++++++++++++++++++++++++++-
 test/integ/connection/protocolinfo.py |   63 ++++++++++++++++++++--------
 3 files changed, 120 insertions(+), 19 deletions(-)

diff --git a/run_tests.py b/run_tests.py
index 2d12a57..76e1abb 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -41,7 +41,7 @@ UNIT_TESTS = (("stem.types.ControlMessage", test.unit.types.control_message.Test
              )
 
 INTEG_TESTS = (("stem.types.ControlMessage", test.integ.types.control_message.TestControlMessage),
-              ("stem.connection.ProtocolInfoResponse", test.integ.connection.protocolinfo.TestProtocolInfoResponse),
+              ("stem.connection.ProtocolInfoResponse", test.integ.connection.protocolinfo.TestProtocolInfo),
                ("stem.util.conf", test.integ.util.conf.TestConf),
                ("stem.util.system", test.integ.util.system.TestSystem),
               )
diff --git a/stem/connection.py b/stem/connection.py
index 3165fba..3e97e9e 100644
--- a/stem/connection.py
+++ b/stem/connection.py
@@ -28,6 +28,78 @@ LOGGER = logging.getLogger("stem")
 
 AuthMethod = stem.util.enum.Enum("NONE", "PASSWORD", "COOKIE", "UNKNOWN")
 
+def get_protocolinfo_port(control_addr = "127.0.0.1", control_port = 9051, keep_alive = False):
+  """
+  Issues a PROTOCOLINFO query to a control port, getting information about the
+  tor process running on it.
+  
+  Arguments:
+    control_addr (str) - ip address of the controller
+    control_port (int) - port number of the controller
+    keep_alive (bool)  - keeps the socket used to issue the PROTOCOLINFO query
+                         open if True, closes otherwise
+  
+  Returns:
+    ProtocolInfoResponse with the response given by the tor process
+  
+  Raises:
+    stem.types.ProtocolError if the PROTOCOLINFO response is malformed
+    stem.types.SocketError if problems arise in establishing or using the
+      socket
+  """
+  
+  control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+  control_socket_file = control_socket.makefile()
+  protocolinfo_response, raised_exc = None, None
+  
+  try:
+    # initiates connection
+    control_socket.connect((control_addr, control_port))
+    
+    # issues the PROTOCOLINFO query
+    control_socket_file.write("PROTOCOLINFO 1\r\n")
+    control_socket_file.flush()
+    
+    protocolinfo_response = stem.types.read_message(control_socket_file)
+    ProtocolInfoResponse.convert(protocolinfo_response)
+  except socket.error, exc:
+    raised_exc = stem.types.SocketError(exc)
+  except (stem.types.ProtocolError, stem.types.SocketError), exc:
+    raised_exc = exc
+  
+  control_socket_file.close() # done with the linked file
+  
+  if not keep_alive or raised_exc:
+    # shut down the socket we were using
+    try: control_socket.shutdown(socket.SHUT_RDWR)
+    except socket.error: pass
+    
+    control_socket.close()
+  else:
+    # if we're keeping the socket open then attach it to the response
+    protocolinfo_response.socket = control_socket
+  
+  if raised_exc:
+    raise raised_exc
+  else:
+    # if we have an unresolved cookie path then try to expand it again, using
+    # our port to infer a pid
+    
+    cookie_path = protocolinfo_response.cookie_file
+    if cookie_path and control_addr == "127.0.0.1" and stem.util.system.is_relative_path(cookie_path):
+      try:
+        tor_pid = stem.util.system.get_pid_by_port(control_port)
+        if not tor_pid: raise IOError("pid lookup failed")
+        
+        tor_cwd = stem.util.system.get_cwd(tor_pid)
+        if not tor_cwd: raise IOError("cwd lookup failed")
+        
+        protocolinfo_response.cookie_file = stem.util.system.expand_path(cookie_path, tor_cwd)
+      except IOError, exc:
+        LOGGER.debug("unable to expand relative tor cookie path by port: %s" % exc)
+    
+    return protocolinfo_response
+
 class ProtocolInfoResponse(stem.types.ControlMessage):
   """
   Version one PROTOCOLINFO query response.
@@ -166,7 +238,7 @@ class ProtocolInfoResponse(stem.types.ControlMessage):
               
               self.cookie_file = stem.util.system.expand_path(self.cookie_file, tor_cwd)
             except IOError, exc:
-              LOGGER.debug("unable to expand relative tor cookie path: %s" % exc)
+              LOGGER.debug("unable to expand relative tor cookie path by name: %s" % exc)
       elif line_type == "VERSION":
         # Line format:
         #   VersionLine = "250-VERSION" SP "Tor=" TorVersion OptArguments CRLF
diff --git a/test/integ/connection/protocolinfo.py b/test/integ/connection/protocolinfo.py
index 27b5f1f..73a7884 100644
--- a/test/integ/connection/protocolinfo.py
+++ b/test/integ/connection/protocolinfo.py
@@ -1,20 +1,21 @@
 """
-Integration tests for the stem.connections.ProtocolInfoResponse class.
+Integration tests for the stem.connections.ProtocolInfoResponse class and
+related functions.
 """
 
-import socket
 import unittest
 
 import test.runner
 import stem.types
 import stem.connection
 
-class TestProtocolInfoResponse(unittest.TestCase):
+class TestProtocolInfo(unittest.TestCase):
   """
-  Processes a ProtocolInfo query for a variety of setups.
+  Queries and parses PROTOCOLINFO. This should be run with the 'CONNECTION'
+  integ target to exercise the widest range of use cases.
   """
   
-  def testProtocolInfoResponse(self):
+  def test_parsing(self):
     """
     Makes a PROTOCOLINFO query and processes the response for our control
     connection.
@@ -29,7 +30,7 @@ class TestProtocolInfoResponse(unittest.TestCase):
     control_socket = runner.get_tor_socket(False)
     control_socket_file = control_socket.makefile()
     
-    control_socket_file.write("PROTOCOLINFO\r\n")
+    control_socket_file.write("PROTOCOLINFO 1\r\n")
     control_socket_file.flush()
     
     protocolinfo_response = stem.types.read_message(control_socket_file)
@@ -42,24 +43,52 @@ class TestProtocolInfoResponse(unittest.TestCase):
     self.assertNotEqual(None, protocolinfo_response.tor_version)
     self.assertNotEqual(None, protocolinfo_response.auth_methods)
     
-    self.assertEqual((), protocolinfo_response.unknown_auth_methods)
     self.assertEqual(None, protocolinfo_response.socket)
+    self.assert_protocolinfo_attr(protocolinfo_response, connection_type)
+  
+  def test_get_protocolinfo_port(self):
+    """
+    Exercises the stem.connection.get_protocolinfo_port function.
+    """
+    
+    connection_type = test.runner.get_runner().get_connection_type()
+    
+    if test.runner.OPT_PORT in test.runner.CONNECTION_OPTS[connection_type]:
+      protocolinfo_response = stem.connection.get_protocolinfo_port(control_port = test.runner.CONTROL_PORT)
+      self.assertEqual(None, protocolinfo_response.socket)
+      self.assert_protocolinfo_attr(protocolinfo_response, connection_type)
+    else:
+      # we don't have a control port
+      self.assertRaises(stem.types.SocketError, stem.connection.get_protocolinfo_port, "127.0.0.1", test.runner.CONTROL_PORT)
+  
+  def assert_protocolinfo_attr(self, protocolinfo_response, connection_type):
+    """
+    Makes assertions that the protocolinfo response's attributes match those of
+    a given connection type.
+    """
+    
+    # This should never have test.runner.TorConnection.NONE. If we somehow got
+    # a protocolinfo_response from that config then we have an issue. :)
     
     if connection_type == test.runner.TorConnection.NO_AUTH:
-      self.assertEqual((stem.connection.AuthMethod.NONE,), protocolinfo_response.auth_methods)
-      self.assertEqual(None, protocolinfo_response.cookie_file)
+      auth_methods = (stem.connection.AuthMethod.NONE,)
     elif connection_type == test.runner.TorConnection.PASSWORD:
-      self.assertEqual((stem.connection.AuthMethod.PASSWORD,), protocolinfo_response.auth_methods)
-      self.assertEqual(None, protocolinfo_response.cookie_file)
+      auth_methods = (stem.connection.AuthMethod.PASSWORD,)
     elif connection_type == test.runner.TorConnection.COOKIE:
-      self.assertEqual((stem.connection.AuthMethod.COOKIE,), protocolinfo_response.auth_methods)
-      self.assertEqual(runner.get_auth_cookie_path(), protocolinfo_response.cookie_file)
+      auth_methods = (stem.connection.AuthMethod.COOKIE,)
     elif connection_type == test.runner.TorConnection.MULTIPLE:
-      self.assertEqual((stem.connection.AuthMethod.COOKIE, stem.connection.AuthMethod.PASSWORD), protocolinfo_response.auth_methods)
-      self.assertEqual(runner.get_auth_cookie_path(), protocolinfo_response.cookie_file)
+      auth_methods = (stem.connection.AuthMethod.COOKIE, stem.connection.AuthMethod.PASSWORD)
     elif connection_type == test.runner.TorConnection.SOCKET:
-      self.assertEqual((stem.connection.AuthMethod.NONE,), protocolinfo_response.auth_methods)
-      self.assertEqual(None, protocolinfo_response.cookie_file)
+      auth_methods = (stem.connection.AuthMethod.NONE,)
     else:
       self.fail("Unrecognized connection type: %s" % connection_type)
+    
+    self.assertEqual((), protocolinfo_response.unknown_auth_methods)
+    self.assertEqual(auth_methods, protocolinfo_response.auth_methods)
+    
+    if test.runner.OPT_COOKIE in test.runner.CONNECTION_OPTS[connection_type]:
+      auth_cookie_path = test.runner.get_runner().get_auth_cookie_path()
+      self.assertEqual(auth_cookie_path, protocolinfo_response.cookie_file)
+    else:
+      self.assertEqual(None, protocolinfo_response.cookie_file)
 





More information about the tor-commits mailing list