exitlist 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. #!/usr/bin/python
  2. # Copyright 2005-2006 Nick Mathewson
  3. # See the LICENSE file in the Tor distribution for licensing information.
  4. # Requires Python 2.2 or later.
  5. """
  6. exitlist -- Given a Tor directory on stdin, lists the Tor servers
  7. that accept connections to given addreses.
  8. example usage:
  9. cat ~/.tor/cached-descriptors* | python exitlist 18.244.0.188:80
  10. You should look at the "FetchUselessDescriptors" and "FetchDirInfoEarly"
  11. config options in the man page.
  12. Note that this script won't give you a perfect list of IP addresses
  13. that might connect to you using Tor.
  14. False negatives:
  15. - Some Tor servers might exit from other addresses than the one they
  16. publish in their descriptor.
  17. False positives:
  18. - This script just looks at the descriptor lists, so it counts relays
  19. that were running a day in the past and aren't running now (or are
  20. now running at a different address).
  21. See https://check.torproject.org/ for an alternative (more accurate!)
  22. approach.
  23. """
  24. #
  25. # Change this to True if you want more verbose output. By default, we
  26. # only print the IPs of the servers that accept any the listed
  27. # addresses, one per line.
  28. #
  29. VERBOSE = False
  30. #
  31. # Change this to True if you want to reverse the output, and list the
  32. # servers that accept *none* of the listed addresses.
  33. #
  34. INVERSE = False
  35. #
  36. # Change this list to contain all of the target services you are interested
  37. # in. It must contain one entry per line, each consisting of an IPv4 address,
  38. # a colon, and a port number. This default is only used if we don't learn
  39. # about any addresses from the command-line.
  40. #
  41. ADDRESSES_OF_INTEREST = """
  42. 1.2.3.4:80
  43. """
  44. #
  45. # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
  46. #
  47. import sys
  48. import re
  49. import getopt
  50. import socket
  51. import struct
  52. import time
  53. assert sys.version_info >= (2,2)
  54. def maskIP(ip,mask):
  55. return "".join([chr(ord(a) & ord(b)) for a,b in zip(ip,mask)])
  56. def maskFromLong(lng):
  57. return struct.pack("!L", lng)
  58. def maskByBits(n):
  59. return maskFromLong(0xffffffffl ^ ((1L<<(32-n))-1))
  60. class Pattern:
  61. """
  62. >>> import socket
  63. >>> ip1 = socket.inet_aton("192.169.64.11")
  64. >>> ip2 = socket.inet_aton("192.168.64.11")
  65. >>> ip3 = socket.inet_aton("18.244.0.188")
  66. >>> print Pattern.parse("18.244.0.188")
  67. 18.244.0.188/255.255.255.255:1-65535
  68. >>> print Pattern.parse("18.244.0.188/16:*")
  69. 18.244.0.0/255.255.0.0:1-65535
  70. >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
  71. 2.0.0.0/2.2.2.2:80-80
  72. >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
  73. 192.168.0.0/255.255.0.0:22-25
  74. >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
  75. >>> import socket
  76. >>> p1.appliesTo(ip1, 22)
  77. False
  78. >>> p1.appliesTo(ip2, 22)
  79. True
  80. >>> p1.appliesTo(ip2, 25)
  81. True
  82. >>> p1.appliesTo(ip2, 26)
  83. False
  84. """
  85. def __init__(self, ip, mask, portMin, portMax):
  86. self.ip = maskIP(ip,mask)
  87. self.mask = mask
  88. self.portMin = portMin
  89. self.portMax = portMax
  90. def __str__(self):
  91. return "%s/%s:%s-%s"%(socket.inet_ntoa(self.ip),
  92. socket.inet_ntoa(self.mask),
  93. self.portMin,
  94. self.portMax)
  95. def parse(s):
  96. if ":" in s:
  97. addrspec, portspec = s.split(":",1)
  98. else:
  99. addrspec, portspec = s, "*"
  100. if addrspec == '*':
  101. ip,mask = "\x00\x00\x00\x00","\x00\x00\x00\x00"
  102. elif '/' not in addrspec:
  103. ip = socket.inet_aton(addrspec)
  104. mask = "\xff\xff\xff\xff"
  105. else:
  106. ip,mask = addrspec.split("/",1)
  107. ip = socket.inet_aton(ip)
  108. if "." in mask:
  109. mask = socket.inet_aton(mask)
  110. else:
  111. mask = maskByBits(int(mask))
  112. if portspec == '*':
  113. portMin = 1
  114. portMax = 65535
  115. elif '-' not in portspec:
  116. portMin = portMax = int(portspec)
  117. else:
  118. portMin, portMax = map(int,portspec.split("-",1))
  119. return Pattern(ip,mask,portMin,portMax)
  120. parse = staticmethod(parse)
  121. def appliesTo(self, ip, port):
  122. return ((maskIP(ip,self.mask) == self.ip) and
  123. (self.portMin <= port <= self.portMax))
  124. class Policy:
  125. """
  126. >>> import socket
  127. >>> ip1 = socket.inet_aton("192.169.64.11")
  128. >>> ip2 = socket.inet_aton("192.168.64.11")
  129. >>> ip3 = socket.inet_aton("18.244.0.188")
  130. >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
  131. >>> print str(pol).strip()
  132. reject 0.0.0.0/0.0.0.0:80-80
  133. accept 18.244.0.188/255.255.255.255:1-65535
  134. >>> pol.accepts(ip1,80)
  135. False
  136. >>> pol.accepts(ip3,80)
  137. False
  138. >>> pol.accepts(ip3,81)
  139. True
  140. """
  141. def __init__(self, lst):
  142. self.lst = lst
  143. def parseLines(lines):
  144. r = []
  145. for item in lines:
  146. a,p=item.split(" ",1)
  147. if a == 'accept':
  148. a = True
  149. elif a == 'reject':
  150. a = False
  151. else:
  152. raise ValueError("Unrecognized action %r",a)
  153. p = Pattern.parse(p)
  154. r.append((p,a))
  155. return Policy(r)
  156. parseLines = staticmethod(parseLines)
  157. def __str__(self):
  158. r = []
  159. for pat, accept in self.lst:
  160. rule = accept and "accept" or "reject"
  161. r.append("%s %s\n"%(rule,pat))
  162. return "".join(r)
  163. def accepts(self, ip, port):
  164. for pattern,accept in self.lst:
  165. if pattern.appliesTo(ip,port):
  166. return accept
  167. return True
  168. class Server:
  169. def __init__(self, name, ip, policy, published, fingerprint):
  170. self.name = name
  171. self.ip = ip
  172. self.policy = policy
  173. self.published = published
  174. self.fingerprint = fingerprint
  175. def uniq_sort(lst):
  176. d = {}
  177. for item in lst: d[item] = 1
  178. lst = d.keys()
  179. lst.sort()
  180. return lst
  181. def run():
  182. global VERBOSE
  183. global INVERSE
  184. global ADDRESSES_OF_INTEREST
  185. if len(sys.argv) > 1:
  186. try:
  187. opts, pargs = getopt.getopt(sys.argv[1:], "vx")
  188. except getopt.GetoptError, e:
  189. print """
  190. usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]]
  191. -v verbose output
  192. -x invert results
  193. """ % sys.argv[0]
  194. sys.exit(0)
  195. for o, a in opts:
  196. if o == "-v":
  197. VERBOSE = True
  198. if o == "-x":
  199. INVERSE = True
  200. if len(pargs):
  201. ADDRESSES_OF_INTEREST = "\n".join(pargs)
  202. servers = []
  203. policy = []
  204. name = ip = None
  205. published = 0
  206. fp = ""
  207. for line in sys.stdin.xreadlines():
  208. if line.startswith('router '):
  209. if name:
  210. servers.append(Server(name, ip, Policy.parseLines(policy),
  211. published, fp))
  212. _, name, ip, rest = line.split(" ", 3)
  213. policy = []
  214. published = 0
  215. fp = ""
  216. elif line.startswith('fingerprint') or \
  217. line.startswith('opt fingerprint'):
  218. elts = line.strip().split()
  219. if elts[0] == 'opt': del elts[0]
  220. assert elts[0] == 'fingerprint'
  221. del elts[0]
  222. fp = "".join(elts)
  223. elif line.startswith('accept ') or line.startswith('reject '):
  224. policy.append(line.strip())
  225. elif line.startswith('published '):
  226. date = time.strptime(line[len('published '):].strip(),
  227. "%Y-%m-%d %H:%M:%S")
  228. published = time.mktime(date)
  229. if name:
  230. servers.append(Server(name, ip, Policy.parseLines(policy), published,
  231. fp))
  232. targets = []
  233. for line in ADDRESSES_OF_INTEREST.split("\n"):
  234. line = line.strip()
  235. if not line: continue
  236. p = Pattern.parse(line)
  237. targets.append((p.ip, p.portMin))
  238. # remove all but the latest server of each IP/Nickname pair.
  239. latest = {}
  240. for s in servers:
  241. if (not latest.has_key((s.fingerprint))
  242. or s.published > latest[(s.fingerprint)]):
  243. latest[s.fingerprint] = s
  244. servers = latest.values()
  245. accepters, rejecters = {}, {}
  246. for s in servers:
  247. for ip,port in targets:
  248. if s.policy.accepts(ip,port):
  249. accepters[s.ip] = s
  250. break
  251. else:
  252. rejecters[s.ip] = s
  253. # If any server at IP foo accepts, the IP does not reject.
  254. for k in accepters.keys():
  255. if rejecters.has_key(k):
  256. del rejecters[k]
  257. if INVERSE:
  258. printlist = rejecters.values()
  259. else:
  260. printlist = accepters.values()
  261. ents = []
  262. if VERBOSE:
  263. ents = uniq_sort([ "%s\t%s"%(s.ip,s.name) for s in printlist ])
  264. else:
  265. ents = uniq_sort([ s.ip for s in printlist ])
  266. for e in ents:
  267. print e
  268. def _test():
  269. import doctest, exitparse
  270. return doctest.testmod(exitparse)
  271. #_test()
  272. run()