2 # Copyright 2005-2006 Nick Mathewson
3 # See the LICENSE file in the Tor distribution for licensing information.
5 # Requires Python 2.2 or later.
8 exitlist -- Given a Tor directory on stdin, lists the Tor servers
9 that accept connections to given addreses.
11 example usage (Tor 0.1.0.x and earlier):
13 python exitlist 18.244.0.188:80 < ~/.tor/cached-directory
15 example usage (Tor 0.1.1.10-alpha and later):
17 cat ~/.tor/cached-routers* | python exitlist 18.244.0.188:80
19 If you're using Tor 0.1.1.18-rc or later, you should look at
20 the "FetchUselessDescriptors" config option in the man page.
22 Note that this script won't give you a perfect list of IP addresses
23 that might connect to you using Tor, since some Tor servers might exit
24 from other addresses than the one they publish.
29 # Change this to True if you want more verbose output. By default, we
30 # only print the IPs of the servers that accept any the listed
31 # addresses, one per line.
36 # Change this to True if you want to reverse the output, and list the
37 # servers that accept *none* of the listed addresses.
42 # Change this list to contain all of the target services you are interested
43 # in. It must contain one entry per line, each consisting of an IPv4 address,
44 # a colon, and a port number. This default is only used if we don't learn
45 # about any addresses from the command-line.
47 ADDRESSES_OF_INTEREST
= """
53 # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
63 assert sys
.version_info
>= (2,2)
67 return "".join([chr(ord(a
) & ord(b
)) for a
,b
in zip(ip
,mask
)])
69 def maskFromLong(lng
):
70 return struct
.pack("!L", lng
)
73 return maskFromLong(0xffffffffl ^
((1L<<(32-n
))-1))
78 >>> ip1 = socket.inet_aton("192.169.64.11")
79 >>> ip2 = socket.inet_aton("192.168.64.11")
80 >>> ip3 = socket.inet_aton("18.244.0.188")
82 >>> print Pattern.parse("18.244.0.188")
83 18.244.0.188/255.255.255.255:1-65535
84 >>> print Pattern.parse("18.244.0.188/16:*")
85 18.244.0.0/255.255.0.0:1-65535
86 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
88 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
89 192.168.0.0/255.255.0.0:22-25
90 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
92 >>> p1.appliesTo(ip1, 22)
94 >>> p1.appliesTo(ip2, 22)
96 >>> p1.appliesTo(ip2, 25)
98 >>> p1.appliesTo(ip2, 26)
101 def __init__(self
, ip
, mask
, portMin
, portMax
):
102 self
.ip
= maskIP(ip
,mask
)
104 self
.portMin
= portMin
105 self
.portMax
= portMax
108 return "%s/%s:%s-%s"%(socket
.inet_ntoa(self
.ip
),
109 socket
.inet_ntoa(self
.mask
),
115 addrspec
, portspec
= s
.split(":",1)
117 addrspec
, portspec
= s
, "*"
120 ip
,mask
= "\x00\x00\x00\x00","\x00\x00\x00\x00"
121 elif '/' not in addrspec
:
122 ip
= socket
.inet_aton(addrspec
)
123 mask
= "\xff\xff\xff\xff"
125 ip
,mask
= addrspec
.split("/",1)
126 ip
= socket
.inet_aton(ip
)
128 mask
= socket
.inet_aton(mask
)
130 mask
= maskByBits(int(mask
))
135 elif '-' not in portspec
:
136 portMin
= portMax
= int(portspec
)
138 portMin
, portMax
= map(int,portspec
.split("-",1))
140 return Pattern(ip
,mask
,portMin
,portMax
)
142 parse
= staticmethod(parse
)
144 def appliesTo(self
, ip
, port
):
145 return ((maskIP(ip
,self
.mask
) == self
.ip
) and
146 (self
.portMin
<= port
<= self
.portMax
))
151 >>> ip1 = socket.inet_aton("192.169.64.11")
152 >>> ip2 = socket.inet_aton("192.168.64.11")
153 >>> ip3 = socket.inet_aton("18.244.0.188")
155 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
156 >>> print str(pol).strip()
157 reject 0.0.0.0/0.0.0.0:80-80
158 accept 18.244.0.188/255.255.255.255:1-65535
159 >>> pol.accepts(ip1,80)
161 >>> pol.accepts(ip3,80)
163 >>> pol.accepts(ip3,81)
167 def __init__(self
, lst
):
170 def parseLines(lines
):
173 a
,p
=item
.split(" ",1)
179 raise ValueError("Unrecognized action %r",a
)
184 parseLines
= staticmethod(parseLines
)
188 for pat
, accept
in self
.lst
:
189 rule
= accept
and "accept" or "reject"
190 r
.append("%s %s\n"%(rule
,pat
))
193 def accepts(self
, ip
, port
):
194 for pattern
,accept
in self
.lst
:
195 if pattern
.appliesTo(ip
,port
):
200 def __init__(self
, name
, ip
, policy
, published
, fingerprint
):
204 self
.published
= published
205 self
.fingerprint
= fingerprint
209 for item
in lst
: d
[item
] = 1
217 global ADDRESSES_OF_INTEREST
219 if len(sys
.argv
) > 1:
221 opts
, pargs
= getopt
.getopt(sys
.argv
[1:], "vx")
222 except getopt
.GetoptError
, e
:
224 usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]]
236 ADDRESSES_OF_INTEREST
= "\n".join(pargs
)
243 for line
in sys
.stdin
.xreadlines():
244 if line
.startswith('router '):
246 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
),
248 _
, name
, ip
, rest
= line
.split(" ", 3)
252 elif line
.startswith('fingerprint') or \
253 line
.startswith('opt fingerprint'):
254 elts
= line
.strip().split()
255 if elts
[0] == 'opt': del elts
[0]
256 assert elts
[0] == 'fingerprint'
259 elif line
.startswith('accept ') or line
.startswith('reject '):
260 policy
.append(line
.strip())
261 elif line
.startswith('published '):
262 date
= time
.strptime(line
[len('published '):].strip(),
264 published
= time
.mktime(date
)
267 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
), published
,
271 for line
in ADDRESSES_OF_INTEREST
.split("\n"):
273 if not line
: continue
274 p
= Pattern
.parse(line
)
275 targets
.append((p
.ip
, p
.portMin
))
277 # remove all but the latest server of each IP/Nickname pair.
280 if (not latest
.has_key((s
.fingerprint
))
281 or s
.published
> latest
[(s
.fingerprint
)]):
282 latest
[s
.fingerprint
] = s
283 servers
= latest
.values()
285 accepters
, rejecters
= {}, {}
287 for ip
,port
in targets
:
288 if s
.policy
.accepts(ip
,port
):
294 # If any server at IP foo accepts, the IP does not reject.
295 for k
in accepters
.keys():
296 if rejecters
.has_key(k
):
300 printlist
= rejecters
.values()
302 printlist
= accepters
.values()
306 ents
= uniq_sort([ "%s\t%s"%(s
.ip
,s
.name
) for s
in printlist
])
308 ents
= uniq_sort([ s
.ip
for s
in printlist
])
313 import doctest
, exitparse
314 return doctest
.testmod(exitparse
)