import re ipv4_pattern = re.compile("(\d+)\.(\d+)\.(\d+)\.(\d+)", re.ASCII) rot13_mapping = {} for a, b, c, d in zip("anAN05", "mzMZ49", "naNA50", "zmZM94"): rot13_mapping.update(dict((chr(k), chr(v)) for k, v in zip(range(ord(a), ord(b) + 1), range(ord(c), ord(d) + 1)))) def right_now(): from datetime import datetime, timezone return datetime.now(timezone.utc) def nonsense_consistent(domain): from random import Random from string import ascii_lowercase from zlib import crc32 rng = Random(crc32(domain.encode("utf-8"))) length = rng.choices((9, 10, 11, 12), (4, 5, 3, 2))[0] return "".join(rng.choice(ascii_lowercase) for i in range(length)) def detect_gfw(r, ip, check): # attempt to detect interference from the Great Firewall of China. #from .ips import china #if r in china: return True # class D or class E, neither of which are correct for a (public?) DNS. #if int(r.partition(".")[0]) >= 224: return True rs = lambda prefix: r.startswith(prefix) de = lambda suffix: check.domain.endswith(suffix) hosted = de("facebook.com") or de("instagram.com") or de("whatsapp.com") if rs("31.13.") and not hosted: return True if rs("66.220."): return True if rs("69.63."): return True if rs("69.171.") and not rs("69.171.250."): return True if rs("74.86."): return True if rs("75.126."): return True if r == "64.13.192.74": return True # more non-facebook GFW stuff: # 31.13.64.33 # 31.13.70.1 # 31.13.70.20 # 31.13.76.16 # 31.13.86.1 # 173.252.110.21 # 192.99.140.48 # 199.16.156.40 # 199.16.158.190 return False async def getaddrs(server, domain, opts): from dns.asyncresolver import Resolver from dns.exception import Timeout from dns.resolver import NXDOMAIN, NoAnswer, NoNameservers #from dns.resolver import Resolver res = Resolver(configure=False) if opts.impatient: res.timeout = 5 res.lifetime = 2 res.nameservers = [server] try: #ans = res.resolve(domain, "A", search=False) ans = await res.resolve(domain, "A", search=False) except NXDOMAIN: return ["NXDOMAIN"] except NoAnswer: return ["NoAnswer"] except NoNameservers: return ["NoNameservers"] except Timeout: return ["Timeout"] #return list(set(rr.address for rr in ans.rrset)) return sorted(set(rr.address for rr in ans.rrset), key=ipkey) def read_ips(f): # TODO: make async and more robust. (regex pls) # TODO: does readlines() block if the pipe is left open i.e. user input? for ip in f.readlines(): if "#" in ip: ip, _, _ = ip.partition("#") ip = ip.strip() if ip.count(".") != 3: continue yield ip def rot13(s): return "".join(rot13_mapping.get(c, c) for c in s) def concat_nonsense(domain): return nonsense_consistent(domain) + "." + domain def head(n, it): res = [] try: while len(res) < n: res.append(next(it)) except StopIteration: pass return res def addr_to_int(ip): match = ipv4_pattern.fullmatch(ip) assert match is not None, row segs = list(map(int, match.group(1, 2, 3, 4))) assert all(0 <= seg <= 255 for seg in segs), match.group(0) numeric = segs[0] << 24 | segs[1] << 16 | segs[2] << 8 | segs[3] return numeric def ipkey(ip_string): # this is more lenient than addr_to_int. segs = [int(s) for s in ip_string.replace(":", ".").split(".")] return sum(256**(3 - i) * seg for i, seg in enumerate(segs)) def taskize(item): from types import CoroutineType from asyncio import Task, create_task if isinstance(item, CoroutineType): assert not isinstance(item, Task) # TODO: paranoid? item = create_task(item) return item def make_pooler(pool_size, finisher=None): # TODO: write a less confusing interface that allows the code to be written more flatly. # maybe like: async for done in apply(doit, [tuple_of_args]): from asyncio import wait, FIRST_COMPLETED pending = set() async def pooler(item=None): nonlocal pending finish = item is None if not finish: pending.add(taskize(item)) desired_size = 0 if finish else pool_size - 1 while len(pending) > desired_size: done, pending = await wait(pending, return_when=FIRST_COMPLETED) finisher(done, pending) return pooler class AttrCheck: """ Inheriting AttrCheck prevents accidentally setting attributes that don't already exist. """ def __setattr__(self, name, value): # NOTE: hasattr doesn't do what we want here. dir does. if name.startswith("_") or name in dir(self): super().__setattr__(name, value) else: raise AttributeError(name)