[tor-commits] [stem/master] ExitPolicy support for accept6 and reject6 rules

atagar at torproject.org atagar at torproject.org
Fri Mar 4 17:36:15 UTC 2016


commit 12c2a7ec541fb11c5e00a0c67a47176f40606076
Author: Damian Johnson <atagar at torproject.org>
Date:   Fri Mar 4 08:39:53 2016 -0800

    ExitPolicy support for accept6 and reject6 rules
    
    Thanks to teor for explaining these! Adding support for these exit policy rule
    types...
    
      https://trac.torproject.org/projects/tor/ticket/16103#comment:5
---
 docs/change_log.rst           |  1 +
 stem/exit_policy.py           | 37 +++++++++++++++++++++++++++----------
 test/unit/exit_policy/rule.py | 19 +++++++++++++++++++
 3 files changed, 47 insertions(+), 10 deletions(-)

diff --git a/docs/change_log.rst b/docs/change_log.rst
index 57667e1..8a92788 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -47,6 +47,7 @@ The following are only available within Stem's `git repository
   * Dramatic, `300x performance improvement <https://github.com/DonnchaC/stem/pull/1>`_ for reading from the control port with python 3
   * Added `stem.manual <api/manual.html>`_, which provides information available about Tor from `its manual <https://www.torproject.org/docs/tor-manual.html.en>`_ (:trac:`8251`)
   * :func:`~stem.connection.connect` and :func:`~stem.control.Controller.from_port` now connect to both port 9051 (relay's default) and 9151 (Tor Browser's default) (:trac:`16075`)
+  * :class:`~stem.exit_policy.ExitPolicy` support for *accept6* and *reject6* rules (:trac:`16103`)
   * Added `support for NETWORK_LIVENESS events <api/response.html#stem.response.events.NetworkLivenessEvent>`_ (:spec:`44aac63`)
   * Added :func:`~stem.control.Controller.is_set` to the :class:`~stem.control.Controller`
   * Added :func:`~stem.control.Controller.is_user_traffic_allowed` to the :class:`~stem.control.Controller`
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 293095b..0f80032 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -632,7 +632,12 @@ class ExitPolicyRule(object):
 
   This should be treated as an immutable object.
 
+  .. versionchanged:: 1.5.0
+     Support for 'accept6/reject6' entries and our **is_ipv6_only** attribute.
+
   :var bool is_accept: indicates if exiting is allowed or disallowed
+  :var bool is_ipv6_only: indicates if this is an accept6 or reject6 rule, only
+    matching ipv6 addresses
 
   :var str address: address that this rule is for
 
@@ -645,17 +650,18 @@ class ExitPolicyRule(object):
   """
 
   def __init__(self, rule):
-    # policy ::= "accept" exitpattern | "reject" exitpattern
+    # policy ::= "accept[6]" exitpattern | "reject[6]" exitpattern
     # exitpattern ::= addrspec ":" portspec
 
-    if rule.startswith('accept'):
-      self.is_accept = True
-    elif rule.startswith('reject'):
-      self.is_accept = False
-    else:
-      raise ValueError("An exit policy must start with either 'accept' or 'reject': %s" % rule)
+    self.is_accept = rule.startswith('accept')
+    self.is_ipv6_only = rule.startswith('accept6') or rule.startswith('reject6')
 
-    exitpattern = rule[6:]
+    if rule.startswith('accept6') or rule.startswith('reject6'):
+      exitpattern = rule[7:]
+    elif rule.startswith('accept') or rule.startswith('reject'):
+      exitpattern = rule[6:]
+    else:
+      raise ValueError("An exit policy must start with either 'accept[6]' or 'reject[6]': %s" % rule)
 
     if not exitpattern.startswith(' '):
       raise ValueError('An exit policy should have a space separating its accept/reject from the exit pattern: %s' % rule)
@@ -738,6 +744,9 @@ class ExitPolicyRule(object):
     # validate our input and check if the argument doesn't match our address type
 
     if address is not None:
+      if self.is_ipv6_only and stem.util.connection.is_valid_ipv4_address(address):
+        return False  # accept6/reject6 don't match ipv4
+
       address_type = self.get_address_type()
 
       if stem.util.connection.is_valid_ipv4_address(address):
@@ -868,7 +877,10 @@ class ExitPolicyRule(object):
     to re-create this rule.
     """
 
-    label = 'accept ' if self.is_accept else 'reject '
+    if self.is_ipv6_only:
+      label = 'accept6 ' if self.is_accept else 'reject6 '
+    else:
+      label = 'accept ' if self.is_accept else 'reject '
 
     if self.is_address_wildcard():
       label += '*:'
@@ -906,7 +918,7 @@ class ExitPolicyRule(object):
     if self._hash is None:
       my_hash = 0
 
-      for attr in ('is_accept', 'address', 'min_port', 'max_port'):
+      for attr in ('is_accept', 'is_ipv6_only', 'address', 'min_port', 'max_port'):
         my_hash *= 1024
 
         attr_value = getattr(self, attr)
@@ -951,6 +963,10 @@ class ExitPolicyRule(object):
       # ip4mask ::= an IPv4 mask in dotted-quad format
       # num_ip4_bits ::= an integer between 0 and 32
 
+      if self.is_ipv6_only:
+        rule_start = 'accept6' if self.is_accept else 'reject6'
+        raise ValueError("'%s' rules should have an IPv6 address, not IPv4 (%s)" % (rule_start, self.address))
+
       self._address_type = _address_type_to_int(AddressType.IPv4)
 
       if addr_extra is None:
@@ -1054,6 +1070,7 @@ class MicroExitPolicyRule(ExitPolicyRule):
 
   def __init__(self, is_accept, min_port, max_port):
     self.is_accept = is_accept
+    self.is_ipv6_only = False
     self.address = None  # wildcard address
     self.min_port = min_port
     self.max_port = max_port
diff --git a/test/unit/exit_policy/rule.py b/test/unit/exit_policy/rule.py
index ecd96a8..9ff0181 100644
--- a/test/unit/exit_policy/rule.py
+++ b/test/unit/exit_policy/rule.py
@@ -42,6 +42,8 @@ class TestExitPolicyRule(unittest.TestCase):
     test_inputs = (
       'accept *:*',
       'reject *:*',
+      'accept6 *:*',
+      'reject6 *:*',
       'accept *:80',
       'accept *:80-443',
       'accept 127.0.0.1:80',
@@ -83,6 +85,10 @@ class TestExitPolicyRule(unittest.TestCase):
       'reject [0000:0000:0000:0000:0000:0000:0000:0000]/64:80': (False, False),
       'reject [0000:0000:0000:0000:0000:0000:0000:0000]/128:80': (False, False),
 
+      'reject6 *:*': (True, True),
+      'reject6 *:80': (True, False),
+      'reject6 [0000:0000:0000:0000:0000:0000:0000:0000]/128:80': (False, False),
+
       'accept 192.168.0.1:0-65535': (False, True),
       'accept 192.168.0.1:1-65535': (False, True),
       'accept 192.168.0.1:2-65535': (False, False),
@@ -352,3 +358,16 @@ class TestExitPolicyRule(unittest.TestCase):
 
       for match_args, expected_result in matches.items():
         self.assertEqual(expected_result, rule.is_match(*match_args))
+
+  def test_ipv6_only_entries(self):
+    # accept6/reject6 shouldn't allow ipv4 addresses
+
+    self.assertRaises(ValueError, ExitPolicyRule, 'accept6 192.168.0.1:*')
+    self.assertRaises(ValueError, ExitPolicyRule, 'reject6 192.168.0.1:*')
+
+    # wildcards match all ipv6 but *not* ipv4
+
+    rule = ExitPolicyRule('accept6 *:*')
+    self.assertTrue(rule.is_ipv6_only)
+    self.assertTrue(rule.is_match('FE80:0000:0000:0000:0202:B3FF:FE1E:8329', 443))
+    self.assertFalse(rule.is_match('192.168.0.1', 443))





More information about the tor-commits mailing list