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 addresses.
13 cat ~/.tor/cached-descriptors* | python exitlist 18.244.0.188:80
15 You should look at the "FetchUselessDescriptors" and "FetchDirInfoEarly"
16 config options in the man page.
18 Note that this script won't give you a perfect list of IP addresses
19 that might connect to you using Tor.
21 - Some Tor servers might exit from other addresses than the one they
22 publish in their descriptor.
24 - This script just looks at the descriptor lists, so it counts relays
25 that were running a day in the past and aren't running now (or are
26 now running at a different address).
28 See https://check.torproject.org/ for an alternative (more accurate!)
34 # Change this to True if you want more verbose output. By default, we
35 # only print the IPs of the servers that accept any the listed
36 # addresses, one per line.
41 # Change this to True if you want to reverse the output, and list the
42 # servers that accept *none* of the listed addresses.
47 # Change this list to contain all of the target services you are interested
48 # in. It must contain one entry per line, each consisting of an IPv4 address,
49 # a colon, and a port number. This default is only used if we don't learn
50 # about any addresses from the command-line.
52 ADDRESSES_OF_INTEREST
= """
58 # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
68 assert sys
.version_info
>= (2,2)
72 return "".join([chr(ord(a
) & ord(b
)) for a
,b
in zip(ip
,mask
)])
74 def maskFromLong(lng
):
75 return struct
.pack("!L", lng
)
78 return maskFromLong(0xffffffffl ^
((1L<<(32-n
))-1))
83 >>> ip1 = socket.inet_aton("192.169.64.11")
84 >>> ip2 = socket.inet_aton("192.168.64.11")
85 >>> ip3 = socket.inet_aton("18.244.0.188")
87 >>> print Pattern.parse("18.244.0.188")
88 18.244.0.188/255.255.255.255:1-65535
89 >>> print Pattern.parse("18.244.0.188/16:*")
90 18.244.0.0/255.255.0.0:1-65535
91 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
93 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
94 192.168.0.0/255.255.0.0:22-25
95 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
97 >>> p1.appliesTo(ip1, 22)
99 >>> p1.appliesTo(ip2, 22)
101 >>> p1.appliesTo(ip2, 25)
103 >>> p1.appliesTo(ip2, 26)
106 def __init__(self
, ip
, mask
, portMin
, portMax
):
107 self
.ip
= maskIP(ip
,mask
)
109 self
.portMin
= portMin
110 self
.portMax
= portMax
113 return "%s/%s:%s-%s"%(socket
.inet_ntoa(self
.ip
),
114 socket
.inet_ntoa(self
.mask
),
120 addrspec
, portspec
= s
.split(":",1)
122 addrspec
, portspec
= s
, "*"
125 ip
,mask
= "\x00\x00\x00\x00","\x00\x00\x00\x00"
126 elif '/' not in addrspec
:
127 ip
= socket
.inet_aton(addrspec
)
128 mask
= "\xff\xff\xff\xff"
130 ip
,mask
= addrspec
.split("/",1)
131 ip
= socket
.inet_aton(ip
)
133 mask
= socket
.inet_aton(mask
)
135 mask
= maskByBits(int(mask
))
140 elif '-' not in portspec
:
141 portMin
= portMax
= int(portspec
)
143 portMin
, portMax
= map(int,portspec
.split("-",1))
145 return Pattern(ip
,mask
,portMin
,portMax
)
147 parse
= staticmethod(parse
)
149 def appliesTo(self
, ip
, port
):
150 return ((maskIP(ip
,self
.mask
) == self
.ip
) and
151 (self
.portMin
<= port
<= self
.portMax
))
156 >>> ip1 = socket.inet_aton("192.169.64.11")
157 >>> ip2 = socket.inet_aton("192.168.64.11")
158 >>> ip3 = socket.inet_aton("18.244.0.188")
160 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
161 >>> print str(pol).strip()
162 reject 0.0.0.0/0.0.0.0:80-80
163 accept 18.244.0.188/255.255.255.255:1-65535
164 >>> pol.accepts(ip1,80)
166 >>> pol.accepts(ip3,80)
168 >>> pol.accepts(ip3,81)
172 def __init__(self
, lst
):
175 def parseLines(lines
):
178 a
,p
=item
.split(" ",1)
184 raise ValueError("Unrecognized action %r",a
)
189 parseLines
= staticmethod(parseLines
)
193 for pat
, accept
in self
.lst
:
194 rule
= accept
and "accept" or "reject"
195 r
.append("%s %s\n"%(rule
,pat
))
198 def accepts(self
, ip
, port
):
199 for pattern
,accept
in self
.lst
:
200 if pattern
.appliesTo(ip
,port
):
205 def __init__(self
, name
, ip
, policy
, published
, fingerprint
):
209 self
.published
= published
210 self
.fingerprint
= fingerprint
214 for item
in lst
: d
[item
] = 1
222 global ADDRESSES_OF_INTEREST
224 if len(sys
.argv
) > 1:
226 opts
, pargs
= getopt
.getopt(sys
.argv
[1:], "vx")
227 except getopt
.GetoptError
, e
:
229 usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]]
241 ADDRESSES_OF_INTEREST
= "\n".join(pargs
)
248 for line
in sys
.stdin
.xreadlines():
249 if line
.startswith('router '):
251 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
),
253 _
, name
, ip
, rest
= line
.split(" ", 3)
257 elif line
.startswith('fingerprint') or \
258 line
.startswith('opt fingerprint'):
259 elts
= line
.strip().split()
260 if elts
[0] == 'opt': del elts
[0]
261 assert elts
[0] == 'fingerprint'
264 elif line
.startswith('accept ') or line
.startswith('reject '):
265 policy
.append(line
.strip())
266 elif line
.startswith('published '):
267 date
= time
.strptime(line
[len('published '):].strip(),
269 published
= time
.mktime(date
)
272 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
), published
,
276 for line
in ADDRESSES_OF_INTEREST
.split("\n"):
278 if not line
: continue
279 p
= Pattern
.parse(line
)
280 targets
.append((p
.ip
, p
.portMin
))
282 # remove all but the latest server of each IP/Nickname pair.
285 if (not latest
.has_key((s
.fingerprint
))
286 or s
.published
> latest
[(s
.fingerprint
)]):
287 latest
[s
.fingerprint
] = s
288 servers
= latest
.values()
290 accepters
, rejecters
= {}, {}
292 for ip
,port
in targets
:
293 if s
.policy
.accepts(ip
,port
):
299 # If any server at IP foo accepts, the IP does not reject.
300 for k
in accepters
.keys():
301 if rejecters
.has_key(k
):
305 printlist
= rejecters
.values()
307 printlist
= accepters
.values()
311 ents
= uniq_sort([ "%s\t%s"%(s
.ip
,s
.name
) for s
in printlist
])
313 ents
= uniq_sort([ s
.ip
for s
in printlist
])
318 import doctest
, exitparse
319 return doctest
.testmod(exitparse
)