"""The system's name servers and hosts file as the default resolver: with no nserver in a configuration, names in the hosts file are answered from it; with nserver lines it is not. Each service resolves names the way the last of defaultresolve, sysresolve and fakeresolve before it says.""" import os import re import sys import time def hosts_path(): if sys.platform == "win32": return os.path.join(os.environ.get("SystemRoot", r"C:\Windows"), "System32", "drivers", "etc", "hosts") return "/etc/hosts" def hosts_localhost(): """Whether the hosts file maps localhost to 127.0.0.1 itself.""" try: with open(hosts_path(), errors="replace") as f: for line in f: words = line.split("#", 1)[0].split() if len(words) > 1 and words[0] == "127.0.0.1" and "localhost" in [w.lower() for w in words[1:]]: return True except OSError: pass return False def system_nameserver(): """Whether there is a name server for 3proxy to find: resolv.conf has one, or on Windows the registry is taken to.""" if sys.platform == "win32": return True try: with open("/etc/resolv.conf") as f: return any(re.match(r"nameserver\s+[0-9a-fA-F.:]+\s*$", l.split("#")[0]) for l in f) except OSError: return False def wait(t, port, name): deadline = time.time() + 5 while time.time() < deadline: if t.dns_query(port, name): return def run(t): if not hosts_localhost() or not system_nameserver(): t.skip("no localhost in the hosts file, or no system name server") return port = t.free_port() t.start("sysdns", f""" log flush auth iponly allow * dnspr -p{port} """) wait(t, port, "localhost") t.eq(["127.0.0.1"], t.dns_query(port, "localhost"), "without nserver, a name of the hosts file is answered from it") t.eq(["127.0.0.1"], t.dns_query(port, "LocalHost"), "whatever its case") # with a name server configured, one that answers nothing, the hosts # file is not used other = t.free_port() t.start("sysdns_nserver", f""" log flush nserver 127.0.0.1:{t.free_port()} nscache 1024 auth iponly allow * dnspr -p{other} """) wait(t, other, "localhost") t.ne(["127.0.0.1"], t.dns_query(other, "localhost") or [], "with nserver, the hosts file is not used") # each service resolves the way the command before it says: fake (to # 127.0.0.2, where the server is not), the system routines (no # nsrecord) or the default (the system's name servers, and nsrecord) fake, system, default, srv = t.free_port(), t.free_port(), t.free_port(), t.free_port() t.start("sysdns_modes", f""" log nscache 1024 nsrecord only3proxy.test 127.0.0.1 flush auth iponly allow * fakeresolve proxy -p{fake} sysresolve proxy -p{system} defaultresolve proxy -p{default} flush auth iponly allow * http echo * /echo** httpsrv -i127.0.0.1 -p{srv} """, ports=[fake, system, default, srv]) t.eq(200, t.http(f"http://localhost:{srv}/echo", proxy=f"127.0.0.1:{default}").status, "defaultresolve: a name of the hosts file resolves") t.eq(200, t.http(f"http://only3proxy.test:{srv}/echo", proxy=f"127.0.0.1:{default}").status, "and an nsrecord name") t.eq(200, t.http(f"http://localhost:{srv}/echo", proxy=f"127.0.0.1:{system}").status, "sysresolve: names resolve through the system routines") t.ne(200, t.http(f"http://only3proxy.test:{srv}/echo", proxy=f"127.0.0.1:{system}").status, "which know no nsrecord name") t.ne(200, t.http(f"http://localhost:{srv}/echo", proxy=f"127.0.0.1:{fake}").status, "fakeresolve: every name is 127.0.0.2, in a service before the others")