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
20 man page. For 0.2.0.13-alpha and later, also look at the
21 "FetchDirInfoEarly" config option.
23 Note that this script won't give you a perfect list of IP addresses
24 that might connect to you using Tor, since some Tor servers might exit
25 from other addresses than the one they publish. See
26 https://check.torproject.org/ for an alternative (more
32 # Change this to True if you want more verbose output. By default, we
33 # only print the IPs of the servers that accept any the listed
34 # addresses, one per line.
39 # Change this to True if you want to reverse the output, and list the
40 # servers that accept *none* of the listed addresses.
45 # Change this list to contain all of the target services you are interested
46 # in. It must contain one entry per line, each consisting of an IPv4 address,
47 # a colon, and a port number. This default is only used if we don't learn
48 # about any addresses from the command-line.
50 ADDRESSES_OF_INTEREST
= """
56 # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
66 assert sys
.version_info
>= (2,2)
70 return "".join([chr(ord(a
) & ord(b
)) for a
,b
in zip(ip
,mask
)])
72 def maskFromLong(lng
):
73 return struct
.pack("!L", lng
)
76 return maskFromLong(0xffffffffl ^
((1L<<(32-n
))-1))
81 >>> ip1 = socket.inet_aton("192.169.64.11")
82 >>> ip2 = socket.inet_aton("192.168.64.11")
83 >>> ip3 = socket.inet_aton("18.244.0.188")
85 >>> print Pattern.parse("18.244.0.188")
86 18.244.0.188/255.255.255.255:1-65535
87 >>> print Pattern.parse("18.244.0.188/16:*")
88 18.244.0.0/255.255.0.0:1-65535
89 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
91 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
92 192.168.0.0/255.255.0.0:22-25
93 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
95 >>> p1.appliesTo(ip1, 22)
97 >>> p1.appliesTo(ip2, 22)
99 >>> p1.appliesTo(ip2, 25)
101 >>> p1.appliesTo(ip2, 26)
104 def __init__(self
, ip
, mask
, portMin
, portMax
):
105 self
.ip
= maskIP(ip
,mask
)
107 self
.portMin
= portMin
108 self
.portMax
= portMax
111 return "%s/%s:%s-%s"%(socket
.inet_ntoa(self
.ip
),
112 socket
.inet_ntoa(self
.mask
),
118 addrspec
, portspec
= s
.split(":",1)
120 addrspec
, portspec
= s
, "*"
123 ip
,mask
= "\x00\x00\x00\x00","\x00\x00\x00\x00"
124 elif '/' not in addrspec
:
125 ip
= socket
.inet_aton(addrspec
)
126 mask
= "\xff\xff\xff\xff"
128 ip
,mask
= addrspec
.split("/",1)
129 ip
= socket
.inet_aton(ip
)
131 mask
= socket
.inet_aton(mask
)
133 mask
= maskByBits(int(mask
))
138 elif '-' not in portspec
:
139 portMin
= portMax
= int(portspec
)
141 portMin
, portMax
= map(int,portspec
.split("-",1))
143 return Pattern(ip
,mask
,portMin
,portMax
)
145 parse
= staticmethod(parse
)
147 def appliesTo(self
, ip
, port
):
148 return ((maskIP(ip
,self
.mask
) == self
.ip
) and
149 (self
.portMin
<= port
<= self
.portMax
))
154 >>> ip1 = socket.inet_aton("192.169.64.11")
155 >>> ip2 = socket.inet_aton("192.168.64.11")
156 >>> ip3 = socket.inet_aton("18.244.0.188")
158 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
159 >>> print str(pol).strip()
160 reject 0.0.0.0/0.0.0.0:80-80
161 accept 18.244.0.188/255.255.255.255:1-65535
162 >>> pol.accepts(ip1,80)
164 >>> pol.accepts(ip3,80)
166 >>> pol.accepts(ip3,81)
170 def __init__(self
, lst
):
173 def parseLines(lines
):
176 a
,p
=item
.split(" ",1)
182 raise ValueError("Unrecognized action %r",a
)
187 parseLines
= staticmethod(parseLines
)
191 for pat
, accept
in self
.lst
:
192 rule
= accept
and "accept" or "reject"
193 r
.append("%s %s\n"%(rule
,pat
))
196 def accepts(self
, ip
, port
):
197 for pattern
,accept
in self
.lst
:
198 if pattern
.appliesTo(ip
,port
):
203 def __init__(self
, name
, ip
, policy
, published
, fingerprint
):
207 self
.published
= published
208 self
.fingerprint
= fingerprint
212 for item
in lst
: d
[item
] = 1
220 global ADDRESSES_OF_INTEREST
222 if len(sys
.argv
) > 1:
224 opts
, pargs
= getopt
.getopt(sys
.argv
[1:], "vx")
225 except getopt
.GetoptError
, e
:
227 usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]]
239 ADDRESSES_OF_INTEREST
= "\n".join(pargs
)
246 for line
in sys
.stdin
.xreadlines():
247 if line
.startswith('router '):
249 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
),
251 _
, name
, ip
, rest
= line
.split(" ", 3)
255 elif line
.startswith('fingerprint') or \
256 line
.startswith('opt fingerprint'):
257 elts
= line
.strip().split()
258 if elts
[0] == 'opt': del elts
[0]
259 assert elts
[0] == 'fingerprint'
262 elif line
.startswith('accept ') or line
.startswith('reject '):
263 policy
.append(line
.strip())
264 elif line
.startswith('published '):
265 date
= time
.strptime(line
[len('published '):].strip(),
267 published
= time
.mktime(date
)
270 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
), published
,
274 for line
in ADDRESSES_OF_INTEREST
.split("\n"):
276 if not line
: continue
277 p
= Pattern
.parse(line
)
278 targets
.append((p
.ip
, p
.portMin
))
280 # remove all but the latest server of each IP/Nickname pair.
283 if (not latest
.has_key((s
.fingerprint
))
284 or s
.published
> latest
[(s
.fingerprint
)]):
285 latest
[s
.fingerprint
] = s
286 servers
= latest
.values()
288 accepters
, rejecters
= {}, {}
290 for ip
,port
in targets
:
291 if s
.policy
.accepts(ip
,port
):
297 # If any server at IP foo accepts, the IP does not reject.
298 for k
in accepters
.keys():
299 if rejecters
.has_key(k
):
303 printlist
= rejecters
.values()
305 printlist
= accepters
.values()
309 ents
= uniq_sort([ "%s\t%s"%(s
.ip
,s
.name
) for s
in printlist
])
311 ents
= uniq_sort([ s
.ip
for s
in printlist
])
316 import doctest
, exitparse
317 return doctest
.testmod(exitparse
)