154 lines
4.9 KiB
Python
154 lines
4.9 KiB
Python
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)
|