[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Revisions for new hidden service methods
commit 2446babb8d03fb58d44ccec7cb333afd472c9395
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Tue Oct 28 10:07:42 2014 -0700
Revisions for new hidden service methods
Lots of changes for the patch from...
https://trac.torproject.org/projects/tor/ticket/12533
Some that come to mind...
* Revised pydocs, added header entries, and updated the changelog.
* Reordered the new methods to be in their own grouping.
* Renamed get_hidden_services_conf and set_hidden_services_conf to drop the
plural.
* Renamed deleted_hidden_service to remove_hidden_service (since we're not
actually deleting anything).
* Making create_hidden_service and remove_hidden_service be idempotent,
returning a boolean to indicate if they did anything or not.
* Dropping hidden services after our integ tests are done.
---
docs/change_log.rst | 1 +
stem/control.py | 357 ++++++++++++++++++++------------------
test/integ/control/controller.py | 106 +++++------
3 files changed, 244 insertions(+), 220 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index 7297cb9..369806a 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -42,6 +42,7 @@ The following are only available within Stem's `git repository
* **Controller**
+ * Added :class:`~stem.control.Controller` methods to more easily work with hidden service configurations: :func:`~stem.control.Controller.get_hidden_service_conf`, :func:`~stem.control.Controller.set_hidden_service_conf`, :func:`~stem.control.Controller.create_hidden_service`, and :func:`~stem.control.Controller.remove_hidden_service` (feature by federico3 and patrickod, :trac:`12533`)
* Added :func:`~stem.control.Controller.get_accounting_stats` to the :class:`~stem.control.Controller`
* Added :func:`~stem.control.Controller.get_effective_rate` to the :class:`~stem.control.Controller`
* Added :func:`~stem.control.BaseController.connection_time` to the :class:`~stem.control.BaseController`
diff --git a/stem/control.py b/stem/control.py
index 1282eb3..93fe354 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -95,6 +95,11 @@ If you're fine with allowing your script to raise exceptions then this can be mo
|- reset_conf - reverts configuration options to their default values
|- set_options - sets or resets the values of multiple configuration options
|
+ |- get_hidden_service_conf - provides our hidden service configuration
+ |- set_hidden_service_conf - sets our hidden service configuration
+ |- create_hidden_service - creates a new hidden service or adds a new port
+ |- remove_hidden_service - removes a hidden service or drops a port
+ |
|- add_event_listener - attaches an event listener to be notified of tor events
|- remove_event_listener - removes a listener so it isn't notified of further events
|
@@ -1894,174 +1899,6 @@ class Controller(BaseController):
else:
raise exc
- def get_hidden_services_conf(self, default = UNDEFINED):
- """
- This provides a mapping of hidden service directories to their
- attribute's key/value pairs.
-
- {
- "/var/lib/tor/hidden_service_empty/": {
- "HiddenServicePort": [
- ]
- },
- "/var/lib/tor/hidden_service_with_two_ports/": {
- "HiddenServiceAuthorizeClient": "stealth a, b",
- "HiddenServicePort": [
- "8020 127.0.0.1:8020", # the ports order is kept
- "8021 127.0.0.1:8021"
- ],
- "HiddenServiceVersion": "2"
- },
- }
-
- :raises:
- * :class:`stem.ControllerError` if the call fails and we weren't provided
- a default response
- """
- start_time = time.time()
-
- try:
- response = self.msg('GETCONF HiddenServiceOptions')
- stem.response.convert('GETCONF', response)
- log.debug('GETCONF HiddenServiceOptions (runtime: %0.4f)' %
- (time.time() - start_time))
- except stem.ControllerError as exc:
- log.debug('GETCONF HiddenServiceOptions (failed: %s)' % exc)
- if default != UNDEFINED:
- return default
- else:
- raise exc
-
- service_dir_map = OrderedDict()
- directory = None
-
- for status_code, divider, content in response.content():
- if content == 'HiddenServiceOptions':
- continue
-
- if not "=" in content:
- continue
-
- k, v = content.split('=', 1)
-
- if k == 'HiddenServiceDir':
- directory = v
- service_dir_map[directory] = {'HiddenServicePort': []}
-
- elif k == 'HiddenServicePort':
- service_dir_map[directory]['HiddenServicePort'].append(v)
-
- else:
- service_dir_map[directory][k] = v
-
- return service_dir_map
-
- def set_hidden_services_conf(self, conf):
- """Update all the configured hidden services from a dictionary having
- the same format as the output of get_hidden_services_conf()
-
- :param dict conf: configuration dictionary
-
- :raises:
- * :class:`stem.ControllerError` if the call fails
- * :class:`stem.InvalidArguments` if configuration options
- requested was invalid
- * :class:`stem.InvalidRequest` if the configuration setting is
- impossible or if there's a syntax error in the configuration values
- * :raises:
- """
-
- # Convert conf dictionary into a list of ordered config tuples
- hidden_service_options = []
- for directory in conf:
- hidden_service_options.append(('HiddenServiceDir', directory))
- for k, v in conf[directory].iteritems():
- if k == 'HiddenServicePort':
- for port in v:
- hidden_service_options.append(('HiddenServicePort', port))
- else:
- hidden_service_options.append((k, str(v)))
- self.set_options(hidden_service_options)
-
- def create_new_hidden_service(self, dirname, virtport, target=None):
- """Create a new hidden service+port. If the directory is already present, a
- new port will be added. If the port is already present, return False.
-
- :param str dirname: directory name
- :param int virtport: virtual port
- :param str target: optional ipaddr:port target e.g. '127.0.0.1:8080'
- :returns: False if the hidden service and port is already in place
- True if the creation is successful
- """
- if stem.util.connection.is_valid_port(virtport):
- virtport = int(virtport)
-
- else:
- raise ValueError("%s isn't a valid port number" % virtport)
-
- conf = self.get_hidden_services_conf()
-
- if dirname in conf:
- ports = conf[dirname]['HiddenServicePort']
- if target is None:
- if str(virtport) in ports:
- return False
-
- if "%d 127.0.0.1:%d" % (virtport, virtport) in ports:
- return False
-
- elif "%d %s" % (virtport, target) in ports:
- return False
-
- else:
- conf[dirname] = {'HiddenServicePort': []}
-
- if target is None:
- conf[dirname]['HiddenServicePort'].append("%d" % virtport)
-
- else:
- conf[dirname]['HiddenServicePort'].append("%d %s" % (virtport, target))
-
- self.set_hidden_services_conf(conf)
- return True
-
- def delete_hidden_service(self, dirname, virtport, target=None):
- """Delete a hidden service+port.
- :param str dirname: directory name
- :param int virtport: virtual port
- :param str target: optional ipaddr:port target e.g. '127.0.0.1:8080'
- :raises:
- """
- if stem.util.connection.is_valid_port(virtport):
- virtport = int(virtport)
-
- else:
- raise ValueError("%s isn't a valid port number" % virtport)
-
- conf = self.get_hidden_services_conf()
-
- if dirname not in conf:
- raise RuntimeError("HiddenServiceDir %r not found" % dirname)
-
- ports = conf[dirname]['HiddenServicePort']
-
- if target is None:
- longport = "%d 127.0.0.1:%d" % (virtport, virtport)
- try:
- ports.pop(ports.index(str(virtport)))
- except ValueError:
- raise stem.InvalidArguments
-
- else:
- longport = "%d %s" % (virtport, target)
- ports.pop(ports.index(longport))
-
- if not ports:
- del(conf[dirname])
-
- self.set_hidden_services_conf(conf)
- return True
-
def _get_conf_dict_to_response(self, config_dict, default, multiple):
"""
Translates a dictionary of 'config key => [value1, value2...]' into the
@@ -2205,6 +2042,190 @@ class Controller(BaseController):
else:
raise stem.ProtocolError('Returned unexpected status code: %s' % response.code)
+ def get_hidden_service_conf(self, default = UNDEFINED):
+ """
+ This provides a mapping of hidden service directories to their
+ attribute's key/value pairs. All hidden services are assured to have a
+ 'HiddenServicePort', but other entries may or may not exist.
+
+ ::
+
+ {
+ "/var/lib/tor/hidden_service_empty/": {
+ "HiddenServicePort": [
+ ]
+ },
+ "/var/lib/tor/hidden_service_with_two_ports/": {
+ "HiddenServiceAuthorizeClient": "stealth a, b",
+ "HiddenServicePort": [
+ "8020 127.0.0.1:8020", # the ports order is kept
+ "8021 127.0.0.1:8021"
+ ],
+ "HiddenServiceVersion": "2"
+ },
+ }
+
+ :param object default: response if the query fails
+
+ :returns: **dict** with the hidden service configuration
+
+ :raises: :class:`stem.ControllerError` if the call fails and we weren't
+ provided a default response
+ """
+
+ start_time = time.time()
+
+ try:
+ response = self.msg('GETCONF HiddenServiceOptions')
+ stem.response.convert('GETCONF', response)
+ log.debug('GETCONF HiddenServiceOptions (runtime: %0.4f)' %
+ (time.time() - start_time))
+ except stem.ControllerError as exc:
+ log.debug('GETCONF HiddenServiceOptions (failed: %s)' % exc)
+
+ if default != UNDEFINED:
+ return default
+ else:
+ raise exc
+
+ service_dir_map = OrderedDict()
+ directory = None
+
+ for status_code, divider, content in response.content():
+ if content == 'HiddenServiceOptions':
+ continue
+
+ if '=' not in content:
+ continue
+
+ k, v = content.split('=', 1)
+
+ if k == 'HiddenServiceDir':
+ directory = v
+ service_dir_map[directory] = {'HiddenServicePort': []}
+ elif k == 'HiddenServicePort':
+ service_dir_map[directory]['HiddenServicePort'].append(v)
+ else:
+ service_dir_map[directory][k] = v
+
+ return service_dir_map
+
+ def set_hidden_service_conf(self, conf):
+ """
+ Update all the configured hidden services from a dictionary having
+ the same format as
+ :func:`~stem.control.Controller.get_hidden_service_conf`.
+
+ :param dict conf: configuration dictionary
+
+ :raises:
+ * :class:`stem.ControllerError` if the call fails
+ * :class:`stem.InvalidArguments` if configuration options
+ requested was invalid
+ * :class:`stem.InvalidRequest` if the configuration setting is
+ impossible or if there's a syntax error in the configuration values
+ """
+
+ # Convert conf dictionary into a list of ordered config tuples
+
+ hidden_service_options = []
+
+ for directory in conf:
+ hidden_service_options.append(('HiddenServiceDir', directory))
+
+ for k, v in conf[directory].items():
+ if k == 'HiddenServicePort':
+ for port in v:
+ hidden_service_options.append(('HiddenServicePort', port))
+ else:
+ hidden_service_options.append((k, str(v)))
+
+ self.set_options(hidden_service_options)
+
+ def create_hidden_service(self, path, port, target = None):
+ """
+ Create a new hidden service. If the directory is already present, a
+ new port is added.
+
+ :param str path: path for the hidden service's data directory
+ :param int port: hidden service port
+ :param str target: optional ipaddr:port target e.g. '127.0.0.1:8080'
+
+ :returns: **True** if the hidden service is created, **False** if the
+ hidden service port is already in use
+
+ :raises: :class:`stem.ControllerError` if the call fails
+ """
+
+ if not stem.util.connection.is_valid_port(port):
+ raise ValueError("%s isn't a valid port number" % port)
+
+ port, conf = str(port), self.get_hidden_service_conf()
+
+ if path in conf:
+ ports = conf[path]['HiddenServicePort']
+
+ if target is None:
+ if port in ports or '%s 127.0.0.1:%s' % (port, port) in ports:
+ return False
+ elif '%s %s' % (port, target) in ports:
+ return False
+ else:
+ conf[path] = {'HiddenServicePort': []}
+
+ conf[path]['HiddenServicePort'].append('%s %s' % (port, target) if target else port)
+ self.set_hidden_service_conf(conf)
+
+ return True
+
+ def remove_hidden_service(self, path, port = None, target = None):
+ """
+ Discontinues a given hidden service.
+
+ :param str path: path for the hidden service's data directory
+ :param int port: hidden service port
+ :param str target: optional ipaddr:port target e.g. '127.0.0.1:8080'
+
+ :returns: **True** if the hidden service is discontinued, **False** if it
+ wasn't running in the first place
+
+ :raises: :class:`stem.ControllerError` if the call fails
+ """
+
+ if not stem.util.connection.is_valid_port(port):
+ raise ValueError("%s isn't a valid port number" % port)
+
+ port, conf = str(port), self.get_hidden_service_conf()
+
+ if path not in conf:
+ return False
+
+ if port:
+ if not target:
+ longport = '%s 127.0.0.1:%s' % (port, port)
+
+ if port in conf[path]['HiddenServicePort']:
+ conf[path]['HiddenServicePort'].remove(port)
+ elif longport in conf[path]['HiddenServicePort']:
+ conf[path]['HiddenServicePort'].remove(longport)
+ else:
+ return False # wasn't configured to be a hidden service
+ else:
+ longport = '%s %s' % (port, target)
+
+ if longport in conf[path]['HiddenServicePort']:
+ conf[path]['HiddenServicePort'].remove(longport)
+ else:
+ return False # wasn't configured to be a hidden service
+
+ if not conf[path]['HiddenServicePort']:
+ del(conf[path]) # no ports left, drop it entirely
+ else:
+ del(conf[path])
+
+ self.set_hidden_service_conf(conf)
+ return True
+
def add_event_listener(self, listener, *events):
"""
Directs further tor controller events to a given function. The function is
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 4935043..1f20f9f 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -457,7 +457,8 @@ class TestController(unittest.TestCase):
def test_hidden_services_conf(self):
"""
- Exercises get_hidden_services_conf with valid and invalid queries.
+ Exercises the hidden service family of methods (get_hidden_service_conf,
+ set_hidden_service_conf, create_hidden_service, and remove_hidden_service).
"""
if test.runner.require_control(self):
@@ -466,67 +467,68 @@ class TestController(unittest.TestCase):
runner = test.runner.get_runner()
with runner.get_tor_controller() as controller:
-
- conf = controller.get_hidden_services_conf()
- self.assertDictEqual({}, conf)
- controller.set_hidden_services_conf(conf)
-
- initialconf = {
- "test_hidden_service1/": {
- "HiddenServicePort": [
- "8020 127.0.0.1:8020",
- "8021 127.0.0.1:8021"
- ],
- "HiddenServiceVersion": "2",
- },
- "test_hidden_service2/": {
- "HiddenServiceAuthorizeClient": "stealth a, b",
- "HiddenServicePort": [
- "8030 127.0.0.1:8030",
- "8031 127.0.0.1:8031",
- "8032 127.0.0.1:8032"
- ]
- },
- "test_hidden_service_empty/": {
- "HiddenServicePort": []
+ try:
+ # initially we shouldn't be running any hidden services
+
+ self.assertDictEqual({}, controller.get_hidden_service_conf())
+
+ # try setting a blank config, shouldn't have any impact
+
+ controller.set_hidden_service_conf({})
+ self.assertDictEqual({}, controller.get_hidden_service_conf())
+
+ # create a hidden service
+
+ initialconf = {
+ 'test_hidden_service1/': {
+ 'HiddenServicePort': [
+ '8020 127.0.0.1:8020',
+ '8021 127.0.0.1:8021',
+ ],
+ 'HiddenServiceVersion': '2',
+ },
+ 'test_hidden_service2/': {
+ 'HiddenServiceAuthorizeClient': 'stealth a, b',
+ 'HiddenServicePort': [
+ '8030 127.0.0.1:8030',
+ '8031 127.0.0.1:8031',
+ '8032 127.0.0.1:8032',
+ ]
+ },
+ 'test_hidden_service_empty/': {
+ 'HiddenServicePort': []
+ }
}
- }
- controller.set_hidden_services_conf(initialconf)
- conf = controller.get_hidden_services_conf()
- self.assertDictEqual(initialconf, dict(conf))
+ controller.set_hidden_service_conf(initialconf)
+ self.assertDictEqual(initialconf, controller.get_hidden_service_conf())
- # Add already existing services, with/without explicit target
- r = controller.create_new_hidden_service('test_hidden_service1/', 8020)
- self.assertFalse(r)
- r = controller.create_new_hidden_service('test_hidden_service1/', 8021, target="127.0.0.1:8021")
- self.assertFalse(r)
+ # add already existing services, with/without explicit target
- # Add new services, with/without explicit target
- r = controller.create_new_hidden_service('test_hidden_serviceX/', 8888)
- self.assertTrue(r)
- r = controller.create_new_hidden_service('test_hidden_serviceX/', 8989, target="127.0.0.1:8021")
- self.assertTrue(r)
+ self.assertFalse(controller.create_hidden_service('test_hidden_service1/', 8020))
+ self.assertFalse(controller.create_hidden_service('test_hidden_service1/', 8021, target = '127.0.0.1:8021'))
+ self.assertDictEqual(initialconf, controller.get_hidden_service_conf())
- conf = controller.get_hidden_services_conf()
- self.assertEqual(len(conf), 4)
- ports = conf['test_hidden_serviceX/']['HiddenServicePort']
- self.assertEqual(len(ports), 2)
+ # add a new service, with/without explicit target
- # Delete services
- controller.delete_hidden_service('test_hidden_serviceX/', 8888)
+ self.assertTrue(controller.create_hidden_service('test_hidden_serviceX/', 8888))
+ self.assertTrue(controller.create_hidden_service('test_hidden_serviceX/', 8989, target = '127.0.0.1:8021'))
- # The service dir should be still there
- conf = controller.get_hidden_services_conf()
- self.assertEqual(len(conf), 4)
+ conf = controller.get_hidden_service_conf()
+ self.assertEqual(4, len(conf))
+ self.assertEqual(2, len(conf['test_hidden_serviceX/']['HiddenServicePort']))
- # Delete service
- controller.delete_hidden_service('test_hidden_serviceX/', 8989, target="127.0.0.1:8021")
+ # remove a hidden service, the service dir should still be there
- # The service dir should be gone
- conf = controller.get_hidden_services_conf()
- self.assertEqual(len(conf), 3)
+ controller.remove_hidden_service('test_hidden_serviceX/', 8888)
+ self.assertEqual(4, len(controller.get_hidden_service_conf()))
+ # remove a service completely, it should now be gone
+
+ controller.remove_hidden_service('test_hidden_serviceX/', 8989, target = '127.0.0.1:8021')
+ self.assertEqual(3, len(controller.get_hidden_service_conf()))
+ finally:
+ controller.set_hidden_service_conf({}) # drop hidden services created during the test
def test_set_conf(self):
"""
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits