3proxy/tests/cases/sysdns.py
Vladimir Dubrovin 3d20e1104e
Some checks are pending
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Waiting to run
resolver: per service, defaultresolve, fakeresolve or sysresolve
Each service resolves names the way the last of defaultresolve (the name
servers in force, configured or the system's with the hosts file),
fakeresolve (127.0.0.2) and sysresolve (the C library) read before it
says; the last one in the configuration also applies outside services.
defaultresolve is the new command, and the default.

The name servers and the hosts file no longer depend on the mode: nserver
lines, or the system's, are always in force, so dnspr keeps forwarding;
with fakeresolve it does not, as before. fakeresolver moves to common.c,
which the standalone services link without resolve.c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:45:48 +03:00

119 lines
4.0 KiB
Python

"""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")