Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- import asyncio
- import random
- import sys
- import urllib.parse
- import argparse
- import subprocess
- import os.path
- import requests
- DESCRIPTION = "Downloads all Tor Relay IP addresses from onionoo.torproject.org and checks whether random Relays are available."
- class TCPSocketConnectChecker:
- def __init__(self, host, port, timeout=10.0):
- self.host = host
- self.port = port
- self.timeout = timeout
- self.connection_status = None
- def __repr__(self):
- return "{}:{}".format(
- self.host if self.host.find(":") == -1 else "[" + self.host + "]",
- self.port)
- async def connect(self):
- try:
- # Open connection
- reader, writer = await asyncio.wait_for(
- asyncio.open_connection(self.host, self.port), self.timeout)
- # And close it
- writer.close()
- await writer.wait_closed()
- self.connection_status = True
- return (True, None)
- except (OSError, asyncio.TimeoutError) as e:
- self.connection_status = False
- return (False, e)
- class TorRelayGrabber:
- def __init__(self, timeout=10.0, proxy=None):
- self.timeout = timeout
- self.proxy = {'https': proxy} if proxy else None
- def _grab(self, url):
- with requests.get(url, timeout=int(self.timeout), proxies=self.proxy) as r:
- return r.json()
- def grab(self, preferred_urls_list=None):
- BASEURL = "https://onionoo.torproject.org/details?type=relay&running=true&fields=fingerprint,or_addresses,country"
- # Use public CORS proxy as a regular proxy in case if onionoo.torproject.org is unreachable
- URLS = [BASEURL,
- "https://icors.vercel.app/?" + urllib.parse.quote(BASEURL),
- "https://github.com/ValdikSS/tor-onionoo-mirror/raw/master/details-running-relays-fingerprint-address-only.json",
- "https://bitbucket.org/ValdikSS/tor-onionoo-mirror/raw/master/details-running-relays-fingerprint-address-only.json"]
- if preferred_urls_list:
- for pref_url in preferred_urls_list:
- URLS.insert(0, pref_url)
- for url in URLS:
- try:
- return self._grab(url)
- except Exception as e:
- print("Can't download Tor Relay data from/via {}: {}".format(
- urllib.parse.urlparse(url).hostname, e
- ), file=sys.stderr)
- def grab_parse(self, preferred_urls_list=None):
- grabbed = self.grab(preferred_urls_list)
- if grabbed:
- grabbed = grabbed["relays"]
- return grabbed
- class TorRelay:
- def __init__(self, relayinfo):
- self.relayinfo = relayinfo
- self.fingerprint = relayinfo["fingerprint"]
- self.iptuples = self._parse_or_addresses(relayinfo["or_addresses"])
- self.reachable = list()
- def reachables(self):
- r = list()
- for i in self.reachable:
- r.append("{}:{} {}".format(i[0] if i[0].find(":") == -1 else "[" + i[0] + "]",
- i[1],
- self.fingerprint,))
- return r
- def _reachable_str(self):
- return "\n".join(self.reachables())
- def __repr__(self):
- if not self.reachable:
- return str(self.relayinfo)
- return self._reachable_str()
- def __len__(self):
- return len(self.reachable)
- def _parse_or_addresses(self, or_addresses):
- ret = list()
- for address in or_addresses:
- parsed = urllib.parse.urlparse("//" + address)
- ret.append((parsed.hostname, parsed.port))
- return ret
- async def check(self, timeout=10.0):
- for i in self.iptuples:
- s = TCPSocketConnectChecker(i[0], i[1], timeout=timeout)
- sc = await s.connect()
- if sc[0]:
- self.reachable.append(i)
- return bool(self.reachable)
- def start_browser():
- browser_cmds=("Browser/start-tor-browser --detach", "Browser/firefox.exe")
- for cmd in browser_cmds:
- if os.path.exists(cmd.split(" ")[0]):
- subprocess.Popen(cmd.split(" "))
- break
- def str_list_with_prefix(prefix, list_):
- return "\n".join([prefix + r for r in list_])
- async def main_async(args):
- NUM_RELAYS = args.num_relays
- WORKING_RELAY_NUM_GOAL = args.working_relay_num_goal
- TIMEOUT = args.timeout
- outstream = args.outfile
- torrc_fmt = args.torrc_fmt
- BRIDGE_PREFIX = "Bridge " if torrc_fmt else ""
- print(f"Tor Relay Scanner. Will scan up to {WORKING_RELAY_NUM_GOAL}" +
- " working relays (or till the end)", file=sys.stderr)
- print("Downloading Tor Relay information from Tor Metrics…", file=sys.stderr)
- relays = TorRelayGrabber(timeout=TIMEOUT, proxy=args.proxy).grab_parse(args.url)
- if not relays:
- print("Tor Relay information can't be downloaded!", file=sys.stderr)
- return 1
- print("Done!", file=sys.stderr)
- random.shuffle(relays)
- if args.preferred_country:
- countries = {}
- for i, c in enumerate(args.preferred_country.split(",")):
- countries[c] = i
- # 1000 is just a sufficiently large number for default sorting
- relays = sorted(relays, key=lambda x: countries.get(x.get("country"), 1000))
- if args.port:
- relays_new = list()
- for relay in relays:
- for ipport in TorRelay(relay).iptuples:
- if ipport[1] in args.port:
- # deep copy needed here, otherwise subsequent loop
- # modifies "previous" value
- relay_copy = relay.copy()
- relay_copy["or_addresses"] = ["{}:{}".format(
- ipport[0] if ipport[0].find(":") == -1 else "[" + ipport[0] + "]",
- ipport[1])
- ]
- relays_new.append(relay_copy)
- relays = relays_new
- if not relays:
- print("There are no relays within specified port number constrains!", file=sys.stderr)
- print("Try changing port numbers.", file=sys.stderr)
- return 2
- working_relays = list()
- ntry = 0
- relaypos = 0
- numtries = round(len(relays) / NUM_RELAYS)
- for ntry in range(numtries):
- if len(working_relays) >= WORKING_RELAY_NUM_GOAL:
- break
- relaynum = min(NUM_RELAYS, len(relays) - relaypos - 1)
- test_relays = [TorRelay(relays[x])
- for x in range(relaypos, relaypos+relaynum)]
- relaypos += NUM_RELAYS
- if not test_relays:
- break
- print(
- f"\nTry {ntry}/{numtries}, We'll test the following {NUM_RELAYS} random relays:", file=sys.stderr)
- for relay in test_relays:
- print(relay, file=sys.stderr)
- print("", file=sys.stderr)
- if ntry:
- print(f"Found {len(working_relays)} good relays so far. Test {ntry}/{numtries} started…", file=sys.stderr)
- else:
- print(f"Test started…", file=sys.stderr)
- tasks = list()
- for relay in test_relays:
- tasks.append(asyncio.create_task(relay.check(TIMEOUT)))
- fin = await asyncio.gather(*tasks)
- print("", file=sys.stderr)
- print("The following relays are reachable this try:", file=sys.stderr)
- for relay in test_relays:
- if relay:
- print(str_list_with_prefix(BRIDGE_PREFIX, relay.reachables()), file=outstream)
- if sys.stdout != outstream:
- print(str_list_with_prefix(BRIDGE_PREFIX, relay.reachables()), file=sys.stderr)
- working_relays.append(relay)
- if not any(test_relays):
- print("No relays are reachable this try.", file=sys.stderr)
- if ntry > 1:
- print("", file=sys.stderr)
- print("All reachable relays:", file=sys.stderr)
- for relay in working_relays:
- if relay:
- print(str_list_with_prefix(BRIDGE_PREFIX, relay.reachables()), file=sys.stderr)
- if not any(working_relays):
- print("No relays are reachable, at all.", file=sys.stderr)
- if any(working_relays):
- if torrc_fmt:
- print("UseBridges 1", file=outstream)
- if args.prefsjs:
- try:
- with open(args.prefsjs, "r+") as f:
- prefsjs = str()
- for line in f:
- if "torbrowser.settings.bridges." not in line:
- prefsjs += line
- # Ugly r.reachables() array flattening, as it may have more than one reachable record.
- for num, relay in enumerate(sum([r.reachables() for r in working_relays], [])):
- prefsjs += f'user_pref("torbrowser.settings.bridges.bridge_strings.{num}", "{relay}");\n'
- prefsjs += 'user_pref("torbrowser.settings.bridges.enabled", true);\n'
- prefsjs += 'user_pref("torbrowser.settings.bridges.source", 2);\n'
- f.seek(0)
- f.truncate()
- f.write(prefsjs)
- except OSError as e:
- print("Can't open Tor Browser configuration:", e, file=sys.stderr)
- if args.start_browser:
- start_browser()
- def main():
- parser = argparse.ArgumentParser(description=DESCRIPTION)
- parser.add_argument('-n', type=int, dest='num_relays', default=30, help='The number of concurrent relays tested.')
- parser.add_argument('-g', '--goal', type=int, dest='working_relay_num_goal', default=5, help='Test until at least this number of working relays are found')
- parser.add_argument('-c', '--preferred-country', type=str, default="", help='Preferred country list, comma-separated. Example: se,gb,nl,de')
- parser.add_argument('--timeout', type=float, default=10.0, help='Socket connection timeout')
- parser.add_argument('-o', '--outfile', type=argparse.FileType('w'), default=sys.stdout, help='Output reachable relays to file')
- parser.add_argument('--torrc', action='store_true', dest='torrc_fmt', help='Output reachable relays in torrc format (with "Bridge" prefix)')
- parser.add_argument('--proxy', type=str, help='Set proxy for onionoo information download. Format: http://user:pass@host:port; socks5h://user:pass@host:port')
- parser.add_argument('--url', type=str, action='append', help='Preferred alternative URL for onionoo relay list. Could be used multiple times.')
- parser.add_argument('-p', type=int, dest='port', action='append', help='Scan for relays running on specified port number. Could be used multiple times.')
- parser.add_argument('--browser', type=str, nargs='?', metavar='/path/to/prefs.js', dest='prefsjs',
- const='Browser/TorBrowser/Data/Browser/profile.default/prefs.js',
- help='Install found relays into Tor Browser configuration file (prefs.js)')
- parser.add_argument('--start-browser', action='store_true', help='Launch browser after scanning')
- args = parser.parse_args()
- try:
- return asyncio.run(main_async(args))
- except (KeyboardInterrupt, SystemExit):
- pass
Add Comment
Please, Sign In to add comment