dnsmasq-china-list/verify.py

280 lines
9.2 KiB
Python
Raw Normal View History

2018-12-13 02:31:54 +08:00
#!/usr/bin/env python
2019-02-28 22:22:33 +08:00
import dns.resolver, dns.rdataclass, dns.rdatatype, dns.name
2018-12-13 02:31:54 +08:00
from termcolor import colored
import random
import ipaddress
import tldextract
from io import StringIO
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
class ChnroutesNotAvailable(Exception):
pass
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
class NSNotAvailable(Exception):
pass
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
# OK
class OK(Exception):
pass
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
class WhitelistMatched(OK):
pass
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
class CDNListVerified(OK):
pass
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
class NSVerified(OK):
pass
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
# Not OK
class NotOK(Exception):
pass
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
class NXDOMAIN(NotOK):
pass
2018-12-13 02:31:54 +08:00
2018-12-14 01:41:07 +08:00
class BlacklistMatched(NotOK):
pass
class CDNListNotVerified(NotOK):
pass
class NSNotVerified(NotOK):
pass
class ChinaListVerify(object):
whitelist_file = "ns-whitelist.txt"
blacklist_file = "ns-blacklist.txt"
cdnlist_file = "cdn-testlist.txt"
2022-08-23 05:53:20 +08:00
chnroutes_file = "/usr/share/china_ip_list.txt"
tld_ns = {}
2018-12-14 01:41:07 +08:00
def __init__(self):
self.whitelist = self.load_list(self.whitelist_file)
self.blacklist = self.load_list(self.blacklist_file)
self.cdnlist = self.load_list(self.cdnlist_file)
try:
self.chnroutes = self.load_list(self.chnroutes_file)
except FileNotFoundError:
print(colored("Failed to load chnroutes, CDN check disabled", "red"))
self.chnroutes = None
def load_list(self, filename):
with open(filename) as f:
return list([l.rstrip('\n') for l in f if l and not l.startswith("#")])
def test_cn_ip(self, domain, response=None):
2018-12-14 01:41:07 +08:00
if self.chnroutes is None:
raise ChnroutesNotAvailable
2019-02-28 22:22:33 +08:00
answers = None
if response:
try:
answers = response.find_rrset(response.additional, dns.name.from_text(domain), dns.rdataclass.IN, dns.rdatatype.A)
except KeyError:
pass
2019-02-28 22:22:33 +08:00
if not answers:
answers = self.resolve(domain, 'A')
2018-12-18 16:39:03 +08:00
for answer in answers:
answer = answer.to_text()
if any(ipaddress.IPv4Address(answer) in ipaddress.IPv4Network(n) for n in self.chnroutes):
return True
return False
2018-12-14 01:41:07 +08:00
2019-02-28 21:07:26 +08:00
def resolve(self, domain, rdtype="A", server=None, authority=False):
# Compatibility between dnspython 1.16 and 2.0
if hasattr(dns.resolver, "resolve"):
action = "resolve"
else:
action = "query"
if not server:
return getattr(dns.resolver, action)(domain, rdtype)
2019-02-28 21:07:26 +08:00
elif not authority:
return getattr(dns.resolver.Resolver(filename=StringIO("nameserver " + server)), action)(domain, rdtype)
2019-02-28 21:07:26 +08:00
else:
answer = getattr(dns.resolver.Resolver(filename=StringIO("nameserver " + server)), action)(domain, rdtype, raise_on_no_answer=False)
return answer.response
def get_ns_for_tld(self, tld):
if tld not in self.tld_ns:
2019-02-28 22:10:06 +08:00
answers = self.resolve(tld + ".", "NS")
for answer in answers:
try:
ips = self.resolve(answer.to_text())
self.tld_ns[tld] = ips[0].to_text()
break
except dns.resolver.NXDOMAIN:
pass
else:
raise
return self.tld_ns[tld]
2018-12-14 01:41:07 +08:00
def check_whitelist(self, nameservers):
if any(i in " ".join(nameservers) for i in self.whitelist):
raise WhitelistMatched
def check_blacklist(self, nameservers):
2021-01-18 22:42:53 +08:00
if any((rule := i) in " ".join(nameservers) for i in self.blacklist):
raise BlacklistMatched(rule)
2018-12-14 01:41:07 +08:00
def check_cdnlist(self, domain):
if self.test_cn_ip(domain):
raise CDNListVerified
else:
raise CDNListNotVerified
def check_domain(self, domain, enable_cdnlist=True):
2018-12-14 01:41:07 +08:00
nameservers = []
nxdomain = False
2018-12-13 02:31:54 +08:00
try:
2019-02-28 22:40:42 +08:00
response = self.resolve(domain + ".", 'NS', self.get_ns_for_tld(tldextract.extract(domain).suffix), authority=True)
2018-12-13 02:31:54 +08:00
except dns.resolver.NXDOMAIN:
nxdomain = True
2018-12-14 01:41:07 +08:00
except:
raise
2018-12-14 01:41:07 +08:00
pass
else:
2019-02-28 22:34:20 +08:00
for rdata in response.authority[0]:
2018-12-14 01:41:07 +08:00
nameserver = rdata.to_text()
if tldextract.extract(nameserver).registered_domain:
nameservers.append(nameserver)
self.check_whitelist(nameservers)
if enable_cdnlist:
for testdomain in self.cdnlist:
if testdomain == domain or testdomain.endswith("." + domain):
try:
self.check_cdnlist(testdomain)
except dns.resolver.NXDOMAIN:
raise NXDOMAIN
# Assuming CDNList for non-TLDs
if domain.count(".") > 1 and tldextract.extract(domain).registered_domain != domain:
try:
self.check_cdnlist(domain)
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.exception.Timeout):
pass
2018-12-14 01:41:07 +08:00
if nxdomain:
# Double check due to false positives
try:
self.resolve("www." + domain, 'A')
except dns.resolver.NXDOMAIN:
raise NXDOMAIN
2018-12-14 01:41:07 +08:00
self.check_blacklist(nameservers)
for nameserver in nameservers:
try:
if self.test_cn_ip(nameserver, response):
2018-12-14 01:41:07 +08:00
raise NSVerified
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.exception.Timeout):
2018-12-14 01:41:07 +08:00
pass
if nameservers:
2021-01-18 22:42:53 +08:00
raise NSNotVerified(nameserver)
2018-12-14 01:41:07 +08:00
else:
raise NSNotAvailable
def check_domain_quiet(self, domain, **kwargs):
2018-12-14 01:41:07 +08:00
try:
self.check_domain(domain, **kwargs)
2018-12-14 01:41:07 +08:00
except OK:
return True
except NotOK:
return False
except:
return None
2018-12-13 02:31:54 +08:00
else:
2018-12-14 01:41:07 +08:00
return None
2019-02-01 20:35:25 +08:00
def check_domain_verbose(self, domain, show_green=False, **kwargs):
2018-12-14 04:37:10 +08:00
try:
2018-12-14 01:41:07 +08:00
try:
self.check_domain(domain, **kwargs)
2018-12-14 01:41:07 +08:00
except NXDOMAIN:
print(colored("NXDOMAIN found in (cdnlist or) domain: " + domain, "white", "on_red"))
2018-12-14 04:37:10 +08:00
raise
2018-12-14 01:41:07 +08:00
except WhitelistMatched:
2019-02-01 20:35:25 +08:00
if show_green:
2019-02-01 20:31:00 +08:00
print(colored("NS Whitelist matched for domain: " + domain, "green"))
2018-12-14 04:37:10 +08:00
raise
2018-12-14 01:41:07 +08:00
except CDNListVerified:
2019-02-01 20:35:25 +08:00
if show_green:
2019-02-01 20:31:00 +08:00
print(colored("CDNList matched and verified for domain: " + domain, "green"))
2018-12-14 04:37:10 +08:00
raise
2018-12-14 01:41:07 +08:00
except CDNListNotVerified:
print(colored("CDNList matched but failed to verify for domain: " + domain, "red"))
2018-12-14 04:37:10 +08:00
raise
2021-01-18 22:42:53 +08:00
except BlacklistMatched as rule:
print(colored(f"NS Blacklist matched for domain: {domain} ({rule})", "red"))
2018-12-14 04:37:10 +08:00
raise
2018-12-14 01:41:07 +08:00
except NSVerified:
2019-02-01 20:35:25 +08:00
if show_green:
2019-02-01 20:31:00 +08:00
print(colored("NS verified for domain: " + domain, "green"))
2018-12-14 04:37:10 +08:00
raise
2021-01-18 22:42:53 +08:00
except NSNotVerified as nameserver:
print(colored(f"NS failed to verify for domain: {domain} ({nameserver})", "red"))
2018-12-14 04:37:10 +08:00
raise
2018-12-14 01:41:07 +08:00
except ChnroutesNotAvailable:
print("Additional Check disabled due to missing chnroutes. domain:", domain)
2018-12-14 04:37:10 +08:00
raise
2018-12-14 01:41:07 +08:00
except NSNotAvailable:
print("Failed to get correct name server for domain:", domain)
2018-12-14 04:37:10 +08:00
raise
2018-12-13 02:31:54 +08:00
else:
2018-12-14 01:41:07 +08:00
raise NotImplementedError
2018-12-14 04:37:10 +08:00
except OK:
return True
except NotOK:
return False
except:
return None
else:
return None
2019-02-01 20:35:25 +08:00
def check_domain_list(self, domain_list, sample=30, show_green=False):
2018-12-14 04:37:10 +08:00
domains = self.load_list(domain_list)
if sample:
domains = random.sample(domains, sample)
2019-02-01 20:35:25 +08:00
else:
random.shuffle(domains)
2018-12-14 04:37:10 +08:00
for domain in domains:
2019-02-01 20:35:25 +08:00
self.check_domain_verbose(domain, show_green=show_green)
2018-12-14 01:41:07 +08:00
if __name__ == "__main__":
2018-12-21 01:59:22 +08:00
import argparse
description = 'A simple verify library for dnsmasq-china-list'
2018-12-21 02:02:03 +08:00
parser = argparse.ArgumentParser(description=description)
parser.add_argument('-f', '--file', nargs='?', default="accelerated-domains.china.raw.txt",
help='File to examine')
2019-02-01 20:31:00 +08:00
parser.add_argument('-s', '--sample', nargs='?', default=0,
2018-12-21 02:02:03 +08:00
help='Verify only a limited sample. Pass 0 to example all entries.')
2019-02-01 20:35:25 +08:00
parser.add_argument('-v', '--verbose', action="store_true",
help='Show green results.')
parser.add_argument('-d', '--domain', nargs='?',
help='Verify a domain instead of checking a list. Will ignore the other options.')
parser.add_argument('-D', '--dns', nargs='?',
help='Specify a DNS server to use instead of the system default one.')
2018-12-21 02:02:03 +08:00
config = parser.parse_args()
2018-12-14 01:41:07 +08:00
v = ChinaListVerify()
if config.dns:
dns.resolver.get_default_resolver().nameservers=[config.dns]
if config.domain:
2019-02-01 20:35:25 +08:00
v.check_domain_verbose(config.domain, show_green=config.verbose)
else:
2019-02-01 20:35:25 +08:00
v.check_domain_list(config.file, show_green=config.verbose, sample=int(config.sample))