[tor-commits] [arm/master] Using stem's ExitPolicy

atagar at torproject.org atagar at torproject.org
Mon Jan 14 05:28:43 UTC 2013


commit 3232ce527da8b95cb78f3a52b19ad0634e1cd9da
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Jan 13 20:24:08 2013 -0800

    Using stem's ExitPolicy
    
    Dropping our ExitPolicy class in favor of stem's. Besides the class replacement
    this also clears out a fair bit of our custom Controller. Stem provides caching
    and an easy method to fetch a policy from descriptors.
    
    The attempt by getRelayExitPolicy() to use router status entries to get an exit
    policy has always been broken. 'GETINFO ns/*' gives v2 router status entries,
    and the exit policy wasn't added until v3.
---
 src/cli/connections/connEntry.py |    2 +-
 src/cli/connections/connPanel.py |    5 +-
 src/util/torTools.py             |  358 ++------------------------------------
 3 files changed, 21 insertions(+), 344 deletions(-)

diff --git a/src/cli/connections/connEntry.py b/src/cli/connections/connEntry.py
index bec5aa9..5b2eda5 100644
--- a/src/cli/connections/connEntry.py
+++ b/src/cli/connections/connEntry.py
@@ -714,7 +714,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
         
         exitPolicy = conn.getRelayExitPolicy(fingerprint)
         
-        if exitPolicy: policyLabel = exitPolicy.getSummary()
+        if exitPolicy: policyLabel = exitPolicy.summary()
         else: policyLabel = "unknown"
         
         dirPortLabel = "" if dirPort == "0" else "dirport: %s" % dirPort
diff --git a/src/cli/connections/connPanel.py b/src/cli/connections/connPanel.py
index 9f3a5ed..79bc3b4 100644
--- a/src/cli/connections/connPanel.py
+++ b/src/cli/connections/connPanel.py
@@ -207,8 +207,11 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     True if exit connections are permissable, false otherwise.
     """
     
+    if not torTools.getOption("ORPort", None):
+      return False # no ORPort
+    
     policy = torTools.getConn().getExitPolicy()
-    return policy and policy.isExitingAllowed()
+    return policy and policy.is_exiting_allowed()
   
   def showSortDialog(self):
     """
diff --git a/src/util/torTools.py b/src/util/torTools.py
index c050ea6..c8d12c5 100644
--- a/src/util/torTools.py
+++ b/src/util/torTools.py
@@ -58,9 +58,6 @@ REQ_EVENTS = {"NOTICE": "this will be unable to detect when tor is shut down",
               "NS": "information related to the consensus will grow stale",
               "NEWCONSENSUS": "information related to the consensus will grow stale"}
 
-# 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")
-
 # This prevents controllers from spawning worker threads (and by extension
 # notifying status listeners). This is important when shutting down to prevent
 # rogue threads from being alive during shutdown.
@@ -286,10 +283,6 @@ class Controller:
     # is.
     self._notificationQueue = Queue.Queue()
     
-    self._exitPolicyChecker = None
-    self._isExitingAllowed = False
-    self._exitPolicyLookupCache = {}    # mappings of ip/port tuples to if they were accepted by the policy or not
-    
     # Logs issues and notices when fetching the path prefix if true. This is
     # only done once for the duration of the application to avoid pointless
     # messages.
@@ -333,10 +326,6 @@ class Controller:
       self._consensusLookupCache = {}
       self._descriptorLookupCache = {}
       
-      self._exitPolicyChecker = self.getExitPolicy()
-      self._isExitingAllowed = self._exitPolicyChecker.isExitingAllowed()
-      self._exitPolicyLookupCache = {}
-      
       self._status = State.INIT
       self._statusTime = time.time()
       
@@ -495,12 +484,6 @@ class Controller:
       if not self.isAlive():
         raise stem.SocketClosed()
       
-      # clears our exit policy chache if it's changing
-      if "exitpolicy" in [k.lower() for (k, v) in paramList]:
-        self._exitPolicyChecker = self.getExitPolicy()
-        self._isExitingAllowed = self._exitPolicyChecker.isExitingAllowed()
-        self._exitPolicyLookupCache = {}
-      
       self.controller.set_options(paramList, isReset)
     except stem.SocketClosed, exc:
       self.close()
@@ -790,19 +773,16 @@ class Controller:
     
     result = False
     if self.isAlive():
-      # query the policy if it isn't yet cached
-      if not (ipAddress, port) in self._exitPolicyLookupCache:
-        # If we allow any exiting then this could be relayed DNS queries,
-        # otherwise the policy is checked. Tor still makes DNS connections to
-        # test when exiting isn't allowed, but nothing is relayed over them.
-        # I'm registering these as non-exiting to avoid likely user confusion:
-        # https://trac.torproject.org/projects/tor/ticket/965
-        
-        if self._isExitingAllowed and port == "53": isAccepted = True
-        else: isAccepted = self._exitPolicyChecker.check(ipAddress, port)
-        self._exitPolicyLookupCache[(ipAddress, port)] = isAccepted
+      # If we allow any exiting then this could be relayed DNS queries,
+      # otherwise the policy is checked. Tor still makes DNS connections to
+      # test when exiting isn't allowed, but nothing is relayed over them.
+      # I'm registering these as non-exiting to avoid likely user confusion:
+      # https://trac.torproject.org/projects/tor/ticket/965
+      
+      our_policy = self.getExitPolicy()
       
-      result = self._exitPolicyLookupCache[(ipAddress, port)]
+      if our_policy and our_policy.is_exiting_allowed() and port == "53": result = True
+      else: result = our_policy and our_policy.can_exit_to(ipAddress, port)
     
     self.connLock.release()
     
@@ -818,35 +798,10 @@ class Controller:
     
     result = None
     if self.isAlive():
-      if self.getOption("ORPort", None):
-        policyEntries = []
-        for exitPolicy in self.getOption("ExitPolicy", [], True):
-          policyEntries += [policy.strip() for policy in exitPolicy.split(",")]
-        
-        # appends the default exit policy
-        defaultExitPolicy = self.getInfo("exit-policy/default", None)
-        
-        if defaultExitPolicy:
-          policyEntries += defaultExitPolicy.split(",")
-        
-        # construct the policy chain backwards
-        policyEntries.reverse()
-        
-        for entry in policyEntries:
-          result = ExitPolicy(entry, result)
-        
-        # Checks if we are rejecting private connections. If set, this appends
-        # 'reject private' and 'reject <my ip>' to the start of our policy chain.
-        isPrivateRejected = self.getOption("ExitPolicyRejectPrivate", True)
-        
-        if isPrivateRejected:
-          myAddress = self.getInfo("address", None)
-          if myAddress: result = ExitPolicy("reject %s" % myAddress, result)
-          
-          result = ExitPolicy("reject private", result)
-      else:
-        # no ORPort is set so all relaying is disabled
-        result = ExitPolicy("reject *:*", None)
+      try:
+        result = self.controller.get_exit_policy(param)
+      except:
+        pass
     
     self.connLock.release()
     
@@ -973,7 +928,7 @@ class Controller:
     
     return result
   
-  def getRelayExitPolicy(self, relayFingerprint, allowImprecision = True):
+  def getRelayExitPolicy(self, relayFingerprint):
     """
     Provides the ExitPolicy instance associated with the given relay. The tor
     consensus entries don't indicate if private addresses are rejected or
@@ -983,7 +938,6 @@ class Controller:
     
     Arguments:
       relayFingerprint - fingerprint of the relay
-      allowImprecision - make use of consensus policies as a fallback
     """
     
     self.connLock.acquire()
@@ -991,55 +945,10 @@ class Controller:
     result = None
     if self.isAlive():
       # attempts to fetch the policy via the descriptor
-      descriptor = self.getDescriptorEntry(relayFingerprint)
+      descriptor = self.controller.get_server_descriptor(relayFingerprint, None)
       
       if descriptor:
-        exitPolicyEntries = []
-        for line in descriptor.split("\n"):
-          if line.startswith("accept ") or line.startswith("reject "):
-            exitPolicyEntries.append(line)
-        
-        # construct the policy chain
-        for entry in reversed(exitPolicyEntries):
-          result = ExitPolicy(entry, result)
-      elif allowImprecision:
-        # Falls back to using the consensus entry, which is both less precise
-        # and unavailable with older tor versions. This assumes that the relay
-        # has ExitPolicyRejectPrivate set and won't include address-specific
-        # policies.
-        
-        consensusLine, relayAddress = None, None
-        
-        nsEntry = self.getConsensusEntry(relayFingerprint)
-        if nsEntry:
-          for line in nsEntry.split("\n"):
-            if line.startswith("r "):
-              # fetch the relay's public address, which we'll need for the
-              # ExitPolicyRejectPrivate policy entry
-              
-              lineComp = line.split(" ")
-              if len(lineComp) >= 7 and connections.isValidIpAddress(lineComp[6]):
-                relayAddress = lineComp[6]
-            elif line.startswith("p "):
-              consensusLine = line
-              break
-        
-        if consensusLine:
-          acceptance, ports = consensusLine.split(" ")[1:]
-          
-          # starts with a reject-all for whitelists and accept-all for blacklists
-          if acceptance == "accept":
-            result = ExitPolicy("reject *:*", None)
-          else:
-            result = ExitPolicy("accept *:*", None)
-          
-          # adds each of the ports listed in the consensus
-          for port in reversed(ports.split(",")):
-            result = ExitPolicy("%s *:%s" % (acceptance, port), result)
-          
-          # adds ExitPolicyRejectPrivate since this is the default
-          if relayAddress: result = ExitPolicy("reject %s" % relayAddress, result)
-          result = ExitPolicy("reject private", result)
+        result = descriptor.exit_policy
     
     self.connLock.release()
     
@@ -1855,238 +1764,3 @@ class Controller:
     
     self.connLock.release()
 
-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 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
-    """
-    
-    # cached summary string
-    self.summaryStr = None
-    
-    # sanitize the input a bit, cleaning up tabs and stripping quotes
-    ruleEntry = ruleEntry.replace("\\t", " ").replace("\"", "")
-    
-    self.ruleEntry = ruleEntry
-    self.nextRule = nextRule
-    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":
-      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)
-      
-      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):
-    """
-    Provides true if the policy allows exiting whatsoever, false otherwise.
-    """
-    
-    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 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 __iter__(self):
-    return ExitPolicyIterator(self)
-  
-  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
-





More information about the tor-commits mailing list