[tor-commits] [stem/master] Better handling for 'private' exit policy entries
atagar at torproject.org
atagar at torproject.org
Wed Sep 3 16:23:10 UTC 2014
commit 0d48b504566c4df7f15991465f19af15a87c0c6b
Author: Damian Johnson <atagar at torproject.org>
Date: Wed Sep 3 09:24:02 2014 -0700
Better handling for 'private' exit policy entries
Commonly we don't want to take 'private' exit policy entries into account...
https://trac.torproject.org/projects/tor/ticket/10107
Adding a method to strip them, and also identify rules that were expanded from
the 'private' keyword.
---
docs/change_log.rst | 1 +
stem/exit_policy.py | 70 +++++++++++++++++++++++++++++++++++++++
test/unit/exit_policy/policy.py | 30 +++++++++++++++++
3 files changed, 101 insertions(+)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index 57ccd88..130b24f 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -44,6 +44,7 @@ The following are only available within Stem's `git repository
* Added :func:`~stem.control.BaseController.connection_time` to the :class:`~stem.control.BaseController`
* Changed :func:`~stem.control.Controller.get_microdescriptor`, :func:`~stem.control.Controller.get_server_descriptor`, and :func:`~stem.control.Controller.get_network_status` to get our own descriptor if no fingerprint or nickname is provided.
+ * Added an :func:`~stem.exit_policy.ExitPolicy.strip_private` method to :class:`~stem.exit_policy.ExitPolicy` and :func:`~stem.exit_policy.ExitPolicy.is_private` to :class:`~stem.exit_policy.ExitPolicyRule`
* **Descriptors**
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 4f622d7..2cade1d 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -30,6 +30,7 @@ exiting to a destination is permissible or not. For instance...
|- can_exit_to - check if exiting to this destination is allowed or not
|- is_exiting_allowed - check if any exiting is allowed
|- summary - provides a short label, similar to a microdescriptor
+ |- strip_private - provides a copy of the policy without 'private' entries
|- __str__ - string representation
+- __iter__ - ExitPolicyRule entries that this contains
@@ -42,6 +43,7 @@ exiting to a destination is permissible or not. For instance...
|- is_match - checks if we match a given destination
|- get_mask - provides the address representation of our mask
|- get_masked_bits - provides the bit representation of our mask
+ |- is_private - flag indicating if this was expanded from a 'private' keyword
+- __str__ - string representation for this rule
get_config_policy - provides the ExitPolicy based on torrc rules
@@ -137,6 +139,48 @@ def get_config_policy(rules):
return ExitPolicy(*result)
+def _flag_private_rules(rules):
+ """
+ Determine if part of our policy was expanded from the 'private' keyword. This
+ doesn't differentiate if this actually came from the 'private' keyword or a
+ series of rules exactly matching it.
+ """
+
+ matches = []
+
+ for i, rule in enumerate(rules):
+ if i + len(PRIVATE_ADDRESSES) > len(rules):
+ break
+
+ rule_str = '%s/%s' % (rule.address, rule.get_masked_bits())
+
+ if rule_str == PRIVATE_ADDRESSES[0]:
+ matches.append(i)
+
+ for start_index in matches:
+ # To match the private policy the following must all be true...
+ #
+ # * series of addresses and bit masks match PRIVATE_ADDRESSES
+ # * all these rules have the same port range and acceptance
+ # * all these rules must be either accept or reject entries
+
+ rule_set = rules[start_index:start_index + len(PRIVATE_ADDRESSES)]
+ min_port, max_port = rule_set[0].min_port, rule_set[0].max_port
+ is_accept = rule_set[0].is_accept
+ is_match = True
+
+ for i, rule in enumerate(rule_set):
+ rule_str = '%s/%s' % (rule.address, rule.get_masked_bits())
+
+ if rule_str != PRIVATE_ADDRESSES[i] or rule.min_port != min_port or rule.max_port != max_port or rule.is_accept != is_accept:
+ is_match = False
+ break
+
+ if is_match:
+ for rule in rule_set:
+ rule._is_private = True
+
+
class ExitPolicy(object):
"""
Policy for the destinations that a relay allows or denies exiting to. This
@@ -149,6 +193,7 @@ class ExitPolicy(object):
def __init__(self, *rules):
# sanity check the types
+
for rule in rules:
if not isinstance(rule, (bytes, unicode, ExitPolicyRule)):
raise TypeError('Exit policy rules can only contain strings or ExitPolicyRules, got a %s (%s)' % (type(rule), rules))
@@ -300,6 +345,15 @@ class ExitPolicy(object):
return (label_prefix + ', '.join(display_ranges)).strip()
+ def strip_private(self):
+ """
+ Provides a copy of this policy without 'private' policy entries.
+
+ :returns: **ExitPolicy** without private rules
+ """
+
+ return ExitPolicy(*[rule for rule in self._get_rules() if not rule.is_private()])
+
def _get_rules(self):
if self._rules is None:
rules = []
@@ -346,6 +400,8 @@ class ExitPolicy(object):
elif is_all_reject:
rules = [ExitPolicyRule('reject *:*')]
+ _flag_private_rules(rules)
+
self._rules = rules
self._input_rules = None
@@ -526,6 +582,12 @@ class ExitPolicyRule(object):
self._submask_wildcard = True
+ # Flags to indicate if this rule seems to be expanded from the 'private'
+ # keyword or tor's default policy suffix.
+
+ self._is_private = False
+ self._is_default_suffix = False # TODO: implement
+
def is_address_wildcard(self):
"""
**True** if we'll match against any address, **False** otherwise.
@@ -570,6 +632,7 @@ class ExitPolicyRule(object):
"""
# validate our input and check if the argument doesn't match our address type
+
if address is not None:
address_type = self.get_address_type()
@@ -660,6 +723,13 @@ class ExitPolicyRule(object):
return self._masked_bits
+ def is_private(self):
+ """
+ True if this rule was expanded from the 'private' keyword, False otherwise.
+ """
+
+ return self._is_private
+
@lru_cache()
def __str__(self):
"""
diff --git a/test/unit/exit_policy/policy.py b/test/unit/exit_policy/policy.py
index 70d6ac1..d00954d 100644
--- a/test/unit/exit_policy/policy.py
+++ b/test/unit/exit_policy/policy.py
@@ -96,6 +96,36 @@ class TestExitPolicy(unittest.TestCase):
policy = ExitPolicy('reject *:80-65535', 'accept *:1-65533', 'reject *:*')
self.assertEquals('accept 1-79', policy.summary())
+ def test_all_private_policy(self):
+ for port in ('*', '80', '1-1024'):
+ private_policy = get_config_policy('reject private:%s' % port)
+
+ for rule in private_policy:
+ self.assertTrue(rule.is_private())
+
+ self.assertEqual(ExitPolicy(), private_policy.strip_private())
+
+ # though not commonly done, technically private policies can be accept rules too
+
+ private_policy = get_config_policy('accept private:*')
+ self.assertEqual(ExitPolicy(), private_policy.strip_private())
+
+ def test_all_non_private_policy(self):
+ nonprivate_policy = get_config_policy('reject *:80-65535, accept *:1-65533, reject *:*')
+
+ for rule in nonprivate_policy:
+ self.assertFalse(rule.is_private())
+
+ self.assertEqual(nonprivate_policy, nonprivate_policy.strip_private())
+
+ def test_mixed_private_policy(self):
+ policy = get_config_policy('accept *:80, reject private:1-65533, accept *:*')
+
+ for rule in policy:
+ self.assertTrue(rule.is_accept != rule.is_private()) # only reject rules are the private ones
+
+ self.assertEqual(get_config_policy('accept *:80, accept *:*'), policy.strip_private())
+
def test_str(self):
# sanity test for our __str__ method
More information about the tor-commits
mailing list