[tor-commits] [torbel/master] Specify and implement narrow exit policy check.

sebastian at torproject.org sebastian at torproject.org
Sun Sep 4 07:25:40 UTC 2011


commit c1092dcf4b4bc5e95d2d37d69feb3a2c62cd85ab
Author: Harry Bock <hbock at ele.uri.edu>
Date:   Fri Sep 3 18:53:14 2010 -0400

    Specify and implement narrow exit policy check.
    
    A "narrow" port is a port that a router explicitly rejects
    our active tester but could possibly accept other traffic;
    e.g., from an exit enclave.
    
    Add a NarrowPorts data field in TorBEL exports.  Implement
    the check, export, and import functionality.
---
 doc/data-spec.txt |    5 ++++
 query.py          |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 router.py         |   30 ++++++++++++++++++++++--
 3 files changed, 93 insertions(+), 6 deletions(-)

diff --git a/doc/data-spec.txt b/doc/data-spec.txt
index 58dbb7e..c5f4014 100644
--- a/doc/data-spec.txt
+++ b/doc/data-spec.txt
@@ -43,6 +43,11 @@ Status: Draft
     * FailedPorts: A list of ports that were not reachable by TorBEL, either
                    due to its exit policy or a temporary failure, when it
 		   was last tested.
+    * NarrowPorts: A list of ports that were not reachable by TorBEL due to
+                   a narrow exit policy that does not include the TorBEL
+		   test server.  Consumers should treat NarrowPorts as
+		   working according to the currently published exit policy
+		   for the router.
   
   The following data are supplied for convenience to the consumer:
 
diff --git a/query.py b/query.py
index 2f223d7..f1d0046 100644
--- a/query.py
+++ b/query.py
@@ -44,6 +44,11 @@ class Router:
         self.exit_policy   = self._build_exit_policy(data["ExitPolicy"])
         self.working_ports = data["WorkingPorts"]
         self.failed_ports  = data["FailedPorts"]
+        self.narrow_ports  = data["NarrowPorts"]
+
+    @property
+    def exit_policy_string(self):
+        return "; ".join(map(str, self.exit_policy))
 
     def _build_exit_policy(self, policy_list):
         return [ExitPolicyRule(line) for line in policy_list]
@@ -58,7 +63,24 @@ class Router:
         # will be accepted."
         return True
 
-    def will_exit_to(self, ip, port):
+    def is_narrow_exit(self, ip, port):
+        """ Returns True if this router accepts exit traffic to port
+        on some IP addresses but rejects traffic to ip. This can be
+        used to detect exit enclaves. """
+        can_accept = False
+        for line in self.exit_policy:
+            if line.reject and line.network == _nulladdr and (port >= line.port_low and port <= line.port_high):
+                can_accept = False
+                break
+
+            if line.accept and (port >= line.port_low and port <= line.port_high):
+                can_accept = True
+                break
+
+        match = self.exit_policy_match(ip, port)
+        return can_accept and not match
+        
+    def will_exit_to(self, ip, port, check_narrow_policy = True):
         # FIXME: In the case that TorBEL has not actually tested this exit,
         # we will trust the router's exit policy.  This may or may not be
         # the Right Thing To Do.
@@ -69,7 +91,7 @@ class Router:
             return True
         elif port in self.failed_ports:
             return False
-
+        # Treat NarrowPorts the same as not tested.
         return self.exit_policy_match(ip, port)
 
     def will_exit_to_ports(self, ip, port_list):
@@ -87,6 +109,7 @@ class ParseError(Exception):
 _exitline = re.compile(r"^(accept|reject) (.+)")
 _portspec = re.compile(r"^\d{1,5}|\d{1,5}-\d{1,5}$")
 _addrspec = re.compile(r"^\[?([\d:.]+)\]?(/[\d:.]+)?$")
+_nulladdr = ipaddr.IPAddress("0.0.0.0")
 class ExitPolicyRule:
     def __init__(self, line):
         self.port_low, self.port_high = -1, -1
@@ -139,6 +162,31 @@ class ExitPolicyRule:
                 return True
             elif self.address and ipaddr.IPAddress(ip) == self.address:
                 return True
+
+    def __str__(self):
+        # 0.0.0.0/0.0.0.0 => *
+        if self.network == _nulladdr:# and line.netmask == 0:
+            ip = "*"
+        else:
+            # ipaddr.IPv4Network always converts to CIDR.
+            if self.network:
+                ip = str(self.network)
+            else:
+                ip = str(self.address)
+
+        # Convert 0-65535 to *
+        if self.port_low == 0 and self.port_high == 0xffff:
+            port = "*"
+        # Use 8 instead of 8-8
+        elif self.port_low == self.port_high:
+            port = str(self.port_low)
+        else:
+            port = "%d-%d" % (self.port_low, self.port_high)
+                
+        if self.accept:
+            return "accept " + ip + ":" + port
+        else:
+            return "reject " + ip + ":" + port
             
 class ExitList:
     def __init__(self, filename, status_filename = None):
@@ -248,7 +296,8 @@ class ExitList:
                     "InConsensus": r[4] == "True",
                     "ExitPolicy":  r[5].split(";"),
                     "WorkingPorts": port_list_from_string(r[6]),
-                    "FailedPorts":  port_list_from_string(r[7])
+                    "FailedPorts":  port_list_from_string(r[7]),
+                    "NarrowPorts":  port_list_from_string(r[8]),
                     }
 
                 self.add_record(data)
@@ -349,6 +398,15 @@ if __name__ == "__main__":
 
             output.close()
 
+    elif command == "test":
+        port = int(sys.argv[2])
+        e = ExitList("torbel_export.csv")
+        print "start"
+        count = 0
+        for r in e.cache_ip.itervalues():
+            if r.is_narrow_exit(ipaddr.IPAddress("131.128.160.242"), port):
+                count += 1
+                print count, r.nickname, r.exit_policy_string
     else:
         usage()
         sys.exit(1)
diff --git a/router.py b/router.py
index c77e178..bc71e86 100644
--- a/router.py
+++ b/router.py
@@ -16,6 +16,7 @@ class RouterRecord(_OldRouterClass):
             self.test_ports = set(ports)
             self.working_ports = set()
             self.failed_ports  = set()
+            self.narrow_ports  = set()
             self.circ_failed = False
 
         def passed(self, port):
@@ -24,6 +25,9 @@ class RouterRecord(_OldRouterClass):
         def failed(self, port):
             self.failed_ports.add(port)
 
+        def narrow(self, port):
+            self.narrow_ports.add(port)
+            
         def start(self):
             self.start_time = time.time()
             return self
@@ -33,7 +37,8 @@ class RouterRecord(_OldRouterClass):
             return self
 
         def is_complete(self):
-            return self.test_ports <= (self.working_ports | self.failed_ports)
+            return self.test_ports <= \
+                (self.working_ports | self.failed_ports | self.narrow_ports)
 
     def __init__(self, *args, **kwargs):
         _OldRouterClass.__init__(self, *args, **kwargs)
@@ -68,6 +73,23 @@ class RouterRecord(_OldRouterClass):
         return len(self.exitpolicy) == 1 and \
             (ep.ip, ep.netmask, ep.port_low, ep.port_high) == (0, 0, 0, 0xffff)
 
+    def is_narrow_exit(self, ip, port):
+        """ Returns True if this router accepts exit traffic to port
+        on some IP addresses but rejects traffic to ip. This can be
+        used to detect exit enclaves. """
+        can_accept = False
+        for line in self.exitpolicy:
+            if not line.match and line.ip == 0 and \
+                    (port >= line.port_low and port <= line.port_high):
+                can_accept = False
+                break
+
+            if line.match and (port >= line.port_low and port <= line.port_high):
+                can_accept = True
+                break
+
+        return can_accept and not self.will_exit_to(ip, port)
+
     def should_export(self):
         """ Returns True if we have found working exit ports, or if we
         have not found working test ports, if this router has a non-reject-all
@@ -171,7 +193,8 @@ class RouterRecord(_OldRouterClass):
                  "LastTestedTimestamp": int(self.last_test.end_time),
                  "ExitPolicy":   self.exit_policy_list(),
                  "WorkingPorts": list(self.last_test.working_ports),
-                 "FailedPorts":  list(self.last_test.failed_ports) }
+                 "FailedPorts":  list(self.last_test.failed_ports),
+                 "NarrowPorts":  list(self.last_test.narrow_ports) }
 
     def export_csv(self, out):
         """ Export record in CSV format, given a Python csv.writer instance. """
@@ -186,7 +209,8 @@ class RouterRecord(_OldRouterClass):
                       not self.stale,               # InConsensus
                       self.exit_policy_string(),    # ExitPolicy
                       list(self.last_test.working_ports), # WorkingPorts
-                      list(self.last_test.failed_ports)]) # FailedPorts
+                      list(self.last_test.failed_ports),  # FailedPorts
+                      list(self.last_test.narrow_ports)]) # NarrowPorts
 
     def __str__(self):
         return "%s (%s)" % (self.idhex, self.nickname)





More information about the tor-commits mailing list