[tor-commits] [stem/master] Overhaul exit_policy.py

atagar at torproject.org atagar at torproject.org
Thu Jul 19 16:01:03 UTC 2012


commit 80228a52e3f9507547cf924156a1bcafdea6540b
Author: Sathyanarayanan Gunasekaran <gsathya.ceg at gmail.com>
Date:   Fri Mar 23 12:21:01 2012 +0530

    Overhaul exit_policy.py
    
    - Fix Indentation issues
    - Remove ExitPolicyIterator class
    - Add ExitPolicyLine class
      - This class represent a single line from the Exit Policy.
        (provides much better abstraction IMHO)
      - __init__(), __str__(), and check() are copied from ExitPolicy
    - Changes in ExitPolicy -
      - Now, it acts a wrapper class to ExitPolicyLine
      - It contains a list ExitPolicy._policies which stores all the policies
        and each item in _policies is an object of ExitPolicyLine
      - check(), isExitingAllowed(), __str__(), and __iter__() are
        changed to provide a wrapper of sorts for ExitPolicyLine
      - add() is now used to add an exit policy line - This creates
        an object of ExitPolicyLine and adds it to ExitPolicy._policies
---
 stem/exit_policy.py |  358 ++++++++++++++++++++-------------------------------
 1 files changed, 138 insertions(+), 220 deletions(-)

diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 40cad39..15c2b2c 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -1,237 +1,155 @@
-import re
+# ip address ranges substituted by the 'private' keyword
+PRIVATE_IP_RANGES = ("0.0.0.0/8", "169.254.0.0/16", "127.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12")
 
-class ExitPolicyIterator:
-  """
-  Basic iterator for cycling through ExitPolicy entries.
-  """
-  
-  def __init__(self, head):
-    self.head = head
-  
-  def next(self):
-    if self.head:
-      lastHead = self.head
-      self.head = self.head.nextRule
-      return lastHead
-    else: raise StopIteration
+class ExitPolicyLine:
+    def __init__(self, ruleEntry):
+        # sanitize the input a bit, cleaning up tabs and stripping quotes
+        ruleEntry = ruleEntry.replace("\\t", " ").replace("\"", "")
 
-class ExitPolicy:
-  """
-  Single rule from the user's exit policy. These are chained together to form
-  complete policies.
-  """
-  
-  def __init__(self, ruleEntry, nextRule):
-    """
-    Exit policy rule constructor.
-    
-    Arguments:
-      ruleEntry - tor exit policy rule (for instance, "reject *:135-139")
-      nextRule  - next rule to be checked when queries don't match this policy
-    """
+        self.ruleEntry = ruleEntry
+        self.isAccept = ruleEntry.startswith("accept")
+
+        # strips off "accept " or "reject " and extra spaces
+        ruleEntry = ruleEntry[7:].replace(" ", "")
+
+        # split ip address (with mask if provided) and port
+        if ":" in ruleEntry: entryIp, entryPort = ruleEntry.split(":", 1)
+        else: entryIp, entryPort = ruleEntry, "*"
+
+        # sets the ip address component
+        self.isIpWildcard = entryIp == "*" or entryIp.endswith("/0")
+
+        # checks for the private alias (which expands this to a chain of entries)
+        if entryIp.lower() == "private":
+            # constructs the chain backwards (last first)
+            prefix = "accept " if self.isAccept else "reject "
+            suffix = ":" + entryPort
+            for addr in PRIVATE_IP_RANGES:
+                # TODO: Add ExitPolicy.add method 
+                ExitPolicy.add(prefix + addr + suffix) 
+
+        if "/" in entryIp:
+            ipComp = entryIp.split("/", 1)
+            self.ipAddress = ipComp[0]
+            self.ipMask = int(ipComp[1])
+        else:
+            self.ipAddress = entryIp
+            self.ipMask = 32
+
+        # constructs the binary address just in case of comparison with a mask
+        if self.ipAddress != "*":
+            self.ipAddressBin = ""
+            for octet in self.ipAddress.split("."):
+                # Converts the int to a binary string, padded with zeros. Source:
+                # http://www.daniweb.com/code/snippet216539.html
+                self.ipAddressBin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)])
+        else:
+            self.ipAddressBin = "0" * 32
+
+        # sets the port component
+        self.minPort, self.maxPort = 0, 0
+        self.isPortWildcard = entryPort == "*"
+
+        if entryPort != "*":
+            if "-" in entryPort:
+                portComp = entryPort.split("-", 1)
+                self.minPort = int(portComp[0])
+                self.maxPort = int(portComp[1])
+            else:
+                self.minPort = int(entryPort)
+                self.maxPort = int(entryPort)
+        
+    def __str__(self):
+        # This provides the actual policy rather than the entry used to construct
+        # it so the 'private' keyword is expanded.
+        
+        acceptanceLabel = "accept" if self.isAccept else "reject"
     
-    # cached summary string
-    self.summaryStr = None
+        if self.isIpWildcard:
+            ipLabel = "*"
+        elif self.ipMask != 32:
+            ipLabel = "%s/%i" % (self.ipAddress, self.ipMask)
+        else: ipLabel = self.ipAddress
     
-    # sanitize the input a bit, cleaning up tabs and stripping quotes
-    ruleEntry = ruleEntry.replace("\\t", " ").replace("\"", "")
+        if self.isPortWildcard:
+            portLabel = "*"
+        elif self.minPort != self.maxPort:
+            portLabel = "%i-%i" % (self.minPort, self.maxPort)
+        else: portLabel = str(self.minPort)
     
-    self.ruleEntry = ruleEntry
-    self.nextRule = nextRule
-    self.isAccept = ruleEntry.startswith("accept")
+        myPolicy = "%s %s:%s" % (acceptanceLabel, ipLabel, portLabel)
+        return myPolicy
     
-    # strips off "accept " or "reject " and extra spaces
-    ruleEntry = ruleEntry[7:].replace(" ", "")
+    def check(self, ipAddress, port):
+        """
+        Checks if the rule chain allows exiting to this address, returning true if
+        so and false otherwise.
+        """
     
-    # split ip address (with mask if provided) and port
-    if ":" in ruleEntry: entryIp, entryPort = ruleEntry.split(":", 1)
-    else: entryIp, entryPort = ruleEntry, "*"
+        port = int(port)
     
-    # sets the ip address component
-    self.isIpWildcard = entryIp == "*" or entryIp.endswith("/0")
+        # does the port check first since comparing ip masks is more work
+        isPortMatch = self.isPortWildcard or (port >= self.minPort and port <= self.maxPort)
     
-    # checks for the private alias (which expands this to a chain of entries)
-    if entryIp.lower() == "private":
-      entryIp = PRIVATE_IP_RANGES[0]
-      
-      # constructs the chain backwards (last first)
-      lastHop = self.nextRule
-      prefix = "accept " if self.isAccept else "reject "
-      suffix = ":" + entryPort
-      for addr in PRIVATE_IP_RANGES[-1:0:-1]:
-        lastHop = ExitPolicy(prefix + addr + suffix, lastHop)
+        if isPortMatch:
+            isIpMatch = self.isIpWildcard or self.ipAddress == ipAddress
+        
+            # expands the check to include the mask if it has one
+            if not isIpMatch and self.ipMask != 32:
+                inputAddressBin = ""
+                for octet in ipAddress.split("."):
+                    inputAddressBin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)])
+
+                isIpMatch = self.ipAddressBin[:self.ipMask] == inputAddressBin[:self.ipMask]
+
+            if isIpMatch: return self.isAccept
+            
+        # fell off the chain without a conclusion (shouldn't happen...)
+        return False
       
-      self.nextRule = lastHop # our next hop is the start of the chain
-    
-    if "/" in entryIp:
-      ipComp = entryIp.split("/", 1)
-      self.ipAddress = ipComp[0]
-      self.ipMask = int(ipComp[1])
-    else:
-      self.ipAddress = entryIp
-      self.ipMask = 32
-    
-    # constructs the binary address just in case of comparison with a mask
-    if self.ipAddress != "*":
-      self.ipAddressBin = ""
-      for octet in self.ipAddress.split("."):
-        # Converts the int to a binary string, padded with zeros. Source:
-        # http://www.daniweb.com/code/snippet216539.html
-        self.ipAddressBin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)])
-    else:
-      self.ipAddressBin = "0" * 32
-    
-    # sets the port component
-    self.minPort, self.maxPort = 0, 0
-    self.isPortWildcard = entryPort == "*"
-    
-    if entryPort != "*":
-      if "-" in entryPort:
-        portComp = entryPort.split("-", 1)
-        self.minPort = int(portComp[0])
-        self.maxPort = int(portComp[1])
-      else:
-        self.minPort = int(entryPort)
-        self.maxPort = int(entryPort)
-    
-    # if both the address and port are wildcards then we're effectively the
-    # last entry so cut off the remaining chain
-    if self.isIpWildcard and self.isPortWildcard:
-      self.nextRule = None
-  
-  def isExitingAllowed(self):
+
+class ExitPolicy:
     """
-    Provides true if the policy allows exiting whatsoever, false otherwise.
+    Single rule from the user's exit policy. These are chained together to form
+    complete policies.
     """
-    
-    if self.isAccept: return True
-    elif self.isIpWildcard and self.isPortWildcard: return False
-    elif not self.nextRule: return False # fell off policy (shouldn't happen)
-    else: return self.nextRule.isExitingAllowed()
   
-  def check(self, ipAddress, port):
-    """
-    Checks if the rule chain allows exiting to this address, returning true if
-    so and false otherwise.
-    """
-    
-    port = int(port)
-    
-    # does the port check first since comparing ip masks is more work
-    isPortMatch = self.isPortWildcard or (port >= self.minPort and port <= self.maxPort)
-    
-    if isPortMatch:
-      isIpMatch = self.isIpWildcard or self.ipAddress == ipAddress
-      
-      # expands the check to include the mask if it has one
-      if not isIpMatch and self.ipMask != 32:
-        inputAddressBin = ""
-        for octet in ipAddress.split("."):
-          inputAddressBin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)])
-        
-        isIpMatch = self.ipAddressBin[:self.ipMask] == inputAddressBin[:self.ipMask]
-      
-      if isIpMatch: return self.isAccept
-    
-    # our policy doesn't concern this address, move on to the next one
-    if self.nextRule: return self.nextRule.check(ipAddress, port)
-    else: return True # fell off the chain without a conclusion (shouldn't happen...)
+    def __init__(self):
+        """
+        Exit policy rule constructor.
+        """
+        self._policies = []
   
-  def getSummary(self):
-    """
-    Provides a summary description of the policy chain similar to the
-    consensus. This excludes entries that don't cover all ips, and is either
-    a whitelist or blacklist policy based on the final entry. For instance...
-    accept 80, 443        # just accepts ports 80/443
-    reject 1-1024, 5555   # just accepts non-privilaged ports, excluding 5555
-    """
-    
-    if not self.summaryStr:
-      # determines if we're a whitelist or blacklist
-      isWhitelist = False # default in case we don't have a catch-all policy at the end
-      
-      for rule in self:
-        if rule.isIpWildcard and rule.isPortWildcard:
-          isWhitelist = not rule.isAccept
-          break
-      
-      # Iterates over the rules and adds the the ports we'll return (ie, allows
-      # if a whitelist and rejects if a blacklist). Reguardless of a port's
-      # allow/reject policy, all further entries with that port are ignored since
-      # policies respect the first matching rule.
-      
-      displayPorts, skipPorts = [], []
-      
-      for rule in self:
-        if not rule.isIpWildcard: continue
-        
-        if rule.minPort == rule.maxPort:
-          portRange = [rule.minPort]
-        else:
-          portRange = range(rule.minPort, rule.maxPort + 1)
-        
-        for port in portRange:
-          if port in skipPorts: continue
-          
-          # if accept + whitelist or reject + blacklist then add
-          if rule.isAccept == isWhitelist:
-            displayPorts.append(port)
-          
-          # all further entries with this port are to be ignored
-          skipPorts.append(port)
-      
-      # gets a list of the port ranges
-      if displayPorts:
-        displayRanges, tmpRange = [], []
-        displayPorts.sort()
-        displayPorts.append(None) # ending item to include last range in loop
-        
-        for port in displayPorts:
-          if not tmpRange or tmpRange[-1] + 1 == port:
-            tmpRange.append(port)
-          else:
-            if len(tmpRange) > 1:
-              displayRanges.append("%i-%i" % (tmpRange[0], tmpRange[-1]))
-            else:
-              displayRanges.append(str(tmpRange[0]))
-            
-            tmpRange = [port]
-      else:
-        # everything for the inverse
-        isWhitelist = not isWhitelist
-        displayRanges = ["1-65535"]
-      
-      # constructs the summary string
-      labelPrefix = "accept " if isWhitelist else "reject "
-      
-      self.summaryStr = (labelPrefix + ", ".join(displayRanges)).strip()
-    
-    return self.summaryStr
+    def add(self, ruleEntry):
+        self._policies.append(ExitPolicyLine(ruleEntry))
+
+    def isExitingAllowed(self):
+        """
+        Provides true if the policy allows exiting whatsoever, false otherwise.
+        """
+        for policy in self._policies:
+          if policy.isAccept: return True
+          elif policy.isIpWildcard and self.isPortWildcard: return False
+
   
-  def __iter__(self):
-    return ExitPolicyIterator(self)
+    def check(self, ipAddress, port):
+        """
+        Checks if the rule chain allows exiting to this address, returning true if
+        so and false otherwise.
+        """
+    
+        for policy in self._policies:
+            if policy.check(ipAddress, port): return True
+
+        return False
   
-  def __str__(self):
-    # This provides the actual policy rather than the entry used to construct
-    # it so the 'private' keyword is expanded.
-    
-    acceptanceLabel = "accept" if self.isAccept else "reject"
-    
-    if self.isIpWildcard:
-      ipLabel = "*"
-    elif self.ipMask != 32:
-      ipLabel = "%s/%i" % (self.ipAddress, self.ipMask)
-    else: ipLabel = self.ipAddress
-    
-    if self.isPortWildcard:
-      portLabel = "*"
-    elif self.minPort != self.maxPort:
-      portLabel = "%i-%i" % (self.minPort, self.maxPort)
-    else: portLabel = str(self.minPort)
-    
-    myPolicy = "%s %s:%s" % (acceptanceLabel, ipLabel, portLabel)
-    
-    if self.nextRule:
-      return myPolicy + ", " + str(self.nextRule)
-    else: return myPolicy
+    def __iter__(self):
+        for policy in self._policies:
+            yield policy
+  
+    def __str__(self):
+        # This provides the actual policy rather than the entry used to construct
+        # it so the 'private' keyword is expanded.
+      
+        return ' , '.join([str(policy) for policy in self._policies])
 





More information about the tor-commits mailing list