On directory servers, old_routers was wasting hundreds of bytes per superseded router...
[tor.git] / contrib / exitlist
blob188b8db44a6aab46c5372f5daed80b03d3ce745e
1 #!/usr/bin/python
2 # Copyright 2005 Nick Mathewson
3 # See the LICENSE file in the Tor distribution for licensing information.
5 """
6 exitlist -- Given a Tor directory on stdin, lists the Tor servers
7 that accept connections to given addreses.
9 example usage:
11 python2.2 exitlist < ~/.tor/cached-directory
12 """
14 # Requires Python 2.2 or later.
17 # Change this to True if you want more verbose output. By default, we
18 # only print the IPs of the servers that accept any the listed
19 # addresses, one per line.
21 VERBOSE = False
24 # Change this to True if you want to reverse the output, and list the
25 # servers that accept *none* of the listed addresses.
27 INVERSE = False
30 # Change this list to contain all of the target services you are interested
31 # in. It must contain one entry per line, each consisting of an IPv4 address,
32 # a colon, and a port number.
34 ADDRESSES_OF_INTEREST = """
35 192.168.0.1:80
36 """
40 # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
43 import sys
44 import re
45 import getopt
46 import socket
47 import struct
49 assert sys.version_info >= (2,2)
52 def maskIP(ip,mask):
53 return "".join([chr(ord(a) & ord(b)) for a,b in zip(ip,mask)])
55 def maskFromLong(lng):
56 return struct.pack("!L", lng)
58 def maskByBits(n):
59 return maskFromLong(0xffffffffl ^ ((1L<<(32-n))-1))
61 class Pattern:
62 """
63 >>> import socket
64 >>> ip1 = socket.inet_aton("192.169.64.11")
65 >>> ip2 = socket.inet_aton("192.168.64.11")
66 >>> ip3 = socket.inet_aton("18.244.0.188")
68 >>> print Pattern.parse("18.244.0.188")
69 18.244.0.188/255.255.255.255:1-65535
70 >>> print Pattern.parse("18.244.0.188/16:*")
71 18.244.0.0/255.255.0.0:1-65535
72 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
73 2.0.0.0/2.2.2.2:80-80
74 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
75 192.168.0.0/255.255.0.0:22-25
76 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
77 >>> import socket
78 >>> p1.appliesTo(ip1, 22)
79 False
80 >>> p1.appliesTo(ip2, 22)
81 True
82 >>> p1.appliesTo(ip2, 25)
83 True
84 >>> p1.appliesTo(ip2, 26)
85 False
86 """
87 def __init__(self, ip, mask, portMin, portMax):
88 self.ip = maskIP(ip,mask)
89 self.mask = mask
90 self.portMin = portMin
91 self.portMax = portMax
93 def __str__(self):
94 return "%s/%s:%s-%s"%(socket.inet_ntoa(self.ip),
95 socket.inet_ntoa(self.mask),
96 self.portMin,
97 self.portMax)
99 def parse(s):
100 if ":" in s:
101 addrspec, portspec = s.split(":",1)
102 else:
103 addrspec, portspec = s, "*"
105 if addrspec == '*':
106 ip,mask = "\x00\x00\x00\x00","\x00\x00\x00\x00"
107 elif '/' not in addrspec:
108 ip = socket.inet_aton(addrspec)
109 mask = "\xff\xff\xff\xff"
110 else:
111 ip,mask = addrspec.split("/",1)
112 ip = socket.inet_aton(ip)
113 if "." in mask:
114 mask = socket.inet_aton(mask)
115 else:
116 mask = maskByBits(int(mask))
118 if portspec == '*':
119 portMin = 1
120 portMax = 65535
121 elif '-' not in portspec:
122 portMin = portMax = int(portspec)
123 else:
124 portMin, portMax = map(int,portspec.split("-",1))
126 return Pattern(ip,mask,portMin,portMax)
128 parse = staticmethod(parse)
130 def appliesTo(self, ip, port):
131 return ((maskIP(ip,self.mask) == self.ip) and
132 (self.portMin <= port <= self.portMax))
134 class Policy:
136 >>> import socket
137 >>> ip1 = socket.inet_aton("192.169.64.11")
138 >>> ip2 = socket.inet_aton("192.168.64.11")
139 >>> ip3 = socket.inet_aton("18.244.0.188")
141 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
142 >>> print str(pol).strip()
143 reject 0.0.0.0/0.0.0.0:80-80
144 accept 18.244.0.188/255.255.255.255:1-65535
145 >>> pol.accepts(ip1,80)
146 False
147 >>> pol.accepts(ip3,80)
148 False
149 >>> pol.accepts(ip3,81)
150 True
153 def __init__(self, lst):
154 self.lst = lst
156 def parseLines(lines):
157 r = []
158 for item in lines:
159 a,p=item.split(" ",1)
160 if a == 'accept':
161 a = True
162 elif a == 'reject':
163 a = False
164 else:
165 raise ValueError("Unrecognized action %r",a)
166 p = Pattern.parse(p)
167 r.append((p,a))
168 return Policy(r)
170 parseLines = staticmethod(parseLines)
172 def __str__(self):
173 r = []
174 for pat, accept in self.lst:
175 rule = accept and "accept" or "reject"
176 r.append("%s %s\n"%(rule,pat))
177 return "".join(r)
179 def accepts(self, ip, port):
180 for pattern,accept in self.lst:
181 if pattern.appliesTo(ip,port):
182 return accept
183 return True
185 class Server:
186 def __init__(self, name, ip, policy):
187 self.name = name
188 self.ip = ip
189 self.policy = policy
191 def run():
192 servers = []
193 policy = []
194 name = ip = None
195 for line in sys.stdin.xreadlines():
196 if line.startswith('router '):
197 if name:
198 servers.append(Server(name, ip, Policy.parseLines(policy)))
199 _, name, ip, rest = line.split(" ", 3)
200 policy = []
201 elif line.startswith('accept ') or line.startswith('reject '):
202 policy.append(line.strip())
204 if name:
205 servers.append(Server(name, ip, Policy.parseLines(policy)))
207 targets = []
208 for line in ADDRESSES_OF_INTEREST.split("\n"):
209 line = line.strip()
210 if not line: continue
211 p = Pattern.parse(line)
212 targets.append((p.ip, p.portMin))
214 accepters, rejecters = [], []
215 for s in servers:
216 for ip,port in targets:
217 if s.policy.accepts(ip,port):
218 accepters.append(s)
219 break
220 else:
221 rejecters.append(s)
223 if INVERSE:
224 printlist = rejecters
225 else:
226 printlist = accepters
228 if VERBOSE:
229 for s in printlist:
230 print "%s\t%s"%(s.ip,s.name)
231 else:
232 for s in printlist:
233 print s.ip
235 def _test():
236 import doctest, exitparse
237 return doctest.testmod(exitparse)
238 #_test()
240 run()