[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