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.2.0.7-alpha and earlier):
13 cat ~/.tor/cached-routers* | python exitlist 18.244.0.188:80
15 example usage (Tor 0.2.0.8-alpha and later):
17 cat ~/.tor/cached-descriptors* | python exitlist 18.244.0.188:80
19 You should look at the "FetchUselessDescriptors" config option in the
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. See
25 https://check.torproject.org/ for an alternative (more
31 # Change this to True if you want more verbose output. By default, we
32 # only print the IPs of the servers that accept any the listed
33 # addresses, one per line.
38 # Change this to True if you want to reverse the output, and list the
39 # servers that accept *none* of the listed addresses.
44 # Change this list to contain all of the target services you are interested
45 # in. It must contain one entry per line, each consisting of an IPv4 address,
46 # a colon, and a port number. This default is only used if we don't learn
47 # about any addresses from the command-line.
49 ADDRESSES_OF_INTEREST
= """
55 # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
65 assert sys
.version_info
>= (2,2)
69 return "".join([chr(ord(a
) & ord(b
)) for a
,b
in zip(ip
,mask
)])
71 def maskFromLong(lng
):
72 return struct
.pack("!L", lng
)
75 return maskFromLong(0xffffffffl ^
((1L<<(32-n
))-1))
80 >>> ip1 = socket.inet_aton("192.169.64.11")
81 >>> ip2 = socket.inet_aton("192.168.64.11")
82 >>> ip3 = socket.inet_aton("18.244.0.188")
84 >>> print Pattern.parse("18.244.0.188")
85 18.244.0.188/255.255.255.255:1-65535
86 >>> print Pattern.parse("18.244.0.188/16:*")
87 18.244.0.0/255.255.0.0:1-65535
88 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
90 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
91 192.168.0.0/255.255.0.0:22-25
92 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
94 >>> p1.appliesTo(ip1, 22)
96 >>> p1.appliesTo(ip2, 22)
98 >>> p1.appliesTo(ip2, 25)
100 >>> p1.appliesTo(ip2, 26)
103 def __init__(self
, ip
, mask
, portMin
, portMax
):
104 self
.ip
= maskIP(ip
,mask
)
106 self
.portMin
= portMin
107 self
.portMax
= portMax
110 return "%s/%s:%s-%s"%(socket
.inet_ntoa(self
.ip
),
111 socket
.inet_ntoa(self
.mask
),
117 addrspec
, portspec
= s
.split(":",1)
119 addrspec
, portspec
= s
, "*"
122 ip
,mask
= "\x00\x00\x00\x00","\x00\x00\x00\x00"
123 elif '/' not in addrspec
:
124 ip
= socket
.inet_aton(addrspec
)
125 mask
= "\xff\xff\xff\xff"
127 ip
,mask
= addrspec
.split("/",1)
128 ip
= socket
.inet_aton(ip
)
130 mask
= socket
.inet_aton(mask
)
132 mask
= maskByBits(int(mask
))
137 elif '-' not in portspec
:
138 portMin
= portMax
= int(portspec
)
140 portMin
, portMax
= map(int,portspec
.split("-",1))
142 return Pattern(ip
,mask
,portMin
,portMax
)
144 parse
= staticmethod(parse
)
146 def appliesTo(self
, ip
, port
):
147 return ((maskIP(ip
,self
.mask
) == self
.ip
) and
148 (self
.portMin
<= port
<= self
.portMax
))
153 >>> ip1 = socket.inet_aton("192.169.64.11")
154 >>> ip2 = socket.inet_aton("192.168.64.11")
155 >>> ip3 = socket.inet_aton("18.244.0.188")
157 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
158 >>> print str(pol).strip()
159 reject 0.0.0.0/0.0.0.0:80-80
160 accept 18.244.0.188/255.255.255.255:1-65535
161 >>> pol.accepts(ip1,80)
163 >>> pol.accepts(ip3,80)
165 >>> pol.accepts(ip3,81)
169 def __init__(self
, lst
):
172 def parseLines(lines
):
175 a
,p
=item
.split(" ",1)
181 raise ValueError("Unrecognized action %r",a
)
186 parseLines
= staticmethod(parseLines
)
190 for pat
, accept
in self
.lst
:
191 rule
= accept
and "accept" or "reject"
192 r
.append("%s %s\n"%(rule
,pat
))
195 def accepts(self
, ip
, port
):
196 for pattern
,accept
in self
.lst
:
197 if pattern
.appliesTo(ip
,port
):
202 def __init__(self
, name
, ip
, policy
, published
, fingerprint
):
206 self
.published
= published
207 self
.fingerprint
= fingerprint
211 for item
in lst
: d
[item
] = 1
219 global ADDRESSES_OF_INTEREST
221 if len(sys
.argv
) > 1:
223 opts
, pargs
= getopt
.getopt(sys
.argv
[1:], "vx")
224 except getopt
.GetoptError
, e
:
226 usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]]
238 ADDRESSES_OF_INTEREST
= "\n".join(pargs
)
245 for line
in sys
.stdin
.xreadlines():
246 if line
.startswith('router '):
248 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
),
250 _
, name
, ip
, rest
= line
.split(" ", 3)
254 elif line
.startswith('fingerprint') or \
255 line
.startswith('opt fingerprint'):
256 elts
= line
.strip().split()
257 if elts
[0] == 'opt': del elts
[0]
258 assert elts
[0] == 'fingerprint'
261 elif line
.startswith('accept ') or line
.startswith('reject '):
262 policy
.append(line
.strip())
263 elif line
.startswith('published '):
264 date
= time
.strptime(line
[len('published '):].strip(),
266 published
= time
.mktime(date
)
269 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
), published
,
273 for line
in ADDRESSES_OF_INTEREST
.split("\n"):
275 if not line
: continue
276 p
= Pattern
.parse(line
)
277 targets
.append((p
.ip
, p
.portMin
))
279 # remove all but the latest server of each IP/Nickname pair.
282 if (not latest
.has_key((s
.fingerprint
))
283 or s
.published
> latest
[(s
.fingerprint
)]):
284 latest
[s
.fingerprint
] = s
285 servers
= latest
.values()
287 accepters
, rejecters
= {}, {}
289 for ip
,port
in targets
:
290 if s
.policy
.accepts(ip
,port
):
296 # If any server at IP foo accepts, the IP does not reject.
297 for k
in accepters
.keys():
298 if rejecters
.has_key(k
):
302 printlist
= rejecters
.values()
304 printlist
= accepters
.values()
308 ents
= uniq_sort([ "%s\t%s"%(s
.ip
,s
.name
) for s
in printlist
])
310 ents
= uniq_sort([ s
.ip
for s
in printlist
])
315 import doctest
, exitparse
316 return doctest
.testmod(exitparse
)