commit 3232ce527da8b95cb78f3a52b19ad0634e1cd9da Author: Damian Johnson atagar@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 -