tor-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
July 2012
- 14 participants
- 949 discussions
commit 607e32a3cf3d69ed185ecee4e62f55e7101d587a
Author: Sathyanarayanan Gunasekaran <gsathya.ceg(a)gmail.com>
Date: Fri Mar 23 14:10:54 2012 +0530
Parse "private" alias better
---
stem/exit_policy.py | 17 +++++++----------
1 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 15c2b2c..287b620 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -19,15 +19,6 @@ class ExitPolicyLine:
# 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]
@@ -121,7 +112,13 @@ class ExitPolicy:
self._policies = []
def add(self, ruleEntry):
- self._policies.append(ExitPolicyLine(ruleEntry))
+ # checks for the private alias (which expands this to a chain of entries)
+ if "private" in ruleEntry.lower():
+ for addr in PRIVATE_IP_RANGES:
+ newEntry = ruleEntry.replace("private", addr)
+ self._policies.append(ExitPolicyLine(newEntry))
+ else:
+ self._policies.append(ExitPolicyLine(ruleEntry))
def isExitingAllowed(self):
"""
1
0
commit 0a1c6238cc95f37a1b26369247d5d9876ba2b804
Author: Sathyanarayanan Gunasekaran <gsathya.ceg(a)gmail.com>
Date: Thu May 17 22:19:10 2012 +0530
Add ExitPolicy.get_summary()
This provides a summary description of the policy chain
similar to the consensus.
---
stem/exit_policy.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 0a51f5f..dcee159 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -3,9 +3,13 @@ Tor Exit Policy information and requirements for its features. These can be
easily parsed and compared, for instance...
>>> exit_policies = stem.exit_policy.ExitPolicy()
+>>> exit_policies.add("accept *:80")
+>>> exit_policies.add("accept *:443")
>>> exit_policies.add("reject *:*")
>>> print exit_policies
-reject *:*
+accept *:80 , accept *:443, reject *:*
+>>> print exit_policies.get_summary()
+accept 80, 443
ExitPolicyLine - Single rule from the exit policy
|- __str__ - string representation
@@ -16,7 +20,9 @@ ExitPolicy - List of ExitPolicyLine objects
|- __iter__ - ExitPolicyLine entries for the exit policy
|- check - check if exiting to this ip is allowed
|- add - add new rule to the exit policy
- +- isExitingAllowed - check if exit node
+ |- get_summary - provides a summary description of the policy chain
+ +- is_exiting_allowed - check if exit node
+
"""
# ip address ranges substituted by the 'private' keyword
@@ -140,6 +146,7 @@ class ExitPolicy:
ExitPolicy constructor
"""
self._policies = []
+ self.summary = ""
def add(self, rule_entry):
"""
@@ -156,7 +163,76 @@ class ExitPolicy:
self._policies.append(ExitPolicyLine(new_entry))
else:
self._policies.append(ExitPolicyLine(rule_entry))
-
+
+ def get_summary(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.
+ """
+
+ if not self.summary:
+ # determines if we're a whitelist or blacklist
+ is_whitelist = False # default in case we don't have a catch-all policy at the end
+
+ for policy in self._policies:
+ if policy.is_ip_wildcard and policy.is_port_wildcard:
+ is_whitelist = not policy.is_accept
+ break
+
+ # Iterates over the policys 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 policy.
+
+ display_ports, skip_ports = [], []
+
+ for policy in self._policies:
+ if not policy.is_ip_wildcard: continue
+
+ if policy.min_port == policy.max_port:
+ port_range = [policy.min_port]
+ else:
+ port_range = range(policy.min_port, policy.max_port + 1)
+
+ for port in port_range:
+ if port in skip_ports: continue
+
+ # if accept + whitelist or reject + blacklist then add
+ if policy.is_accept == is_whitelist:
+ display_ports.append(port)
+
+ # all further entries with this port are to be ignored
+ skip_ports.append(port)
+
+ # gets a list of the port ranges
+ if display_ports:
+ display_ranges, temp_range = [], []
+ display_ports.sort()
+ display_ports.append(None) # ending item to include last range in loop
+
+ for port in display_ports:
+ if not temp_range or temp_range[-1] + 1 == port:
+ temp_range.append(port)
+ else:
+ if len(temp_range) > 1:
+ display_ranges.append("%i-%i" % (temp_range[0], temp_range[-1]))
+ else:
+ display_ranges.append(str(temp_range[0]))
+
+ temp_range = [port]
+ else:
+ # everything for the inverse
+ is_whitelist = not is_whitelist
+ display_ranges = ["1-65535"]
+
+ # constructs the summary string
+ label_prefix = "accept " if is_whitelist else "reject "
+
+ self.summary = (label_prefix + ", ".join(display_ranges)).strip()
+
+ return self.summary
+
def is_exiting_allowed(self):
"""
Provides true if the policy allows exiting whatsoever, false otherwise.
@@ -188,4 +264,4 @@ class ExitPolicy:
Provides the string used to construct the Exit Policy
"""
return ' , '.join([str(policy) for policy in self._policies])
-
+
1
0
commit 4d88884b0cfdc98ca4c4741d4e1073267418038c
Author: Sathyanarayanan Gunasekaran <gsathya.ceg(a)gmail.com>
Date: Fri Mar 23 15:18:44 2012 +0530
Add doc and fix whitespace
---
stem/exit_policy.py | 303 +++++++++++++++++++++++++++++----------------------
1 files changed, 171 insertions(+), 132 deletions(-)
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 287b620..0a51f5f 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -1,152 +1,191 @@
+"""
+Tor Exit Policy information and requirements for its features. These can be
+easily parsed and compared, for instance...
+
+>>> exit_policies = stem.exit_policy.ExitPolicy()
+>>> exit_policies.add("reject *:*")
+>>> print exit_policies
+reject *:*
+
+ExitPolicyLine - Single rule from the exit policy
+ |- __str__ - string representation
+ +- check - check if exiting to this ip is allowed
+
+ExitPolicy - List of ExitPolicyLine objects
+ |- __str__ - string representation
+ |- __iter__ - ExitPolicyLine entries for the exit policy
+ |- check - check if exiting to this ip is allowed
+ |- add - add new rule to the exit policy
+ +- isExitingAllowed - check if exit node
+"""
+
# 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 ExitPolicyLine:
- def __init__(self, ruleEntry):
- # sanitize the input a bit, cleaning up tabs and stripping quotes
- ruleEntry = ruleEntry.replace("\\t", " ").replace("\"", "")
-
- 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")
-
- 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
+ """
+ Single rule from the user's exit policy. These are chained together to form
+ complete policies.
+ """
- # 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"
+ def __init__(self, rule_entry):
+ """
+ Exit Policy line constructor.
+ """
+ # sanitize the input a bit, cleaning up tabs and stripping quotes
+ rule_entry = rule_entry.replace("\\t", " ").replace("\"", "")
- if self.isIpWildcard:
- ipLabel = "*"
- elif self.ipMask != 32:
- ipLabel = "%s/%i" % (self.ipAddress, self.ipMask)
- else: ipLabel = self.ipAddress
+ self.rule_entry = rule_entry
+ self.is_accept = rule_entry.startswith("accept")
- if self.isPortWildcard:
- portLabel = "*"
- elif self.minPort != self.maxPort:
- portLabel = "%i-%i" % (self.minPort, self.maxPort)
- else: portLabel = str(self.minPort)
+ # strips off "accept " or "reject " and extra spaces
+ rule_entry = rule_entry[7:].replace(" ", "")
- myPolicy = "%s %s:%s" % (acceptanceLabel, ipLabel, portLabel)
- return myPolicy
+ # split ip address (with mask if provided) and port
+ if ":" in rule_entry: entry_ip, entry_port = rule_entry.split(":", 1)
+ else: entry_ip, entry_port = rule_entry, "*"
- def check(self, ipAddress, port):
- """
- Checks if the rule chain allows exiting to this address, returning true if
- so and false otherwise.
- """
+ # sets the ip address component
+ self.is_ip_wildcard = entry_ip == "*" or entry_ip.endswith("/0")
- port = int(port)
+ # separate host and mask
+ if "/" in entry_ip:
+ ip_comp = entry_ip.split("/", 1)
+ self.ip_address = ip_comp[0]
+ self.ip_mask = int(ip_comp[1])
+ else:
+ self.ip_address = entry_ip
+ self.ip_mask = 32
- # does the port check first since comparing ip masks is more work
- isPortMatch = self.isPortWildcard or (port >= self.minPort and port <= self.maxPort)
+ # constructs the binary address just in case of comparison with a mask
+ if self.ip_address != "*":
+ self.ip_address_bin = ""
+ for octet in self.ip_address.split("."):
+ # Converts the int to a binary string, padded with zeros. Source:
+ # http://www.daniweb.com/code/snippet216539.html
+ self.ip_address_bin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)])
+ else:
+ self.ip_address_bin = "0" * 32
+
+ # sets the port component
+ self.min_port, self.max_port = 0, 0
+ self.is_port_wildcard = entry_port == "*"
- if isPortMatch:
- isIpMatch = self.isIpWildcard or self.ipAddress == ipAddress
+ if entry_port != "*":
+ if "-" in entry_port:
+ port_comp = entry_port.split("-", 1)
+ self.min_port = int(port_comp[0])
+ self.max_port = int(port_comp[1])
+ else:
+ self.min_port = int(entry_port)
+ self.max_port = int(entry_port)
- # 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
+ def __str__(self):
+ # This provides the actual policy rather than the entry used to construct
+ # it so the 'private' keyword is expanded.
+
+ acceptance_label = "accept" if self.is_accept else "reject"
+
+ if self.is_ip_wildcard:
+ ip_label = "*"
+ elif self.ip_mask != 32:
+ ip_label = "%s/%i" % (self.ip_address, self.ip_mask)
+ else: ip_label = self.ip_address
+
+ if self.is_port_wildcard:
+ port_label = "*"
+ elif self.min_port != self.max_port:
+ port_label = "%i-%i" % (self.min_port, self.max_port)
+ else: port_label = str(self.min_port)
+
+ my_policy = "%s %s:%s" % (acceptance_label, ip_label, port_label)
+ return my_policy
+
+ def check(self, ip_address, 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
+ is_port_match = self.is_port_wildcard or (port >= self.min_port and port <= self.max_port)
+
+ if is_port_match:
+ is_ip_match = self.is_ip_wildcard or self.ip_address == ip_address
+
+ # expands the check to include the mask if it has one
+ if not is_ip_match and self.ip_mask != 32:
+ input_address_bin = ""
+ for octet in ip_address.split("."):
+ input_address_bin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)])
+
+ is_ip_match = self.ip_address_bin[:self.ip_mask] == input_address_bin[:self.ip_mask]
+
+ if is_ip_match: return self.is_accept
- # fell off the chain without a conclusion (shouldn't happen...)
- return False
+ # fell off the chain without a conclusion (shouldn't happen...)
+ return False
-
+
class ExitPolicy:
+ """
+ Provides a wrapper to ExitPolicyLine. This is iterable and can be stringified for
+ individual Exit Policy lines.
+ """
+
+ def __init__(self):
"""
- Single rule from the user's exit policy. These are chained together to form
- complete policies.
+ ExitPolicy constructor
"""
-
- def __init__(self):
- """
- Exit policy rule constructor.
- """
- self._policies = []
-
- def add(self, ruleEntry):
- # checks for the private alias (which expands this to a chain of entries)
- if "private" in ruleEntry.lower():
- for addr in PRIVATE_IP_RANGES:
- newEntry = ruleEntry.replace("private", addr)
- self._policies.append(ExitPolicyLine(newEntry))
- else:
- 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 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
+ self._policies = []
- return False
-
- def __iter__(self):
- for policy in self._policies:
- yield policy
+ def add(self, rule_entry):
+ """
+ This method is used to add an Exit Policy rule to the list of policies.
+
+ Arguments:
+ rule_entry (str) - exit policy rule in the format "accept|reject ADDR[/MASK][:PORT]"
+ ex - "accept 18.7.22.69:*"
+ """
+ # checks for the private alias (which expands this to a chain of entries)
+ if "private" in rule_entry.lower():
+ for addr in PRIVATE_IP_RANGES:
+ new_entry = rule_entry.replace("private", addr)
+ self._policies.append(ExitPolicyLine(new_entry))
+ else:
+ self._policies.append(ExitPolicyLine(rule_entry))
+
+ def is_exiting_allowed(self):
+ """
+ Provides true if the policy allows exiting whatsoever, false otherwise.
+ """
+ for policy in self._policies:
+ if policy.is_accept: return True
+ elif policy.is_ip_wildcard and policy.is_port_wildcard: return False
+
+ def check(self, ip_address, 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(ip_address, port): return True
+
+ return False
+
+ def __iter__(self):
+ """
+ Provides an ordered listing of policies in this Exit Policy
+ """
+ for policy in self._policies:
+ yield policy
+
+ def __str__(self):
+ """
+ Provides the string used to construct the Exit Policy
+ """
+ return ' , '.join([str(policy) for policy in self._policies])
- 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])
-
1
0

[stem/master] Make stem.util.connection.is_valid_port accept a list
by atagar@torproject.org 19 Jul '12
by atagar@torproject.org 19 Jul '12
19 Jul '12
commit b51d8da9bd2a6d4377019b37bbad768ad8ba17ca
Author: Sathyanarayanan Gunasekaran <gsathya.ceg(a)gmail.com>
Date: Tue Jun 26 08:48:23 2012 +0530
Make stem.util.connection.is_valid_port accept a list
Iterate through the list and check if each item is a valid
port or not.
---
stem/util/connection.py | 9 +++++++--
1 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/stem/util/connection.py b/stem/util/connection.py
index 03dced9..13f2d47 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -65,13 +65,18 @@ def is_valid_port(entry, allow_zero = False):
"""
Checks if a string or int is a valid port number.
- :param str,int entry: string or integer to be checked
+ :param list, str, int entry: string, integer or list to be checked
:param bool allow_zero: accept port number of zero (reserved by defintion)
:returns: True if input is an integer and within the valid port range, False otherwise
"""
- if isinstance(entry, str):
+ if isinstance(entry, list):
+ for port in entry:
+ if not is_valid_port(port):
+ return False
+
+ elif isinstance(entry, str):
if not entry.isdigit():
return False
elif entry[0] == "0" and len(entry) > 1:
1
0
commit a8b654e046e143bb89c40021c0602dd9510912fe
Author: Sathyanarayanan Gunasekaran <gsathya.ceg(a)gmail.com>
Date: Mon Jun 11 23:27:47 2012 +0530
Add MicrodescriptorExitPolicy
This class has four methods -
add() - which is the public method that the user calls to add
exit rules or policy. If a policy is added, then this instance
can't be changed further. Rules can be added till we we get a
wildcard('*') as a rule.
add_rule() - parses the rule
add_policy() - parses the policy
__str__() - creates a string representation of the policy
---
stem/exit_policy.py | 217 ++++++++++++++++++++++++++++++++++++---------------
1 files changed, 155 insertions(+), 62 deletions(-)
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index dcee159..bae183c 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -170,69 +170,66 @@ class ExitPolicy:
consensus. This excludes entries that don't cover all ips, and is either
a whitelist or blacklist policy based on the final entry.
"""
-
- if not self.summary:
- # determines if we're a whitelist or blacklist
- is_whitelist = False # default in case we don't have a catch-all policy at the end
-
- for policy in self._policies:
- if policy.is_ip_wildcard and policy.is_port_wildcard:
- is_whitelist = not policy.is_accept
- break
-
- # Iterates over the policys 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 policy.
-
- display_ports, skip_ports = [], []
-
- for policy in self._policies:
- if not policy.is_ip_wildcard: continue
-
- if policy.min_port == policy.max_port:
- port_range = [policy.min_port]
- else:
- port_range = range(policy.min_port, policy.max_port + 1)
-
- for port in port_range:
- if port in skip_ports: continue
-
- # if accept + whitelist or reject + blacklist then add
- if policy.is_accept == is_whitelist:
- display_ports.append(port)
-
- # all further entries with this port are to be ignored
- skip_ports.append(port)
+
+ # determines if we're a whitelist or blacklist
+ is_whitelist = False # default in case we don't have a catch-all policy at the end
+
+ for policy in self._policies:
+ if policy.is_ip_wildcard and policy.is_port_wildcard:
+ is_whitelist = not policy.is_accept
+ break
+
+ # Iterates over the policys 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 policy.
+
+ display_ports, skip_ports = [], []
+
+ for policy in self._policies:
+ if not policy.is_ip_wildcard: continue
+
+ if policy.min_port == policy.max_port:
+ port_range = [policy.min_port]
+ else:
+ port_range = range(policy.min_port, policy.max_port + 1)
+
+ for port in port_range:
+ if port in skip_ports: continue
+
+ # if accept + whitelist or reject + blacklist then add
+ if policy.is_accept == is_whitelist:
+ display_ports.append(port)
- # gets a list of the port ranges
- if display_ports:
- display_ranges, temp_range = [], []
- display_ports.sort()
- display_ports.append(None) # ending item to include last range in loop
-
- for port in display_ports:
- if not temp_range or temp_range[-1] + 1 == port:
- temp_range.append(port)
+ # all further entries with this port are to be ignored
+ skip_ports.append(port)
+
+ # gets a list of the port ranges
+ if display_ports:
+ display_ranges, temp_range = [], []
+ display_ports.sort()
+ display_ports.append(None) # ending item to include last range in loop
+
+ for port in display_ports:
+ if not temp_range or temp_range[-1] + 1 == port:
+ temp_range.append(port)
+ else:
+ if len(temp_range) > 1:
+ display_ranges.append("%i-%i" % (temp_range[0], temp_range[-1]))
else:
- if len(temp_range) > 1:
- display_ranges.append("%i-%i" % (temp_range[0], temp_range[-1]))
- else:
- display_ranges.append(str(temp_range[0]))
-
- temp_range = [port]
- else:
- # everything for the inverse
- is_whitelist = not is_whitelist
- display_ranges = ["1-65535"]
-
- # constructs the summary string
- label_prefix = "accept " if is_whitelist else "reject "
-
- self.summary = (label_prefix + ", ".join(display_ranges)).strip()
-
- return self.summary
-
+ display_ranges.append(str(temp_range[0]))
+
+ temp_range = [port]
+ else:
+ # everything for the inverse
+ is_whitelist = not is_whitelist
+ display_ranges = ["1-65535"]
+
+ # constructs the summary string
+ label_prefix = "accept " if is_whitelist else "reject "
+
+ self.summary = (label_prefix + ", ".join(display_ranges)).strip()
+
def is_exiting_allowed(self):
"""
Provides true if the policy allows exiting whatsoever, false otherwise.
@@ -264,4 +261,100 @@ class ExitPolicy:
Provides the string used to construct the Exit Policy
"""
return ' , '.join([str(policy) for policy in self._policies])
-
+
+
+class MicrodescriptorExitPolicy:
+ def __init__(self):
+ self.ports = []
+ self.policy = None
+ # assume it's an accepted list of ports
+ self.is_accept = True
+ self.is_policy = False
+
+ def __str__(self):
+
+ if self.policy:
+ return self.policy
+
+ self.ports.sort()
+
+ port_range = []
+ start_port = self.ports[0]
+
+ for id, port in enumerate(self.ports):
+ if port+1 == self.ports[id+1]:
+ end_port = port
+ else:
+ if start_port == end_port:
+ port_range.append(start_port)
+ else:
+ port_range.append("%d-%d" % start_port, end_port)
+ start_port = port+1
+
+ ports = ','.join(port_range)
+
+ if self.is_accept: policy = 'accept %s' % ports
+ else: policy = 'reject %s' % ports
+
+ if len(policy) > 1000:
+ #raise PolicyLengthException
+ pass
+
+ # it's a policy, no more changes to the rules
+ if self.is_policy:
+ self.policy = policy
+
+ return policy
+
+ def add(self, rule):
+ # it's a polciy, we can't add more rules
+ if self.is_policy:
+ #raise PolicyException
+ pass
+
+ # sanitize the input a bit, cleaning up tabs and stripping quotes
+ rule = rule.replace("\\t", " ").replace("\"", "")
+
+ if ',' in rule:
+ self.add_policy(rule)
+ else:
+ self.add_rule(rule)
+
+ def add_policy(self):
+ self.is_policy = True
+ self.is_accept = rule.startswith("accept")
+
+ # remove "accept " or "reject "
+ ports = rule[7:]
+
+ for ports in rule.split(','):
+ if '-' in port:
+ start_port, end_port = ports.split('-', 1)
+ for port in range(int(start_port), int(end_port)):
+ self.ports.append(int(port))
+ else:
+ self.ports.append(int(ports))
+
+ def add_rule(self):
+ is_accept = rule.startswith("accept")
+ # remove "accept " or "reject "
+ rule = rule[7:]
+
+ # parse 'ip:port' and 'port'
+ if ':' in rule: ports = rule.split(":", 1)[1]
+ else: ports = rule
+
+ # last entry
+ if ports is "*":
+ if self.is_accept is not is_accept:
+ self.is_accept = not is_accept
+ self.policy = True
+ else:
+ # we can't have accept 80 and then accept *
+ # raise PolicyException
+ pass
+ # it's a rule
+ else:
+ self.is_accept = is_accept
+ self.ports.append(int(ports))
+
1
0

[stem/master] Remove MicrodescriptorExitPolicy, add validation and exception
by atagar@torproject.org 19 Jul '12
by atagar@torproject.org 19 Jul '12
19 Jul '12
commit 289063fefe8857c6ca10e95d2a3a90fcbd925f51
Author: Sathyanarayanan Gunasekaran <gsathya.ceg(a)gmail.com>
Date: Tue Jun 26 08:49:53 2012 +0530
Remove MicrodescriptorExitPolicy, add validation and exception
The micro desc exit policy is currently useless until we figure
out what it's used for.
Validate all the ip address and ports. Raise exception if wrong
---
stem/exit_policy.py | 115 +++++++-------------------------------------------
1 files changed, 16 insertions(+), 99 deletions(-)
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index bae183c..314110c 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -25,6 +25,9 @@ ExitPolicy - List of ExitPolicyLine objects
"""
+import stem.util.connection
+
+
# 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")
@@ -65,6 +68,8 @@ class ExitPolicyLine:
# constructs the binary address just in case of comparison with a mask
if self.ip_address != "*":
+ if not (stem.util.connection.is_valid_ip_address(self.ip_address) and stem.util.connection.is_valid_ipv6_address(self.ip_address)):
+ raise ExitPolicyError
self.ip_address_bin = ""
for octet in self.ip_address.split("."):
# Converts the int to a binary string, padded with zeros. Source:
@@ -80,9 +85,13 @@ class ExitPolicyLine:
if entry_port != "*":
if "-" in entry_port:
port_comp = entry_port.split("-", 1)
+ if not stem.util.connection.is_valid_port(port_comp):
+ raise ExitPolicyError
self.min_port = int(port_comp[0])
self.max_port = int(port_comp[1])
else:
+ if not stem.util.connection.is_valid_port(entry_port):
+ raise ExitPolicyError
self.min_port = int(entry_port)
self.max_port = int(entry_port)
@@ -180,7 +189,7 @@ class ExitPolicy:
break
# Iterates over the policys and adds the the ports we'll return (ie, allows
- # if a whitelist and rejects if a blacklist). Reguardless of a port's
+ # if a whitelist and rejects if a blacklist). Regardless of a port's
# allow/reject policy, all further entries with that port are ignored since
# policies respect the first matching policy.
@@ -247,7 +256,7 @@ class ExitPolicy:
for policy in self._policies:
if policy.check(ip_address, port): return True
- return False
+ return False
def __iter__(self):
"""
@@ -260,101 +269,9 @@ class ExitPolicy:
"""
Provides the string used to construct the Exit Policy
"""
- return ' , '.join([str(policy) for policy in self._policies])
-
-
-class MicrodescriptorExitPolicy:
- def __init__(self):
- self.ports = []
- self.policy = None
- # assume it's an accepted list of ports
- self.is_accept = True
- self.is_policy = False
-
- def __str__(self):
-
- if self.policy:
- return self.policy
-
- self.ports.sort()
-
- port_range = []
- start_port = self.ports[0]
-
- for id, port in enumerate(self.ports):
- if port+1 == self.ports[id+1]:
- end_port = port
- else:
- if start_port == end_port:
- port_range.append(start_port)
- else:
- port_range.append("%d-%d" % start_port, end_port)
- start_port = port+1
-
- ports = ','.join(port_range)
-
- if self.is_accept: policy = 'accept %s' % ports
- else: policy = 'reject %s' % ports
-
- if len(policy) > 1000:
- #raise PolicyLengthException
- pass
+ return ', '.join([str(policy) for policy in self._policies])
- # it's a policy, no more changes to the rules
- if self.is_policy:
- self.policy = policy
-
- return policy
-
- def add(self, rule):
- # it's a polciy, we can't add more rules
- if self.is_policy:
- #raise PolicyException
- pass
-
- # sanitize the input a bit, cleaning up tabs and stripping quotes
- rule = rule.replace("\\t", " ").replace("\"", "")
-
- if ',' in rule:
- self.add_policy(rule)
- else:
- self.add_rule(rule)
-
- def add_policy(self):
- self.is_policy = True
- self.is_accept = rule.startswith("accept")
-
- # remove "accept " or "reject "
- ports = rule[7:]
-
- for ports in rule.split(','):
- if '-' in port:
- start_port, end_port = ports.split('-', 1)
- for port in range(int(start_port), int(end_port)):
- self.ports.append(int(port))
- else:
- self.ports.append(int(ports))
-
- def add_rule(self):
- is_accept = rule.startswith("accept")
- # remove "accept " or "reject "
- rule = rule[7:]
-
- # parse 'ip:port' and 'port'
- if ':' in rule: ports = rule.split(":", 1)[1]
- else: ports = rule
-
- # last entry
- if ports is "*":
- if self.is_accept is not is_accept:
- self.is_accept = not is_accept
- self.policy = True
- else:
- # we can't have accept 80 and then accept *
- # raise PolicyException
- pass
- # it's a rule
- else:
- self.is_accept = is_accept
- self.ports.append(int(ports))
-
+class ExitPolicyError(Exception):
+ """
+ Base error for exit policy issues.
+ """
1
0
commit 03f625b328ba9148909dc523196a16f4356b9dc2
Author: Sathyanarayanan Gunasekaran <gsathya.ceg(a)gmail.com>
Date: Tue Jun 26 08:51:24 2012 +0530
Add unit test for exit_policy
Test the validation and parsing of the exit policy
---
test/unit/exit_policy.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 58 insertions(+), 0 deletions(-)
diff --git a/test/unit/exit_policy.py b/test/unit/exit_policy.py
new file mode 100644
index 0000000..d4fd7e0
--- /dev/null
+++ b/test/unit/exit_policy.py
@@ -0,0 +1,58 @@
+"""
+Unit tests for the stem.exit_policy.ExitPolicy parsing and class.
+"""
+
+import unittest
+import stem.exit_policy
+import stem.util.system
+
+import test.mocking as mocking
+
+class TestExitPolicy(unittest.TestCase):
+ def tearDown(self):
+ pass
+
+ def test_parsing(self):
+ """
+ Tests parsing by the ExitPolicy class constructor.
+ """
+
+ exit_policies = stem.exit_policy.ExitPolicy()
+ exit_policies.add("accept *:80")
+ exit_policies.add("accept *:443")
+ exit_policies.add("reject *:*")
+ self.assertEqual(str(exit_policies), "accept *:80, accept *:443, reject *:*")
+
+ exit_policies = stem.exit_policy.ExitPolicy()
+
+ # check ip address
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 256.255.255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -10.255.255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.-10.255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.255.-10.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -255.255.255.-10:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept a.b.c.d:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept :80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept ...:80")
+
+ # check input string
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "foo 255.255.255.255:80")
+
+ # check ports
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:0001")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:0")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:-1")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:+1")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:+1-1")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:a")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:70000")
+
+ def test_check(self):
+ """
+ Tests if exiting to this ip is allowed.
+ """
+ pass
1
0
commit fdb140763097c67c9612c5e7090534330e89a68f
Author: Sathyanarayanan Gunasekaran <gsathya.ceg(a)gmail.com>
Date: Mon Jul 2 19:19:56 2012 +0530
Add exit policy test to run_tests
---
run_tests.py | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index cff77f8..96622e8 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -33,6 +33,7 @@ import test.unit.util.proc
import test.unit.util.system
import test.unit.util.tor_tools
import test.unit.version
+import test.unit.exit_policy
import test.integ.connection.authentication
import test.integ.connection.connect
import test.integ.control.base_controller
@@ -117,6 +118,7 @@ UNIT_TESTS = (
test.unit.response.protocolinfo.TestProtocolInfoResponse,
test.unit.response.authchallenge.TestAuthChallengeResponse,
test.unit.connection.authentication.TestAuthenticate,
+ test.unit.exit_policy.TestExitPolicy
)
INTEG_TESTS = (
1
0
commit ebec5fbd4a8080b1003ae87681cd5b804f5a81d7
Author: Damian Johnson <atagar(a)torproject.org>
Date: Fri Jul 13 09:54:42 2012 -0700
Utilities for IPv4 and IPv6 addresses
Adding the utilities that I need for the ExitPolicy to handle and translate
addresses and their masks. Pity that these aren't provided by the python
builtins. The IPy package seems to do it but this isn't worth adding a new
dependency.
---
run_tests.py | 4 +-
stem/util/connection.py | 152 +++++++++++++++++++++++++++++++++++++++++-
test/unit/util/connection.py | 77 +++++++++++++++++++++
3 files changed, 230 insertions(+), 3 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 96622e8..23f993e 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -32,8 +32,8 @@ import test.unit.util.enum
import test.unit.util.proc
import test.unit.util.system
import test.unit.util.tor_tools
-import test.unit.version
import test.unit.exit_policy
+import test.unit.version
import test.integ.connection.authentication
import test.integ.connection.connect
import test.integ.control.base_controller
@@ -109,6 +109,7 @@ UNIT_TESTS = (
test.unit.descriptor.reader.TestDescriptorReader,
test.unit.descriptor.server_descriptor.TestServerDescriptor,
test.unit.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor,
+ test.unit.exit_policy.TestExitPolicy,
test.unit.version.TestVersion,
test.unit.response.control_message.TestControlMessage,
test.unit.response.control_line.TestControlLine,
@@ -118,7 +119,6 @@ UNIT_TESTS = (
test.unit.response.protocolinfo.TestProtocolInfoResponse,
test.unit.response.authchallenge.TestAuthChallengeResponse,
test.unit.connection.authentication.TestAuthenticate,
- test.unit.exit_policy.TestExitPolicy
)
INTEG_TESTS = (
diff --git a/stem/util/connection.py b/stem/util/connection.py
index 13f2d47..a70389e 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -3,6 +3,18 @@ Connection and networking based utility functions. This will likely be expanded
later to have all of `arm's functions
<https://gitweb.torproject.org/arm.git/blob/HEAD:/src/util/connections.py>`_,
but for now just moving the parts we need.
+
+::
+
+ is_valid_ip_address - checks if a string is a valid IPv4 address
+ is_valid_ip_ipv6_address - checks if a string is a valid IPv6 address
+ is_valid_port - checks if something is a valid representation for a port
+ expand_ipv6_address - provides an IPv6 address with its collapsed portions expanded
+ get_mask - provides the mask representation for a given number of bits
+ get_masked_bits - provides the number of bits represented by a mask
+ get_mask_ipv6 - provides the IPv6 mask representation for a given number of bits
+ get_binary - provides the binary representation for an integer with padding
+ get_address_binary - provides the binary representation for an address
"""
import os
@@ -65,7 +77,7 @@ def is_valid_port(entry, allow_zero = False):
"""
Checks if a string or int is a valid port number.
- :param list, str, int entry: string, integer or list to be checked
+ :param list,str,int entry: string, integer or list to be checked
:param bool allow_zero: accept port number of zero (reserved by defintion)
:returns: True if input is an integer and within the valid port range, False otherwise
@@ -88,6 +100,144 @@ def is_valid_port(entry, allow_zero = False):
return entry > 0 and entry < 65536
+def expand_ipv6_address(address):
+ """
+ Expands abbreviated IPv6 addresses to their full colon separated hex format.
+ For instance...
+
+ ::
+
+ >>> expand_ipv6_address("2001:db8::ff00:42:8329")
+ "2001:0db8:0000:0000:0000:ff00:0042:8329"
+
+ >>> expand_ipv6_address("::")
+ "0000:0000:0000:0000:0000:0000:0000:0000"
+
+ :param str address: IPv6 address to be expanded
+
+ :raises: ValueError if the address can't be expanded due to being malformed
+ """
+
+ if not is_valid_ipv6_address(address):
+ raise ValueError("'%s' isn't a valid IPv6 address" % address)
+
+ # expands collapsed groupings, there can only be a single '::' in a valid
+ # address
+ if "::" in address:
+ missing_groups = 7 - address.count(":")
+ address = address.replace("::", "::" + ":" * missing_groups)
+
+ # inserts missing zeros
+ for i in xrange(8):
+ start = i * 5
+ end = address.index(":", start) if i != 7 else len(address)
+ missing_zeros = 4 - (end - start)
+
+ if missing_zeros > 0:
+ address = address[:start] + "0" * missing_zeros + address[start:]
+
+ return address
+
+def get_mask(bits):
+ """
+ Provides the IPv4 mask for a given number of bits, in the dotted-quad format.
+
+ :param int bits: number of bits to be converted
+
+ :returns: str with the subnet mask representation for this many bits
+
+ :raises: ValueError if given a number of bits outside the range of 0-32
+ """
+
+ if bits > 32 or bits < 0:
+ raise ValueError("A mask can only be 0-32 bits, got %i" % bits)
+
+ # get the binary representation of the mask
+ mask_bin = get_binary(2 ** bits - 1, 32)[::-1]
+
+ # breaks it into eight character groupings
+ octets = [mask_bin[8 * i : 8 * (i + 1)] for i in xrange(4)]
+
+ # converts each octet into its integer value
+ return ".".join([str(int(octet, 2)) for octet in octets])
+
+def get_masked_bits(mask):
+ """
+ Provides the number of bits that an IPv4 subnet mask represents. Note that
+ not all masks can be represented by a bit count.
+
+ :param str mask: mask to be converted
+
+ :returns: int with the number of bits represented by the mask
+
+ :raises: ValueError if the mask is invalid or can't be converted
+ """
+
+ if not is_valid_ip_address(mask):
+ raise ValueError("'%s' is an invalid subnet mask" % mask)
+
+ # converts octets to binary representatino
+ mask_bin = get_address_binary(mask)
+ mask_match = re.match("^(1*)(0*)$", mask_bin)
+
+ if mask_match:
+ return 32 - len(mask_match.groups()[1])
+ else:
+ raise ValueError("Unable to convert mask to a bit count: %s" % mask)
+
+def get_mask_ipv6(bits):
+ """
+ Provides the IPv6 mask for a given number of bits, in the hex colon-delimited
+ format.
+
+ :param int bits: number of bits to be converted
+
+ :returns: str with the subnet mask representation for this many bits
+
+ :raises: ValueError if given a number of bits outside the range of 0-128
+ """
+
+ if bits > 128 or bits < 0:
+ raise ValueError("A mask can only be 0-128 bits, got %i" % bits)
+
+ # get the binary representation of the mask
+ mask_bin = get_binary(2 ** bits - 1, 128)[::-1]
+
+ # breaks it into sixteen character groupings
+ groupings = [mask_bin[16 * i : 16 * (i + 1)] for i in xrange(8)]
+
+ # converts each group into its hex value
+ return ":".join(["%04x" % int(group, 2) for group in groupings]).upper()
+
+def get_binary(value, bits):
+ """
+ Provides the given value as a binary string, padded with zeros to the given
+ number of bits.
+
+ :param int value: value to be converted
+ :param int bits: number of bits to pad to
+ """
+
+ # http://www.daniweb.com/code/snippet216539.html
+ return "".join([str((value >> y) & 1) for y in range(bits - 1, -1, -1)])
+
+def get_address_binary(address):
+ """
+ Provides the binary value for an IPv4 or IPv6 address.
+
+ :returns: str with the binary prepresentation of this address
+
+ :raises: ValueError if address is neither an IPv4 nor IPv6 address
+ """
+
+ if is_valid_ip_address(address):
+ return "".join([get_binary(int(octet), 8) for octet in address.split(".")])
+ elif is_valid_ipv6_address(address):
+ address = expand_ipv6_address(address)
+ return "".join([get_binary(int(grouping, 16), 16) for grouping in address.split(":")])
+ else:
+ raise ValueError("'%s' is neither an IPv4 or IPv6 address" % address)
+
def hmac_sha256(key, msg):
"""
Generates a sha256 digest using the given key and message.
diff --git a/test/unit/util/connection.py b/test/unit/util/connection.py
index b4c7223..4df0298 100644
--- a/test/unit/util/connection.py
+++ b/test/unit/util/connection.py
@@ -76,4 +76,81 @@ class TestConnection(unittest.TestCase):
self.assertTrue(stem.util.connection.is_valid_port(0, allow_zero = True))
self.assertTrue(stem.util.connection.is_valid_port("0", allow_zero = True))
+
+ def test_expand_ipv6_address(self):
+ """
+ Checks the expand_ipv6_address function.
+ """
+
+ test_values = {
+ "2001:db8::ff00:42:8329": "2001:0db8:0000:0000:0000:ff00:0042:8329",
+ "::": "0000:0000:0000:0000:0000:0000:0000:0000",
+ "::1": "0000:0000:0000:0000:0000:0000:0000:0001",
+ "1::1": "0001:0000:0000:0000:0000:0000:0000:0001",
+ }
+
+ for test_arg, expected in test_values.items():
+ self.assertEquals(expected, stem.util.connection.expand_ipv6_address(test_arg))
+
+ self.assertRaises(ValueError, stem.util.connection.expand_ipv6_address, "127.0.0.1")
+
+ def test_get_mask(self):
+ """
+ Checks the get_mask function.
+ """
+
+ self.assertEquals("255.255.255.255", stem.util.connection.get_mask(32))
+ self.assertEquals("255.255.255.248", stem.util.connection.get_mask(29))
+ self.assertEquals("255.255.254.0", stem.util.connection.get_mask(23))
+ self.assertEquals("0.0.0.0", stem.util.connection.get_mask(0))
+
+ self.assertRaises(ValueError, stem.util.connection.get_mask, -1)
+ self.assertRaises(ValueError, stem.util.connection.get_mask, 33)
+
+ def test_get_masked_bits(self):
+ """
+ Checks the get_masked_bits function.
+ """
+
+ self.assertEquals(32, stem.util.connection.get_masked_bits("255.255.255.255"))
+ self.assertEquals(29, stem.util.connection.get_masked_bits("255.255.255.248"))
+ self.assertEquals(23, stem.util.connection.get_masked_bits("255.255.254.0"))
+ self.assertEquals(0, stem.util.connection.get_masked_bits("0.0.0.0"))
+
+ self.assertRaises(ValueError, stem.util.connection.get_masked_bits, "blarg")
+ self.assertRaises(ValueError, stem.util.connection.get_masked_bits, "255.255.0.255")
+
+ def test_get_mask_ipv6(self):
+ """
+ Checks the get_mask_ipv6 function.
+ """
+
+ self.assertEquals("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", stem.util.connection.get_mask_ipv6(128))
+ self.assertEquals("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFE:0000", stem.util.connection.get_mask_ipv6(111))
+ self.assertEquals("0000:0000:0000:0000:0000:0000:0000:0000", stem.util.connection.get_mask_ipv6(0))
+
+ self.assertRaises(ValueError, stem.util.connection.get_mask_ipv6, -1)
+ self.assertRaises(ValueError, stem.util.connection.get_mask, 129)
+
+ def test_get_address_binary(self):
+ """
+ Checks the get_address_binary function.
+ """
+
+ test_values = {
+ "0.0.0.0": "00000000000000000000000000000000",
+ "1.2.3.4": "00000001000000100000001100000100",
+ "127.0.0.1": "01111111000000000000000000000001",
+ "255.255.255.255": "11111111111111111111111111111111",
+ "::": "0" * 128,
+ "::1": ("0" * 127) + "1",
+ "1::1": "0000000000000001" + ("0" * 111) + "1",
+ "2001:db8::ff00:42:8329": "00100000000000010000110110111000000000000000000000000000000000000000000000000000111111110000000000000000010000101000001100101001",
+ }
+
+ for test_arg, expected in test_values.items():
+ self.assertEquals(expected, stem.util.connection.get_address_binary(test_arg))
+
+ self.assertRaises(ValueError, stem.util.connection.get_address_binary, "")
+ self.assertRaises(ValueError, stem.util.connection.get_address_binary, "blarg")
1
0
commit 0c069727eb5337cc7c3f422387e3f4d0ad5caa9c
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jul 14 18:32:50 2012 -0700
ExitPolicyRule class
Adding a class that conforms with the dir-spec's exitpattern entity. Plus lots
'o unit tests that told me over and over and over again that I was being
stupid. Hopefully this version is at least kinda sorta close to being right...
---
run_tests.py | 6 +-
stem/exit_policy.py | 363 +++++++++++++++++++++++++++++++++----
stem/util/connection.py | 17 ++-
test/unit/exit_policy.py | 110 -----------
test/unit/exit_policy/__init__.py | 6 +
test/unit/exit_policy/policy.py | 107 +++++++++++
test/unit/exit_policy/rule.py | 327 +++++++++++++++++++++++++++++++++
7 files changed, 780 insertions(+), 156 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 23f993e..3b36046 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -32,7 +32,8 @@ import test.unit.util.enum
import test.unit.util.proc
import test.unit.util.system
import test.unit.util.tor_tools
-import test.unit.exit_policy
+import test.unit.exit_policy.policy
+import test.unit.exit_policy.rule
import test.unit.version
import test.integ.connection.authentication
import test.integ.connection.connect
@@ -109,7 +110,8 @@ UNIT_TESTS = (
test.unit.descriptor.reader.TestDescriptorReader,
test.unit.descriptor.server_descriptor.TestServerDescriptor,
test.unit.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor,
- test.unit.exit_policy.TestExitPolicy,
+ test.unit.exit_policy.rule.TestExitPolicyRule,
+ test.unit.exit_policy.policy.TestExitPolicy,
test.unit.version.TestVersion,
test.unit.response.control_message.TestControlMessage,
test.unit.response.control_line.TestControlLine,
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 33f5cc1..7040563 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -1,61 +1,342 @@
"""
-Tor Exit Policy information and requirements for its features. These can be
-easily parsed and compared, for instance...
+Representation of tor exit policies. These can be easily used to check if
+exiting to a destination is permissable or not. For instance...
->>> exit_policies = stem.exit_policy.ExitPolicy()
->>> exit_policies.add("accept *:80")
->>> exit_policies.add("accept *:443")
->>> exit_policies.add("reject *:*")
->>> print exit_policies
-accept *:80 , accept *:443, reject *:*
->>> print exit_policies.get_summary()
-accept 80, 443
->>> exit_policies.check("www.google.com", 80)
-True
+::
->>> microdesc_exit_policy = stem.exit_policy.MicrodescriptorExitPolicy("accept 80,443")
->>> print microdesc_exit_policy
-accept 80,443
->>> microdesc_exit_policy.check("www.google.com", 80)
-True
->>> microdesc_exit_policy.check(80)
-True
+ >>> exit_policies = stem.exit_policy.ExitPolicy()
+ >>> exit_policies.add("accept *:80")
+ >>> exit_policies.add("accept *:443")
+ >>> exit_policies.add("reject *:*")
+ >>> print exit_policies
+ accept *:80 , accept *:443, reject *:*
+ >>> print exit_policies.get_summary()
+ accept 80, 443
+ >>> exit_policies.check("www.google.com", 80)
+ True
+
+ >>> microdesc_exit_policy = stem.exit_policy.MicrodescriptorExitPolicy("accept 80,443")
+ >>> print microdesc_exit_policy
+ accept 80,443
+ >>> microdesc_exit_policy.check("www.google.com", 80)
+ True
+ >>> microdesc_exit_policy.check(80)
+ True
+
+::
+
+ ExitPolicyRule - Single rule of an exit policy
+ |- is_address_wildcard - checks if we'll accept any address for our type
+ |- is_port_wildcard - checks if we'll accept any port
+ |- is_match - checks if we match a given destination
+ +- __str__ - string representation for this rule
-ExitPolicyLine - Single rule from the exit policy
- |- __str__ - string representation
- +- check - check if exiting to this ip is allowed
+ ExitPolicyLine - Single rule from the exit policy
+ |- __str__ - string representation
+ +- check - check if exiting to this ip is allowed
-ExitPolicy - List of ExitPolicyLine objects
- |- __str__ - string representation
- |- __iter__ - ExitPolicyLine entries for the exit policy
- |- check - check if exiting to this ip is allowed
- |- add - add new rule to the exit policy
- |- get_summary - provides a summary description of the policy chain
- +- is_exiting_allowed - check if exit node
+ ExitPolicy - List of ExitPolicyLine objects
+ |- __str__ - string representation
+ |- __iter__ - ExitPolicyLine entries for the exit policy
+ |- check - check if exiting to this ip is allowed
+ |- add - add new rule to the exit policy
+ |- get_summary - provides a summary description of the policy chain
+ +- is_exiting_allowed - check if exit node
-MicrodescriptorExitPolicy - Microdescriptor exit policy
- |- check - check if exiting to this port is allowed
- |- ports - returns a list of ports
- |- is_accept - check if it's a list of accepted/rejected ports
- +- __str__ - return the summary
+ MicrodescriptorExitPolicy - Microdescriptor exit policy
+ |- check - check if exiting to this port is allowed
+ |- ports - returns a list of ports
+ |- is_accept - check if it's a list of accepted/rejected ports
+ +- __str__ - return the summary
"""
import stem.util.connection
+import stem.util.enum
+AddressType = stem.util.enum.Enum(("WILDCARD", "Wildcard"), ("IPv4", "IPv4"), ("IPv6", "IPv6"))
# 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")
+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 ExitPolicyRule:
+ """
+ Single rule from the user's exit policy. These rules are chained together to
+ form complete policies that describe where a relay will and will not allow
+ traffic to exit.
+
+ The format of these rules are formally described in the dir-spec as an
+ "exitpattern". Note that while these are similar to tor's man page entry for
+ ExitPolicies, it's not the exact same. An exitpattern is better defined and
+ scricter in what it'll accept. For instance, ports are not optional and it
+ does not contain the 'private' alias.
+
+ :var str rule: rule that we were created from
+ :var bool is_accept: indicates if exiting is allowed or disallowed
+
+ :var AddressType address_type: type of address that we have
+ :var str address: address that this rule is for
+ :var str mask: subnet mask for the address (ex. "255.255.255.0")
+ :var int masked_bits: number of bits the subnet mask represents, None if the mask can't have a bit representation
+
+ :var int min_port: lower end of the port range that we include (inclusive)
+ :var int max_port: upper end of the port range that we include (inclusive)
+
+ :param str rule: exit policy rule to be parsed
+
+ :raises: ValueError if input isn't a valid tor exit policy rule
+ """
+
+ # TODO: Exitpatterns are used everywhere except the torrc. This is fine for
+ # now, but we should add a subclass to handle those slight differences later
+ # if we want to provide the ability to parse torrcs.
+
+ def __init__(self, rule):
+ self.rule = rule
+
+ # policy ::= "accept" exitpattern | "reject" 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)
+
+ exitpattern = rule[6:]
+
+ if not exitpattern.startswith(" ") or (len(exitpattern) - 1 != len(exitpattern.lstrip())) :
+ raise ValueError("An exit policy should have a space separating its accept/reject from the exit pattern: %s" % rule)
+
+ exitpattern = exitpattern[1:]
+
+ if not ":" in exitpattern:
+ raise ValueError("An exitpattern must be of the form 'addrspec:portspec': %s" % rule)
+
+ addrspec, portspec = exitpattern.rsplit(":", 1)
+ self._addr_bin = self._mask_bin = None
+
+ # Parses the addrspec...
+ # addrspec ::= "*" | ip4spec | ip6spec
+
+ if "/" in addrspec:
+ self.address, addr_extra = addrspec.split("/", 1)
+ else:
+ self.address, addr_extra = addrspec, None
+
+ if addrspec == "*":
+ self.address_type = AddressType.WILDCARD
+ self.address = self.mask = self.masked_bits = None
+ elif stem.util.connection.is_valid_ip_address(self.address):
+ # ipv4spec ::= ip4 | ip4 "/" num_ip4_bits | ip4 "/" ip4mask
+ # ip4 ::= an IPv4 address in dotted-quad format
+ # ip4mask ::= an IPv4 mask in dotted-quad format
+ # num_ip4_bits ::= an integer between 0 and 32
+
+ self.address_type = AddressType.IPv4
+
+ if addr_extra is None:
+ self.mask = stem.util.connection.FULL_IPv4_MASK
+ self.masked_bits = 32
+ elif stem.util.connection.is_valid_ip_address(addr_extra):
+ # provided with an ip4mask
+ self.mask = addr_extra
+
+ try:
+ self.masked_bits = stem.util.connection.get_masked_bits(addr_extra)
+ except ValueError:
+ # mask can't be represented as a number of bits (ex. "255.255.0.255")
+ self.masked_bits = None
+ elif addr_extra.isdigit():
+ # provided with a num_ip4_bits
+ self.mask = stem.util.connection.get_mask(int(addr_extra))
+ self.masked_bits = int(addr_extra)
+ else:
+ raise ValueError("The '%s' isn't a mask nor number of bits: %s" % (addr_extra, rule))
+ elif self.address.startswith("[") and self.address.endswith("]") and \
+ stem.util.connection.is_valid_ipv6_address(self.address[1:-1]):
+ # ip6spec ::= ip6 | ip6 "/" num_ip6_bits
+ # ip6 ::= an IPv6 address, surrounded by square brackets.
+ # num_ip6_bits ::= an integer between 0 and 128
+
+ self.address = stem.util.connection.expand_ipv6_address(self.address[1:-1].upper())
+ self.address_type = AddressType.IPv6
+
+ if addr_extra is None:
+ self.mask = stem.util.connection.FULL_IPv6_MASK
+ self.masked_bits = 128
+ elif addr_extra.isdigit():
+ # provided with a num_ip6_bits
+ self.mask = stem.util.connection.get_mask_ipv6(int(addr_extra))
+ self.masked_bits = int(addr_extra)
+ else:
+ raise ValueError("The '%s' isn't a number of bits: %s" % (addr_extra, rule))
+ else:
+ raise ValueError("Address isn't a wildcard, IPv4, or IPv6 address: %s" % rule)
+
+ # Parses the portspec...
+ # portspec ::= "*" | port | port "-" port
+ # port ::= an integer between 1 and 65535, inclusive.
+ #
+ # Due to a tor bug the spec says that we should accept port of zero, but
+ # connections to port zero are never permitted.
+
+ if portspec == "*":
+ self.min_port, self.max_port = 1, 65535
+ elif portspec.isdigit():
+ # provided with a single port
+ if stem.util.connection.is_valid_port(portspec, allow_zero = True):
+ self.min_port = self.max_port = int(portspec)
+ else:
+ raise ValueError("'%s' isn't within a valid port range: %s" % (portspec, rule))
+ elif "-" in portspec:
+ # provided with a port range
+ port_comp = portspec.split("-", 1)
+
+ if stem.util.connection.is_valid_port(port_comp, allow_zero = True):
+ self.min_port = int(port_comp[0])
+ self.max_port = int(port_comp[1])
+
+ if self.min_port > self.max_port:
+ raise ValueError("Port range has a lower bound that's greater than its upper bound: %s" % rule)
+ else:
+ raise ValueError("Malformed port range: %s" % rule)
+ else:
+ raise ValueError("Port value isn't a wildcard, integer, or range: %s" % rule)
+
+ # Pre-calculating the integer representation of our mask and masked
+ # address. These are used by our is_match() method to compare ourselves to
+ # other addresses.
+
+ if self.address_type == AddressType.WILDCARD:
+ # is_match() will short circuit so these are unused
+ self._mask_bin = self._addr_bin = None
+ else:
+ self._mask_bin = int(stem.util.connection.get_address_binary(self.mask), 2)
+ self._addr_bin = int(stem.util.connection.get_address_binary(self.address), 2) & self._mask_bin
+
+ self._str_representation = None
+
+ def is_address_wildcard(self):
+ """
+ True if we'll match against any address for our type, False otherwise.
+
+ :returns: bool for if our address matching is a wildcard
+ """
+
+ return self.address_type == AddressType.WILDCARD or self.masked_bits == 0
+
+ def is_port_wildcard(self):
+ """
+ True if we'll match against any port, False otherwise.
+
+ :returns: bool for if our port matching is a wildcard
+ """
+
+ return self.min_port in (0, 1) and self.max_port == 65535
+
+ def is_match(self, address = None, port = None):
+ """
+ True if we match against the given destination, False otherwise. If the
+ address or port is omitted then that'll only match against a wildcard.
+
+ :param str address: IPv4 or IPv6 address (with or without brackets)
+ :param int port: port number
+
+ :returns: bool indicating if we match against this destination
+
+ :raises: ValueError if provided with a malformed address or port
+ """
+
+ # validate our input and check if the argumement doens't match our address type
+ if address != None:
+ if stem.util.connection.is_valid_ip_address(address):
+ if self.address_type == AddressType.IPv6: return False
+ elif stem.util.connection.is_valid_ipv6_address(address, allow_brackets = True):
+ if self.address_type == AddressType.IPv4: return False
+
+ address = address.lstrip("[").rstrip("]")
+ else:
+ raise ValueError("'%s' isn't a valid ipv4 or ipv6 address" % address)
+
+ if port != None and not stem.util.connection.is_valid_port(port):
+ raise ValueError("'%s' isn't a valid port" % port)
+
+ if address is None:
+ if self.address_type != AddressType.WILDCARD:
+ return False
+ elif not self.is_address_wildcard():
+ # Already got the integer representation of our mask and our address
+ # with the mask applied. Just need to check if this address with the
+ # mask applied matches.
+
+ comparison_addr_bin = int(stem.util.connection.get_address_binary(address), 2)
+ comparison_addr_bin &= self._mask_bin
+ if self._addr_bin != comparison_addr_bin: return False
+
+ if not self.is_port_wildcard():
+ if port is None:
+ return False
+ elif port < self.min_port or port > self.max_port:
+ return False
+
+ return True
+
+ def __str__(self):
+ """
+ Provides the string representation of our policy. This does not
+ necessarily match the rule that we were constructed from (due to things
+ like IPv6 address collapsing or the multiple representations that our mask
+ can have). However, it is a valid that would be accepted by our constructor
+ to re-create this rule.
+ """
+
+ if self._str_representation is None:
+ label = "accept " if self.is_accept else "reject "
+
+ if self.address_type == AddressType.WILDCARD:
+ label += "*:"
+ else:
+ if self.address_type == AddressType.IPv4:
+ label += self.address
+ else:
+ label += "[%s]" % self.address
+
+ # Including our mask label as follows...
+ # - exclde our mask if it doesn't do anything
+ # - use our masked bit count if we can
+ # - use the mask itself otherwise
+
+ if self.mask in (stem.util.connection.FULL_IPv4_MASK, stem.util.connection.FULL_IPv6_MASK):
+ label += ":"
+ elif not self.masked_bits is None:
+ label += "/%i:" % self.masked_bits
+ else:
+ label += "/%s:" % self.mask
+
+ if self.is_port_wildcard():
+ label += "*"
+ elif self.min_port == self.max_port:
+ label += str(self.min_port)
+ else:
+ label += "%i-%i" % (self.min_port, self.max_port)
+
+ self._str_representation = label
+
+ return self._str_representation
class ExitPolicyLine:
"""
Single rule from the user's exit policy. These are chained together to form
complete policies.
"""
-
+
def __init__(self, rule_entry):
"""
Exit Policy line constructor.
"""
+
# sanitize the input a bit, cleaning up tabs and stripping quotes
rule_entry = rule_entry.replace("\\t", " ").replace("\"", "")
@@ -83,8 +364,10 @@ class ExitPolicyLine:
# constructs the binary address just in case of comparison with a mask
if self.ip_address != "*":
- if not (stem.util.connection.is_valid_ip_address(self.ip_address) and stem.util.connection.is_valid_ipv6_address(self.ip_address)):
- raise ExitPolicyError
+ if not stem.util.connection.is_valid_ip_address(self.ip_address) and \
+ not stem.util.connection.is_valid_ipv6_address(self.ip_address):
+ raise ExitPolicyError()
+
self.ip_address_bin = ""
for octet in self.ip_address.split("."):
# Converts the int to a binary string, padded with zeros. Source:
@@ -92,7 +375,7 @@ class ExitPolicyLine:
self.ip_address_bin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)])
else:
self.ip_address_bin = "0" * 32
-
+
# sets the port component
self.min_port, self.max_port = 0, 0
self.is_port_wildcard = entry_port == "*"
@@ -109,7 +392,7 @@ class ExitPolicyLine:
raise ExitPolicyError
self.min_port = int(entry_port)
self.max_port = int(entry_port)
-
+
def __str__(self):
# This provides the actual policy rather than the entry used to construct
# it so the 'private' keyword is expanded.
diff --git a/stem/util/connection.py b/stem/util/connection.py
index a70389e..e1e8890 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -7,7 +7,7 @@ but for now just moving the parts we need.
::
is_valid_ip_address - checks if a string is a valid IPv4 address
- is_valid_ip_ipv6_address - checks if a string is a valid IPv6 address
+ is_valid_ipv6_address - checks if a string is a valid IPv6 address
is_valid_port - checks if something is a valid representation for a port
expand_ipv6_address - provides an IPv6 address with its collapsed portions expanded
get_mask - provides the mask representation for a given number of bits
@@ -24,6 +24,9 @@ import hashlib
CRYPTOVARIABLE_EQUALITY_COMPARISON_NONCE = os.urandom(32)
+FULL_IPv4_MASK = "255.255.255.255"
+FULL_IPv6_MASK = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"
+
def is_valid_ip_address(address):
"""
Checks if a string is a valid IPv4 address.
@@ -45,15 +48,20 @@ def is_valid_ip_address(address):
return True
-def is_valid_ipv6_address(address):
+def is_valid_ipv6_address(address, allow_brackets = False):
"""
Checks if a string is a valid IPv6 address.
:param str address: string to be checked
+ :param bool allow_brackets: ignore brackets which form '[address]'
:returns: True if input is a valid IPv6 address, False otherwise
"""
+ if allow_brackets:
+ if address.startswith("[") and address.endswith("]"):
+ address = address[1:-1]
+
# addresses are made up of eight colon separated groups of four hex digits
# with leading zeros being optional
# https://en.wikipedia.org/wiki/IPv6#Address_format
@@ -85,9 +93,10 @@ def is_valid_port(entry, allow_zero = False):
if isinstance(entry, list):
for port in entry:
- if not is_valid_port(port):
+ if not is_valid_port(port, allow_zero):
return False
-
+
+ return True
elif isinstance(entry, str):
if not entry.isdigit():
return False
diff --git a/test/unit/exit_policy.py b/test/unit/exit_policy.py
deleted file mode 100644
index b12cd9a..0000000
--- a/test/unit/exit_policy.py
+++ /dev/null
@@ -1,110 +0,0 @@
-"""
-Unit tests for the stem.exit_policy.ExitPolicy parsing and class.
-"""
-
-import unittest
-import stem.exit_policy
-import stem.util.system
-
-import test.mocking as mocking
-
-class TestExitPolicy(unittest.TestCase):
- def tearDown(self):
- pass
-
- def test_parsing(self):
- """
- Tests parsing by the ExitPolicy class constructor.
- """
-
- exit_policies = stem.exit_policy.ExitPolicy()
- exit_policies.add("accept *:80")
- exit_policies.add("accept *:443")
- exit_policies.add("reject *:*")
- self.assertEqual(str(exit_policies), "accept *:80, accept *:443, reject *:*")
-
- exit_policies = stem.exit_policy.ExitPolicy()
-
- # check ip address
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 256.255.255.255:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -10.255.255.255:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.-10.255.255:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.255.-10.255:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -255.255.255.-10:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept a.b.c.d:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.255.255:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -255.255:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -:80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept :80")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept ...:80")
-
- # check input string
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "foo 255.255.255.255:80")
-
- # check ports
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:0001")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:0")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:-1")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:+1")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:+1-1")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:a")
- self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:70000")
-
- def test_check(self):
- """
- Tests if exiting to this ip is allowed.
- """
-
- exit_policies = stem.exit_policy.ExitPolicy()
- exit_policies = stem.exit_policy.ExitPolicy()
- exit_policies.add("accept *:80")
- exit_policies.add("accept *:443")
- exit_policies.add("reject *:*")
-
- self.assertTrue(exit_policies.check("www.google.com", 80))
- self.assertTrue(exit_policies.check("www.atagar.com", 443))
-
- self.assertFalse(exit_policies.check("www.atagar.com", 22))
- self.assertFalse(exit_policies.check("www.atagar.com", 8118))
-
- def test_is_exiting_allowed(self):
- """
- Tests if this is an exit node
- """
-
- exit_policies = stem.exit_policy.ExitPolicy()
- exit_policies = stem.exit_policy.ExitPolicy()
- exit_policies.add("accept *:80")
- exit_policies.add("accept *:443")
- exit_policies.add("reject *:*")
-
- self.assertTrue(exit_policies.is_exiting_allowed())
-
- exit_policies = stem.exit_policy.ExitPolicy()
- exit_policies = stem.exit_policy.ExitPolicy()
- exit_policies.add("reject *:*")
-
- self.assertFalse(exit_policies.is_exiting_allowed())
-
- def test_microdesc_exit_parsing(self):
- microdesc_exit_policy = stem.exit_policy.MicrodescriptorExitPolicy("accept 80,443")
-
- self.assertEqual(str(microdesc_exit_policy),"accept 80,443")
-
- self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "accept 80,-443")
- self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "accept 80,+443")
- self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "accept 80,66666")
- self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "reject 80,foo")
- self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "bar 80,foo")
- self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "foo")
- self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "bar 80-foo")
-
- def test_micodesc_exit_check(self):
- microdesc_exit_policy = stem.exit_policy.MicrodescriptorExitPolicy("accept 80,443")
-
- self.assertTrue(microdesc_exit_policy.check(80))
- self.assertTrue(microdesc_exit_policy.check("www.atagar.com", 443))
-
- self.assertFalse(microdesc_exit_policy.check(22))
- self.assertFalse(microdesc_exit_policy.check("www.atagar.com", 8118))
diff --git a/test/unit/exit_policy/__init__.py b/test/unit/exit_policy/__init__.py
new file mode 100644
index 0000000..99a4651
--- /dev/null
+++ b/test/unit/exit_policy/__init__.py
@@ -0,0 +1,6 @@
+"""
+Unit tests for stem.exit_policy.py contents.
+"""
+
+__all__ = ["policy", "rule"]
+
diff --git a/test/unit/exit_policy/policy.py b/test/unit/exit_policy/policy.py
new file mode 100644
index 0000000..10088e2
--- /dev/null
+++ b/test/unit/exit_policy/policy.py
@@ -0,0 +1,107 @@
+"""
+Unit tests for the stem.exit_policy.ExitPolicy parsing and class.
+"""
+
+import unittest
+import stem.exit_policy
+import stem.util.system
+
+import test.mocking as mocking
+
+class TestExitPolicy(unittest.TestCase):
+ def test_parsing(self):
+ """
+ Tests parsing by the ExitPolicy class constructor.
+ """
+
+ exit_policies = stem.exit_policy.ExitPolicy()
+ exit_policies.add("accept *:80")
+ exit_policies.add("accept *:443")
+ exit_policies.add("reject *:*")
+ self.assertEqual(str(exit_policies), "accept *:80, accept *:443, reject *:*")
+
+ exit_policies = stem.exit_policy.ExitPolicy()
+
+ # check ip address
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 256.255.255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -10.255.255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.-10.255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.255.-10.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -255.255.255.-10:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept a.b.c.d:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255.255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -255.255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept 255:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept -:80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept :80")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept ...:80")
+
+ # check input string
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "foo 255.255.255.255:80")
+
+ # check ports
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:0001")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:0")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:-1")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:+1")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:+1-1")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:a")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, exit_policies.add, "accept *:70000")
+
+ def test_check(self):
+ """
+ Tests if exiting to this ip is allowed.
+ """
+
+ exit_policies = stem.exit_policy.ExitPolicy()
+ exit_policies = stem.exit_policy.ExitPolicy()
+ exit_policies.add("accept *:80")
+ exit_policies.add("accept *:443")
+ exit_policies.add("reject *:*")
+
+ self.assertTrue(exit_policies.check("www.google.com", 80))
+ self.assertTrue(exit_policies.check("www.atagar.com", 443))
+
+ self.assertFalse(exit_policies.check("www.atagar.com", 22))
+ self.assertFalse(exit_policies.check("www.atagar.com", 8118))
+
+ def test_is_exiting_allowed(self):
+ """
+ Tests if this is an exit node
+ """
+
+ exit_policies = stem.exit_policy.ExitPolicy()
+ exit_policies = stem.exit_policy.ExitPolicy()
+ exit_policies.add("accept *:80")
+ exit_policies.add("accept *:443")
+ exit_policies.add("reject *:*")
+
+ self.assertTrue(exit_policies.is_exiting_allowed())
+
+ exit_policies = stem.exit_policy.ExitPolicy()
+ exit_policies = stem.exit_policy.ExitPolicy()
+ exit_policies.add("reject *:*")
+
+ self.assertFalse(exit_policies.is_exiting_allowed())
+
+ def test_microdesc_exit_parsing(self):
+ microdesc_exit_policy = stem.exit_policy.MicrodescriptorExitPolicy("accept 80,443")
+
+ self.assertEqual(str(microdesc_exit_policy),"accept 80,443")
+
+ self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "accept 80,-443")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "accept 80,+443")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "accept 80,66666")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "reject 80,foo")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "bar 80,foo")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "foo")
+ self.assertRaises(stem.exit_policy.ExitPolicyError, stem.exit_policy.MicrodescriptorExitPolicy, "bar 80-foo")
+
+ def test_micodesc_exit_check(self):
+ microdesc_exit_policy = stem.exit_policy.MicrodescriptorExitPolicy("accept 80,443")
+
+ self.assertTrue(microdesc_exit_policy.check(80))
+ self.assertTrue(microdesc_exit_policy.check("www.atagar.com", 443))
+
+ self.assertFalse(microdesc_exit_policy.check(22))
+ self.assertFalse(microdesc_exit_policy.check("www.atagar.com", 8118))
diff --git a/test/unit/exit_policy/rule.py b/test/unit/exit_policy/rule.py
new file mode 100644
index 0000000..13a414d
--- /dev/null
+++ b/test/unit/exit_policy/rule.py
@@ -0,0 +1,327 @@
+"""
+Unit tests for the stem.exit_policy.ExitPolicyRule class.
+"""
+
+import unittest
+
+from stem.exit_policy import AddressType, ExitPolicyRule
+
+class TestExitPolicyRule(unittest.TestCase):
+ def test_accept_or_reject(self):
+ self.assertTrue(ExitPolicyRule("accept *:*").is_accept)
+ self.assertFalse(ExitPolicyRule("reject *:*").is_accept)
+
+ invalid_inputs = (
+ "accept",
+ "reject",
+ "accept *:*",
+ "accept\t*:*",
+ "accept\n*:*",
+ "acceptt *:*",
+ "rejectt *:*",
+ "blarg *:*",
+ " *:*",
+ "*:*",
+ "",
+ )
+
+ for rule_arg in invalid_inputs:
+ self.assertRaises(ValueError, ExitPolicyRule, rule_arg)
+
+ def test_str_unchanged(self):
+ # provides a series of test inputs where the str() representation should
+ # match the input rule
+
+ test_inputs = (
+ "accept *:*",
+ "reject *:*",
+ "accept *:80",
+ "accept *:80-443",
+ "accept 127.0.0.1:80",
+ "accept 87.0.0.1/24:80",
+ "accept 156.5.38.3/255.255.0.255:80",
+ "accept [FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:80",
+ "accept [FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]/32:80",
+ )
+
+ for rule_arg in test_inputs:
+ rule = ExitPolicyRule(rule_arg)
+ self.assertEquals(rule_arg, rule.rule)
+ self.assertEquals(rule_arg, str(rule))
+
+ def test_str_changed(self):
+ # some instances where our rule is valid but won't match our str() representation
+ test_inputs = {
+ "accept 10.0.0.1/32:80": "accept 10.0.0.1:80",
+ "accept 192.168.0.1/255.255.255.0:80": "accept 192.168.0.1/24:80",
+ "accept [::]/32:*": "accept [0000:0000:0000:0000:0000:0000:0000:0000]/32:*",
+ "accept [::]/128:*": "accept [0000:0000:0000:0000:0000:0000:0000:0000]:*",
+ }
+
+ for rule_arg, expected_str in test_inputs.items():
+ rule = ExitPolicyRule(rule_arg)
+ self.assertEquals(rule_arg, rule.rule)
+ self.assertEquals(expected_str, str(rule))
+
+ def test_valid_wildcard(self):
+ test_inputs = {
+ "reject *:*": (True, True),
+ "reject *:80": (True, False),
+ "accept 192.168.0.1:*": (False, True),
+ "accept 192.168.0.1:80": (False, False),
+
+ "reject 127.0.0.1/0:*": (True, True),
+ "reject 127.0.0.1/16:*": (False, True),
+ "reject 127.0.0.1/32:*": (False, True),
+ "reject [0000:0000:0000:0000:0000:0000:0000:0000]/0:80": (True, False),
+ "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),
+
+ "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),
+ "accept 192.168.0.1:1-65534": (False, False),
+ }
+
+ for rule_arg, attr in test_inputs.items():
+ is_address_wildcard, is_port_wildcard = attr
+
+ rule = ExitPolicyRule(rule_arg)
+ self.assertEquals(is_address_wildcard, rule.is_address_wildcard())
+ self.assertEquals(is_port_wildcard, rule.is_port_wildcard())
+
+ def test_invalid_wildcard(self):
+ test_inputs = (
+ "reject */16:*",
+ "reject 127.0.0.1/*:*",
+ "reject *:0-*",
+ "reject *:*-15",
+ )
+
+ for rule_arg in test_inputs:
+ self.assertRaises(ValueError, ExitPolicyRule, rule_arg)
+
+ def test_wildcard_attributes(self):
+ rule = ExitPolicyRule("reject *:*")
+ self.assertEquals(AddressType.WILDCARD, rule.address_type)
+ self.assertEquals(None, rule.address)
+ self.assertEquals(None, rule.mask)
+ self.assertEquals(None, rule.masked_bits)
+ self.assertEquals(1, rule.min_port)
+ self.assertEquals(65535, rule.max_port)
+
+ def test_valid_ipv4_addresses(self):
+ test_inputs = {
+ "0.0.0.0": ("0.0.0.0", "255.255.255.255", 32),
+ "127.0.0.1/32": ("127.0.0.1", "255.255.255.255", 32),
+ "192.168.0.50/24": ("192.168.0.50", "255.255.255.0", 24),
+ "255.255.255.255/0": ("255.255.255.255", "0.0.0.0", 0),
+ }
+
+ for rule_addr, attr in test_inputs.items():
+ address, mask, masked_bits = attr
+
+ rule = ExitPolicyRule("accept %s:*" % rule_addr)
+ self.assertEquals(AddressType.IPv4, rule.address_type)
+ self.assertEquals(address, rule.address)
+ self.assertEquals(mask, rule.mask)
+ self.assertEquals(masked_bits, rule.masked_bits)
+
+ def test_invalid_ipv4_addresses(self):
+ test_inputs = {
+ "256.0.0.0",
+ "-1.0.0.0",
+ "0.0.0",
+ "0.0.0.",
+ "0.0.0.a",
+ "127.0.0.1/-1",
+ "127.0.0.1/33",
+ }
+
+ for rule_addr in test_inputs:
+ self.assertRaises(ValueError, ExitPolicyRule, "accept %s:*" % rule_addr)
+
+ def test_valid_ipv6_addresses(self):
+ test_inputs = {
+ "[fe80:0000:0000:0000:0202:b3ff:fe1e:8329]":
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8329",
+ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", 128),
+ "[FE80::0202:b3ff:fe1e:8329]":
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8329",
+ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", 128),
+ "[0000:0000:0000:0000:0000:0000:0000:0000]/0":
+ ("0000:0000:0000:0000:0000:0000:0000:0000",
+ "0000:0000:0000:0000:0000:0000:0000:0000", 0),
+ "[::]":
+ ("0000:0000:0000:0000:0000:0000:0000:0000",
+ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", 128),
+ }
+
+ for rule_addr, attr in test_inputs.items():
+ address, mask, masked_bits = attr
+
+ rule = ExitPolicyRule("accept %s:*" % rule_addr)
+ self.assertEquals(AddressType.IPv6, rule.address_type)
+ self.assertEquals(address, rule.address)
+ self.assertEquals(mask, rule.mask)
+ self.assertEquals(masked_bits, rule.masked_bits)
+
+ def test_invalid_ipv6_addresses(self):
+ test_inputs = (
+ "fe80::0202:b3ff:fe1e:8329",
+ "[fe80::0202:b3ff:fe1e:8329",
+ "fe80::0202:b3ff:fe1e:8329]",
+ "[fe80::0202:b3ff:fe1e:832g]",
+ "[fe80:::b3ff:fe1e:8329]",
+ "[fe80::b3ff::fe1e:8329]",
+ "[fe80::0202:b3ff:fe1e:8329]/-1",
+ "[fe80::0202:b3ff:fe1e:8329]/129",
+ )
+
+ for rule_addr in test_inputs:
+ self.assertRaises(ValueError, ExitPolicyRule, "accept %s:*" % rule_addr)
+
+ def test_valid_ports(self):
+ test_inputs = {
+ "0": (0, 0),
+ "1": (1, 1),
+ "80": (80, 80),
+ "80-443": (80, 443),
+ }
+
+ for rule_port, attr in test_inputs.items():
+ min_port, max_port = attr
+
+ rule = ExitPolicyRule("accept 127.0.0.1:%s" % rule_port)
+ self.assertEquals(min_port, rule.min_port)
+ self.assertEquals(max_port, rule.max_port)
+
+ def test_invalid_ports(self):
+ test_inputs = (
+ "65536",
+ "a",
+ "5-3",
+ "5-",
+ "-3",
+ )
+
+ for rule_port in test_inputs:
+ self.assertRaises(ValueError, ExitPolicyRule, "accept 127.0.0.1:%s" % rule_port)
+
+ def test_is_match_wildcard(self):
+ test_inputs = {
+ "reject *:*": {
+ ("192.168.0.1", 80): True,
+ ("0.0.0.0", 80): True,
+ ("255.255.255.255", 80): True,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8329", 80): True,
+ ("[FE80:0000:0000:0000:0202:B3FF:FE1E:8329]", 80): True,
+ ("192.168.0.1", None): True,
+ (None, 80): True,
+ (None, None): True,
+ },
+ "reject 255.255.255.255/0:*": {
+ ("192.168.0.1", 80): True,
+ ("0.0.0.0", 80): True,
+ ("255.255.255.255", 80): True,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8329", 80): False,
+ ("[FE80:0000:0000:0000:0202:B3FF:FE1E:8329]", 80): False,
+ ("192.168.0.1", None): True,
+ (None, 80): False,
+ (None, None): False,
+ },
+ }
+
+ for rule_arg, matches in test_inputs.items():
+ rule = ExitPolicyRule(rule_arg)
+
+ for match_args, expected_result in matches.items():
+ self.assertEquals(expected_result, rule.is_match(*match_args))
+
+ # port zero is special in that exit policies can include it, but it's not
+ # something that we can match against
+
+ rule = ExitPolicyRule("reject *:*")
+ self.assertRaises(ValueError, rule.is_match, "127.0.0.1", 0)
+
+ def test_is_match_ipv4(self):
+ test_inputs = {
+ "reject 192.168.0.50:*": {
+ ("192.168.0.50", 80): True,
+ ("192.168.0.51", 80): False,
+ ("192.168.0.49", 80): False,
+ (None, 80): False,
+ ("192.168.0.50", None): True,
+ },
+ "reject 0.0.0.0/24:*": {
+ ("0.0.0.0", 80): True,
+ ("0.0.0.1", 80): True,
+ ("0.0.0.255", 80): True,
+ ("0.0.1.0", 80): False,
+ ("0.1.0.0", 80): False,
+ ("1.0.0.0", 80): False,
+ (None, 80): False,
+ ("0.0.0.0", None): True,
+ },
+ }
+
+ for rule_arg, matches in test_inputs.items():
+ rule = ExitPolicyRule(rule_arg)
+
+ for match_args, expected_result in matches.items():
+ self.assertEquals(expected_result, rule.is_match(*match_args))
+
+ def test_is_match_ipv6(self):
+ test_inputs = {
+ "reject [FE80:0000:0000:0000:0202:B3FF:FE1E:8329]:*": {
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8329", 80): True,
+ ("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", 80): True,
+ ("[FE80:0000:0000:0000:0202:B3FF:FE1E:8329]", 80): True,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8330", 80): False,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8328", 80): False,
+ (None, 80): False,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8329", None): True,
+ },
+ "reject [FE80:0000:0000:0000:0202:B3FF:FE1E:8329]/112:*": {
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8329", 80): True,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:0000", 80): True,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:FFFF", 80): True,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1F:8329", 80): False,
+ ("FE81:0000:0000:0000:0202:B3FF:FE1E:8329", 80): False,
+ (None, 80): False,
+ ("FE80:0000:0000:0000:0202:B3FF:FE1E:8329", None): True,
+ },
+ }
+
+ for rule_arg, matches in test_inputs.items():
+ rule = ExitPolicyRule(rule_arg)
+
+ for match_args, expected_result in matches.items():
+ self.assertEquals(expected_result, rule.is_match(*match_args))
+
+ def test_is_match_port(self):
+ test_inputs = {
+ "reject *:80": {
+ ("192.168.0.50", 80): True,
+ ("192.168.0.50", 81): False,
+ ("192.168.0.50", 79): False,
+ (None, 80): True,
+ ("192.168.0.50", None): False,
+ },
+ "reject *:80-85": {
+ ("192.168.0.50", 79): False,
+ ("192.168.0.50", 80): True,
+ ("192.168.0.50", 83): True,
+ ("192.168.0.50", 85): True,
+ ("192.168.0.50", 86): False,
+ (None, 83): True,
+ ("192.168.0.50", None): False,
+ },
+ }
+
+ for rule_arg, matches in test_inputs.items():
+ rule = ExitPolicyRule(rule_arg)
+
+ for match_args, expected_result in matches.items():
+ self.assertEquals(expected_result, rule.is_match(*match_args))
+
1
0