[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Supporting controllers in the connection module
commit af691f5b54d1f571ee51d8c67010ea19e4c672fd
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Sat May 5 17:48:29 2012 -0700
Supporting controllers in the connection module
Users will be using a BaseController subclass unless they really need to work
at a low level, in which case they'll be using a ControlSocket. Making the
connection module (which does authentication) support both.
---
stem/connection.py | 86 +++++++++++++++++++------------
test/integ/connection/authentication.py | 26 +++++++---
test/integ/connection/connect.py | 4 +-
test/runner.py | 38 +++++++++++---
4 files changed, 103 insertions(+), 51 deletions(-)
diff --git a/stem/connection.py b/stem/connection.py
index d950049..825edf5 100644
--- a/stem/connection.py
+++ b/stem/connection.py
@@ -277,7 +277,7 @@ def _connect(control_socket, password, chroot_path, controller):
print "Unable to authenticate: %s" % exc
return None
-def authenticate(control_socket, password = None, chroot_path = None, protocolinfo_response = None):
+def authenticate(controller, password = None, chroot_path = None, protocolinfo_response = None):
"""
Authenticates to a control socket using the information provided by a
PROTOCOLINFO response. In practice this will often be all we need to
@@ -288,7 +288,8 @@ def authenticate(control_socket, password = None, chroot_path = None, protocolin
about, then have a AuthenticationFailure catch-all at the end.
Arguments:
- control_socket (stem.socket.ControlSocket) - socket to be authenticated
+ controller (stem.socket.ControlSocket or stem.control.BaseController) -
+ tor controller connection to be authenticated
password (str) - passphrase to present to the socket if it uses password
authentication (skips password auth if None)
chroot_path (str) - path prefix if in a chroot environment
@@ -302,8 +303,8 @@ def authenticate(control_socket, password = None, chroot_path = None, protocolin
follows...
stem.connection.IncorrectSocketType
- The control_socket does not speak the tor control protocol. Most often
- this happened because the user confused the SocksPort or ORPort with the
+ The controller does not speak the tor control protocol. Most often this
+ happened because the user confused the SocksPort or ORPort with the
ControlPort.
stem.connection.UnrecognizedAuthMethods
@@ -355,7 +356,7 @@ def authenticate(control_socket, password = None, chroot_path = None, protocolin
if not protocolinfo_response:
try:
- protocolinfo_response = get_protocolinfo(control_socket)
+ protocolinfo_response = get_protocolinfo(controller)
except stem.socket.ProtocolError:
raise IncorrectSocketType("unable to use the control socket")
except stem.socket.SocketError, exc:
@@ -397,16 +398,16 @@ def authenticate(control_socket, password = None, chroot_path = None, protocolin
try:
if auth_type == AuthMethod.NONE:
- authenticate_none(control_socket, False)
+ authenticate_none(controller, False)
elif auth_type == AuthMethod.PASSWORD:
- authenticate_password(control_socket, password, False)
+ authenticate_password(controller, password, False)
elif auth_type == AuthMethod.COOKIE:
cookie_path = protocolinfo_response.cookie_path
if chroot_path:
cookie_path = os.path.join(chroot_path, cookie_path.lstrip(os.path.sep))
- authenticate_cookie(control_socket, cookie_path, False)
+ authenticate_cookie(controller, cookie_path, False)
return # success!
except OpenAuthRejected, exc:
@@ -439,7 +440,7 @@ def authenticate(control_socket, password = None, chroot_path = None, protocolin
raise AssertionError("BUG: Authentication failed without providing a recognized exception: %s" % str(auth_exceptions))
-def authenticate_none(control_socket, suppress_ctl_errors = True):
+def authenticate_none(controller, suppress_ctl_errors = True):
"""
Authenticates to an open control socket. All control connections need to
authenticate before they can be used, even if tor hasn't been configured to
@@ -452,7 +453,8 @@ def authenticate_none(control_socket, suppress_ctl_errors = True):
For general usage use the authenticate() function instead.
Arguments:
- control_socket (stem.socket.ControlSocket) - socket to be authenticated
+ controller (stem.socket.ControlSocket or stem.control.BaseController) -
+ tor controller connection
suppress_ctl_errors (bool) - reports raised stem.socket.ControllerError as
authentication rejection if True, otherwise they're re-raised
@@ -462,23 +464,22 @@ def authenticate_none(control_socket, suppress_ctl_errors = True):
"""
try:
- control_socket.send("AUTHENTICATE")
- auth_response = control_socket.recv()
+ auth_response = _msg(controller, "AUTHENTICATE")
# if we got anything but an OK response then error
if str(auth_response) != "OK":
- try: control_socket.connect()
+ try: controller.connect()
except: pass
raise OpenAuthRejected(str(auth_response), auth_response)
except stem.socket.ControllerError, exc:
- try: control_socket.connect()
+ try: controller.connect()
except: pass
if not suppress_ctl_errors: raise exc
else: raise OpenAuthRejected("Socket failed (%s)" % exc)
-def authenticate_password(control_socket, password, suppress_ctl_errors = True):
+def authenticate_password(controller, password, suppress_ctl_errors = True):
"""
Authenticates to a control socket that uses a password (via the
HashedControlPassword torrc option). Quotes in the password are escaped.
@@ -495,7 +496,8 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
future versions.
Arguments:
- control_socket (stem.socket.ControlSocket) - socket to be authenticated
+ controller (stem.socket.ControlSocket or stem.control.BaseController) -
+ tor controller connection
password (str) - passphrase to present to the socket
suppress_ctl_errors (bool) - reports raised stem.socket.ControllerError as
authentication rejection if True, otherwise they're re-raised
@@ -514,12 +516,11 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
password = password.replace('"', '\\"')
try:
- control_socket.send("AUTHENTICATE \"%s\"" % password)
- auth_response = control_socket.recv()
+ auth_response = _msg(controller, "AUTHENTICATE \"%s\"" % password)
# if we got anything but an OK response then error
if str(auth_response) != "OK":
- try: control_socket.connect()
+ try: controller.connect()
except: pass
# all we have to go on is the error message from tor...
@@ -531,13 +532,13 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
else:
raise PasswordAuthRejected(str(auth_response), auth_response)
except stem.socket.ControllerError, exc:
- try: control_socket.connect()
+ try: controller.connect()
except: pass
if not suppress_ctl_errors: raise exc
else: raise PasswordAuthRejected("Socket failed (%s)" % exc)
-def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True):
+def authenticate_cookie(controller, cookie_path, suppress_ctl_errors = True):
"""
Authenticates to a control socket that uses the contents of an authentication
cookie (generated via the CookieAuthentication torrc option). This does basic
@@ -559,7 +560,8 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
future versions.
Arguments:
- control_socket (stem.socket.ControlSocket) - socket to be authenticated
+ controller (stem.socket.ControlSocket or stem.control.BaseController) -
+ tor controller connection
cookie_path (str) - path of the authentication cookie to send to tor
suppress_ctl_errors (bool) - reports raised stem.socket.ControllerError as
authentication rejection if True, otherwise they're re-raised
@@ -599,12 +601,12 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
raise UnreadableCookieFile("Authentication failed: unable to read '%s' (%s)" % (cookie_path, exc), cookie_path)
try:
- control_socket.send("AUTHENTICATE %s" % binascii.b2a_hex(auth_cookie_contents))
- auth_response = control_socket.recv()
+ msg = "AUTHENTICATE %s" % binascii.b2a_hex(auth_cookie_contents)
+ auth_response = _msg(controller, msg)
# if we got anything but an OK response then error
if str(auth_response) != "OK":
- try: control_socket.connect()
+ try: controller.connect()
except: pass
# all we have to go on is the error message from tor...
@@ -617,20 +619,21 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
else:
raise CookieAuthRejected(str(auth_response), cookie_path, auth_response)
except stem.socket.ControllerError, exc:
- try: control_socket.connect()
+ try: controller.connect()
except: pass
if not suppress_ctl_errors: raise exc
else: raise CookieAuthRejected("Socket failed (%s)" % exc, cookie_path)
-def get_protocolinfo(control_socket):
+def get_protocolinfo(controller):
"""
Issues a PROTOCOLINFO query to a control socket, getting information about
the tor process running on it. If the socket is already closed then it is
first reconnected.
Arguments:
- control_socket (stem.socket.ControlSocket) - connected tor control socket
+ controller (stem.socket.ControlSocket or stem.control.BaseController) -
+ tor controller connection
Returns:
stem.connection.ProtocolInfoResponse provided by tor
@@ -642,8 +645,7 @@ def get_protocolinfo(control_socket):
"""
try:
- control_socket.send("PROTOCOLINFO 1")
- protocolinfo_response = control_socket.recv()
+ protocolinfo_response = _msg(controller, "PROTOCOLINFO 1")
except:
protocolinfo_response = None
@@ -651,17 +653,22 @@ def get_protocolinfo(control_socket):
# next followed by authentication. Transparently reconnect if that happens.
if not protocolinfo_response or str(protocolinfo_response) == "Authentication required.":
- control_socket.connect()
+ controller.connect()
try:
- control_socket.send("PROTOCOLINFO 1")
- protocolinfo_response = control_socket.recv()
+ protocolinfo_response = _msg(controller, "PROTOCOLINFO 1")
except stem.socket.SocketClosed, exc:
raise stem.socket.SocketError(exc)
ProtocolInfoResponse.convert(protocolinfo_response)
- # attempt ot expand relative cookie paths via the control port or socket file
+ # attempt to expand relative cookie paths via the control port or socket file
+
+ if isinstance(controller, stem.socket.ControlSocket):
+ control_socket = controller
+ else:
+ control_socket = controller.get_socket()
+
if isinstance(control_socket, stem.socket.ControlPort):
if control_socket.get_address() == "127.0.0.1":
pid_method = stem.util.system.get_pid_by_port
@@ -672,6 +679,17 @@ def get_protocolinfo(control_socket):
return protocolinfo_response
+def _msg(controller, message):
+ """
+ Sends and receives a message with either a ControlSocket or BaseController.
+ """
+
+ if isinstance(controller, stem.socket.ControlSocket):
+ controller.send(message)
+ return controller.recv()
+ else:
+ return controller.msg(message)
+
def _expand_cookie_path(protocolinfo_response, pid_resolver, pid_resolution_arg):
"""
Attempts to expand a relative cookie path with the given pid resolver. This
diff --git a/test/integ/connection/authentication.py b/test/integ/connection/authentication.py
index f80b848..c209d2b 100644
--- a/test/integ/connection/authentication.py
+++ b/test/integ/connection/authentication.py
@@ -80,7 +80,7 @@ class TestAuthenticate(unittest.TestCase):
def setUp(self):
test.runner.require_control(self)
- def test_authenticate_general(self):
+ def test_authenticate_general_socket(self):
"""
Tests that the authenticate function can authenticate to our socket.
"""
@@ -88,7 +88,17 @@ class TestAuthenticate(unittest.TestCase):
runner = test.runner.get_runner()
with runner.get_tor_socket(False) as control_socket:
stem.connection.authenticate(control_socket, test.runner.CONTROL_PASSWORD, runner.get_chroot())
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
+
+ def test_authenticate_general_controller(self):
+ """
+ Tests that the authenticate function can authenticate via a Controller.
+ """
+
+ runner = test.runner.get_runner()
+ with runner.get_tor_controller(False) as controller:
+ stem.connection.authenticate(controller, test.runner.CONTROL_PASSWORD, runner.get_chroot())
+ test.runner.exercise_controller(self, controller)
def test_authenticate_general_example(self):
"""
@@ -108,7 +118,7 @@ class TestAuthenticate(unittest.TestCase):
try:
# this authenticate call should work for everything but password-only auth
stem.connection.authenticate(control_socket, chroot_path = runner.get_chroot())
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
except stem.connection.IncorrectSocketType:
self.fail()
except stem.connection.MissingPassword:
@@ -117,7 +127,7 @@ class TestAuthenticate(unittest.TestCase):
try:
stem.connection.authenticate_password(control_socket, controller_password)
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
except stem.connection.PasswordAuthFailed:
self.fail()
except stem.connection.AuthenticationFailure:
@@ -144,7 +154,7 @@ class TestAuthenticate(unittest.TestCase):
self.assertRaises(stem.connection.MissingPassword, stem.connection.authenticate, control_socket)
else:
stem.connection.authenticate(control_socket, chroot_path = runner.get_chroot())
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
# tests with the incorrect password
with runner.get_tor_socket(False) as control_socket:
@@ -152,12 +162,12 @@ class TestAuthenticate(unittest.TestCase):
self.assertRaises(stem.connection.IncorrectPassword, stem.connection.authenticate, control_socket, "blarg")
else:
stem.connection.authenticate(control_socket, "blarg", runner.get_chroot())
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
# tests with the right password
with runner.get_tor_socket(False) as control_socket:
stem.connection.authenticate(control_socket, test.runner.CONTROL_PASSWORD, runner.get_chroot())
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
def test_authenticate_none(self):
"""
@@ -299,7 +309,7 @@ class TestAuthenticate(unittest.TestCase):
elif auth_type == stem.connection.AuthMethod.COOKIE:
stem.connection.authenticate_cookie(control_socket, auth_arg)
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
except stem.connection.AuthenticationFailure, exc:
# authentication functions should re-attach on failure
self.assertTrue(control_socket.is_alive())
diff --git a/test/integ/connection/connect.py b/test/integ/connection/connect.py
index 29cf2dc..ac66ceb 100644
--- a/test/integ/connection/connect.py
+++ b/test/integ/connection/connect.py
@@ -34,7 +34,7 @@ class TestConnect(unittest.TestCase):
controller = stem.connection.Controller.NONE)
if test.runner.Torrc.PORT in runner.get_options():
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
control_socket.close()
else:
self.assertEquals(control_socket, None)
@@ -53,7 +53,7 @@ class TestConnect(unittest.TestCase):
controller = stem.connection.Controller.NONE)
if test.runner.Torrc.SOCKET in runner.get_options():
- test.runner.exercise_socket(self, control_socket)
+ test.runner.exercise_controller(self, control_socket)
control_socket.close()
else:
self.assertEquals(control_socket, None)
diff --git a/test/runner.py b/test/runner.py
index 6702b8b..66636f2 100644
--- a/test/runner.py
+++ b/test/runner.py
@@ -8,7 +8,7 @@ TorInaccessable - Tor can't be queried for the information
require_control - skips the test unless tor provides a controller endpoint
require_version - skips the test unless we meet a tor version requirement
-exercise_socket - Does a basic sanity check that a control socket can be used
+exercise_controller - basic sanity check that a controller connection can be used
get_runner - Singleton for fetching our runtime context.
Runner - Runtime context for our integration tests.
@@ -25,7 +25,8 @@ Runner - Runtime context for our integration tests.
|- get_tor_cwd - current working directory of our tor process
|- get_chroot - provides the path of our emulated chroot if we have one
|- get_pid - process id of our tor process
- |- get_tor_socket - provides a socket to the tor instance
+ |- get_tor_socket - provides a socket to our test instance
+ |- get_tor_controller - provides a controller for our test instance
|- get_tor_version - provides the version of tor we're running against
+- get_tor_command - provides the command used to start tor
"""
@@ -115,14 +116,15 @@ def require_version(test_case, req_version):
if get_runner().get_tor_version() < req_version:
test_case.skipTest("(requires %s)" % req_version)
-def exercise_socket(test_case, control_socket):
+def exercise_controller(test_case, controller):
"""
Checks that we can now use the socket by issuing a 'GETINFO config-file'
query.
Arguments:
test_case (unittest.TestCase) - test being ran
- control_socket (stem.socket.ControlSocket) - socket to be tested
+ controller (stem.socket.ControlSocket or stem.control.BaseController) -
+ tor controller connection to be authenticated
"""
runner = get_runner()
@@ -131,8 +133,12 @@ def exercise_socket(test_case, control_socket):
if chroot_path and torrc_path.startswith(chroot_path):
torrc_path = torrc_path[len(chroot_path):]
- control_socket.send("GETINFO config-file")
- config_file_response = control_socket.recv()
+ if isinstance(controller, stem.socket.ControlSocket):
+ controller.send("GETINFO config-file")
+ config_file_response = controller.recv()
+ else:
+ config_file_response = controller.msg("GETINFO config-file")
+
test_case.assertEquals("config-file=%s\nOK" % torrc_path, str(config_file_response))
def get_runner():
@@ -429,7 +435,7 @@ class Runner:
def get_tor_socket(self, authenticate = True):
"""
- Provides a socket connected to the tor test instance's control socket.
+ Provides a socket connected to our tor test instance.
Arguments:
authenticate (bool) - if True then the socket is authenticated
@@ -452,6 +458,24 @@ class Runner:
return control_socket
+ def get_tor_controller(self, authenticate = True):
+ """
+ Provides a controller connected to our tor test instance.
+
+ Arguments:
+ authenticate (bool) - if True then the socket is authenticated
+
+ Returns:
+ stem.socket.BaseController connected with our testing instance
+
+ Raises:
+ TorInaccessable if tor can't be connected to
+ """
+
+ # TODO: replace with our general controller when we have one
+ control_socket = self.get_tor_socket(authenticate)
+ return stem.control.BaseController(control_socket)
+
def get_tor_version(self):
"""
Queries our test instance for tor's version.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits