[tor-commits] [stem/master] Controller method to get the exit policy

atagar at torproject.org atagar at torproject.org
Mon Jan 14 01:39:16 UTC 2013


commit 716f8a693e9b814da5e2c9df551dbe6768f4f324
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Jan 13 17:19:33 2013 -0800

    Controller method to get the exit policy
    
    What started out with 'we should have a method to get our exit policy' turned
    out to not be as simple as I thought. A relay's exit policy is really the
    combination of three things:
    
    * 'reject private:*, reject [public address]:*' if ExitPolicyRejectPrivate is
      set.
    
    * Our ExitPolicy. Tor provides the torrc value rather than a proper policy
      spec, so I added a get_config_policy() function to account for the
      differences.
    
    * The default exit policy (as per 'GETINFO exit-policy/default').
---
 stem/control.py                  |   51 +++++++++++++++++++++++++++++++++++++-
 stem/exit_policy.py              |    2 +
 test/integ/control/controller.py |   35 ++++++++++++++++++++++++++
 test/unit/control/controller.py  |   43 ++++++++++++++++++++++++++++++++
 4 files changed, 130 insertions(+), 1 deletions(-)

diff --git a/stem/control.py b/stem/control.py
index 4d3bb52..2090983 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -16,7 +16,8 @@ providing its own for interacting at a higher level.
     |- authenticate - authenticates this controller with tor
     |
     |- get_info - issues a GETINFO query for a parameter
-    |- get_version - convenience method to get tor version
+    |- get_version - provides our tor version
+    |- get_exit_policy - provides our exit policy
     |- get_socks_listeners - provides where tor is listening for SOCKS connections
     |- get_protocolinfo - information about the controller interface
     |
@@ -135,6 +136,7 @@ import time
 
 import stem.descriptor.router_status_entry
 import stem.descriptor.server_descriptor
+import stem.exit_policy
 import stem.response
 import stem.socket
 import stem.util.connection
@@ -813,6 +815,50 @@ class Controller(BaseController):
       else:
         return default
 
+  def get_exit_policy(self, default = UNDEFINED):
+    """
+    Effective ExitPolicy for our relay. This accounts for
+    ExitPolicyRejectPrivate and default policies.
+
+    :param object default: response if the query fails
+
+    :returns: :class:`~stem.exit_policy.ExitPolicy` of the tor instance that
+      we're connected to
+
+    :raises:
+      * :class:`stem.ControllerError` if unable to query the policy
+      * **ValueError** if unable to parse the policy
+
+      An exception is only raised if we weren't provided a default response.
+    """
+
+    with self._msg_lock:
+      try:
+        if not "exit_policy" in self._request_cache:
+          policy = []
+
+          if self.get_conf("ExitPolicyRejectPrivate") == "1":
+            policy.append("reject private:*")
+
+            public_addr = self.get_info("address", None)
+
+            if public_addr:
+              policy.append("reject %s:*" % public_addr)
+
+          for policy_line in self.get_conf("ExitPolicy", multiple = True):
+            policy += policy_line.split(",")
+
+          policy += self.get_info("exit-policy/default").split(",")
+
+          self._request_cache["exit_policy"] = stem.exit_policy.get_config_policy(policy)
+
+        return self._request_cache["exit_policy"]
+      except Exception, exc:
+        if default == UNDEFINED:
+          raise exc
+        else:
+          return default
+
   def get_socks_listeners(self, default = UNDEFINED):
     """
     Provides the SOCKS **(address, port)** tuples that tor has open.
@@ -1312,6 +1358,9 @@ class Controller(BaseController):
             self._request_cache[cache_key] = [value]
           else:
             self._request_cache[cache_key] = value
+
+          if param.lower() == "exitpolicy" and "exit_policy" in self._request_cache:
+            del self._request_cache["exit_policy"]
     else:
       log.debug("%s (failed, code: %s, message: %s)" % (query, response.code, response.message))
 
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 2809e5e..e568deb 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -38,6 +38,8 @@ exiting to a destination is permissible or not. For instance...
     |- get_masked_bits - provides the bit representation of our mask
     +- __str__ - string representation for this rule
 
+  get_config_policy - provides the ExitPolicy based on torrc rules
+
 .. data:: AddressType (enum)
 
   Enumerations for IP address types that can be in an exit policy.
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index bf6b229..8b75c6f 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -23,6 +23,7 @@ import test.runner
 import test.util
 
 from stem.control import EventType
+from stem.exit_policy import ExitPolicy
 from stem.version import Requirement
 
 
@@ -247,6 +248,40 @@ class TestController(unittest.TestCase):
       self.assertTrue(isinstance(version, stem.version.Version))
       self.assertEqual(version, runner.get_tor_version())
 
+  def test_get_exit_policy(self):
+    """
+    Sanity test for get_exit_policy(). We have the default policy (no
+    ExitPolicy set) which is a little... long due to the boilerplate.
+    """
+
+    if test.runner.require_control(self):
+      return
+
+    expected = ExitPolicy(
+      'reject 0.0.0.0/8:*',
+      'reject 169.254.0.0/16:*',
+      'reject 127.0.0.0/8:*',
+      'reject 192.168.0.0/16:*',
+      'reject 10.0.0.0/8:*',
+      'reject 172.16.0.0/12:*',
+      'reject *:25',
+      'reject *:119',
+      'reject *:135-139',
+      'reject *:445',
+      'reject *:563',
+      'reject *:1214',
+      'reject *:4661-4666',
+      'reject *:6346-6429',
+      'reject *:6699',
+      'reject *:6881-6999',
+      'accept *:*',
+    )
+
+    runner = test.runner.get_runner()
+
+    with runner.get_tor_controller() as controller:
+      self.assertEqual(expected, controller.get_exit_policy())
+
   def test_authenticate(self):
     """
     Test that the convenient method authenticate() works.
diff --git a/test/unit/control/controller.py b/test/unit/control/controller.py
index 09b0684..77c8f84 100644
--- a/test/unit/control/controller.py
+++ b/test/unit/control/controller.py
@@ -11,6 +11,7 @@ import stem.version
 
 from stem import InvalidArguments, InvalidRequest, ProtocolError
 from stem.control import _parse_circ_path, Controller, EventType
+from stem.exit_policy import ExitPolicy
 from test import mocking
 
 
@@ -69,6 +70,48 @@ class TestControl(unittest.TestCase):
       # Turn caching back on before we leave.
       self.controller._is_caching_enabled = True
 
+  def test_get_exit_policy(self):
+    """
+    Exercises the get_exit_policy() method.
+    """
+
+    mocking.mock_method(Controller, "get_conf", mocking.return_for_args({
+      ("ExitPolicyRejectPrivate",): "1",
+      ("ExitPolicy", "multiple=True"): ["accept *:80,   accept *:443", "accept 43.5.5.5,reject *:22"]
+    }, is_method = True))
+
+    mocking.mock_method(Controller, "get_info", mocking.return_for_args({
+      ("address", None): "123.45.67.89",
+      ("exit-policy/default",): "reject *:25,reject *:119,reject *:135-139,reject *:445,reject *:563,reject *:1214,reject *:4661-4666,reject *:6346-6429,reject *:6699,reject *:6881-6999,accept *:*"
+    }, is_method = True))
+
+    expected = ExitPolicy(
+      'reject 0.0.0.0/8:*',  # private entries
+      'reject 169.254.0.0/16:*',
+      'reject 127.0.0.0/8:*',
+      'reject 192.168.0.0/16:*',
+      'reject 10.0.0.0/8:*',
+      'reject 172.16.0.0/12:*',
+      'reject 123.45.67.89:*',  # relay's public address
+      'accept *:80',  # finally we get to our ExitPolicy
+      'accept *:443',
+      'accept 43.5.5.5:*',
+      'reject *:22',
+      'reject *:25',  # default policy
+      'reject *:119',
+      'reject *:135-139',
+      'reject *:445',
+      'reject *:563',
+      'reject *:1214',
+      'reject *:4661-4666',
+      'reject *:6346-6429',
+      'reject *:6699',
+      'reject *:6881-6999',
+      'accept *:*',
+    )
+
+    self.assertEqual(expected, self.controller.get_exit_policy())
+
   def test_get_socks_listeners_old(self):
     """
     Exercises the get_socks_listeners() method as though talking to an old tor





More information about the tor-commits mailing list