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.
62 assert sys
.version_info
>= (2,2)
66 return "".join([chr(ord(a
) & ord(b
)) for a
,b
in zip(ip
,mask
)])
68 def maskFromLong(lng
):
69 return struct
.pack("!L", lng
)
72 return maskFromLong(0xffffffffl ^
((1L<<(32-n
))-1))
77 >>> ip1 = socket.inet_aton("192.169.64.11")
78 >>> ip2 = socket.inet_aton("192.168.64.11")
79 >>> ip3 = socket.inet_aton("18.244.0.188")
81 >>> print Pattern.parse("18.244.0.188")
82 18.244.0.188/255.255.255.255:1-65535
83 >>> print Pattern.parse("18.244.0.188/16:*")
84 18.244.0.0/255.255.0.0:1-65535
85 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
87 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
88 192.168.0.0/255.255.0.0:22-25
89 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
91 >>> p1.appliesTo(ip1, 22)
93 >>> p1.appliesTo(ip2, 22)
95 >>> p1.appliesTo(ip2, 25)
97 >>> p1.appliesTo(ip2, 26)
100 def __init__(self
, ip
, mask
, portMin
, portMax
):
101 self
.ip
= maskIP(ip
,mask
)
103 self
.portMin
= portMin
104 self
.portMax
= portMax
107 return "%s/%s:%s-%s"%(socket
.inet_ntoa(self
.ip
),
108 socket
.inet_ntoa(self
.mask
),
114 addrspec
, portspec
= s
.split(":",1)
116 addrspec
, portspec
= s
, "*"
119 ip
,mask
= "\x00\x00\x00\x00","\x00\x00\x00\x00"
120 elif '/' not in addrspec
:
121 ip
= socket
.inet_aton(addrspec
)
122 mask
= "\xff\xff\xff\xff"
124 ip
,mask
= addrspec
.split("/",1)
125 ip
= socket
.inet_aton(ip
)
127 mask
= socket
.inet_aton(mask
)
129 mask
= maskByBits(int(mask
))
134 elif '-' not in portspec
:
135 portMin
= portMax
= int(portspec
)
137 portMin
, portMax
= map(int,portspec
.split("-",1))
139 return Pattern(ip
,mask
,portMin
,portMax
)
141 parse
= staticmethod(parse
)
143 def appliesTo(self
, ip
, port
):
144 return ((maskIP(ip
,self
.mask
) == self
.ip
) and
145 (self
.portMin
<= port
<= self
.portMax
))
150 >>> ip1 = socket.inet_aton("192.169.64.11")
151 >>> ip2 = socket.inet_aton("192.168.64.11")
152 >>> ip3 = socket.inet_aton("18.244.0.188")
154 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
155 >>> print str(pol).strip()
156 reject 0.0.0.0/0.0.0.0:80-80
157 accept 18.244.0.188/255.255.255.255:1-65535
158 >>> pol.accepts(ip1,80)
160 >>> pol.accepts(ip3,80)
162 >>> pol.accepts(ip3,81)
166 def __init__(self
, lst
):
169 def parseLines(lines
):
172 a
,p
=item
.split(" ",1)
178 raise ValueError("Unrecognized action %r",a
)
183 parseLines
= staticmethod(parseLines
)
187 for pat
, accept
in self
.lst
:
188 rule
= accept
and "accept" or "reject"
189 r
.append("%s %s\n"%(rule
,pat
))
192 def accepts(self
, ip
, port
):
193 for pattern
,accept
in self
.lst
:
194 if pattern
.appliesTo(ip
,port
):
199 def __init__(self
, name
, ip
, policy
):
206 for item
in lst
: d
[item
] = 1
214 global ADDRESSES_OF_INTEREST
216 if len(sys
.argv
) > 1:
218 opts
, pargs
= getopt
.getopt(sys
.argv
[1:], "vx")
219 except getopt
.GetoptError
, e
:
221 usage: %s [-v] [-x] [host:port [host:port [...]]]
233 ADDRESSES_OF_INTEREST
= "\n".join(pargs
)
238 for line
in sys
.stdin
.xreadlines():
239 if line
.startswith('router '):
241 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
)))
242 _
, name
, ip
, rest
= line
.split(" ", 3)
244 elif line
.startswith('accept ') or line
.startswith('reject '):
245 policy
.append(line
.strip())
248 servers
.append(Server(name
, ip
, Policy
.parseLines(policy
)))
251 for line
in ADDRESSES_OF_INTEREST
.split("\n"):
253 if not line
: continue
254 p
= Pattern
.parse(line
)
255 targets
.append((p
.ip
, p
.portMin
))
257 accepters
, rejecters
= [], []
259 for ip
,port
in targets
:
260 if s
.policy
.accepts(ip
,port
):
267 printlist
= rejecters
269 printlist
= accepters
273 ents
= uniq_sort([ "%s\t%s"%(s
.ip
,s
.name
) for s
in printlist
])
275 ents
= uniq_sort([ s
.ip
for s
in printlist
])
280 import doctest
, exitparse
281 return doctest
.testmod(exitparse
)