mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-31 14:35:26 +00:00
Merge branch 'tkrizek/python-codestyle' into 'main'
Enforce Python codestyle with black See merge request isc-projects/bind9!6404
This commit is contained in:
@@ -21,7 +21,7 @@ variables:
|
||||
ASAN_SYMBOLIZER_PATH: /usr/lib/llvm-13/bin/llvm-symbolizer
|
||||
CLANG_FORMAT: clang-format-13
|
||||
|
||||
CFLAGS_COMMON: -fno-omit-frame-pointer -fno-optimize-sibling-calls -O1 -g -Wall -Wextra
|
||||
CFLAGS_COMMON: -fno-omit-frame-pointer -fno-optimize-sibling-calls -O1 -g -Wall -Wextra
|
||||
|
||||
# Pass run-time flags to AddressSanitizer to get core dumps on error.
|
||||
ASAN_OPTIONS: abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1
|
||||
@@ -427,6 +427,19 @@ misc:
|
||||
expire_in: "1 day"
|
||||
when: on_failure
|
||||
|
||||
black:
|
||||
<<: *precheck_job
|
||||
needs: []
|
||||
script:
|
||||
- black $(git ls-files '*.py')
|
||||
- git diff > black.patch
|
||||
- if test "$(git status --porcelain | grep -Ev '\?\?' | wc -l)" -gt "0"; then git status --short; exit 1; fi
|
||||
artifacts:
|
||||
paths:
|
||||
- black.patch
|
||||
expire_in: "1 week"
|
||||
when: on_failure
|
||||
|
||||
clang-format:
|
||||
<<: *precheck_job
|
||||
needs: []
|
||||
@@ -447,6 +460,14 @@ coccinelle:
|
||||
- util/check-cocci
|
||||
- if test "$(git status --porcelain | grep -Ev '\?\?' | wc -l)" -gt "0"; then git status --short; exit 1; fi
|
||||
|
||||
pylint:
|
||||
<<: *precheck_job
|
||||
needs: []
|
||||
script:
|
||||
- pylint --rcfile $CI_PROJECT_DIR/.pylintrc $(git ls-files '*.py' | grep -vE '(ans\.py|dangerfile\.py|^bin/tests/system/)')
|
||||
# Ignore Pylint wrong-import-position error in system test to enable use of pytest.importorskip
|
||||
- pylint --rcfile $CI_PROJECT_DIR/.pylintrc --disable=wrong-import-position $(git ls-files 'bin/tests/system/*.py' | grep -vE 'ans\.py')
|
||||
|
||||
reuse:
|
||||
<<: *precheck_job
|
||||
needs: []
|
||||
@@ -467,32 +488,6 @@ danger:
|
||||
variables:
|
||||
- $DANGER_GITLAB_API_TOKEN
|
||||
|
||||
flake8:
|
||||
<<: *default_triggering_rules
|
||||
<<: *base_image
|
||||
stage: postcheck
|
||||
needs:
|
||||
- job: autoreconf
|
||||
artifacts: true
|
||||
script:
|
||||
- *configure
|
||||
- flake8 --max-line-length=80 $(git ls-files '*.py' | grep -vE '(ans\.py|dangerfile\.py|^bin/tests/system/)')
|
||||
# Ignore Flake8 E402 error (module level import not at top of file) in system test to enable use of pytest.importorskip
|
||||
- flake8 --max-line-length=80 --extend-ignore=E402 $(git ls-files 'bin/tests/system/*.py' | grep -vE 'ans\.py')
|
||||
|
||||
pylint:
|
||||
<<: *default_triggering_rules
|
||||
<<: *base_image
|
||||
stage: postcheck
|
||||
needs:
|
||||
- job: autoreconf
|
||||
artifacts: true
|
||||
script:
|
||||
- *configure
|
||||
- pylint --rcfile $CI_PROJECT_DIR/.pylintrc $(git ls-files '*.py' | grep -vE '(ans\.py|dangerfile\.py|^bin/tests/system/)')
|
||||
# Ignore Pylint wrong-import-position error in system test to enable use of pytest.importorskip
|
||||
- pylint --rcfile $CI_PROJECT_DIR/.pylintrc --disable=wrong-import-position $(git ls-files 'bin/tests/system/*.py' | grep -vE 'ans\.py')
|
||||
|
||||
tarball-create:
|
||||
stage: precheck
|
||||
<<: *base_image
|
||||
|
@@ -76,9 +76,7 @@ def walk_trss(source_dir):
|
||||
|
||||
# try to find dir/file path for a clickable link
|
||||
try:
|
||||
t["rel_file_path"] = find_test_relative_path(
|
||||
source_dir, test_name
|
||||
)
|
||||
t["rel_file_path"] = find_test_relative_path(source_dir, test_name)
|
||||
except KeyError:
|
||||
pass # no existing path found
|
||||
|
||||
|
@@ -16,61 +16,63 @@ import time
|
||||
|
||||
|
||||
def run_rndc(server, rndc_command):
|
||||
'''
|
||||
"""
|
||||
Send the specified 'rndc_command' to 'server' with a timeout of 10 seconds
|
||||
'''
|
||||
rndc = os.getenv('RNDC')
|
||||
port = os.getenv('CONTROLPORT')
|
||||
"""
|
||||
rndc = os.getenv("RNDC")
|
||||
port = os.getenv("CONTROLPORT")
|
||||
|
||||
cmdline = [rndc, '-c', '../common/rndc.conf', '-p', port, '-s', server]
|
||||
cmdline = [rndc, "-c", "../common/rndc.conf", "-p", port, "-s", server]
|
||||
cmdline.extend(rndc_command)
|
||||
|
||||
subprocess.check_output(cmdline, stderr=subprocess.STDOUT, timeout=10)
|
||||
|
||||
|
||||
def rndc_loop(test_state, domain):
|
||||
'''
|
||||
"""
|
||||
Run "rndc addzone", "rndc modzone", and "rndc delzone" in a tight loop
|
||||
until the test is considered finished, ignoring errors
|
||||
'''
|
||||
"""
|
||||
rndc_commands = [
|
||||
['addzone', domain,
|
||||
'{ type primary; file "example.db"; };'],
|
||||
['modzone', domain,
|
||||
'{ type primary; file "example.db"; allow-transfer { any; }; };'],
|
||||
['delzone', domain],
|
||||
["addzone", domain, '{ type primary; file "example.db"; };'],
|
||||
[
|
||||
"modzone",
|
||||
domain,
|
||||
'{ type primary; file "example.db"; allow-transfer { any; }; };',
|
||||
],
|
||||
["delzone", domain],
|
||||
]
|
||||
|
||||
while not test_state['finished']:
|
||||
while not test_state["finished"]:
|
||||
for command in rndc_commands:
|
||||
try:
|
||||
run_rndc('10.53.0.3', command)
|
||||
run_rndc("10.53.0.3", command)
|
||||
except subprocess.SubprocessError:
|
||||
pass
|
||||
|
||||
|
||||
def check_if_server_is_responsive():
|
||||
'''
|
||||
"""
|
||||
Check if server status can be successfully retrieved using "rndc status"
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
run_rndc('10.53.0.3', ['status'])
|
||||
run_rndc("10.53.0.3", ["status"])
|
||||
return True
|
||||
except subprocess.SubprocessError:
|
||||
return False
|
||||
|
||||
|
||||
def test_rndc_deadlock():
|
||||
'''
|
||||
"""
|
||||
Test whether running "rndc addzone", "rndc modzone", and "rndc delzone"
|
||||
commands concurrently does not trigger a deadlock
|
||||
'''
|
||||
test_state = {'finished': False}
|
||||
"""
|
||||
test_state = {"finished": False}
|
||||
|
||||
# Create 4 worker threads running "rndc" commands in a loop.
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
for i in range(1, 5):
|
||||
domain = 'example%d' % i
|
||||
domain = "example%d" % i
|
||||
executor.submit(rndc_loop, test_state, domain)
|
||||
|
||||
# Run "rndc status" 10 times, with 1-second pauses between attempts.
|
||||
@@ -84,7 +86,7 @@ def test_rndc_deadlock():
|
||||
time.sleep(1)
|
||||
|
||||
# Signal worker threads that the test is finished.
|
||||
test_state['finished'] = True
|
||||
test_state["finished"] = True
|
||||
|
||||
# Check whether all "rndc status" commands succeeded.
|
||||
assert server_is_responsive
|
||||
|
@@ -69,18 +69,22 @@ from dns.name import *
|
||||
############################################################################
|
||||
actions = []
|
||||
rrs = []
|
||||
|
||||
|
||||
def ctl_channel(msg):
|
||||
global actions, rrs
|
||||
|
||||
msg = msg.splitlines().pop(0)
|
||||
print ('received control message: %s' % msg)
|
||||
print("received control message: %s" % msg)
|
||||
|
||||
msg = msg.split(b'|')
|
||||
msg = msg.split(b"|")
|
||||
if len(msg) == 0:
|
||||
return
|
||||
|
||||
actions = [x.strip() for x in msg[0].split(b',')]
|
||||
n = functools.reduce(lambda n, act: (n + (2 if act == b'dname' else 1)), [0] + actions)
|
||||
actions = [x.strip() for x in msg[0].split(b",")]
|
||||
n = functools.reduce(
|
||||
lambda n, act: (n + (2 if act == b"dname" else 1)), [0] + actions
|
||||
)
|
||||
|
||||
if len(msg) == 1:
|
||||
rrs = []
|
||||
@@ -89,29 +93,30 @@ def ctl_channel(msg):
|
||||
rrs.append((i, b))
|
||||
return
|
||||
|
||||
rlist = [x.strip() for x in msg[1].split(b',')]
|
||||
rlist = [x.strip() for x in msg[1].split(b",")]
|
||||
rrs = []
|
||||
for item in rlist:
|
||||
if item[0] == b's'[0]:
|
||||
if item[0] == b"s"[0]:
|
||||
i = int(item[1:].strip()) - 1
|
||||
if i > n:
|
||||
print ('invalid index %d' + (i + 1))
|
||||
print("invalid index %d" + (i + 1))
|
||||
continue
|
||||
rrs.append((int(item[1:]) - 1, True))
|
||||
else:
|
||||
i = int(item) - 1
|
||||
if i > n:
|
||||
print ('invalid index %d' % (i + 1))
|
||||
print("invalid index %d" % (i + 1))
|
||||
continue
|
||||
rrs.append((i, False))
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
############################################################################
|
||||
def create_response(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
labels = qname.lower().split('.')
|
||||
labels = qname.lower().split(".")
|
||||
wantsigs = True if m.ednsflags & dns.flags.DO else False
|
||||
|
||||
# get qtype
|
||||
@@ -124,27 +129,27 @@ def create_response(msg):
|
||||
# - sld is 'example'
|
||||
# - tld is 'com.'
|
||||
name = labels.pop(0)
|
||||
domain = '.'.join(labels)
|
||||
domain = ".".join(labels)
|
||||
sld = labels.pop(0)
|
||||
tld = '.'.join(labels)
|
||||
tld = ".".join(labels)
|
||||
|
||||
print ('query: ' + qname + '/' + typename)
|
||||
print ('domain: ' + domain)
|
||||
print("query: " + qname + "/" + typename)
|
||||
print("domain: " + domain)
|
||||
|
||||
# default answers, depending on QTYPE.
|
||||
# currently only A, AAAA, TXT and NS are supported.
|
||||
ttl = 86400
|
||||
additionalA = '10.53.0.4'
|
||||
additionalAAAA = 'fd92:7065:b8e:ffff::4'
|
||||
if typename == 'A':
|
||||
final = '10.53.0.4'
|
||||
elif typename == 'AAAA':
|
||||
final = 'fd92:7065:b8e:ffff::4'
|
||||
elif typename == 'TXT':
|
||||
final = 'Some\ text\ here'
|
||||
elif typename == 'NS':
|
||||
additionalA = "10.53.0.4"
|
||||
additionalAAAA = "fd92:7065:b8e:ffff::4"
|
||||
if typename == "A":
|
||||
final = "10.53.0.4"
|
||||
elif typename == "AAAA":
|
||||
final = "fd92:7065:b8e:ffff::4"
|
||||
elif typename == "TXT":
|
||||
final = "Some\ text\ here"
|
||||
elif typename == "NS":
|
||||
domain = qname
|
||||
final = ('ns1.%s' % domain)
|
||||
final = "ns1.%s" % domain
|
||||
else:
|
||||
final = None
|
||||
|
||||
@@ -153,9 +158,9 @@ def create_response(msg):
|
||||
delta = timedelta(30)
|
||||
t1 = t - delta
|
||||
t2 = t + delta
|
||||
inception=t1.strftime('%Y%m%d000000')
|
||||
expiry=t2.strftime('%Y%m%d000000')
|
||||
sigdata='OCXH2De0yE4NMTl9UykvOsJ4IBGs/ZIpff2rpaVJrVG7jQfmj50otBAp A0Zo7dpBU4ofv0N/F2Ar6LznCncIojkWptEJIAKA5tHegf/jY39arEpO cevbGp6DKxFhlkLXNcw7k9o7DSw14OaRmgAjXdTFbrl4AiAa0zAttFko Tso='
|
||||
inception = t1.strftime("%Y%m%d000000")
|
||||
expiry = t2.strftime("%Y%m%d000000")
|
||||
sigdata = "OCXH2De0yE4NMTl9UykvOsJ4IBGs/ZIpff2rpaVJrVG7jQfmj50otBAp A0Zo7dpBU4ofv0N/F2Ar6LznCncIojkWptEJIAKA5tHegf/jY39arEpO cevbGp6DKxFhlkLXNcw7k9o7DSw14OaRmgAjXdTFbrl4AiAa0zAttFko Tso="
|
||||
|
||||
# construct answer set.
|
||||
answers = []
|
||||
@@ -165,71 +170,97 @@ def create_response(msg):
|
||||
i = 0
|
||||
|
||||
for action in actions:
|
||||
if name != 'test':
|
||||
if name != "test":
|
||||
continue
|
||||
if action == b'xname':
|
||||
owner = curname + '.' + curdom
|
||||
newname = 'cname%d' % i
|
||||
if action == b"xname":
|
||||
owner = curname + "." + curdom
|
||||
newname = "cname%d" % i
|
||||
i += 1
|
||||
newdom = 'domain%d.%s' % (i, tld)
|
||||
newdom = "domain%d.%s" % (i, tld)
|
||||
i += 1
|
||||
target = newname + '.' + newdom
|
||||
print ('add external CNAME %s to %s' % (owner, target))
|
||||
target = newname + "." + newdom
|
||||
print("add external CNAME %s to %s" % (owner, target))
|
||||
answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target))
|
||||
rrsig = 'CNAME 5 3 %d %s %s 12345 %s %s' % \
|
||||
(ttl, expiry, inception, domain, sigdata)
|
||||
print ('add external RRISG(CNAME) %s to %s' % (owner, target))
|
||||
rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % (
|
||||
ttl,
|
||||
expiry,
|
||||
inception,
|
||||
domain,
|
||||
sigdata,
|
||||
)
|
||||
print("add external RRISG(CNAME) %s to %s" % (owner, target))
|
||||
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
|
||||
curname = newname
|
||||
curdom = newdom
|
||||
continue
|
||||
|
||||
if action == b'cname':
|
||||
owner = curname + '.' + curdom
|
||||
newname = 'cname%d' % i
|
||||
target = newname + '.' + curdom
|
||||
if action == b"cname":
|
||||
owner = curname + "." + curdom
|
||||
newname = "cname%d" % i
|
||||
target = newname + "." + curdom
|
||||
i += 1
|
||||
print ('add CNAME %s to %s' % (owner, target))
|
||||
print("add CNAME %s to %s" % (owner, target))
|
||||
answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target))
|
||||
rrsig = 'CNAME 5 3 %d %s %s 12345 %s %s' % \
|
||||
(ttl, expiry, inception, domain, sigdata)
|
||||
print ('add RRSIG(CNAME) %s to %s' % (owner, target))
|
||||
rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % (
|
||||
ttl,
|
||||
expiry,
|
||||
inception,
|
||||
domain,
|
||||
sigdata,
|
||||
)
|
||||
print("add RRSIG(CNAME) %s to %s" % (owner, target))
|
||||
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
|
||||
curname = newname
|
||||
continue
|
||||
|
||||
if action == b'dname':
|
||||
if action == b"dname":
|
||||
owner = curdom
|
||||
newdom = 'domain%d.%s' % (i, tld)
|
||||
newdom = "domain%d.%s" % (i, tld)
|
||||
i += 1
|
||||
print ('add DNAME %s to %s' % (owner, newdom))
|
||||
print("add DNAME %s to %s" % (owner, newdom))
|
||||
answers.append(dns.rrset.from_text(owner, ttl, IN, DNAME, newdom))
|
||||
rrsig = 'DNAME 5 3 %d %s %s 12345 %s %s' % \
|
||||
(ttl, expiry, inception, domain, sigdata)
|
||||
print ('add RRSIG(DNAME) %s to %s' % (owner, newdom))
|
||||
rrsig = "DNAME 5 3 %d %s %s 12345 %s %s" % (
|
||||
ttl,
|
||||
expiry,
|
||||
inception,
|
||||
domain,
|
||||
sigdata,
|
||||
)
|
||||
print("add RRSIG(DNAME) %s to %s" % (owner, newdom))
|
||||
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
|
||||
owner = curname + '.' + curdom
|
||||
target = curname + '.' + newdom
|
||||
print ('add synthesized CNAME %s to %s' % (owner, target))
|
||||
owner = curname + "." + curdom
|
||||
target = curname + "." + newdom
|
||||
print("add synthesized CNAME %s to %s" % (owner, target))
|
||||
answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target))
|
||||
rrsig = 'CNAME 5 3 %d %s %s 12345 %s %s' % \
|
||||
(ttl, expiry, inception, domain, sigdata)
|
||||
print ('add synthesized RRSIG(CNAME) %s to %s' % (owner, target))
|
||||
rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % (
|
||||
ttl,
|
||||
expiry,
|
||||
inception,
|
||||
domain,
|
||||
sigdata,
|
||||
)
|
||||
print("add synthesized RRSIG(CNAME) %s to %s" % (owner, target))
|
||||
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
|
||||
curdom = newdom
|
||||
continue
|
||||
|
||||
# now add the final answer
|
||||
owner = curname + '.' + curdom
|
||||
owner = curname + "." + curdom
|
||||
answers.append(dns.rrset.from_text(owner, ttl, IN, rrtype, final))
|
||||
rrsig = '%s 5 3 %d %s %s 12345 %s %s' % \
|
||||
(typename, ttl, expiry, inception, domain, sigdata)
|
||||
rrsig = "%s 5 3 %d %s %s 12345 %s %s" % (
|
||||
typename,
|
||||
ttl,
|
||||
expiry,
|
||||
inception,
|
||||
domain,
|
||||
sigdata,
|
||||
)
|
||||
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
|
||||
|
||||
# prepare the response and convert to wire format
|
||||
r = dns.message.make_response(m)
|
||||
|
||||
if name != 'test':
|
||||
if name != "test":
|
||||
r.answer.append(answers[-1])
|
||||
if wantsigs:
|
||||
r.answer.append(sigs[-1])
|
||||
@@ -242,24 +273,29 @@ def create_response(msg):
|
||||
else:
|
||||
r.answer.append(answers[i])
|
||||
|
||||
if typename != 'NS':
|
||||
r.authority.append(dns.rrset.from_text(domain, ttl, IN, "NS",
|
||||
("ns1.%s" % domain)))
|
||||
r.additional.append(dns.rrset.from_text(('ns1.%s' % domain), 86400,
|
||||
IN, A, additionalA))
|
||||
r.additional.append(dns.rrset.from_text(('ns1.%s' % domain), 86400,
|
||||
IN, AAAA, additionalAAAA))
|
||||
if typename != "NS":
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(domain, ttl, IN, "NS", ("ns1.%s" % domain))
|
||||
)
|
||||
r.additional.append(
|
||||
dns.rrset.from_text(("ns1.%s" % domain), 86400, IN, A, additionalA)
|
||||
)
|
||||
r.additional.append(
|
||||
dns.rrset.from_text(("ns1.%s" % domain), 86400, IN, AAAA, additionalAAAA)
|
||||
)
|
||||
|
||||
r.flags |= dns.flags.AA
|
||||
r.use_edns()
|
||||
return r.to_wire()
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print ("Shutting down now...")
|
||||
os.remove('ans.pid')
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
#
|
||||
@@ -270,11 +306,15 @@ def sigterm(signum, frame):
|
||||
ip4 = "10.53.0.4"
|
||||
ip6 = "fd92:7065:b8e:ffff::4"
|
||||
|
||||
try: port=int(os.environ['PORT'])
|
||||
except: port=5300
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
try: ctrlport=int(os.environ['EXTRAPORT1'])
|
||||
except: ctrlport=5300
|
||||
try:
|
||||
ctrlport = int(os.environ["EXTRAPORT1"])
|
||||
except:
|
||||
ctrlport = 5300
|
||||
|
||||
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_socket.bind((ip4, port))
|
||||
@@ -296,18 +336,18 @@ ctrl_socket.listen(5)
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open('ans.pid', 'w')
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print (pid, file=f)
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print ("Listening on %s port %d" % (ip4, port))
|
||||
print("Listening on %s port %d" % (ip4, port))
|
||||
if havev6:
|
||||
print ("Listening on %s port %d" % (ip6, port))
|
||||
print ("Control channel on %s port %d" % (ip4, ctrlport))
|
||||
print ("Ctrl-c to quit")
|
||||
print("Listening on %s port %d" % (ip6, port))
|
||||
print("Control channel on %s port %d" % (ip4, ctrlport))
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
input = [query4_socket, query6_socket, ctrl_socket]
|
||||
@@ -328,7 +368,7 @@ while running:
|
||||
if s == ctrl_socket:
|
||||
# Handle control channel input
|
||||
conn, addr = s.accept()
|
||||
print ("Control channel connected")
|
||||
print("Control channel connected")
|
||||
while True:
|
||||
msg = conn.recv(65535)
|
||||
if not msg:
|
||||
@@ -336,8 +376,7 @@ while running:
|
||||
ctl_channel(msg)
|
||||
conn.close()
|
||||
if s == query4_socket or s == query6_socket:
|
||||
print ("Query received on %s" %
|
||||
(ip4 if s == query4_socket else ip6))
|
||||
print("Query received on %s" % (ip4 if s == query4_socket else ip6))
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
rsp = create_response(msg[0])
|
||||
|
@@ -19,7 +19,7 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip('dns', minversion='2.0.0')
|
||||
pytest.importorskip("dns", minversion="2.0.0")
|
||||
import dns.exception
|
||||
import dns.message
|
||||
import dns.name
|
||||
@@ -55,18 +55,22 @@ def has_signed_apex_nsec(zone, response):
|
||||
|
||||
|
||||
def do_query(server, qname, qtype, tcp=False):
|
||||
query = dns.message.make_query(qname, qtype, use_edns=True,
|
||||
want_dnssec=True)
|
||||
query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
|
||||
try:
|
||||
if tcp:
|
||||
response = dns.query.tcp(query, server.nameservers[0], timeout=3,
|
||||
port=server.port)
|
||||
response = dns.query.tcp(
|
||||
query, server.nameservers[0], timeout=3, port=server.port
|
||||
)
|
||||
else:
|
||||
response = dns.query.udp(query, server.nameservers[0], timeout=3,
|
||||
port=server.port)
|
||||
response = dns.query.udp(
|
||||
query, server.nameservers[0], timeout=3, port=server.port
|
||||
)
|
||||
except dns.exception.Timeout:
|
||||
print("error: query timeout for query {} {} to {}".format(
|
||||
qname, qtype, server.nameservers[0]))
|
||||
print(
|
||||
"error: query timeout for query {} {} to {}".format(
|
||||
qname, qtype, server.nameservers[0]
|
||||
)
|
||||
)
|
||||
return None
|
||||
|
||||
return response
|
||||
@@ -77,10 +81,10 @@ def verify_zone(zone, transfer):
|
||||
assert verify is not None
|
||||
|
||||
filename = "{}out".format(zone)
|
||||
with open(filename, 'w', encoding='utf-8') as file:
|
||||
with open(filename, "w", encoding="utf-8") as file:
|
||||
for rr in transfer.answer:
|
||||
file.write(rr.to_text())
|
||||
file.write('\n')
|
||||
file.write("\n")
|
||||
|
||||
# dnssec-verify command with default arguments.
|
||||
verify_cmd = [verify, "-z", "-o", zone, filename]
|
||||
@@ -108,30 +112,39 @@ def read_statefile(server, zone):
|
||||
if response.rcode() == dns.rcode.NOERROR:
|
||||
# fetch key id from response.
|
||||
for rr in response.answer:
|
||||
if rr.match(dns.name.from_text(zone), dns.rdataclass.IN,
|
||||
dns.rdatatype.DS, dns.rdatatype.NONE):
|
||||
if rr.match(
|
||||
dns.name.from_text(zone),
|
||||
dns.rdataclass.IN,
|
||||
dns.rdatatype.DS,
|
||||
dns.rdatatype.NONE,
|
||||
):
|
||||
if count == 0:
|
||||
keyid = list(dict(rr.items).items())[0][0].key_tag
|
||||
count += 1
|
||||
|
||||
if count != 1:
|
||||
print("error: expected a single DS in response for {} from {},"
|
||||
"got {}".format(zone, addr, count))
|
||||
print(
|
||||
"error: expected a single DS in response for {} from {},"
|
||||
"got {}".format(zone, addr, count)
|
||||
)
|
||||
return {}
|
||||
else:
|
||||
print("error: {} response for {} DNSKEY from {}".format(
|
||||
dns.rcode.to_text(response.rcode()), zone, addr))
|
||||
print(
|
||||
"error: {} response for {} DNSKEY from {}".format(
|
||||
dns.rcode.to_text(response.rcode()), zone, addr
|
||||
)
|
||||
)
|
||||
return {}
|
||||
|
||||
filename = "ns9/K{}+013+{:05d}.state".format(zone, keyid)
|
||||
print("read state file {}".format(filename))
|
||||
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
with open(filename, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
if line.startswith(';'):
|
||||
if line.startswith(";"):
|
||||
continue
|
||||
key, val = line.strip().split(':', 1)
|
||||
key, val = line.strip().split(":", 1)
|
||||
state[key.strip()] = val.strip()
|
||||
|
||||
except FileNotFoundError:
|
||||
@@ -147,14 +160,17 @@ def zone_check(server, zone):
|
||||
# wait until zone is fully signed.
|
||||
signed = False
|
||||
for _ in range(10):
|
||||
response = do_query(server, zone, 'NSEC')
|
||||
response = do_query(server, zone, "NSEC")
|
||||
if not isinstance(response, dns.message.Message):
|
||||
print("error: no response for {} NSEC from {}".format(zone, addr))
|
||||
elif response.rcode() == dns.rcode.NOERROR:
|
||||
signed = has_signed_apex_nsec(zone, response)
|
||||
else:
|
||||
print("error: {} response for {} NSEC from {}".format(
|
||||
dns.rcode.to_text(response.rcode()), zone, addr))
|
||||
print(
|
||||
"error: {} response for {} NSEC from {}".format(
|
||||
dns.rcode.to_text(response.rcode()), zone, addr
|
||||
)
|
||||
)
|
||||
|
||||
if signed:
|
||||
break
|
||||
@@ -165,14 +181,17 @@ def zone_check(server, zone):
|
||||
|
||||
# check if zone if DNSSEC valid.
|
||||
verified = False
|
||||
transfer = do_query(server, zone, 'AXFR', tcp=True)
|
||||
transfer = do_query(server, zone, "AXFR", tcp=True)
|
||||
if not isinstance(transfer, dns.message.Message):
|
||||
print("error: no response for {} AXFR from {}".format(zone, addr))
|
||||
elif transfer.rcode() == dns.rcode.NOERROR:
|
||||
verified = verify_zone(zone, transfer)
|
||||
else:
|
||||
print("error: {} response for {} AXFR from {}".format(
|
||||
dns.rcode.to_text(transfer.rcode()), zone, addr))
|
||||
print(
|
||||
"error: {} response for {} AXFR from {}".format(
|
||||
dns.rcode.to_text(transfer.rcode()), zone, addr
|
||||
)
|
||||
)
|
||||
|
||||
assert verified
|
||||
|
||||
@@ -182,7 +201,7 @@ def keystate_check(server, zone, key):
|
||||
deny = False
|
||||
|
||||
search = key
|
||||
if key.startswith('!'):
|
||||
if key.startswith("!"):
|
||||
deny = True
|
||||
search = key[1:]
|
||||
|
||||
@@ -213,7 +232,7 @@ def wait_for_log(filename, log):
|
||||
print("read log file {}".format(filename))
|
||||
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
with open(filename, "r", encoding="utf-8") as file:
|
||||
s = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
if s.find(bytes(log, "ascii")) != -1:
|
||||
found = True
|
||||
@@ -241,67 +260,89 @@ def test_checkds_dspublished(named_port):
|
||||
|
||||
# DS correctly published in parent.
|
||||
zone_check(server, "dspublished.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone dspublished.checkds/IN (signed): checkds: " "DS response from 10.53.0.2",
|
||||
)
|
||||
keystate_check(parent, "dspublished.checkds.", "DSPublish")
|
||||
|
||||
# DS correctly published in parent (reference to parental-agent).
|
||||
zone_check(server, "reference.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone reference.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone reference.checkds/IN (signed): checkds: " "DS response from 10.53.0.2",
|
||||
)
|
||||
keystate_check(parent, "reference.checkds.", "DSPublish")
|
||||
|
||||
# DS not published in parent.
|
||||
zone_check(server, "missing-dspublished.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone missing-dspublished.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone missing-dspublished.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5",
|
||||
)
|
||||
keystate_check(parent, "missing-dspublished.checkds.", "!DSPublish")
|
||||
|
||||
# Badly configured parent.
|
||||
zone_check(server, "bad-dspublished.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone bad-dspublished.checkds/IN (signed): checkds: "
|
||||
"bad DS response from 10.53.0.6")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone bad-dspublished.checkds/IN (signed): checkds: "
|
||||
"bad DS response from 10.53.0.6",
|
||||
)
|
||||
keystate_check(parent, "bad-dspublished.checkds.", "!DSPublish")
|
||||
|
||||
# TBD: DS published in parent, but bogus signature.
|
||||
|
||||
# DS correctly published in all parents.
|
||||
zone_check(server, "multiple-dspublished.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone multiple-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone multiple-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.4")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone multiple-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone multiple-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.4",
|
||||
)
|
||||
keystate_check(parent, "multiple-dspublished.checkds.", "DSPublish")
|
||||
|
||||
# DS published in only one of multiple parents.
|
||||
zone_check(server, "incomplete-dspublished.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone incomplete-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone incomplete-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.4")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone incomplete-dspublished.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone incomplete-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone incomplete-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.4",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone incomplete-dspublished.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5",
|
||||
)
|
||||
keystate_check(parent, "incomplete-dspublished.checkds.", "!DSPublish")
|
||||
|
||||
# One of the parents is badly configured.
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone bad2-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone bad2-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.4")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone bad2-dspublished.checkds/IN (signed): checkds: "
|
||||
"bad DS response from 10.53.0.6")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone bad2-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone bad2-dspublished.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.4",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone bad2-dspublished.checkds/IN (signed): checkds: "
|
||||
"bad DS response from 10.53.0.6",
|
||||
)
|
||||
keystate_check(parent, "bad2-dspublished.checkds.", "!DSPublish")
|
||||
|
||||
# TBD: DS published in all parents, but one has bogus signature.
|
||||
@@ -323,60 +364,82 @@ def test_checkds_dswithdrawn(named_port):
|
||||
|
||||
# DS correctly published in single parent.
|
||||
zone_check(server, "dswithdrawn.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5",
|
||||
)
|
||||
keystate_check(parent, "dswithdrawn.checkds.", "DSRemoved")
|
||||
|
||||
# DS not withdrawn from parent.
|
||||
zone_check(server, "missing-dswithdrawn.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone missing-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone missing-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2",
|
||||
)
|
||||
keystate_check(parent, "missing-dswithdrawn.checkds.", "!DSRemoved")
|
||||
|
||||
# Badly configured parent.
|
||||
zone_check(server, "bad-dswithdrawn.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone bad-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"bad DS response from 10.53.0.6")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone bad-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"bad DS response from 10.53.0.6",
|
||||
)
|
||||
keystate_check(parent, "bad-dswithdrawn.checkds.", "!DSRemoved")
|
||||
|
||||
# TBD: DS published in parent, but bogus signature.
|
||||
|
||||
# DS correctly withdrawn from all parents.
|
||||
zone_check(server, "multiple-dswithdrawn.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone multiple-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone multiple-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.7")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone multiple-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone multiple-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.7",
|
||||
)
|
||||
keystate_check(parent, "multiple-dswithdrawn.checkds.", "DSRemoved")
|
||||
|
||||
# DS withdrawn from only one of multiple parents.
|
||||
zone_check(server, "incomplete-dswithdrawn.checkds.")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.7")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"DS response from 10.53.0.2",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.7",
|
||||
)
|
||||
keystate_check(parent, "incomplete-dswithdrawn.checkds.", "!DSRemoved")
|
||||
|
||||
# One of the parents is badly configured.
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.7")
|
||||
wait_for_log("ns9/named.run",
|
||||
"zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"bad DS response from 10.53.0.6")
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.5",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"empty DS response from 10.53.0.7",
|
||||
)
|
||||
wait_for_log(
|
||||
"ns9/named.run",
|
||||
"zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
|
||||
"bad DS response from 10.53.0.6",
|
||||
)
|
||||
keystate_check(parent, "bad2-dswithdrawn.checkds.", "!DSRemoved")
|
||||
|
||||
# TBD: DS withdrawn from all parents, but one has bogus signature.
|
||||
|
@@ -16,16 +16,16 @@ import os
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
@pytest.fixture(scope="session")
|
||||
def named_port():
|
||||
return int(os.environ.get('PORT', default=5300))
|
||||
return int(os.environ.get("PORT", default=5300))
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
@pytest.fixture(scope="session")
|
||||
def named_tlsport():
|
||||
return int(os.environ.get('TLSPORT', default=8853))
|
||||
return int(os.environ.get("TLSPORT", default=8853))
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
@pytest.fixture(scope="session")
|
||||
def control_port():
|
||||
return int(os.environ.get('CONTROLPORT', default=9953))
|
||||
return int(os.environ.get("CONTROLPORT", default=9953))
|
||||
|
@@ -40,20 +40,17 @@ def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
|
||||
|
||||
# DNS 2.0 keyring specifies the algorithm
|
||||
try:
|
||||
keyring = dns.tsigkeyring.from_text({ "foo" : {
|
||||
"hmac-sha256",
|
||||
"aaaaaaaaaaaa"
|
||||
} ,
|
||||
"fake" : {
|
||||
"hmac-sha256",
|
||||
"aaaaaaaaaaaa"
|
||||
}
|
||||
})
|
||||
keyring = dns.tsigkeyring.from_text(
|
||||
{
|
||||
"foo": {"hmac-sha256", "aaaaaaaaaaaa"},
|
||||
"fake": {"hmac-sha256", "aaaaaaaaaaaa"},
|
||||
}
|
||||
)
|
||||
except:
|
||||
keyring = dns.tsigkeyring.from_text({ "foo" : "aaaaaaaaaaaa",
|
||||
"fake" : "aaaaaaaaaaaa" })
|
||||
keyring = dns.tsigkeyring.from_text({"foo": "aaaaaaaaaaaa", "fake": "aaaaaaaaaaaa"})
|
||||
|
||||
dopass2 = False
|
||||
|
||||
@@ -81,7 +78,7 @@ def create_response(msg, tcp, first, ns10):
|
||||
m = dns.message.from_wire(msg, keyring=keyring)
|
||||
qname = m.question[0].name.to_text()
|
||||
lqname = qname.lower()
|
||||
labels = lqname.split('.')
|
||||
labels = lqname.split(".")
|
||||
rrtype = m.question[0].rdtype
|
||||
typename = dns.rdatatype.to_text(rrtype)
|
||||
|
||||
@@ -113,27 +110,31 @@ def create_response(msg, tcp, first, ns10):
|
||||
# Add a server cookie to the response
|
||||
if labels[0] != "nocookie":
|
||||
for o in m.options:
|
||||
if o.otype == 10: # Use 10 instead of COOKIE
|
||||
if first and labels[0] == "withtsig" and not tcp:
|
||||
r.use_tsig(keyring = keyring,
|
||||
keyname = dns.name.from_text("fake"),
|
||||
algorithm = HMAC_SHA256)
|
||||
elif labels[0] != "tcponly" or tcp:
|
||||
cookie = o
|
||||
if len(o.data) == 8:
|
||||
cookie.data = o.data + o.data
|
||||
else:
|
||||
cookie.data = o.data
|
||||
r.use_edns(options=[cookie])
|
||||
if o.otype == 10: # Use 10 instead of COOKIE
|
||||
if first and labels[0] == "withtsig" and not tcp:
|
||||
r.use_tsig(
|
||||
keyring=keyring,
|
||||
keyname=dns.name.from_text("fake"),
|
||||
algorithm=HMAC_SHA256,
|
||||
)
|
||||
elif labels[0] != "tcponly" or tcp:
|
||||
cookie = o
|
||||
if len(o.data) == 8:
|
||||
cookie.data = o.data + o.data
|
||||
else:
|
||||
cookie.data = o.data
|
||||
r.use_edns(options=[cookie])
|
||||
r.flags |= dns.flags.AA
|
||||
return r
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print ("Shutting down now...")
|
||||
os.remove('ans.pid')
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
#
|
||||
@@ -146,8 +147,10 @@ ip4_addr2 = "10.53.0.10"
|
||||
ip6_addr1 = "fd92:7065:b8e:ffff::9"
|
||||
ip6_addr2 = "fd92:7065:b8e:ffff::10"
|
||||
|
||||
try: port=int(os.environ['PORT'])
|
||||
except: port=5300
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_udp1.bind((ip4_addr1, port))
|
||||
@@ -195,24 +198,32 @@ except:
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open('ans.pid', 'w')
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print (pid, file=f)
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print ("Using DNS version %s" % dns.version.version)
|
||||
print ("Listening on %s port %d" % (ip4_addr1, port))
|
||||
print ("Listening on %s port %d" % (ip4_addr2, port))
|
||||
print("Using DNS version %s" % dns.version.version)
|
||||
print("Listening on %s port %d" % (ip4_addr1, port))
|
||||
print("Listening on %s port %d" % (ip4_addr2, port))
|
||||
if havev6:
|
||||
print ("Listening on %s port %d" % (ip6_addr1, port))
|
||||
print ("Listening on %s port %d" % (ip6_addr2, port))
|
||||
print ("Ctrl-c to quit")
|
||||
print("Listening on %s port %d" % (ip6_addr1, port))
|
||||
print("Listening on %s port %d" % (ip6_addr2, port))
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
input = [query4_udp1, query6_udp1, query4_tcp1, query6_tcp1,
|
||||
query4_udp2, query6_udp2, query4_tcp2, query6_tcp2]
|
||||
input = [
|
||||
query4_udp1,
|
||||
query6_udp1,
|
||||
query4_tcp1,
|
||||
query6_tcp1,
|
||||
query4_udp2,
|
||||
query6_udp2,
|
||||
query4_tcp2,
|
||||
query6_tcp2,
|
||||
]
|
||||
else:
|
||||
input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2]
|
||||
|
||||
@@ -228,14 +239,19 @@ while running:
|
||||
|
||||
for s in inputready:
|
||||
ns10 = False
|
||||
if s == query4_udp1 or s == query6_udp1 or \
|
||||
s == query4_udp2 or s == query6_udp2:
|
||||
if s == query4_udp1 or s == query6_udp1 or s == query4_udp2 or s == query6_udp2:
|
||||
if s == query4_udp1 or s == query6_udp1:
|
||||
print ("UDP Query received on %s" %
|
||||
(ip4_addr1 if s == query4_udp1 else ip6_addr1), end=" ")
|
||||
print(
|
||||
"UDP Query received on %s"
|
||||
% (ip4_addr1 if s == query4_udp1 else ip6_addr1),
|
||||
end=" ",
|
||||
)
|
||||
if s == query4_udp2 or s == query6_udp2:
|
||||
print ("UDP Query received on %s" %
|
||||
(ip4_addr2 if s == query4_udp2 else ip6_addr2), end=" ")
|
||||
print(
|
||||
"UDP Query received on %s"
|
||||
% (ip4_addr2 if s == query4_udp2 else ip6_addr2),
|
||||
end=" ",
|
||||
)
|
||||
ns10 = True
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
@@ -244,31 +260,36 @@ while running:
|
||||
print(dns.rcode.to_text(rsp.rcode()))
|
||||
s.sendto(rsp.to_wire(), msg[1])
|
||||
if dopass2:
|
||||
print ("Sending second UDP response without TSIG", end=" ")
|
||||
print("Sending second UDP response without TSIG", end=" ")
|
||||
rsp = create_response(msg[0], False, False, ns10)
|
||||
s.sendto(rsp.to_wire(), msg[1])
|
||||
print(dns.rcode.to_text(rsp.rcode()))
|
||||
|
||||
if s == query4_tcp1 or s == query6_tcp1 or \
|
||||
s == query4_tcp2 or s == query6_tcp2:
|
||||
if s == query4_tcp1 or s == query6_tcp1 or s == query4_tcp2 or s == query6_tcp2:
|
||||
try:
|
||||
(cs, _) = s.accept()
|
||||
if s == query4_tcp1 or s == query6_tcp1:
|
||||
print ("TCP Query received on %s" %
|
||||
(ip4_addr1 if s == query4_tcp1 else ip6_addr1), end=" ")
|
||||
print(
|
||||
"TCP Query received on %s"
|
||||
% (ip4_addr1 if s == query4_tcp1 else ip6_addr1),
|
||||
end=" ",
|
||||
)
|
||||
if s == query4_tcp2 or s == query6_tcp2:
|
||||
print ("TCP Query received on %s" %
|
||||
(ip4_addr2 if s == query4_tcp2 else ip6_addr2), end=" ")
|
||||
print(
|
||||
"TCP Query received on %s"
|
||||
% (ip4_addr2 if s == query4_tcp2 else ip6_addr2),
|
||||
end=" ",
|
||||
)
|
||||
ns10 = True
|
||||
# get TCP message length
|
||||
buf = cs.recv(2)
|
||||
length = struct.unpack('>H', buf[:2])[0]
|
||||
length = struct.unpack(">H", buf[:2])[0]
|
||||
# grep DNS message
|
||||
msg = cs.recv(length)
|
||||
rsp = create_response(msg, True, True, ns10)
|
||||
print(dns.rcode.to_text(rsp.rcode()))
|
||||
wire = rsp.to_wire()
|
||||
cs.send(struct.pack('>H', len(wire)))
|
||||
cs.send(struct.pack(">H", len(wire)))
|
||||
cs.send(wire)
|
||||
cs.close()
|
||||
except s.timeout:
|
||||
|
@@ -21,14 +21,15 @@ import dns, dns.message
|
||||
from dns.rcode import *
|
||||
|
||||
modes = [
|
||||
b"silent", # Do not respond
|
||||
b"close", # UDP: same as silent; TCP: also close the connection
|
||||
b"servfail", # Always respond with SERVFAIL
|
||||
b"unstable", # Constantly switch between "silent" and "servfail"
|
||||
b"silent", # Do not respond
|
||||
b"close", # UDP: same as silent; TCP: also close the connection
|
||||
b"servfail", # Always respond with SERVFAIL
|
||||
b"unstable", # Constantly switch between "silent" and "servfail"
|
||||
]
|
||||
mode = modes[0]
|
||||
n = 0
|
||||
|
||||
|
||||
def ctrl_channel(msg):
|
||||
global modes, mode, n
|
||||
|
||||
@@ -40,6 +41,7 @@ def ctrl_channel(msg):
|
||||
n = 0
|
||||
print("New mode: %s" % str(mode))
|
||||
|
||||
|
||||
def create_servfail(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
@@ -54,19 +56,25 @@ def create_servfail(msg):
|
||||
r.set_rcode(SERVFAIL)
|
||||
return r
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
ip4 = "10.53.0.8"
|
||||
|
||||
try: port=int(os.environ["PORT"])
|
||||
except: port=5300
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
try: ctrlport=int(os.environ['EXTRAPORT1'])
|
||||
except: ctrlport=5300
|
||||
try:
|
||||
ctrlport = int(os.environ["EXTRAPORT1"])
|
||||
except:
|
||||
ctrlport = 5300
|
||||
|
||||
query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_udp.bind((ip4, port))
|
||||
@@ -85,14 +93,14 @@ signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print (pid, file=f)
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print ("Listening on %s port %d" % (ip4, port))
|
||||
print ("Listening on %s port %d" % (ip4, ctrlport))
|
||||
print ("Ctrl-c to quit")
|
||||
print("Listening on %s port %d" % (ip4, port))
|
||||
print("Listening on %s port %d" % (ip4, ctrlport))
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
input = [query4_udp, query4_tcp, ctrl4_tcp]
|
||||
|
||||
@@ -113,7 +121,11 @@ while running:
|
||||
n = n + 1
|
||||
print("UDP query received on %s" % ip4, end=" ")
|
||||
msg = s.recvfrom(65535)
|
||||
if mode == b"silent" or mode == b"close" or (mode == b"unstable" and n % 2 == 1):
|
||||
if (
|
||||
mode == b"silent"
|
||||
or mode == b"close"
|
||||
or (mode == b"unstable" and n % 2 == 1)
|
||||
):
|
||||
# Do not respond.
|
||||
print("NO RESPONSE (%s)" % str(mode))
|
||||
continue
|
||||
@@ -125,7 +137,7 @@ while running:
|
||||
else:
|
||||
print("NO RESPONSE (can not create a response)")
|
||||
else:
|
||||
raise(Exception("unsupported mode: %s" % mode))
|
||||
raise (Exception("unsupported mode: %s" % mode))
|
||||
elif s == query4_tcp:
|
||||
n = n + 1
|
||||
print("TCP query received on %s" % ip4, end=" ")
|
||||
@@ -151,7 +163,7 @@ while running:
|
||||
print("NO RESPONSE (can not read the message length)")
|
||||
conn.close()
|
||||
continue
|
||||
length = struct.unpack('>H', msg[:2])[0]
|
||||
length = struct.unpack(">H", msg[:2])[0]
|
||||
msg = conn.recv(length)
|
||||
if len(msg) != length:
|
||||
print("NO RESPONSE (can not read the message)")
|
||||
@@ -161,12 +173,12 @@ while running:
|
||||
if rsp:
|
||||
print(dns.rcode.to_text(rsp.rcode()))
|
||||
wire = rsp.to_wire()
|
||||
conn.send(struct.pack('>H', len(wire)))
|
||||
conn.send(struct.pack(">H", len(wire)))
|
||||
conn.send(wire)
|
||||
else:
|
||||
print("NO RESPONSE (can not create a response)")
|
||||
else:
|
||||
raise(Exception("unsupported mode: %s" % mode))
|
||||
raise (Exception("unsupported mode: %s" % mode))
|
||||
except socket.error as e:
|
||||
print("NO RESPONSE (error: %s)" % str(e))
|
||||
if conn:
|
||||
|
@@ -32,7 +32,7 @@ def port():
|
||||
|
||||
def udp_listen(port):
|
||||
udp = socket.socket(type=socket.SOCK_DGRAM)
|
||||
udp.bind(('10.53.0.3', port))
|
||||
udp.bind(("10.53.0.3", port))
|
||||
|
||||
return udp
|
||||
|
||||
@@ -40,7 +40,7 @@ def udp_listen(port):
|
||||
def tcp_listen(port):
|
||||
tcp = socket.socket(type=socket.SOCK_STREAM)
|
||||
tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
tcp.bind(('10.53.0.3', port))
|
||||
tcp.bind(("10.53.0.3", port))
|
||||
tcp.listen(100)
|
||||
|
||||
return tcp
|
||||
@@ -62,12 +62,12 @@ def tcp_once(tcp):
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
os.remove('ans.pid')
|
||||
os.remove("ans.pid")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def write_pid():
|
||||
with open('ans.pid', 'w') as f:
|
||||
with open("ans.pid", "w") as f:
|
||||
pid = os.getpid()
|
||||
f.write("{}".format(pid))
|
||||
|
||||
|
@@ -20,7 +20,8 @@ import dns.rcode
|
||||
|
||||
|
||||
def test_connreset(named_port):
|
||||
msg = dns.message.make_query("sub.example.", "A", want_dnssec=True,
|
||||
use_edns=0, payload=1232)
|
||||
msg = dns.message.make_query(
|
||||
"sub.example.", "A", want_dnssec=True, use_edns=0, payload=1232
|
||||
)
|
||||
ans = dns.query.udp(msg, "10.53.0.2", timeout=10, port=named_port)
|
||||
assert ans.rcode() == dns.rcode.SERVFAIL
|
||||
|
@@ -30,6 +30,7 @@ def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
# SOA gets a unsigned response.
|
||||
@@ -54,10 +55,16 @@ def create_response(msg):
|
||||
now = datetime.today()
|
||||
expire = now + timedelta(days=30)
|
||||
inception = now - timedelta(days=1)
|
||||
rrsig = "A 13 2 60 " + expire.strftime("%Y%m%d%H%M%S") + " " + \
|
||||
inception.strftime("%Y%m%d%H%M%S") + " 12345 " + qname + \
|
||||
" gB+eISXAhSPZU2i/II0W9ZUhC2SCIrb94mlNvP5092WAeXxqN/vG43/1nmDl" + \
|
||||
"y2Qs7y5VCjSMOGn85bnaMoAc7w=="
|
||||
rrsig = (
|
||||
"A 13 2 60 "
|
||||
+ expire.strftime("%Y%m%d%H%M%S")
|
||||
+ " "
|
||||
+ inception.strftime("%Y%m%d%H%M%S")
|
||||
+ " 12345 "
|
||||
+ qname
|
||||
+ " gB+eISXAhSPZU2i/II0W9ZUhC2SCIrb94mlNvP5092WAeXxqN/vG43/1nmDl"
|
||||
+ "y2Qs7y5VCjSMOGn85bnaMoAc7w=="
|
||||
)
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, RRSIG, rrsig))
|
||||
elif rrtype == NS:
|
||||
@@ -69,12 +76,14 @@ def create_response(msg):
|
||||
r.flags |= dns.flags.AA
|
||||
return r
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print ("Shutting down now...")
|
||||
os.remove('ans.pid')
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
#
|
||||
@@ -85,8 +94,10 @@ def sigterm(signum, frame):
|
||||
ip4 = "10.53.0.10"
|
||||
ip6 = "fd92:7065:b8e:ffff::10"
|
||||
|
||||
try: port=int(os.environ['PORT'])
|
||||
except: port=5300
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_socket.bind((ip4, port))
|
||||
@@ -102,17 +113,17 @@ except:
|
||||
havev6 = False
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open('ans.pid', 'w')
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print (pid, file=f)
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print ("Listening on %s port %d" % (ip4, port))
|
||||
print("Listening on %s port %d" % (ip4, port))
|
||||
if havev6:
|
||||
print ("Listening on %s port %d" % (ip6, port))
|
||||
print ("Ctrl-c to quit")
|
||||
print("Listening on %s port %d" % (ip6, port))
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
input = [query4_socket, query6_socket]
|
||||
@@ -131,8 +142,9 @@ while running:
|
||||
|
||||
for s in inputready:
|
||||
if s == query4_socket or s == query6_socket:
|
||||
print ("Query received on %s" %
|
||||
(ip4 if s == query4_socket else ip6), end=" ")
|
||||
print(
|
||||
"Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
|
||||
)
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
rsp = create_response(msg[0])
|
||||
|
@@ -22,7 +22,7 @@ import pprint
|
||||
|
||||
DNSTAP_READ = sys.argv[1]
|
||||
DATAFILE = sys.argv[2]
|
||||
ARGS = [DNSTAP_READ, '-y', DATAFILE]
|
||||
ARGS = [DNSTAP_READ, "-y", DATAFILE]
|
||||
|
||||
with subprocess.Popen(ARGS, stdout=subprocess.PIPE) as f:
|
||||
for y in yaml.load_all(f.stdout, Loader=yaml.SafeLoader):
|
||||
|
@@ -20,15 +20,18 @@ import pytest
|
||||
@pytest.fixture
|
||||
def gnutls_cli_executable():
|
||||
# Ensure gnutls-cli is available.
|
||||
executable = shutil.which('gnutls-cli')
|
||||
executable = shutil.which("gnutls-cli")
|
||||
if not executable:
|
||||
pytest.skip('gnutls-cli not found in PATH')
|
||||
pytest.skip("gnutls-cli not found in PATH")
|
||||
|
||||
# Ensure gnutls-cli supports the --logfile command-line option.
|
||||
output = subprocess.run([executable, '--logfile=/dev/null'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
check=False).stdout
|
||||
if b'illegal option' in output:
|
||||
pytest.skip('gnutls-cli does not support the --logfile option')
|
||||
output = subprocess.run(
|
||||
[executable, "--logfile=/dev/null"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=False,
|
||||
).stdout
|
||||
if b"illegal option" in output:
|
||||
pytest.skip("gnutls-cli does not support the --logfile option")
|
||||
|
||||
return executable
|
||||
|
@@ -12,5 +12,6 @@
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import ssl
|
||||
|
||||
version = ssl.OPENSSL_VERSION_INFO
|
||||
print(version[0], version[1], version[2])
|
||||
|
@@ -36,7 +36,7 @@ if rlimit_nofile[0] < 1024:
|
||||
|
||||
# Introduce some random delay
|
||||
def jitter():
|
||||
time.sleep((500 + random.randint(0, 250))/1000000.0)
|
||||
time.sleep((500 + random.randint(0, 250)) / 1000000.0)
|
||||
|
||||
|
||||
# A set of simple procedures to get the test's configuration options
|
||||
@@ -77,8 +77,9 @@ class TCPConnector:
|
||||
tries = CONNECT_TRIES
|
||||
while tries > 0:
|
||||
try:
|
||||
sock = socket.create_connection(address=(self.host, self.port),
|
||||
timeout=None)
|
||||
sock = socket.create_connection(
|
||||
address=(self.host, self.port), timeout=None
|
||||
)
|
||||
self.connections.append(sock)
|
||||
break
|
||||
except ConnectionResetError:
|
||||
@@ -137,9 +138,10 @@ class SubDIG:
|
||||
|
||||
def run(self):
|
||||
# pylint: disable=consider-using-with
|
||||
with open(os.devnull, 'w', encoding='utf-8') as devnull:
|
||||
self.sub_process = subprocess.Popen(self.get_command(), shell=True,
|
||||
stdout=devnull)
|
||||
with open(os.devnull, "w", encoding="utf-8") as devnull:
|
||||
self.sub_process = subprocess.Popen(
|
||||
self.get_command(), shell=True, stdout=devnull
|
||||
)
|
||||
|
||||
def wait(self, timeout=None):
|
||||
res = None
|
||||
@@ -158,13 +160,11 @@ class SubDIG:
|
||||
# A simple wrapper class which allows running multiple dig instances
|
||||
# and examining their statuses in one logical operation.
|
||||
class MultiDIG:
|
||||
def __init__(self, numdigs, http_secure=None,
|
||||
extra_args=None):
|
||||
def __init__(self, numdigs, http_secure=None, extra_args=None):
|
||||
assert int(numdigs) > 0
|
||||
digs = []
|
||||
for _ in range(1, int(numdigs) + 1):
|
||||
digs.append(SubDIG(http_secure=http_secure,
|
||||
extra_args=extra_args))
|
||||
digs.append(SubDIG(http_secure=http_secure, extra_args=extra_args))
|
||||
self.digs = digs
|
||||
assert len(self.digs) == int(numdigs)
|
||||
|
||||
@@ -179,12 +179,11 @@ class MultiDIG:
|
||||
# status. Returns true or false.
|
||||
def wait_for_result(self, result):
|
||||
return reduce(
|
||||
lambda a, b: ((a == result or a is True) and b == result),
|
||||
self.wait())
|
||||
lambda a, b: ((a == result or a is True) and b == result), self.wait()
|
||||
)
|
||||
|
||||
def alive(self):
|
||||
return reduce(lambda a, b: (a and b), map(lambda p: (p.alive()),
|
||||
self.digs))
|
||||
return reduce(lambda a, b: (a and b), map(lambda p: (p.alive()), self.digs))
|
||||
|
||||
def completed(self):
|
||||
total = 0
|
||||
@@ -203,8 +202,7 @@ def run_test(http_secure=True):
|
||||
assert subdig.wait() == 0, "DIG was expected to succeed"
|
||||
# Let's create a lot of TCP connections to the server stress the
|
||||
# HTTP quota
|
||||
connector = TCPConnector(get_http_host(),
|
||||
get_http_port(http_secure=http_secure))
|
||||
connector = TCPConnector(get_http_host(), get_http_port(http_secure=http_secure))
|
||||
# Let's make queries until the quota kicks in
|
||||
subdig = SubDIG(http_secure=http_secure, extra_args=query_args)
|
||||
subdig.run()
|
||||
@@ -218,25 +216,28 @@ def run_test(http_secure=True):
|
||||
# At this point quota has kicked in. Additionally, let's create a
|
||||
# bunch of dig processes all trying to make a query against the
|
||||
# server with exceeded quota
|
||||
multidig = MultiDIG(MULTIDIG_INSTANCES, http_secure=http_secure,
|
||||
extra_args=query_args)
|
||||
multidig = MultiDIG(
|
||||
MULTIDIG_INSTANCES, http_secure=http_secure, extra_args=query_args
|
||||
)
|
||||
multidig.run()
|
||||
# Wait for the dig instance to complete. Not a single instance has
|
||||
# a chance to complete successfully because of the exceeded quota
|
||||
assert subdig.wait(timeout=5) is None,\
|
||||
"The single DIG instance has stopped prematurely"
|
||||
assert (
|
||||
subdig.wait(timeout=5) is None
|
||||
), "The single DIG instance has stopped prematurely"
|
||||
assert subdig.alive(), "The single DIG instance is expected to be alive"
|
||||
assert multidig.alive(), \
|
||||
("The DIG instances from the set are all expected to "
|
||||
"be alive, but {} of them have completed")\
|
||||
.format(multidig.completed())
|
||||
assert multidig.alive(), (
|
||||
"The DIG instances from the set are all expected to "
|
||||
"be alive, but {} of them have completed"
|
||||
).format(multidig.completed())
|
||||
# Let's close opened connections (in random order) to let all dig
|
||||
# processes to complete
|
||||
connector.disconnect_all()
|
||||
# Wait for all processes to complete successfully
|
||||
assert subdig.wait() == 0, "Single DIG instance failed"
|
||||
assert multidig.wait_for_result(0) is True,\
|
||||
"One or more of DIG instances returned unexpected results"
|
||||
assert (
|
||||
multidig.wait_for_result(0) is True
|
||||
), "One or more of DIG instances returned unexpected results"
|
||||
|
||||
|
||||
def main():
|
||||
|
@@ -18,7 +18,7 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip('dns')
|
||||
pytest.importorskip("dns")
|
||||
import dns.exception
|
||||
import dns.message
|
||||
import dns.name
|
||||
@@ -28,18 +28,28 @@ import dns.rdatatype
|
||||
|
||||
def test_gnutls_cli_query(gnutls_cli_executable, named_tlsport):
|
||||
# Prepare the example/SOA query which will be sent over TLS.
|
||||
query = dns.message.make_query('example.', dns.rdatatype.SOA)
|
||||
query = dns.message.make_query("example.", dns.rdatatype.SOA)
|
||||
query_wire = query.to_wire()
|
||||
query_with_length = struct.pack('>H', len(query_wire)) + query_wire
|
||||
query_with_length = struct.pack(">H", len(query_wire)) + query_wire
|
||||
|
||||
# Run gnutls-cli.
|
||||
gnutls_cli_args = [gnutls_cli_executable, '--no-ca-verification', '-V',
|
||||
'--no-ocsp', '--alpn=dot', '--logfile=gnutls-cli.log',
|
||||
'--port=%d' % named_tlsport, '10.53.0.1']
|
||||
with open('gnutls-cli.err', 'wb') as gnutls_cli_stderr, \
|
||||
subprocess.Popen(gnutls_cli_args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=gnutls_cli_stderr,
|
||||
bufsize=0) as gnutls_cli:
|
||||
gnutls_cli_args = [
|
||||
gnutls_cli_executable,
|
||||
"--no-ca-verification",
|
||||
"-V",
|
||||
"--no-ocsp",
|
||||
"--alpn=dot",
|
||||
"--logfile=gnutls-cli.log",
|
||||
"--port=%d" % named_tlsport,
|
||||
"10.53.0.1",
|
||||
]
|
||||
with open("gnutls-cli.err", "wb") as gnutls_cli_stderr, subprocess.Popen(
|
||||
gnutls_cli_args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=gnutls_cli_stderr,
|
||||
bufsize=0,
|
||||
) as gnutls_cli:
|
||||
# Send the example/SOA query to the standard input of gnutls-cli. Do
|
||||
# not close standard input yet because that causes gnutls-cli to close
|
||||
# the TLS connection immediately, preventing the response from being
|
||||
@@ -56,8 +66,8 @@ def test_gnutls_cli_query(gnutls_cli_executable, named_tlsport):
|
||||
selector = selectors.DefaultSelector()
|
||||
selector.register(gnutls_cli.stdout, selectors.EVENT_READ)
|
||||
deadline = time.time() + 10
|
||||
gnutls_cli_output = b''
|
||||
response = b''
|
||||
gnutls_cli_output = b""
|
||||
response = b""
|
||||
while not response and not gnutls_cli.poll():
|
||||
if not selector.select(timeout=deadline - time.time()):
|
||||
break
|
||||
@@ -80,16 +90,19 @@ def test_gnutls_cli_query(gnutls_cli_executable, named_tlsport):
|
||||
gnutls_cli.kill()
|
||||
|
||||
# Store the response received for diagnostic purposes.
|
||||
with open('gnutls-cli.out.bin', 'wb') as response_bin:
|
||||
with open("gnutls-cli.out.bin", "wb") as response_bin:
|
||||
response_bin.write(gnutls_cli_output)
|
||||
if response:
|
||||
with open('gnutls-cli.out.txt', 'w', encoding='utf-8') as response_txt:
|
||||
with open("gnutls-cli.out.txt", "w", encoding="utf-8") as response_txt:
|
||||
response_txt.write(response.to_text())
|
||||
|
||||
# Check whether a response was received and whether it is sane.
|
||||
assert response
|
||||
assert query.id == response.id
|
||||
assert len(response.answer) == 1
|
||||
assert response.answer[0].match(dns.name.from_text('example.'),
|
||||
dns.rdataclass.IN, dns.rdatatype.SOA,
|
||||
dns.rdatatype.NONE)
|
||||
assert response.answer[0].match(
|
||||
dns.name.from_text("example."),
|
||||
dns.rdataclass.IN,
|
||||
dns.rdatatype.SOA,
|
||||
dns.rdatatype.NONE,
|
||||
)
|
||||
|
@@ -31,11 +31,13 @@ def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
|
||||
|
||||
# Create a UDP listener
|
||||
def udp_listen(ip, port, is_ipv6 = False):
|
||||
def udp_listen(ip, port, is_ipv6=False):
|
||||
try:
|
||||
udp = socket.socket(socket.AF_INET6 if is_ipv6 else socket.AF_INET,
|
||||
socket.SOCK_DGRAM)
|
||||
udp = socket.socket(
|
||||
socket.AF_INET6 if is_ipv6 else socket.AF_INET, socket.SOCK_DGRAM
|
||||
)
|
||||
try:
|
||||
udp.bind((ip, port))
|
||||
except:
|
||||
@@ -49,11 +51,13 @@ def udp_listen(ip, port, is_ipv6 = False):
|
||||
|
||||
return udp
|
||||
|
||||
|
||||
# Create a TCP listener
|
||||
def tcp_listen(ip, port, is_ipv6 = False):
|
||||
def tcp_listen(ip, port, is_ipv6=False):
|
||||
try:
|
||||
tcp = socket.socket(socket.AF_INET6 if is_ipv6 else socket.AF_INET,
|
||||
socket.SOCK_STREAM)
|
||||
tcp = socket.socket(
|
||||
socket.AF_INET6 if is_ipv6 else socket.AF_INET, socket.SOCK_STREAM
|
||||
)
|
||||
try:
|
||||
tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
tcp.bind((ip, port))
|
||||
@@ -69,10 +73,13 @@ def tcp_listen(ip, port, is_ipv6 = False):
|
||||
|
||||
return tcp
|
||||
|
||||
|
||||
############################################################################
|
||||
# Control channel - send "1" or "0" to enable or disable the "silent" mode.
|
||||
############################################################################
|
||||
silent = False
|
||||
|
||||
|
||||
def ctrl_channel(msg):
|
||||
global silent
|
||||
|
||||
@@ -83,14 +90,15 @@ def ctrl_channel(msg):
|
||||
return
|
||||
|
||||
if silent:
|
||||
if msg == b'0':
|
||||
if msg == b"0":
|
||||
silent = False
|
||||
print("Silent mode was disabled")
|
||||
else:
|
||||
if msg == b'1':
|
||||
if msg == b"1":
|
||||
silent = True
|
||||
print("Silent mode was enabled")
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
############################################################################
|
||||
@@ -107,8 +115,8 @@ def create_response(msg):
|
||||
r = dns.message.make_response(m)
|
||||
r.set_rcode(NOERROR)
|
||||
if rrtype == A:
|
||||
tld=qname.split('.')[-2] + '.'
|
||||
ns="local." + tld
|
||||
tld = qname.split(".")[-2] + "."
|
||||
ns = "local." + tld
|
||||
r.answer.append(dns.rrset.from_text(qname, 300, IN, A, "10.53.0.11"))
|
||||
r.answer.append(dns.rrset.from_text(tld, 300, IN, NS, "local." + tld))
|
||||
r.additional.append(dns.rrset.from_text(ns, 300, IN, A, "10.53.0.11"))
|
||||
@@ -121,12 +129,14 @@ def create_response(msg):
|
||||
r.flags |= dns.flags.AA
|
||||
return r
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print ("Shutting down now...")
|
||||
os.remove('ans.pid')
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
#
|
||||
@@ -137,11 +147,15 @@ def sigterm(signum, frame):
|
||||
ip4 = "10.53.0.11"
|
||||
ip6 = "fd92:7065:b8e:ffff::11"
|
||||
|
||||
try: port=int(os.environ['PORT'])
|
||||
except: port=5300
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
try: ctrlport=int(os.environ['EXTRAPORT1'])
|
||||
except: ctrlport=5300
|
||||
try:
|
||||
ctrlport = int(os.environ["EXTRAPORT1"])
|
||||
except:
|
||||
ctrlport = 5300
|
||||
|
||||
ctrl4_tcp = tcp_listen(ip4, ctrlport)
|
||||
query4_udp = udp_listen(ip4, port)
|
||||
@@ -153,19 +167,19 @@ havev6 = query6_udp is not None and query6_tcp is not None
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open('ans.pid', 'w')
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print (pid, file=f)
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print ("Listening on %s port %d" % (ip4, ctrlport))
|
||||
print ("Listening on %s port %d" % (ip4, port))
|
||||
print("Listening on %s port %d" % (ip4, ctrlport))
|
||||
print("Listening on %s port %d" % (ip4, port))
|
||||
if havev6:
|
||||
print ("Listening on %s port %d" % (ip6, port))
|
||||
print("Listening on %s port %d" % (ip6, port))
|
||||
|
||||
print ("Ctrl-c to quit")
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
input = [ctrl4_tcp, query4_udp, query6_udp, query4_tcp, query6_tcp]
|
||||
@@ -200,8 +214,9 @@ while running:
|
||||
if conn:
|
||||
conn.close()
|
||||
elif s == query4_tcp or s == query6_tcp:
|
||||
print("TCP query received on %s" %
|
||||
(ip4 if s == query4_tcp else ip6), end=" ")
|
||||
print(
|
||||
"TCP query received on %s" % (ip4 if s == query4_tcp else ip6), end=" "
|
||||
)
|
||||
conn = None
|
||||
try:
|
||||
# Handle incoming queries
|
||||
@@ -213,7 +228,7 @@ while running:
|
||||
print("NO RESPONSE (can not read the message length)")
|
||||
conn.close()
|
||||
continue
|
||||
length = struct.unpack('>H', msg[:2])[0]
|
||||
length = struct.unpack(">H", msg[:2])[0]
|
||||
msg = conn.recv(length)
|
||||
if len(msg) != length:
|
||||
print("NO RESPONSE (can not read the message)")
|
||||
@@ -223,7 +238,7 @@ while running:
|
||||
if rsp:
|
||||
print(dns.rcode.to_text(rsp.rcode()))
|
||||
wire = rsp.to_wire()
|
||||
conn.send(struct.pack('>H', len(wire)))
|
||||
conn.send(struct.pack(">H", len(wire)))
|
||||
conn.send(wire)
|
||||
else:
|
||||
print("NO RESPONSE (can not create a response)")
|
||||
@@ -237,8 +252,9 @@ while running:
|
||||
if conn:
|
||||
conn.close()
|
||||
elif s == query4_udp or s == query6_udp:
|
||||
print("UDP query received on %s" %
|
||||
(ip4 if s == query4_udp else ip6), end=" ")
|
||||
print(
|
||||
"UDP query received on %s" % (ip4 if s == query4_udp else ip6), end=" "
|
||||
)
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
if not silent:
|
||||
|
@@ -14,29 +14,29 @@ import struct
|
||||
|
||||
|
||||
class RawFormatHeader(dict):
|
||||
'''
|
||||
"""
|
||||
A dictionary of raw-format header fields read from a zone file.
|
||||
'''
|
||||
"""
|
||||
|
||||
fields = [
|
||||
'format',
|
||||
'version',
|
||||
'dumptime',
|
||||
'flags',
|
||||
'sourceserial',
|
||||
'lastxfrin',
|
||||
"format",
|
||||
"version",
|
||||
"dumptime",
|
||||
"flags",
|
||||
"sourceserial",
|
||||
"lastxfrin",
|
||||
]
|
||||
|
||||
def __init__(self, file_name):
|
||||
header = struct.Struct('>IIIIII')
|
||||
with open(file_name, 'rb') as data:
|
||||
header = struct.Struct(">IIIIII")
|
||||
with open(file_name, "rb") as data:
|
||||
header_data = data.read(header.size)
|
||||
super().__init__(zip(self.fields, header.unpack_from(header_data)))
|
||||
|
||||
|
||||
def test_unsigned_serial_number():
|
||||
|
||||
'''
|
||||
"""
|
||||
Check whether all signed zone files in the "ns8" subdirectory contain the
|
||||
serial number of the unsigned version of the zone in the raw-format header.
|
||||
The test assumes that all "*.signed" files in the "ns8" subdirectory are in
|
||||
@@ -51,18 +51,18 @@ def test_unsigned_serial_number():
|
||||
|
||||
- example[0-9][0-9].com.db.signed files are initially signed by
|
||||
dnssec-signzone while the others - by named.
|
||||
'''
|
||||
"""
|
||||
|
||||
zones_with_unsigned_serial_missing = []
|
||||
|
||||
for signed_zone in sorted(glob.glob('ns8/*.signed')):
|
||||
for signed_zone in sorted(glob.glob("ns8/*.signed")):
|
||||
raw_header = RawFormatHeader(signed_zone)
|
||||
# Ensure the unsigned serial number is placed where it is expected.
|
||||
assert raw_header['format'] == 2
|
||||
assert raw_header['version'] == 1
|
||||
assert raw_header["format"] == 2
|
||||
assert raw_header["version"] == 1
|
||||
# Check whether the header flags indicate that the unsigned serial
|
||||
# number is set and that the latter is indeed set.
|
||||
if raw_header['flags'] & 0x02 == 0 or raw_header['sourceserial'] == 0:
|
||||
if raw_header["flags"] & 0x02 == 0 or raw_header["sourceserial"] == 0:
|
||||
zones_with_unsigned_serial_missing.append(signed_zone)
|
||||
|
||||
assert not zones_with_unsigned_serial_missing
|
||||
|
@@ -47,25 +47,28 @@ import struct
|
||||
DELAY = 0.5
|
||||
THREADS = []
|
||||
|
||||
|
||||
def log(msg):
|
||||
print(datetime.datetime.now().strftime('%d-%b-%Y %H:%M:%S.%f ') + msg)
|
||||
print(datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S.%f ") + msg)
|
||||
|
||||
|
||||
def sigterm(*_):
|
||||
log('SIGTERM received, shutting down')
|
||||
log("SIGTERM received, shutting down")
|
||||
for thread in THREADS:
|
||||
thread.close()
|
||||
thread.join()
|
||||
os.remove('ans.pid')
|
||||
os.remove("ans.pid")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
class TCPDelayer(threading.Thread):
|
||||
""" For a given TCP connection conn we open a connection to (ip, port),
|
||||
and then we delay each incoming packet by DELAY by putting it in a
|
||||
queue.
|
||||
In the pipelined test TCP should not be used, but it's here for
|
||||
completnes.
|
||||
"""For a given TCP connection conn we open a connection to (ip, port),
|
||||
and then we delay each incoming packet by DELAY by putting it in a
|
||||
queue.
|
||||
In the pipelined test TCP should not be used, but it's here for
|
||||
completnes.
|
||||
"""
|
||||
|
||||
def __init__(self, conn, ip, port):
|
||||
threading.Thread.__init__(self)
|
||||
self.conn = conn
|
||||
@@ -81,13 +84,15 @@ class TCPDelayer(threading.Thread):
|
||||
while self.running:
|
||||
curr_timeout = 0.5
|
||||
try:
|
||||
curr_timeout = self.queue[0][0]-time.time()
|
||||
curr_timeout = self.queue[0][0] - time.time()
|
||||
except StopIteration:
|
||||
pass
|
||||
if curr_timeout > 0:
|
||||
if curr_timeout == 0:
|
||||
curr_timeout = 0.5
|
||||
rfds, _, _ = select.select([self.conn, self.cconn], [], [], curr_timeout)
|
||||
rfds, _, _ = select.select(
|
||||
[self.conn, self.cconn], [], [], curr_timeout
|
||||
)
|
||||
if self.conn in rfds:
|
||||
data = self.conn.recv(65535)
|
||||
if not data:
|
||||
@@ -99,17 +104,19 @@ class TCPDelayer(threading.Thread):
|
||||
return
|
||||
self.conn.send(data)
|
||||
try:
|
||||
while self.queue[0][0]-time.time() < 0:
|
||||
while self.queue[0][0] - time.time() < 0:
|
||||
_, data = self.queue.pop(0)
|
||||
self.cconn.send(data)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
|
||||
class UDPDelayer(threading.Thread):
|
||||
""" Every incoming UDP packet is put in a queue for DELAY time, then
|
||||
it's sent to (ip, port). We remember the query id to send the
|
||||
response we get to a proper source, responses are not delayed.
|
||||
"""Every incoming UDP packet is put in a queue for DELAY time, then
|
||||
it's sent to (ip, port). We remember the query id to send the
|
||||
response we get to a proper source, responses are not delayed.
|
||||
"""
|
||||
|
||||
def __init__(self, usock, ip, port):
|
||||
threading.Thread.__init__(self)
|
||||
self.sock = usock
|
||||
@@ -126,50 +133,56 @@ class UDPDelayer(threading.Thread):
|
||||
while self.running:
|
||||
curr_timeout = 0.5
|
||||
if self.queue:
|
||||
curr_timeout = self.queue[0][0]-time.time()
|
||||
curr_timeout = self.queue[0][0] - time.time()
|
||||
if curr_timeout >= 0:
|
||||
if curr_timeout == 0:
|
||||
curr_timeout = 0.5
|
||||
rfds, _, _ = select.select([self.sock, self.csock], [], [], curr_timeout)
|
||||
rfds, _, _ = select.select(
|
||||
[self.sock, self.csock], [], [], curr_timeout
|
||||
)
|
||||
if self.sock in rfds:
|
||||
data, addr = self.sock.recvfrom(65535)
|
||||
if not data:
|
||||
return
|
||||
self.queue.append((time.time() + DELAY, data))
|
||||
qid = struct.unpack('>H', data[:2])[0]
|
||||
log('Received a query from %s, queryid %d' % (str(addr), qid))
|
||||
qid = struct.unpack(">H", data[:2])[0]
|
||||
log("Received a query from %s, queryid %d" % (str(addr), qid))
|
||||
self.qid_mapping[qid] = addr
|
||||
if self.csock in rfds:
|
||||
data, addr = self.csock.recvfrom(65535)
|
||||
if not data:
|
||||
return
|
||||
qid = struct.unpack('>H', data[:2])[0]
|
||||
qid = struct.unpack(">H", data[:2])[0]
|
||||
dst = self.qid_mapping.get(qid)
|
||||
if dst is not None:
|
||||
self.sock.sendto(data, dst)
|
||||
log('Received a response from %s, queryid %d, sending to %s' % (str(addr), qid, str(dst)))
|
||||
while self.queue and self.queue[0][0]-time.time() < 0:
|
||||
log(
|
||||
"Received a response from %s, queryid %d, sending to %s"
|
||||
% (str(addr), qid, str(dst))
|
||||
)
|
||||
while self.queue and self.queue[0][0] - time.time() < 0:
|
||||
_, data = self.queue.pop(0)
|
||||
qid = struct.unpack('>H', data[:2])[0]
|
||||
log('Sending a query to %s, queryid %d' % (str(self.dst), qid))
|
||||
qid = struct.unpack(">H", data[:2])[0]
|
||||
log("Sending a query to %s, queryid %d" % (str(self.dst), qid))
|
||||
self.csock.sendto(data, self.dst)
|
||||
|
||||
|
||||
def main():
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
signal.signal(signal.SIGINT, sigterm)
|
||||
|
||||
with open('ans.pid', 'w') as pidfile:
|
||||
with open("ans.pid", "w") as pidfile:
|
||||
print(os.getpid(), file=pidfile)
|
||||
|
||||
listenip = '10.53.0.5'
|
||||
serverip = '10.53.0.2'
|
||||
listenip = "10.53.0.5"
|
||||
serverip = "10.53.0.2"
|
||||
|
||||
try:
|
||||
port = int(os.environ['PORT'])
|
||||
port = int(os.environ["PORT"])
|
||||
except KeyError:
|
||||
port = 5300
|
||||
|
||||
log('Listening on %s:%d' % (listenip, port))
|
||||
log("Listening on %s:%d" % (listenip, port))
|
||||
|
||||
usock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
usock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
@@ -187,12 +200,13 @@ def main():
|
||||
while True:
|
||||
try:
|
||||
(clientsock, _) = sock.accept()
|
||||
log('Accepted connection from %s' % clientsock)
|
||||
log("Accepted connection from %s" % clientsock)
|
||||
thread = TCPDelayer(clientsock, serverip, port)
|
||||
thread.start()
|
||||
THREADS.append(thread)
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@@ -16,5 +16,6 @@ import os
|
||||
import pytest
|
||||
|
||||
|
||||
long_test = pytest.mark.skipif(not os.environ.get('CI_ENABLE_ALL_TESTS'),
|
||||
reason='CI_ENABLE_ALL_TESTS not set')
|
||||
long_test = pytest.mark.skipif(
|
||||
not os.environ.get("CI_ENABLE_ALL_TESTS"), reason="CI_ENABLE_ALL_TESTS not set"
|
||||
)
|
||||
|
@@ -31,9 +31,11 @@ def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
|
||||
|
||||
def endswith(domain, labels):
|
||||
return domain.endswith("." + labels) or domain == labels
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
# For good. it serves:
|
||||
@@ -65,7 +67,7 @@ def create_response(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
lqname = qname.lower()
|
||||
labels = lqname.split('.')
|
||||
labels = lqname.split(".")
|
||||
|
||||
# get qtype
|
||||
rrtype = m.question[0].rdtype
|
||||
@@ -88,22 +90,61 @@ def create_response(msg):
|
||||
# Direct query - give direct answer
|
||||
if endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."):
|
||||
# Delegate to ns3
|
||||
r.authority.append(dns.rrset.from_text("8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good."))
|
||||
r.additional.append(dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3"))
|
||||
elif lqname == "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa." and rrtype == PTR:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good."
|
||||
)
|
||||
)
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3")
|
||||
)
|
||||
elif (
|
||||
lqname
|
||||
== "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa."
|
||||
and rrtype == PTR
|
||||
):
|
||||
# Direct query - give direct answer
|
||||
r.answer.append(dns.rrset.from_text("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.", 1, IN, PTR, "nee.com."))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.",
|
||||
1,
|
||||
IN,
|
||||
PTR,
|
||||
"nee.com.",
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "1.0.0.2.ip6.arpa." and rrtype == NS:
|
||||
# NS query at the apex
|
||||
r.answer.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good."))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good.")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif endswith("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.", lqname):
|
||||
elif endswith(
|
||||
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.",
|
||||
lqname,
|
||||
):
|
||||
# NODATA answer
|
||||
r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.0.0.2.ip6.arpa.",
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NXDOMAIN
|
||||
r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.0.0.2.ip6.arpa.",
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
return r
|
||||
elif endswith(lqname, "ip6.arpa."):
|
||||
@@ -113,35 +154,71 @@ def create_response(msg):
|
||||
r.flags |= dns.flags.AA
|
||||
elif endswith("1.0.0.2.ip6.arpa.", lqname):
|
||||
# NODATA answer
|
||||
r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"ip6.arpa.",
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NXDOMAIN
|
||||
r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"ip6.arpa.",
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
return r
|
||||
elif endswith(lqname, "stale."):
|
||||
if endswith(lqname, "a.b.stale."):
|
||||
# Delegate to ns.a.b.stale.
|
||||
r.authority.append(dns.rrset.from_text("a.b.stale.", 2, IN, NS, "ns.a.b.stale."))
|
||||
r.additional.append(dns.rrset.from_text("ns.a.b.stale.", 2, IN, A, "10.53.0.3"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text("a.b.stale.", 2, IN, NS, "ns.a.b.stale.")
|
||||
)
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 2, IN, A, "10.53.0.3")
|
||||
)
|
||||
elif endswith(lqname, "b.stale."):
|
||||
# Delegate to ns.b.stale.
|
||||
r.authority.append(dns.rrset.from_text("b.stale.", 2, IN, NS, "ns.b.stale."))
|
||||
r.additional.append(dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.4"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text("b.stale.", 2, IN, NS, "ns.b.stale.")
|
||||
)
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.4")
|
||||
)
|
||||
elif lqname == "stale." and rrtype == NS:
|
||||
# NS query at the apex.
|
||||
r.answer.append(dns.rrset.from_text("stale.", 2, IN, NS, "ns2.stale."))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "stale." and rrtype == SOA:
|
||||
# SOA query at the apex.
|
||||
r.answer.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.stale. 1 2 3 4 5"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "stale.":
|
||||
# NODATA answer
|
||||
r.authority.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NXDOMAIN
|
||||
r.authority.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
return r
|
||||
elif endswith(lqname, "bad."):
|
||||
@@ -168,43 +245,72 @@ def create_response(msg):
|
||||
|
||||
# Good/bad/ugly differs only in how we treat non-empty terminals
|
||||
if endswith(lqname, "zoop.boing."):
|
||||
r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, NS, "ns3." + suffix))
|
||||
elif lqname == "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z." and rrtype == A:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text("zoop.boing." + suffix, 1, IN, NS, "ns3." + suffix)
|
||||
)
|
||||
elif (
|
||||
lqname == "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z."
|
||||
and rrtype == A
|
||||
):
|
||||
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "" and rrtype == NS:
|
||||
r.answer.append(dns.rrset.from_text(suffix, 30, IN, NS, "ns2." + suffix))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "ns2." and rrtype == A:
|
||||
r.answer.append(dns.rrset.from_text("ns2."+suffix, 30, IN, A, "10.53.0.2"))
|
||||
r.answer.append(dns.rrset.from_text("ns2." + suffix, 30, IN, A, "10.53.0.2"))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "ns2." and rrtype == AAAA:
|
||||
r.answer.append(dns.rrset.from_text("ns2."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::2"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns2." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::2")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "ns3." and rrtype == A:
|
||||
r.answer.append(dns.rrset.from_text("ns3."+suffix, 30, IN, A, "10.53.0.3"))
|
||||
r.answer.append(dns.rrset.from_text("ns3." + suffix, 30, IN, A, "10.53.0.3"))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "ns3." and rrtype == AAAA:
|
||||
r.answer.append(dns.rrset.from_text("ns3."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::3"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns3." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::3")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "ns4." and rrtype == A:
|
||||
r.answer.append(dns.rrset.from_text("ns4."+suffix, 30, IN, A, "10.53.0.4"))
|
||||
r.answer.append(dns.rrset.from_text("ns4." + suffix, 30, IN, A, "10.53.0.4"))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "ns4." and rrtype == AAAA:
|
||||
r.answer.append(dns.rrset.from_text("ns4."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::4"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns4." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::4")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "a.bit.longer.ns.name." and rrtype == A:
|
||||
r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, A, "10.53.0.4"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("a.bit.longer.ns.name." + suffix, 1, IN, A, "10.53.0.4")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "a.bit.longer.ns.name." and rrtype == AAAA:
|
||||
r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::4"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"a.bit.longer.ns.name." + suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::4"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
else:
|
||||
r.authority.append(dns.rrset.from_text(suffix, 1, IN, SOA, "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
|
||||
if bad or not \
|
||||
(endswith("icky.icky.icky.ptang.zoop.boing.", lqname) or \
|
||||
endswith("many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.", lqname) or \
|
||||
endswith("a.bit.longer.ns.name.", lqname)):
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
suffix,
|
||||
1,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
if bad or not (
|
||||
endswith("icky.icky.icky.ptang.zoop.boing.", lqname)
|
||||
or endswith(
|
||||
"many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.",
|
||||
lqname,
|
||||
)
|
||||
or endswith("a.bit.longer.ns.name.", lqname)
|
||||
):
|
||||
r.set_rcode(NXDOMAIN)
|
||||
if ugly:
|
||||
r.set_rcode(FORMERR)
|
||||
@@ -214,11 +320,12 @@ def create_response(msg):
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print ("Shutting down now...")
|
||||
os.remove('ans.pid')
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
#
|
||||
@@ -229,8 +336,10 @@ def sigterm(signum, frame):
|
||||
ip4 = "10.53.0.2"
|
||||
ip6 = "fd92:7065:b8e:ffff::2"
|
||||
|
||||
try: port=int(os.environ['PORT'])
|
||||
except: port=5300
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_socket.bind((ip4, port))
|
||||
@@ -248,17 +357,17 @@ except:
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open('ans.pid', 'w')
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print (pid, file=f)
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print ("Listening on %s port %d" % (ip4, port))
|
||||
print("Listening on %s port %d" % (ip4, port))
|
||||
if havev6:
|
||||
print ("Listening on %s port %d" % (ip6, port))
|
||||
print ("Ctrl-c to quit")
|
||||
print("Listening on %s port %d" % (ip6, port))
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
input = [query4_socket, query6_socket]
|
||||
@@ -277,8 +386,9 @@ while running:
|
||||
|
||||
for s in inputready:
|
||||
if s == query4_socket or s == query6_socket:
|
||||
print ("Query received on %s" %
|
||||
(ip4 if s == query4_socket else ip6), end=" ")
|
||||
print(
|
||||
"Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
|
||||
)
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
rsp = create_response(msg[0])
|
||||
|
@@ -31,9 +31,11 @@ def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
|
||||
|
||||
def endswith(domain, labels):
|
||||
return domain.endswith("." + labels) or domain == labels
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
# For good. it serves:
|
||||
@@ -54,7 +56,7 @@ def create_response(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
lqname = qname.lower()
|
||||
labels = lqname.split('.')
|
||||
labels = lqname.split(".")
|
||||
|
||||
# get qtype
|
||||
rrtype = m.question[0].rdtype
|
||||
@@ -101,17 +103,31 @@ def create_response(msg):
|
||||
elif rrtype == NS:
|
||||
# NS a.b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
|
||||
r.additional.append(dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3"))
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif rrtype == SOA:
|
||||
# SOA a.b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
else:
|
||||
# NODATA.
|
||||
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
else:
|
||||
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
# NXDOMAIN.
|
||||
return r
|
||||
@@ -121,21 +137,51 @@ def create_response(msg):
|
||||
|
||||
# Good/bad differs only in how we treat non-empty terminals
|
||||
if lqname == "zoop.boing." and rrtype == NS:
|
||||
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3."+suffix))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3." + suffix)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif endswith(lqname, "icky.ptang.zoop.boing."):
|
||||
r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"icky.ptang.zoop.boing." + suffix,
|
||||
1,
|
||||
IN,
|
||||
NS,
|
||||
"a.bit.longer.ns.name." + suffix,
|
||||
)
|
||||
)
|
||||
elif endswith("icky.ptang.zoop.boing.", lqname):
|
||||
r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"zoop.boing." + suffix,
|
||||
1,
|
||||
IN,
|
||||
SOA,
|
||||
"ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
if bad:
|
||||
r.set_rcode(NXDOMAIN)
|
||||
if ugly:
|
||||
r.set_rcode(FORMERR)
|
||||
elif endswith(lqname, "zoop.boing."):
|
||||
r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"zoop.boing." + suffix,
|
||||
1,
|
||||
IN,
|
||||
SOA,
|
||||
"ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
elif ip6req:
|
||||
r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns4.good."))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns4.good."
|
||||
)
|
||||
)
|
||||
r.additional.append(dns.rrset.from_text("ns4.good.", 60, IN, A, "10.53.0.4"))
|
||||
else:
|
||||
r.set_rcode(REFUSED)
|
||||
@@ -146,11 +192,12 @@ def create_response(msg):
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print ("Shutting down now...")
|
||||
os.remove('ans.pid')
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
#
|
||||
@@ -161,8 +208,10 @@ def sigterm(signum, frame):
|
||||
ip4 = "10.53.0.3"
|
||||
ip6 = "fd92:7065:b8e:ffff::3"
|
||||
|
||||
try: port=int(os.environ['PORT'])
|
||||
except: port=5300
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_socket.bind((ip4, port))
|
||||
@@ -180,17 +229,17 @@ except:
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open('ans.pid', 'w')
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print (pid, file=f)
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print ("Listening on %s port %d" % (ip4, port))
|
||||
print("Listening on %s port %d" % (ip4, port))
|
||||
if havev6:
|
||||
print ("Listening on %s port %d" % (ip6, port))
|
||||
print ("Ctrl-c to quit")
|
||||
print("Listening on %s port %d" % (ip6, port))
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
input = [query4_socket, query6_socket]
|
||||
@@ -209,8 +258,9 @@ while running:
|
||||
|
||||
for s in inputready:
|
||||
if s == query4_socket or s == query6_socket:
|
||||
print ("Query received on %s" %
|
||||
(ip4 if s == query4_socket else ip6), end=" ")
|
||||
print(
|
||||
"Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
|
||||
)
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
rsp = create_response(msg[0])
|
||||
|
@@ -31,9 +31,11 @@ def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
|
||||
|
||||
def endswith(domain, labels):
|
||||
return domain.endswith("." + labels) or domain == labels
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
# For good. it serves:
|
||||
@@ -55,7 +57,7 @@ def create_response(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
lqname = qname.lower()
|
||||
labels = lqname.split('.')
|
||||
labels = lqname.split(".")
|
||||
|
||||
# get qtype
|
||||
rrtype = m.question[0].rdtype
|
||||
@@ -102,30 +104,54 @@ def create_response(msg):
|
||||
elif rrtype == NS:
|
||||
# NS a.b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
|
||||
r.additional.append(dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3"))
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif rrtype == SOA:
|
||||
# SOA a.b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
else:
|
||||
# NODATA.
|
||||
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
elif lqname == "b.stale.":
|
||||
if rrtype == NS:
|
||||
# NS b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.b.stale."))
|
||||
r.additional.append(dns.rrset.from_text("ns.b.stale.", 1, IN, A, "10.53.0.4"))
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.b.stale.", 1, IN, A, "10.53.0.4")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif rrtype == SOA:
|
||||
# SOA b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
else:
|
||||
# NODATA.
|
||||
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
else:
|
||||
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
# NXDOMAIN.
|
||||
return r
|
||||
@@ -141,24 +167,67 @@ def create_response(msg):
|
||||
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "icky.ptang.zoop.boing." and rrtype == NS:
|
||||
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "a.bit.longer.ns.name."+suffix))
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
lqname + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif endswith(lqname, "icky.ptang.zoop.boing."):
|
||||
r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, SOA, "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"icky.ptang.zoop.boing." + suffix,
|
||||
1,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
if bad or not endswith("more.icky.icky.icky.ptang.zoop.boing.", lqname):
|
||||
r.set_rcode(NXDOMAIN)
|
||||
if ugly:
|
||||
r.set_rcode(FORMERR)
|
||||
elif ip6req:
|
||||
r.flags |= dns.flags.AA
|
||||
if lqname == "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa." and rrtype == TXT:
|
||||
r.answer.append(dns.rrset.from_text("test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 1, IN, TXT, "long_ip6_name"))
|
||||
elif endswith("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", lqname):
|
||||
#NODATA answer
|
||||
r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, SOA, "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16"))
|
||||
if (
|
||||
lqname
|
||||
== "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."
|
||||
and rrtype == TXT
|
||||
):
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
1,
|
||||
IN,
|
||||
TXT,
|
||||
"long_ip6_name",
|
||||
)
|
||||
)
|
||||
elif endswith(
|
||||
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
lqname,
|
||||
):
|
||||
# NODATA answer
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
60,
|
||||
IN,
|
||||
SOA,
|
||||
"ns4.good. hostmaster.arpa. 2018050100 120 30 320 16",
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NXDOMAIN
|
||||
r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, SOA, "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16"))
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
60,
|
||||
IN,
|
||||
SOA,
|
||||
"ns4.good. hostmaster.arpa. 2018050100 120 30 320 16",
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
else:
|
||||
r.set_rcode(REFUSED)
|
||||
@@ -169,11 +238,12 @@ def create_response(msg):
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print ("Shutting down now...")
|
||||
os.remove('ans.pid')
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
#
|
||||
@@ -184,8 +254,10 @@ def sigterm(signum, frame):
|
||||
ip4 = "10.53.0.4"
|
||||
ip6 = "fd92:7065:b8e:ffff::4"
|
||||
|
||||
try: port=int(os.environ['PORT'])
|
||||
except: port=5300
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_socket.bind((ip4, port))
|
||||
@@ -203,17 +275,17 @@ except:
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open('ans.pid', 'w')
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print (pid, file=f)
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print ("Listening on %s port %d" % (ip4, port))
|
||||
print("Listening on %s port %d" % (ip4, port))
|
||||
if havev6:
|
||||
print ("Listening on %s port %d" % (ip6, port))
|
||||
print ("Ctrl-c to quit")
|
||||
print("Listening on %s port %d" % (ip6, port))
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
input = [query4_socket, query6_socket]
|
||||
@@ -232,8 +304,9 @@ while running:
|
||||
|
||||
for s in inputready:
|
||||
if s == query4_socket or s == query6_socket:
|
||||
print ("Query received on %s" %
|
||||
(ip4 if s == query4_socket else ip6), end=" ")
|
||||
print(
|
||||
"Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
|
||||
)
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
rsp = create_response(msg[0])
|
||||
|
@@ -15,24 +15,24 @@ import os
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip('dns')
|
||||
pytest.importorskip("dns")
|
||||
import dns.resolver
|
||||
|
||||
|
||||
def test_rpz_passthru_logging(named_port):
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.nameservers = ['10.53.0.1']
|
||||
resolver.nameservers = ["10.53.0.1"]
|
||||
resolver.port = named_port
|
||||
|
||||
# Should generate a log entry into rpz_passthru.txt
|
||||
ans = resolver.query('allowed.', 'A')
|
||||
ans = resolver.query("allowed.", "A")
|
||||
for rd in ans:
|
||||
assert rd.address == "10.53.0.2"
|
||||
|
||||
# baddomain.com isn't allowed (CNAME .), should return NXDOMAIN
|
||||
# Should generate a log entry into rpz.txt
|
||||
with pytest.raises(dns.resolver.NXDOMAIN):
|
||||
resolver.query('baddomain.', 'A')
|
||||
resolver.query("baddomain.", "A")
|
||||
|
||||
rpz_passthru_logfile = os.path.join("ns1", "rpz_passthru.txt")
|
||||
rpz_logfile = os.path.join("ns1", "rpz.txt")
|
||||
@@ -40,11 +40,11 @@ def test_rpz_passthru_logging(named_port):
|
||||
assert os.path.isfile(rpz_passthru_logfile)
|
||||
assert os.path.isfile(rpz_logfile)
|
||||
|
||||
with open(rpz_passthru_logfile, encoding='utf-8') as log_file:
|
||||
with open(rpz_passthru_logfile, encoding="utf-8") as log_file:
|
||||
line = log_file.read()
|
||||
assert "rpz QNAME PASSTHRU rewrite allowed/A/IN" in line
|
||||
|
||||
with open(rpz_logfile, encoding='utf-8') as log_file:
|
||||
with open(rpz_logfile, encoding="utf-8") as log_file:
|
||||
line = log_file.read()
|
||||
assert "rpz QNAME PASSTHRU rewrite allowed/A/IN" not in line
|
||||
assert "rpz QNAME NXDOMAIN rewrite baddomain/A/IN" in line
|
||||
|
@@ -21,47 +21,47 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip('dns')
|
||||
pytest.importorskip("dns")
|
||||
import dns.exception
|
||||
import dns.resolver
|
||||
|
||||
|
||||
def do_work(named_proc, resolver, rndc_cmd, kill_method, n_workers, n_queries):
|
||||
"""Creates a number of A queries to run in parallel
|
||||
in order simulate a slightly more realistic test scenario.
|
||||
in order simulate a slightly more realistic test scenario.
|
||||
|
||||
The main idea of this function is to create and send a bunch
|
||||
of A queries to a target named instance and during this process
|
||||
a request for shutting down named will be issued.
|
||||
The main idea of this function is to create and send a bunch
|
||||
of A queries to a target named instance and during this process
|
||||
a request for shutting down named will be issued.
|
||||
|
||||
In the process of shutting down named, a couple control connections
|
||||
are created (by launching rndc) to ensure that the crash was fixed.
|
||||
In the process of shutting down named, a couple control connections
|
||||
are created (by launching rndc) to ensure that the crash was fixed.
|
||||
|
||||
if kill_method=="rndc" named will be asked to shutdown by
|
||||
means of rndc stop.
|
||||
if kill_method=="sigterm" named will be killed by SIGTERM on
|
||||
POSIX systems or by TerminateProcess() on Windows systems.
|
||||
if kill_method=="rndc" named will be asked to shutdown by
|
||||
means of rndc stop.
|
||||
if kill_method=="sigterm" named will be killed by SIGTERM on
|
||||
POSIX systems or by TerminateProcess() on Windows systems.
|
||||
|
||||
:param named_proc: named process instance
|
||||
:type named_proc: subprocess.Popen
|
||||
:param named_proc: named process instance
|
||||
:type named_proc: subprocess.Popen
|
||||
|
||||
:param resolver: target resolver
|
||||
:type resolver: dns.resolver.Resolver
|
||||
:param resolver: target resolver
|
||||
:type resolver: dns.resolver.Resolver
|
||||
|
||||
:param rndc_cmd: rndc command with default arguments
|
||||
:type rndc_cmd: list of strings, e.g. ["rndc", "-p", "23750"]
|
||||
:param rndc_cmd: rndc command with default arguments
|
||||
:type rndc_cmd: list of strings, e.g. ["rndc", "-p", "23750"]
|
||||
|
||||
:kill_method: "rndc" or "sigterm"
|
||||
:type kill_method: str
|
||||
:kill_method: "rndc" or "sigterm"
|
||||
:type kill_method: str
|
||||
|
||||
:param n_workers: Number of worker threads to create
|
||||
:type n_workers: int
|
||||
:param n_workers: Number of worker threads to create
|
||||
:type n_workers: int
|
||||
|
||||
:param n_queries: Total number of queries to send
|
||||
:type n_queries: int
|
||||
:param n_queries: Total number of queries to send
|
||||
:type n_queries: int
|
||||
"""
|
||||
# pylint: disable-msg=too-many-arguments
|
||||
# pylint: disable-msg=too-many-locals
|
||||
# pylint: disable-msg=too-many-arguments
|
||||
# pylint: disable-msg=too-many-locals
|
||||
|
||||
# helper function, args must be a list or tuple with arguments to rndc.
|
||||
def launch_rndc(args):
|
||||
@@ -91,21 +91,22 @@ def do_work(named_proc, resolver, rndc_cmd, kill_method, n_workers, n_queries):
|
||||
else:
|
||||
tag = "bad"
|
||||
length = random.randint(4, 10)
|
||||
relname = "".join(letters[
|
||||
random.randrange(len(letters))] for i in range(length))
|
||||
relname = "".join(
|
||||
letters[random.randrange(len(letters))] for i in range(length)
|
||||
)
|
||||
|
||||
qname = relname + ".test"
|
||||
futures[executor.submit(resolver.query, qname, 'A')] = tag
|
||||
futures[executor.submit(resolver.query, qname, "A")] = tag
|
||||
elif shutdown: # We attempt to stop named in the middle
|
||||
shutdown = False
|
||||
if kill_method == "rndc":
|
||||
futures[executor.submit(launch_rndc, ['stop'])] = 'stop'
|
||||
futures[executor.submit(launch_rndc, ["stop"])] = "stop"
|
||||
else:
|
||||
futures[executor.submit(named_proc.terminate)] = 'kill'
|
||||
futures[executor.submit(named_proc.terminate)] = "kill"
|
||||
else:
|
||||
# We attempt to send couple rndc commands while named is
|
||||
# being shutdown
|
||||
futures[executor.submit(launch_rndc, ['status'])] = 'status'
|
||||
futures[executor.submit(launch_rndc, ["status"])] = "status"
|
||||
|
||||
ret_code = -1
|
||||
for future in as_completed(futures):
|
||||
@@ -120,9 +121,11 @@ def do_work(named_proc, resolver, rndc_cmd, kill_method, n_workers, n_queries):
|
||||
if futures[future] == "stop":
|
||||
ret_code = result
|
||||
|
||||
except (dns.resolver.NXDOMAIN,
|
||||
dns.resolver.NoNameservers,
|
||||
dns.exception.Timeout):
|
||||
except (
|
||||
dns.resolver.NXDOMAIN,
|
||||
dns.resolver.NoNameservers,
|
||||
dns.exception.Timeout,
|
||||
):
|
||||
pass
|
||||
|
||||
if kill_method == "rndc":
|
||||
@@ -148,12 +151,11 @@ def test_named_shutdown(named_port, control_port):
|
||||
assert os.path.isfile(rndc_cfg)
|
||||
|
||||
# rndc command with default arguments.
|
||||
rndc_cmd = [rndc, "-c", rndc_cfg, "-p", str(control_port),
|
||||
"-s", "10.53.0.3"]
|
||||
rndc_cmd = [rndc, "-c", rndc_cfg, "-p", str(control_port), "-s", "10.53.0.3"]
|
||||
|
||||
# We create a resolver instance that will be used to send queries.
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.nameservers = ['10.53.0.3']
|
||||
resolver.nameservers = ["10.53.0.3"]
|
||||
resolver.port = named_port
|
||||
|
||||
# We test named shutting down using two methods:
|
||||
@@ -168,13 +170,14 @@ def test_named_shutdown(named_port, control_port):
|
||||
# wait for named to finish loading
|
||||
for _ in range(10):
|
||||
try:
|
||||
resolver.query('version.bind', 'TXT', 'CH')
|
||||
resolver.query("version.bind", "TXT", "CH")
|
||||
break
|
||||
except (dns.resolver.NoNameservers, dns.exception.Timeout):
|
||||
time.sleep(1)
|
||||
|
||||
do_work(named_proc, resolver, rndc_cmd,
|
||||
kill_method, n_workers=12, n_queries=16)
|
||||
do_work(
|
||||
named_proc, resolver, rndc_cmd, kill_method, n_workers=12, n_queries=16
|
||||
)
|
||||
|
||||
# Wait named to exit for a maximum of MAX_TIMEOUT seconds.
|
||||
MAX_TIMEOUT = 10
|
||||
|
@@ -14,7 +14,7 @@ import os
|
||||
|
||||
|
||||
# ISO datetime format without msec
|
||||
fmt = '%Y-%m-%dT%H:%M:%SZ'
|
||||
fmt = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
# The constants were taken from BIND 9 source code (lib/dns/zone.c)
|
||||
max_refresh = timedelta(seconds=2419200) # 4 weeks
|
||||
@@ -71,9 +71,9 @@ def zone_mtime(zonedir, name):
|
||||
|
||||
def test_zone_timers_primary(fetch_zones, load_timers, **kwargs):
|
||||
|
||||
statsip = kwargs['statsip']
|
||||
statsport = kwargs['statsport']
|
||||
zonedir = kwargs['zonedir']
|
||||
statsip = kwargs["statsip"]
|
||||
statsport = kwargs["statsport"]
|
||||
zonedir = kwargs["zonedir"]
|
||||
|
||||
zones = fetch_zones(statsip, statsport)
|
||||
|
||||
@@ -85,9 +85,9 @@ def test_zone_timers_primary(fetch_zones, load_timers, **kwargs):
|
||||
|
||||
def test_zone_timers_secondary(fetch_zones, load_timers, **kwargs):
|
||||
|
||||
statsip = kwargs['statsip']
|
||||
statsport = kwargs['statsport']
|
||||
zonedir = kwargs['zonedir']
|
||||
statsip = kwargs["statsip"]
|
||||
statsport = kwargs["statsport"]
|
||||
zonedir = kwargs["zonedir"]
|
||||
|
||||
zones = fetch_zones(statsip, statsport)
|
||||
|
||||
@@ -99,12 +99,12 @@ def test_zone_timers_secondary(fetch_zones, load_timers, **kwargs):
|
||||
|
||||
def test_zone_with_many_keys(fetch_zones, load_zone, **kwargs):
|
||||
|
||||
statsip = kwargs['statsip']
|
||||
statsport = kwargs['statsport']
|
||||
statsip = kwargs["statsip"]
|
||||
statsport = kwargs["statsport"]
|
||||
|
||||
zones = fetch_zones(statsip, statsport)
|
||||
|
||||
for zone in zones:
|
||||
name = load_zone(zone)
|
||||
if name == 'manykeys':
|
||||
if name == "manykeys":
|
||||
check_manykeys(name)
|
||||
|
@@ -20,8 +20,9 @@ TIMEOUT = 10
|
||||
|
||||
|
||||
def create_msg(qname, qtype):
|
||||
msg = dns.message.make_query(qname, qtype, want_dnssec=True,
|
||||
use_edns=0, payload=4096)
|
||||
msg = dns.message.make_query(
|
||||
qname, qtype, want_dnssec=True, use_edns=0, payload=4096
|
||||
)
|
||||
|
||||
return msg
|
||||
|
||||
@@ -43,15 +44,16 @@ def tcp_query(ip, port, msg):
|
||||
|
||||
|
||||
def create_expected(data):
|
||||
expected = {"dns-tcp-requests-sizes-received-ipv4": defaultdict(int),
|
||||
"dns-tcp-responses-sizes-sent-ipv4": defaultdict(int),
|
||||
"dns-tcp-requests-sizes-received-ipv6": defaultdict(int),
|
||||
"dns-tcp-responses-sizes-sent-ipv6": defaultdict(int),
|
||||
"dns-udp-requests-sizes-received-ipv4": defaultdict(int),
|
||||
"dns-udp-requests-sizes-received-ipv6": defaultdict(int),
|
||||
"dns-udp-responses-sizes-sent-ipv4": defaultdict(int),
|
||||
"dns-udp-responses-sizes-sent-ipv6": defaultdict(int),
|
||||
}
|
||||
expected = {
|
||||
"dns-tcp-requests-sizes-received-ipv4": defaultdict(int),
|
||||
"dns-tcp-responses-sizes-sent-ipv4": defaultdict(int),
|
||||
"dns-tcp-requests-sizes-received-ipv6": defaultdict(int),
|
||||
"dns-tcp-responses-sizes-sent-ipv6": defaultdict(int),
|
||||
"dns-udp-requests-sizes-received-ipv4": defaultdict(int),
|
||||
"dns-udp-requests-sizes-received-ipv6": defaultdict(int),
|
||||
"dns-udp-responses-sizes-sent-ipv4": defaultdict(int),
|
||||
"dns-udp-responses-sizes-sent-ipv6": defaultdict(int),
|
||||
}
|
||||
|
||||
for k, v in data.items():
|
||||
for kk, vv in v.items():
|
||||
@@ -89,9 +91,9 @@ def check_traffic(data, expected):
|
||||
|
||||
def test_traffic(fetch_traffic, **kwargs):
|
||||
|
||||
statsip = kwargs['statsip']
|
||||
statsport = kwargs['statsport']
|
||||
port = kwargs['port']
|
||||
statsip = kwargs["statsip"]
|
||||
statsport = kwargs["statsport"]
|
||||
port = kwargs["port"]
|
||||
|
||||
data = fetch_traffic(statsip, statsport)
|
||||
exp = create_expected(data)
|
||||
|
@@ -19,16 +19,18 @@ import pytest
|
||||
|
||||
import generic
|
||||
|
||||
pytestmark = pytest.mark.skipif(not os.environ.get('HAVEJSONSTATS'),
|
||||
reason='json-c support disabled in the build')
|
||||
requests = pytest.importorskip('requests')
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not os.environ.get("HAVEJSONSTATS"), reason="json-c support disabled in the build"
|
||||
)
|
||||
requests = pytest.importorskip("requests")
|
||||
|
||||
|
||||
# JSON helper functions
|
||||
def fetch_zones_json(statsip, statsport):
|
||||
|
||||
r = requests.get("http://{}:{}/json/v1/zones".format(statsip, statsport),
|
||||
timeout=600)
|
||||
r = requests.get(
|
||||
"http://{}:{}/json/v1/zones".format(statsip, statsport), timeout=600
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
data = r.json()
|
||||
@@ -37,8 +39,9 @@ def fetch_zones_json(statsip, statsport):
|
||||
|
||||
def fetch_traffic_json(statsip, statsport):
|
||||
|
||||
r = requests.get("http://{}:{}/json/v1/traffic".format(statsip, statsport),
|
||||
timeout=600)
|
||||
r = requests.get(
|
||||
"http://{}:{}/json/v1/traffic".format(statsip, statsport), timeout=600
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
data = r.json()
|
||||
@@ -48,52 +51,61 @@ def fetch_traffic_json(statsip, statsport):
|
||||
|
||||
def load_timers_json(zone, primary=True):
|
||||
|
||||
name = zone['name']
|
||||
name = zone["name"]
|
||||
|
||||
# Check if the primary zone timer exists
|
||||
assert 'loaded' in zone
|
||||
loaded = datetime.strptime(zone['loaded'], generic.fmt)
|
||||
assert "loaded" in zone
|
||||
loaded = datetime.strptime(zone["loaded"], generic.fmt)
|
||||
|
||||
if primary:
|
||||
# Check if the secondary zone timers does not exist
|
||||
assert 'expires' not in zone
|
||||
assert 'refresh' not in zone
|
||||
assert "expires" not in zone
|
||||
assert "refresh" not in zone
|
||||
expires = None
|
||||
refresh = None
|
||||
else:
|
||||
assert 'expires' in zone
|
||||
assert 'refresh' in zone
|
||||
expires = datetime.strptime(zone['expires'], generic.fmt)
|
||||
refresh = datetime.strptime(zone['refresh'], generic.fmt)
|
||||
assert "expires" in zone
|
||||
assert "refresh" in zone
|
||||
expires = datetime.strptime(zone["expires"], generic.fmt)
|
||||
refresh = datetime.strptime(zone["refresh"], generic.fmt)
|
||||
|
||||
return (name, loaded, expires, refresh)
|
||||
|
||||
|
||||
def load_zone_json(zone):
|
||||
name = zone['name']
|
||||
name = zone["name"]
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def test_zone_timers_primary_json(statsport):
|
||||
generic.test_zone_timers_primary(fetch_zones_json, load_timers_json,
|
||||
statsip="10.53.0.1", statsport=statsport,
|
||||
zonedir="ns1")
|
||||
generic.test_zone_timers_primary(
|
||||
fetch_zones_json,
|
||||
load_timers_json,
|
||||
statsip="10.53.0.1",
|
||||
statsport=statsport,
|
||||
zonedir="ns1",
|
||||
)
|
||||
|
||||
|
||||
def test_zone_timers_secondary_json(statsport):
|
||||
generic.test_zone_timers_secondary(fetch_zones_json, load_timers_json,
|
||||
statsip="10.53.0.3", statsport=statsport,
|
||||
zonedir="ns3")
|
||||
generic.test_zone_timers_secondary(
|
||||
fetch_zones_json,
|
||||
load_timers_json,
|
||||
statsip="10.53.0.3",
|
||||
statsport=statsport,
|
||||
zonedir="ns3",
|
||||
)
|
||||
|
||||
|
||||
def test_zone_with_many_keys_json(statsport):
|
||||
generic.test_zone_with_many_keys(fetch_zones_json, load_zone_json,
|
||||
statsip="10.53.0.2", statsport=statsport)
|
||||
generic.test_zone_with_many_keys(
|
||||
fetch_zones_json, load_zone_json, statsip="10.53.0.2", statsport=statsport
|
||||
)
|
||||
|
||||
|
||||
def test_traffic_json(named_port, statsport):
|
||||
generic_dnspython = pytest.importorskip('generic_dnspython')
|
||||
generic_dnspython.test_traffic(fetch_traffic_json,
|
||||
statsip="10.53.0.2", statsport=statsport,
|
||||
port=named_port)
|
||||
generic_dnspython = pytest.importorskip("generic_dnspython")
|
||||
generic_dnspython.test_traffic(
|
||||
fetch_traffic_json, statsip="10.53.0.2", statsport=statsport, port=named_port
|
||||
)
|
||||
|
@@ -20,41 +20,43 @@ import pytest
|
||||
|
||||
import generic
|
||||
|
||||
pytestmark = pytest.mark.skipif(not os.environ.get('HAVEXMLSTATS'),
|
||||
reason='libxml2 support disabled in the build')
|
||||
requests = pytest.importorskip('requests')
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not os.environ.get("HAVEXMLSTATS"), reason="libxml2 support disabled in the build"
|
||||
)
|
||||
requests = pytest.importorskip("requests")
|
||||
|
||||
|
||||
# XML helper functions
|
||||
def fetch_zones_xml(statsip, statsport):
|
||||
|
||||
r = requests.get("http://{}:{}/xml/v3/zones".format(statsip, statsport),
|
||||
timeout=600)
|
||||
r = requests.get(
|
||||
"http://{}:{}/xml/v3/zones".format(statsip, statsport), timeout=600
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
root = ET.fromstring(r.text)
|
||||
|
||||
default_view = None
|
||||
for view in root.find('views').iter('view'):
|
||||
if view.attrib['name'] == "_default":
|
||||
for view in root.find("views").iter("view"):
|
||||
if view.attrib["name"] == "_default":
|
||||
default_view = view
|
||||
break
|
||||
assert default_view is not None
|
||||
|
||||
return default_view.find('zones').findall('zone')
|
||||
return default_view.find("zones").findall("zone")
|
||||
|
||||
|
||||
def fetch_traffic_xml(statsip, statsport):
|
||||
|
||||
def load_counters(data):
|
||||
out = {}
|
||||
for counter in data.findall("counter"):
|
||||
out[counter.attrib['name']] = int(counter.text)
|
||||
out[counter.attrib["name"]] = int(counter.text)
|
||||
|
||||
return out
|
||||
|
||||
r = requests.get("http://{}:{}/xml/v3/traffic".format(statsip, statsport),
|
||||
timeout=600)
|
||||
r = requests.get(
|
||||
"http://{}:{}/xml/v3/traffic".format(statsip, statsport), timeout=600
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
root = ET.fromstring(r.text)
|
||||
@@ -64,7 +66,7 @@ def fetch_traffic_xml(statsip, statsport):
|
||||
for proto in ["udp", "tcp"]:
|
||||
proto_root = root.find("traffic").find(ip).find(proto)
|
||||
for counters in proto_root.findall("counters"):
|
||||
if counters.attrib['type'] == "request-size":
|
||||
if counters.attrib["type"] == "request-size":
|
||||
key = "dns-{}-requests-sizes-received-{}".format(proto, ip)
|
||||
else:
|
||||
key = "dns-{}-responses-sizes-sent-{}".format(proto, ip)
|
||||
@@ -77,14 +79,14 @@ def fetch_traffic_xml(statsip, statsport):
|
||||
|
||||
def load_timers_xml(zone, primary=True):
|
||||
|
||||
name = zone.attrib['name']
|
||||
name = zone.attrib["name"]
|
||||
|
||||
loaded_el = zone.find('loaded')
|
||||
loaded_el = zone.find("loaded")
|
||||
assert loaded_el is not None
|
||||
loaded = datetime.strptime(loaded_el.text, generic.fmt)
|
||||
|
||||
expires_el = zone.find('expires')
|
||||
refresh_el = zone.find('refresh')
|
||||
expires_el = zone.find("expires")
|
||||
refresh_el = zone.find("refresh")
|
||||
if primary:
|
||||
assert expires_el is None
|
||||
assert refresh_el is None
|
||||
@@ -100,30 +102,39 @@ def load_timers_xml(zone, primary=True):
|
||||
|
||||
|
||||
def load_zone_xml(zone):
|
||||
name = zone.attrib['name']
|
||||
name = zone.attrib["name"]
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def test_zone_timers_primary_xml(statsport):
|
||||
generic.test_zone_timers_primary(fetch_zones_xml, load_timers_xml,
|
||||
statsip="10.53.0.1", statsport=statsport,
|
||||
zonedir="ns1")
|
||||
generic.test_zone_timers_primary(
|
||||
fetch_zones_xml,
|
||||
load_timers_xml,
|
||||
statsip="10.53.0.1",
|
||||
statsport=statsport,
|
||||
zonedir="ns1",
|
||||
)
|
||||
|
||||
|
||||
def test_zone_timers_secondary_xml(statsport):
|
||||
generic.test_zone_timers_secondary(fetch_zones_xml, load_timers_xml,
|
||||
statsip="10.53.0.3", statsport=statsport,
|
||||
zonedir="ns3")
|
||||
generic.test_zone_timers_secondary(
|
||||
fetch_zones_xml,
|
||||
load_timers_xml,
|
||||
statsip="10.53.0.3",
|
||||
statsport=statsport,
|
||||
zonedir="ns3",
|
||||
)
|
||||
|
||||
|
||||
def test_zone_with_many_keys_xml(statsport):
|
||||
generic.test_zone_with_many_keys(fetch_zones_xml, load_zone_xml,
|
||||
statsip="10.53.0.2", statsport=statsport)
|
||||
generic.test_zone_with_many_keys(
|
||||
fetch_zones_xml, load_zone_xml, statsip="10.53.0.2", statsport=statsport
|
||||
)
|
||||
|
||||
|
||||
def test_traffic_xml(named_port, statsport):
|
||||
generic_dnspython = pytest.importorskip('generic_dnspython')
|
||||
generic_dnspython.test_traffic(fetch_traffic_xml,
|
||||
statsip="10.53.0.2", statsport=statsport,
|
||||
port=named_port)
|
||||
generic_dnspython = pytest.importorskip("generic_dnspython")
|
||||
generic_dnspython.test_traffic(
|
||||
fetch_traffic_xml, statsip="10.53.0.2", statsport=statsport, port=named_port
|
||||
)
|
||||
|
@@ -42,10 +42,11 @@ import time
|
||||
|
||||
# Timeout for establishing all connections requested by a single 'open' command.
|
||||
OPEN_TIMEOUT = 2
|
||||
VERSION_QUERY = b'\x00\x1e\xaf\xb8\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03'
|
||||
VERSION_QUERY = b"\x00\x1e\xaf\xb8\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03"
|
||||
|
||||
|
||||
def log(msg):
|
||||
print(datetime.datetime.now().strftime('%d-%b-%Y %H:%M:%S.%f ') + msg)
|
||||
print(datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S.%f ") + msg)
|
||||
|
||||
|
||||
def open_connections(active_conns, count, host, port):
|
||||
@@ -58,14 +59,14 @@ def open_connections(active_conns, count, host, port):
|
||||
except socket.error:
|
||||
family = socket.AF_INET6
|
||||
|
||||
log('Opening %d connections...' % count)
|
||||
log("Opening %d connections..." % count)
|
||||
|
||||
for _ in range(count):
|
||||
sock = socket.socket(family, socket.SOCK_STREAM)
|
||||
sock.setblocking(0)
|
||||
err = sock.connect_ex((host, port))
|
||||
if err not in (0, errno.EINPROGRESS):
|
||||
log('%s on connect for socket %s' % (errno.errorcode[err], sock))
|
||||
log("%s on connect for socket %s" % (errno.errorcode[err], sock))
|
||||
errors.append(sock)
|
||||
else:
|
||||
queued.append(sock)
|
||||
@@ -81,35 +82,35 @@ def open_connections(active_conns, count, host, port):
|
||||
queued.remove(sock)
|
||||
err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
|
||||
if err:
|
||||
log('%s for socket %s' % (errno.errorcode[err], sock))
|
||||
log("%s for socket %s" % (errno.errorcode[err], sock))
|
||||
errors.append(sock)
|
||||
else:
|
||||
sock.send(VERSION_QUERY)
|
||||
active_conns.append(sock)
|
||||
|
||||
if errors:
|
||||
log('result=FAIL: %d connection(s) failed' % len(errors))
|
||||
log("result=FAIL: %d connection(s) failed" % len(errors))
|
||||
elif queued:
|
||||
log('result=FAIL: Timed out, aborting %d pending connections' % len(queued))
|
||||
log("result=FAIL: Timed out, aborting %d pending connections" % len(queued))
|
||||
for sock in queued:
|
||||
sock.close()
|
||||
else:
|
||||
log('result=OK: Successfully opened %d connections' % count)
|
||||
log("result=OK: Successfully opened %d connections" % count)
|
||||
|
||||
|
||||
def close_connections(active_conns, count):
|
||||
log('Closing %s connections...' % "all" if count == 0 else str(count))
|
||||
log("Closing %s connections..." % "all" if count == 0 else str(count))
|
||||
if count == 0:
|
||||
count = len(active_conns)
|
||||
for _ in range(count):
|
||||
sock = active_conns.pop(0)
|
||||
sock.close()
|
||||
log('result=OK: Successfully closed %d connections' % count)
|
||||
log("result=OK: Successfully closed %d connections" % count)
|
||||
|
||||
|
||||
def sigterm(*_):
|
||||
log('SIGTERM received, shutting down')
|
||||
os.remove('ans.pid')
|
||||
log("SIGTERM received, shutting down")
|
||||
os.remove("ans.pid")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
@@ -118,16 +119,16 @@ def main():
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
with open('ans.pid', 'w') as pidfile:
|
||||
with open("ans.pid", "w") as pidfile:
|
||||
print(os.getpid(), file=pidfile)
|
||||
|
||||
listenip = '10.53.0.6'
|
||||
listenip = "10.53.0.6"
|
||||
try:
|
||||
port = int(os.environ['CONTROLPORT'])
|
||||
port = int(os.environ["CONTROLPORT"])
|
||||
except KeyError:
|
||||
port = 5309
|
||||
|
||||
log('Listening on %s:%d' % (listenip, port))
|
||||
log("Listening on %s:%d" % (listenip, port))
|
||||
|
||||
ctlsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
ctlsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
@@ -136,21 +137,21 @@ def main():
|
||||
|
||||
while True:
|
||||
(clientsock, _) = ctlsock.accept()
|
||||
log('Accepted control connection from %s' % clientsock)
|
||||
cmdline = clientsock.recv(512).decode('ascii').strip()
|
||||
log("Accepted control connection from %s" % clientsock)
|
||||
cmdline = clientsock.recv(512).decode("ascii").strip()
|
||||
if cmdline:
|
||||
log('Received command: %s' % cmdline)
|
||||
log("Received command: %s" % cmdline)
|
||||
cmd = cmdline.split()
|
||||
if cmd[0] == 'open':
|
||||
if cmd[0] == "open":
|
||||
count, host, port = cmd[1:]
|
||||
open_connections(active_conns, int(count), host, int(port))
|
||||
elif cmd[0] == 'close':
|
||||
(count, ) = cmd[1:]
|
||||
elif cmd[0] == "close":
|
||||
(count,) = cmd[1:]
|
||||
close_connections(active_conns, int(count))
|
||||
else:
|
||||
log('result=FAIL: Unknown command')
|
||||
log("result=FAIL: Unknown command")
|
||||
clientsock.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@@ -19,7 +19,7 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip('dns', minversion='2.0.0')
|
||||
pytest.importorskip("dns", minversion="2.0.0")
|
||||
import dns.message
|
||||
import dns.query
|
||||
|
||||
@@ -54,8 +54,8 @@ def test_tcp_garbage(named_port):
|
||||
|
||||
# Send DNS message shorter than DNS message header (12),
|
||||
# this should cause the connection to be terminated
|
||||
sock.send(struct.pack('!H', 11))
|
||||
sock.send(struct.pack('!s', b'0123456789a'))
|
||||
sock.send(struct.pack("!H", 11))
|
||||
sock.send(struct.pack("!s", b"0123456789a"))
|
||||
|
||||
with pytest.raises(EOFError):
|
||||
try:
|
||||
@@ -96,8 +96,7 @@ def test_close_wait(named_port):
|
||||
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
|
||||
(response, rtime) = dns.query.receive_tcp(sock, timeout())
|
||||
|
||||
msg = dns.message.make_query("a.example.", "A", use_edns=0,
|
||||
payload=1232)
|
||||
msg = dns.message.make_query("a.example.", "A", use_edns=0, payload=1232)
|
||||
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
|
||||
|
||||
# Shutdown the socket, but ignore the other side closing the socket
|
||||
|
@@ -18,7 +18,7 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip('dns', minversion='2.0.0')
|
||||
pytest.importorskip("dns", minversion="2.0.0")
|
||||
import dns.edns
|
||||
import dns.message
|
||||
import dns.name
|
||||
@@ -33,8 +33,9 @@ TIMEOUT = 10
|
||||
|
||||
|
||||
def create_msg(qname, qtype):
|
||||
msg = dns.message.make_query(qname, qtype, want_dnssec=True,
|
||||
use_edns=0, payload=4096)
|
||||
msg = dns.message.make_query(
|
||||
qname, qtype, want_dnssec=True, use_edns=0, payload=4096
|
||||
)
|
||||
return msg
|
||||
|
||||
|
||||
@@ -94,7 +95,7 @@ def test_keepalive_timeout(named_port):
|
||||
# Keepalive is 7 seconds, so the third message should succeed.
|
||||
#
|
||||
msg = create_msg("example.", "A")
|
||||
kopt = dns.edns.GenericOption(11, b'\x00')
|
||||
kopt = dns.edns.GenericOption(11, b"\x00")
|
||||
msg.use_edns(edns=True, payload=4096, options=[kopt])
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
@@ -163,18 +164,22 @@ def test_long_axfr(named_port):
|
||||
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
|
||||
|
||||
# Receive the initial DNS message with SOA
|
||||
(response, rtime) = dns.query.receive_tcp(sock, timeout(),
|
||||
one_rr_per_rrset=True)
|
||||
soa = response.get_rrset(dns.message.ANSWER, name,
|
||||
dns.rdataclass.IN, dns.rdatatype.SOA)
|
||||
(response, rtime) = dns.query.receive_tcp(
|
||||
sock, timeout(), one_rr_per_rrset=True
|
||||
)
|
||||
soa = response.get_rrset(
|
||||
dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
|
||||
)
|
||||
assert soa is not None
|
||||
|
||||
# Pull DNS message from wire until the second SOA is received
|
||||
while True:
|
||||
(response, rtime) = dns.query.receive_tcp(sock, timeout(),
|
||||
one_rr_per_rrset=True)
|
||||
soa = response.get_rrset(dns.message.ANSWER, name,
|
||||
dns.rdataclass.IN, dns.rdatatype.SOA)
|
||||
(response, rtime) = dns.query.receive_tcp(
|
||||
sock, timeout(), one_rr_per_rrset=True
|
||||
)
|
||||
soa = response.get_rrset(
|
||||
dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
|
||||
)
|
||||
if soa is not None:
|
||||
break
|
||||
assert soa is not None
|
||||
@@ -216,10 +221,12 @@ def test_max_transfer_idle_out(named_port):
|
||||
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
|
||||
|
||||
# Receive the initial DNS message with SOA
|
||||
(response, rtime) = dns.query.receive_tcp(sock, timeout(),
|
||||
one_rr_per_rrset=True)
|
||||
soa = response.get_rrset(dns.message.ANSWER, name,
|
||||
dns.rdataclass.IN, dns.rdatatype.SOA)
|
||||
(response, rtime) = dns.query.receive_tcp(
|
||||
sock, timeout(), one_rr_per_rrset=True
|
||||
)
|
||||
soa = response.get_rrset(
|
||||
dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
|
||||
)
|
||||
assert soa is not None
|
||||
|
||||
time.sleep(61) # max-transfer-idle-out is 1 minute
|
||||
@@ -227,11 +234,12 @@ def test_max_transfer_idle_out(named_port):
|
||||
with pytest.raises(ConnectionResetError):
|
||||
# Process queued TCP messages
|
||||
while True:
|
||||
(response, rtime) = \
|
||||
dns.query.receive_tcp(sock, timeout(),
|
||||
one_rr_per_rrset=True)
|
||||
soa = response.get_rrset(dns.message.ANSWER, name,
|
||||
dns.rdataclass.IN, dns.rdatatype.SOA)
|
||||
(response, rtime) = dns.query.receive_tcp(
|
||||
sock, timeout(), one_rr_per_rrset=True
|
||||
)
|
||||
soa = response.get_rrset(
|
||||
dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
|
||||
)
|
||||
if soa is not None:
|
||||
break
|
||||
assert soa is None
|
||||
@@ -247,21 +255,24 @@ def test_max_transfer_time_out(named_port):
|
||||
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
|
||||
|
||||
# Receive the initial DNS message with SOA
|
||||
(response, rtime) = dns.query.receive_tcp(sock, timeout(),
|
||||
one_rr_per_rrset=True)
|
||||
soa = response.get_rrset(dns.message.ANSWER, name,
|
||||
dns.rdataclass.IN, dns.rdatatype.SOA)
|
||||
(response, rtime) = dns.query.receive_tcp(
|
||||
sock, timeout(), one_rr_per_rrset=True
|
||||
)
|
||||
soa = response.get_rrset(
|
||||
dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
|
||||
)
|
||||
assert soa is not None
|
||||
|
||||
# The loop should timeout at the 5 minutes (max-transfer-time-out)
|
||||
with pytest.raises(EOFError):
|
||||
while True:
|
||||
time.sleep(1)
|
||||
(response, rtime) = \
|
||||
dns.query.receive_tcp(sock, timeout(),
|
||||
one_rr_per_rrset=True)
|
||||
soa = response.get_rrset(dns.message.ANSWER, name,
|
||||
dns.rdataclass.IN, dns.rdatatype.SOA)
|
||||
(response, rtime) = dns.query.receive_tcp(
|
||||
sock, timeout(), one_rr_per_rrset=True
|
||||
)
|
||||
soa = response.get_rrset(
|
||||
dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
|
||||
)
|
||||
if soa is not None:
|
||||
break
|
||||
assert soa is None
|
||||
|
@@ -45,19 +45,21 @@ from hypothesis.strategies import binary, integers
|
||||
|
||||
|
||||
# labels of a zone with * A 192.0.2.1 wildcard
|
||||
WILDCARD_ZONE = ('allwild', 'test', '')
|
||||
WILDCARD_ZONE = ("allwild", "test", "")
|
||||
WILDCARD_RDTYPE = dns.rdatatype.A
|
||||
WILDCARD_RDATA = '192.0.2.1'
|
||||
IPADDR = '10.53.0.1'
|
||||
WILDCARD_RDATA = "192.0.2.1"
|
||||
IPADDR = "10.53.0.1"
|
||||
TIMEOUT = 5 # seconds, just a sanity check
|
||||
|
||||
|
||||
# Helpers
|
||||
def is_nonexpanding_rdtype(rdtype):
|
||||
"""skip meta types to avoid weird rcodes caused by AXFR etc.; RFC 6895"""
|
||||
return not(rdtype == WILDCARD_RDTYPE
|
||||
or dns.rdatatype.is_metatype(rdtype) # known metatypes: OPT ...
|
||||
or 128 <= rdtype <= 255) # unknown meta types
|
||||
return not (
|
||||
rdtype == WILDCARD_RDTYPE
|
||||
or dns.rdatatype.is_metatype(rdtype) # known metatypes: OPT ...
|
||||
or 128 <= rdtype <= 255
|
||||
) # unknown meta types
|
||||
|
||||
|
||||
def tcp_query(where, port, qname, qtype):
|
||||
@@ -67,15 +69,16 @@ def tcp_query(where, port, qname, qtype):
|
||||
|
||||
|
||||
def query(where, port, label, rdtype):
|
||||
labels = (label, ) + WILDCARD_ZONE
|
||||
labels = (label,) + WILDCARD_ZONE
|
||||
qname = dns.name.Name(labels)
|
||||
return tcp_query(where, port, qname, rdtype)
|
||||
|
||||
|
||||
# Tests
|
||||
@given(label=binary(min_size=1, max_size=63),
|
||||
rdtype=integers(min_value=0, max_value=65535).filter(
|
||||
is_nonexpanding_rdtype))
|
||||
@given(
|
||||
label=binary(min_size=1, max_size=63),
|
||||
rdtype=integers(min_value=0, max_value=65535).filter(is_nonexpanding_rdtype),
|
||||
)
|
||||
def test_wildcard_rdtype_mismatch(label, rdtype, named_port):
|
||||
"""any label non-matching rdtype must result in to NODATA"""
|
||||
check_answer_nodata(*query(IPADDR, named_port, label, rdtype))
|
||||
@@ -97,10 +100,13 @@ def check_answer_noerror(querymsg, answer):
|
||||
assert querymsg.is_response(answer), str(answer)
|
||||
assert answer.rcode() == dns.rcode.NOERROR, str(answer)
|
||||
assert len(querymsg.question) == 1, str(answer)
|
||||
expected_answer = [dns.rrset.from_text(
|
||||
querymsg.question[0].name,
|
||||
300, # TTL, ignored by dnspython comparison
|
||||
dns.rdataclass.IN,
|
||||
WILDCARD_RDTYPE,
|
||||
WILDCARD_RDATA)]
|
||||
expected_answer = [
|
||||
dns.rrset.from_text(
|
||||
querymsg.question[0].name,
|
||||
300, # TTL, ignored by dnspython comparison
|
||||
dns.rdataclass.IN,
|
||||
WILDCARD_RDTYPE,
|
||||
WILDCARD_RDATA,
|
||||
)
|
||||
]
|
||||
assert answer.answer == expected_answer, str(answer)
|
||||
|
225
dangerfile.py
225
dangerfile.py
@@ -15,24 +15,30 @@ import re
|
||||
|
||||
# Helper functions and variables
|
||||
|
||||
|
||||
def added_lines(target_branch, paths):
|
||||
import subprocess
|
||||
subprocess.check_output(['/usr/bin/git', 'fetch', '--depth', '1', 'origin',
|
||||
target_branch])
|
||||
diff = subprocess.check_output(['/usr/bin/git', 'diff', 'FETCH_HEAD..',
|
||||
'--'] + paths)
|
||||
|
||||
subprocess.check_output(
|
||||
["/usr/bin/git", "fetch", "--depth", "1", "origin", target_branch]
|
||||
)
|
||||
diff = subprocess.check_output(
|
||||
["/usr/bin/git", "diff", "FETCH_HEAD..", "--"] + paths
|
||||
)
|
||||
added_lines = []
|
||||
for line in diff.splitlines():
|
||||
if line.startswith(b'+') and not line.startswith(b'+++'):
|
||||
if line.startswith(b"+") and not line.startswith(b"+++"):
|
||||
added_lines.append(line)
|
||||
return added_lines
|
||||
|
||||
def lines_containing(lines, string):
|
||||
return [l for l in lines if bytes(string, 'utf-8') in l]
|
||||
|
||||
changes_issue_or_mr_id_regex = re.compile(br'\[(GL [#!]|RT #)[0-9]+\]')
|
||||
relnotes_issue_or_mr_id_regex = re.compile(br':gl:`[#!][0-9]+`')
|
||||
release_notes_regex = re.compile(r'doc/(arm|notes)/notes-.*\.(rst|xml)')
|
||||
def lines_containing(lines, string):
|
||||
return [l for l in lines if bytes(string, "utf-8") in l]
|
||||
|
||||
|
||||
changes_issue_or_mr_id_regex = re.compile(rb"\[(GL [#!]|RT #)[0-9]+\]")
|
||||
relnotes_issue_or_mr_id_regex = re.compile(rb":gl:`[#!][0-9]+`")
|
||||
release_notes_regex = re.compile(r"doc/(arm|notes)/notes-.*\.(rst|xml)")
|
||||
|
||||
modified_files = danger.git.modified_files
|
||||
mr_labels = danger.gitlab.mr.labels
|
||||
@@ -75,33 +81,39 @@ fixup_error_logged = False
|
||||
for commit in danger.git.commits:
|
||||
message_lines = commit.message.splitlines()
|
||||
subject = message_lines[0]
|
||||
if (not fixup_error_logged and
|
||||
(subject.startswith('fixup!') or
|
||||
subject.startswith('Apply suggestion'))):
|
||||
fail('Fixup commits are still present in this merge request. '
|
||||
'Please squash them before merging.')
|
||||
fixup_error_logged = True
|
||||
if len(subject) > 72 and not subject.startswith('Merge branch '):
|
||||
warn(
|
||||
f'Subject line for commit {commit.sha} is too long: '
|
||||
f'```{subject}``` ({len(subject)} > 72 characters).'
|
||||
if not fixup_error_logged and (
|
||||
subject.startswith("fixup!") or subject.startswith("Apply suggestion")
|
||||
):
|
||||
fail(
|
||||
"Fixup commits are still present in this merge request. "
|
||||
"Please squash them before merging."
|
||||
)
|
||||
if subject[-1] == '.':
|
||||
fail(f'Trailing dot found in the subject of commit {commit.sha}.')
|
||||
fixup_error_logged = True
|
||||
if len(subject) > 72 and not subject.startswith("Merge branch "):
|
||||
warn(
|
||||
f"Subject line for commit {commit.sha} is too long: "
|
||||
f"```{subject}``` ({len(subject)} > 72 characters)."
|
||||
)
|
||||
if subject[-1] == ".":
|
||||
fail(f"Trailing dot found in the subject of commit {commit.sha}.")
|
||||
if len(message_lines) > 1 and message_lines[1]:
|
||||
fail(f'No empty line after subject for commit {commit.sha}.')
|
||||
if (len(message_lines) < 3 and
|
||||
'fixup! ' not in subject and
|
||||
' CHANGES ' not in subject and
|
||||
' release note' not in subject):
|
||||
warn(f'Please write a log message for commit {commit.sha}.')
|
||||
fail(f"No empty line after subject for commit {commit.sha}.")
|
||||
if (
|
||||
len(message_lines) < 3
|
||||
and "fixup! " not in subject
|
||||
and " CHANGES " not in subject
|
||||
and " release note" not in subject
|
||||
):
|
||||
warn(f"Please write a log message for commit {commit.sha}.")
|
||||
for line in message_lines[2:]:
|
||||
if (len(line) > 72 and
|
||||
not line.startswith(' ') and
|
||||
not re.match(r'\[[0-9]+\]', line)):
|
||||
if (
|
||||
len(line) > 72
|
||||
and not line.startswith(" ")
|
||||
and not re.match(r"\[[0-9]+\]", line)
|
||||
):
|
||||
warn(
|
||||
f'Line too long in log message for commit {commit.sha}: '
|
||||
f'```{line}``` ({len(line)} > 72 characters).'
|
||||
f"Line too long in log message for commit {commit.sha}: "
|
||||
f"```{line}``` ({len(line)} > 72 characters)."
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
@@ -111,7 +123,7 @@ for commit in danger.git.commits:
|
||||
# FAIL if the merge request is not assigned to any milestone.
|
||||
|
||||
if not danger.gitlab.mr.milestone:
|
||||
fail('Please assign this merge request to a milestone.')
|
||||
fail("Please assign this merge request to a milestone.")
|
||||
|
||||
###############################################################################
|
||||
# VERSION LABELS
|
||||
@@ -129,15 +141,19 @@ if not danger.gitlab.mr.milestone:
|
||||
# request is not a backport, version labels are used for indicating
|
||||
# backporting preferences.)
|
||||
|
||||
backport_label_set = 'Backport' in mr_labels
|
||||
version_labels = [l for l in mr_labels if l.startswith('v9.')]
|
||||
backport_label_set = "Backport" in mr_labels
|
||||
version_labels = [l for l in mr_labels if l.startswith("v9.")]
|
||||
if backport_label_set and len(version_labels) != 1:
|
||||
fail('The *Backport* label is set for this merge request. '
|
||||
'Please also set exactly one version label (*v9.x*).')
|
||||
fail(
|
||||
"The *Backport* label is set for this merge request. "
|
||||
"Please also set exactly one version label (*v9.x*)."
|
||||
)
|
||||
if not backport_label_set and not version_labels:
|
||||
fail('If this merge request is a backport, set the *Backport* label and '
|
||||
'a single version label (*v9.x*) indicating the target branch. '
|
||||
'If not, set version labels for all targeted backport branches.')
|
||||
fail(
|
||||
"If this merge request is a backport, set the *Backport* label and "
|
||||
"a single version label (*v9.x*) indicating the target branch. "
|
||||
"If not, set version labels for all targeted backport branches."
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# OTHER LABELS
|
||||
@@ -151,12 +167,16 @@ if not backport_label_set and not version_labels:
|
||||
# remind developers about the need to set the latter on merge requests which
|
||||
# passed review.)
|
||||
|
||||
if 'Review' not in mr_labels:
|
||||
warn('This merge request does not have the *Review* label set. '
|
||||
'Please set it if you would like the merge request to be reviewed.')
|
||||
elif 'LGTM (Merge OK)' not in mr_labels:
|
||||
warn('This merge request is currently in review. '
|
||||
'It should not be merged until it is marked with the *LGTM* label.')
|
||||
if "Review" not in mr_labels:
|
||||
warn(
|
||||
"This merge request does not have the *Review* label set. "
|
||||
"Please set it if you would like the merge request to be reviewed."
|
||||
)
|
||||
elif "LGTM (Merge OK)" not in mr_labels:
|
||||
warn(
|
||||
"This merge request is currently in review. "
|
||||
"It should not be merged until it is marked with the *LGTM* label."
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# 'CHANGES' FILE
|
||||
@@ -178,25 +198,31 @@ elif 'LGTM (Merge OK)' not in mr_labels:
|
||||
# * The merge request adds a new CHANGES entry that is not a placeholder and
|
||||
# does not contain any GitLab/RT issue/MR identifiers.
|
||||
|
||||
changes_modified = 'CHANGES' in modified_files
|
||||
no_changes_label_set = 'No CHANGES' in mr_labels
|
||||
changes_modified = "CHANGES" in modified_files
|
||||
no_changes_label_set = "No CHANGES" in mr_labels
|
||||
if not changes_modified and not no_changes_label_set:
|
||||
fail('This merge request does not modify `CHANGES`. '
|
||||
'Add a `CHANGES` entry or set the *No CHANGES* label.')
|
||||
fail(
|
||||
"This merge request does not modify `CHANGES`. "
|
||||
"Add a `CHANGES` entry or set the *No CHANGES* label."
|
||||
)
|
||||
if changes_modified and no_changes_label_set:
|
||||
fail('This merge request modifies `CHANGES`. '
|
||||
'Revert `CHANGES` modifications or unset the *No Changes* label.')
|
||||
fail(
|
||||
"This merge request modifies `CHANGES`. "
|
||||
"Revert `CHANGES` modifications or unset the *No Changes* label."
|
||||
)
|
||||
|
||||
changes_added_lines = added_lines(target_branch, ['CHANGES'])
|
||||
placeholders_added = lines_containing(changes_added_lines, '[placeholder]')
|
||||
changes_added_lines = added_lines(target_branch, ["CHANGES"])
|
||||
placeholders_added = lines_containing(changes_added_lines, "[placeholder]")
|
||||
identifiers_found = filter(changes_issue_or_mr_id_regex.search, changes_added_lines)
|
||||
if changes_added_lines:
|
||||
if placeholders_added:
|
||||
if target_branch != 'main':
|
||||
fail('This MR adds at least one placeholder entry to `CHANGES`. '
|
||||
'It should be targeting the `main` branch.')
|
||||
if target_branch != "main":
|
||||
fail(
|
||||
"This MR adds at least one placeholder entry to `CHANGES`. "
|
||||
"It should be targeting the `main` branch."
|
||||
)
|
||||
elif not any(identifiers_found):
|
||||
fail('No valid issue/MR identifiers found in added `CHANGES` entries.')
|
||||
fail("No valid issue/MR identifiers found in added `CHANGES` entries.")
|
||||
|
||||
###############################################################################
|
||||
# RELEASE NOTES
|
||||
@@ -221,25 +247,31 @@ if changes_added_lines:
|
||||
# identifiers are found in the lines added to the release notes by this
|
||||
# MR.
|
||||
|
||||
release_notes_regex = re.compile(r'doc/(arm|notes)/notes-.*\.(rst|xml)')
|
||||
release_notes_regex = re.compile(r"doc/(arm|notes)/notes-.*\.(rst|xml)")
|
||||
release_notes_changed = list(filter(release_notes_regex.match, modified_files))
|
||||
release_notes_label_set = 'Release Notes' in mr_labels
|
||||
release_notes_label_set = "Release Notes" in mr_labels
|
||||
if not release_notes_changed:
|
||||
if release_notes_label_set:
|
||||
fail('This merge request has the *Release Notes* label set. '
|
||||
'Add a release note or unset the *Release Notes* label.')
|
||||
elif 'Customer' in mr_labels:
|
||||
warn('This merge request has the *Customer* label set. '
|
||||
'Add a release note unless the changes introduced are trivial.')
|
||||
fail(
|
||||
"This merge request has the *Release Notes* label set. "
|
||||
"Add a release note or unset the *Release Notes* label."
|
||||
)
|
||||
elif "Customer" in mr_labels:
|
||||
warn(
|
||||
"This merge request has the *Customer* label set. "
|
||||
"Add a release note unless the changes introduced are trivial."
|
||||
)
|
||||
if release_notes_changed and not release_notes_label_set:
|
||||
fail('This merge request modifies release notes. '
|
||||
'Revert release note modifications or set the *Release Notes* label.')
|
||||
fail(
|
||||
"This merge request modifies release notes. "
|
||||
"Revert release note modifications or set the *Release Notes* label."
|
||||
)
|
||||
|
||||
if release_notes_changed:
|
||||
notes_added_lines = added_lines(target_branch, release_notes_changed)
|
||||
identifiers_found = filter(relnotes_issue_or_mr_id_regex.search, notes_added_lines)
|
||||
if notes_added_lines and not any(identifiers_found):
|
||||
warn('No valid issue/MR identifiers found in added release notes.')
|
||||
warn("No valid issue/MR identifiers found in added release notes.")
|
||||
else:
|
||||
notes_added_lines = []
|
||||
|
||||
@@ -251,13 +283,17 @@ else:
|
||||
# identifier is missing from either the added CHANGES entry or the added
|
||||
# release note.
|
||||
|
||||
if lines_containing(changes_added_lines, '[security]'):
|
||||
if not lines_containing(changes_added_lines, '(CVE-20'):
|
||||
fail('This merge request fixes a security issue. '
|
||||
'Please add a CHANGES entry which includes a CVE identifier.')
|
||||
if not lines_containing(notes_added_lines, 'CVE-20'):
|
||||
fail('This merge request fixes a security issue. '
|
||||
'Please add a release note which includes a CVE identifier.')
|
||||
if lines_containing(changes_added_lines, "[security]"):
|
||||
if not lines_containing(changes_added_lines, "(CVE-20"):
|
||||
fail(
|
||||
"This merge request fixes a security issue. "
|
||||
"Please add a CHANGES entry which includes a CVE identifier."
|
||||
)
|
||||
if not lines_containing(notes_added_lines, "CVE-20"):
|
||||
fail(
|
||||
"This merge request fixes a security issue. "
|
||||
"Please add a release note which includes a CVE identifier."
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# PAIRWISE TESTING
|
||||
@@ -266,13 +302,16 @@ if lines_containing(changes_added_lines, '[security]'):
|
||||
# FAIL if the merge request adds any new ./configure switch without an
|
||||
# associated annotation used for pairwise testing.
|
||||
|
||||
configure_added_lines = added_lines(target_branch, ['configure.ac'])
|
||||
switches_added = (lines_containing(configure_added_lines, 'AC_ARG_ENABLE') +
|
||||
lines_containing(configure_added_lines, 'AC_ARG_WITH'))
|
||||
annotations_added = lines_containing(configure_added_lines, '# [pairwise: ')
|
||||
configure_added_lines = added_lines(target_branch, ["configure.ac"])
|
||||
switches_added = lines_containing(
|
||||
configure_added_lines, "AC_ARG_ENABLE"
|
||||
) + lines_containing(configure_added_lines, "AC_ARG_WITH")
|
||||
annotations_added = lines_containing(configure_added_lines, "# [pairwise: ")
|
||||
if len(switches_added) > len(annotations_added):
|
||||
fail('This merge request adds at least one new `./configure` switch that '
|
||||
'is not annotated for pairwise testing purposes.')
|
||||
fail(
|
||||
"This merge request adds at least one new `./configure` switch that "
|
||||
"is not annotated for pairwise testing purposes."
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# USER-VISIBLE LOG LEVELS
|
||||
@@ -281,16 +320,18 @@ if len(switches_added) > len(annotations_added):
|
||||
# WARN if the merge request adds new user-visible log messages (INFO or above)
|
||||
|
||||
user_visible_log_levels = [
|
||||
'ISC_LOG_INFO',
|
||||
'ISC_LOG_NOTICE',
|
||||
'ISC_LOG_WARNING',
|
||||
'ISC_LOG_ERROR',
|
||||
'ISC_LOG_CRITICAL',
|
||||
"ISC_LOG_INFO",
|
||||
"ISC_LOG_NOTICE",
|
||||
"ISC_LOG_WARNING",
|
||||
"ISC_LOG_ERROR",
|
||||
"ISC_LOG_CRITICAL",
|
||||
]
|
||||
source_added_lines = added_lines(target_branch, ['*.[ch]'])
|
||||
source_added_lines = added_lines(target_branch, ["*.[ch]"])
|
||||
for log_level in user_visible_log_levels:
|
||||
if (lines_containing(source_added_lines, log_level)):
|
||||
warn('This merge request adds new user-visible log messages with '
|
||||
'level INFO or above. Please double-check log levels and make '
|
||||
'sure none of the messages added is a leftover debug message.')
|
||||
if lines_containing(source_added_lines, log_level):
|
||||
warn(
|
||||
"This merge request adds new user-visible log messages with "
|
||||
"level INFO or above. Please double-check log levels and make "
|
||||
"sure none of the messages added is a leftover debug message."
|
||||
)
|
||||
break
|
||||
|
@@ -28,17 +28,18 @@ try:
|
||||
except ImportError:
|
||||
# pylint: disable=too-few-public-methods
|
||||
class ReferenceRole(roles.GenericRole):
|
||||
'''
|
||||
"""
|
||||
The ReferenceRole class (used as a base class by GitLabRefRole
|
||||
below) is only defined in Sphinx >= 2.0.0. For older Sphinx
|
||||
versions, this stub version of the ReferenceRole class is used
|
||||
instead.
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('', nodes.strong)
|
||||
super().__init__("", nodes.strong)
|
||||
|
||||
|
||||
GITLAB_BASE_URL = 'https://gitlab.isc.org/isc-projects/bind9/-/'
|
||||
GITLAB_BASE_URL = "https://gitlab.isc.org/isc-projects/bind9/-/"
|
||||
|
||||
|
||||
# Custom Sphinx role enabling automatic hyperlinking to GitLab issues/MRs.
|
||||
@@ -48,25 +49,26 @@ class GitLabRefRole(ReferenceRole):
|
||||
super().__init__()
|
||||
|
||||
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||||
gl_identifier = '[GL %s]' % self.target
|
||||
gl_identifier = "[GL %s]" % self.target
|
||||
|
||||
target_id = 'index-%s' % self.env.new_serialno('index')
|
||||
entries = [('single', 'GitLab; ' + gl_identifier, target_id, '', None)]
|
||||
target_id = "index-%s" % self.env.new_serialno("index")
|
||||
entries = [("single", "GitLab; " + gl_identifier, target_id, "", None)]
|
||||
|
||||
index = addnodes.index(entries=entries)
|
||||
target = nodes.target('', '', ids=[target_id])
|
||||
target = nodes.target("", "", ids=[target_id])
|
||||
self.inliner.document.note_explicit_target(target)
|
||||
|
||||
try:
|
||||
refuri = self.build_uri()
|
||||
reference = nodes.reference('', '', internal=False, refuri=refuri,
|
||||
classes=['gl'])
|
||||
reference = nodes.reference(
|
||||
"", "", internal=False, refuri=refuri, classes=["gl"]
|
||||
)
|
||||
if self.has_explicit_title:
|
||||
reference += nodes.strong(self.title, self.title)
|
||||
else:
|
||||
reference += nodes.strong(gl_identifier, gl_identifier)
|
||||
except ValueError:
|
||||
error_text = 'invalid GitLab identifier %s' % self.target
|
||||
error_text = "invalid GitLab identifier %s" % self.target
|
||||
msg = self.inliner.reporter.error(error_text, line=self.lineno)
|
||||
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
|
||||
return [prb], [msg]
|
||||
@@ -74,16 +76,17 @@ class GitLabRefRole(ReferenceRole):
|
||||
return [index, target, reference], []
|
||||
|
||||
def build_uri(self):
|
||||
if self.target[0] == '#':
|
||||
return self.base_url + 'issues/%d' % int(self.target[1:])
|
||||
if self.target[0] == '!':
|
||||
return self.base_url + 'merge_requests/%d' % int(self.target[1:])
|
||||
if self.target[0] == "#":
|
||||
return self.base_url + "issues/%d" % int(self.target[1:])
|
||||
if self.target[0] == "!":
|
||||
return self.base_url + "merge_requests/%d" % int(self.target[1:])
|
||||
raise ValueError
|
||||
|
||||
|
||||
def setup(app):
|
||||
roles.register_local_role('gl', GitLabRefRole(GITLAB_BASE_URL))
|
||||
app.add_crossref_type('iscman', 'iscman', 'pair: %s; manual page')
|
||||
roles.register_local_role("gl", GitLabRefRole(GITLAB_BASE_URL))
|
||||
app.add_crossref_type("iscman", "iscman", "pair: %s; manual page")
|
||||
|
||||
|
||||
#
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
@@ -105,23 +108,25 @@ def setup(app):
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'BIND 9'
|
||||
project = "BIND 9"
|
||||
# pylint: disable=redefined-builtin
|
||||
copyright = '2022, Internet Systems Consortium'
|
||||
author = 'Internet Systems Consortium'
|
||||
copyright = "2022, Internet Systems Consortium"
|
||||
author = "Internet Systems Consortium"
|
||||
|
||||
m4_vars = {}
|
||||
with open('../../configure.ac', encoding='utf-8') as configure_ac:
|
||||
with open("../../configure.ac", encoding="utf-8") as configure_ac:
|
||||
for line in configure_ac:
|
||||
match = re.match(r'm4_define\(\[(?P<key>bind_VERSION_[A-Z]+)\], (?P<val>[^)]*)\)dnl', line)
|
||||
match = re.match(
|
||||
r"m4_define\(\[(?P<key>bind_VERSION_[A-Z]+)\], (?P<val>[^)]*)\)dnl", line
|
||||
)
|
||||
if match:
|
||||
m4_vars[match.group('key')] = match.group('val')
|
||||
m4_vars[match.group("key")] = match.group("val")
|
||||
|
||||
version = '%s.%s.%s%s' % (
|
||||
m4_vars['bind_VERSION_MAJOR'],
|
||||
m4_vars['bind_VERSION_MINOR'],
|
||||
m4_vars['bind_VERSION_PATCH'],
|
||||
m4_vars['bind_VERSION_EXTRA'],
|
||||
version = "%s.%s.%s%s" % (
|
||||
m4_vars["bind_VERSION_MAJOR"],
|
||||
m4_vars["bind_VERSION_MINOR"],
|
||||
m4_vars["bind_VERSION_PATCH"],
|
||||
m4_vars["bind_VERSION_EXTRA"],
|
||||
)
|
||||
release = version
|
||||
|
||||
@@ -133,43 +138,42 @@ release = version
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = [
|
||||
'_build',
|
||||
'Thumbs.db',
|
||||
'.DS_Store',
|
||||
'*.inc.rst'
|
||||
]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "*.inc.rst"]
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_static_path = ['_static']
|
||||
html_css_files = [
|
||||
'custom.css'
|
||||
]
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = ["custom.css"]
|
||||
|
||||
# -- Options for EPUB output -------------------------------------------------
|
||||
|
||||
epub_basename = 'Bv9ARM'
|
||||
epub_basename = "Bv9ARM"
|
||||
|
||||
# -- Options for LaTeX output ------------------------------------------------
|
||||
latex_engine = 'xelatex'
|
||||
latex_engine = "xelatex"
|
||||
|
||||
# pylint disable=line-too-long
|
||||
latex_documents = [
|
||||
(master_doc, 'Bv9ARM.tex', 'BIND 9 Administrator Reference Manual', author, 'manual'),
|
||||
]
|
||||
(
|
||||
master_doc,
|
||||
"Bv9ARM.tex",
|
||||
"BIND 9 Administrator Reference Manual",
|
||||
author,
|
||||
"manual",
|
||||
),
|
||||
]
|
||||
|
||||
latex_logo = "isc-logo.pdf"
|
||||
|
||||
|
@@ -78,7 +78,7 @@ conform well to BIND 9 C style:
|
||||
set showmode
|
||||
set autoindent
|
||||
set expandtab
|
||||
|
||||
|
||||
filetype plugin on
|
||||
let c_syntax_for_h = 1
|
||||
autocmd FileType c,cc,cpp set cindent
|
||||
@@ -86,7 +86,7 @@ conform well to BIND 9 C style:
|
||||
autocmd FileType c,cc,cpp set fo=rotcq
|
||||
autocmd FileType c,cc,cpp set noexpandtab ts=8
|
||||
autocmd FileType python set ts=4 sw=4
|
||||
|
||||
|
||||
filetype indent on
|
||||
|
||||
#### Vertical Whitespace
|
||||
@@ -136,7 +136,7 @@ Good:
|
||||
/*
|
||||
* Private variables.
|
||||
*/
|
||||
|
||||
|
||||
static int a /* Description of 'a'. */
|
||||
static int b /* Description of 'b'. */
|
||||
static char * c /* Description of 'c'. */
|
||||
@@ -186,13 +186,13 @@ or for public files that do not declare any functions.
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
/*****
|
||||
***** Module Info
|
||||
*****/
|
||||
|
||||
|
||||
/*
|
||||
* (Module name here.)
|
||||
*
|
||||
@@ -216,20 +216,20 @@ or for public files that do not declare any functions.
|
||||
* Standards:
|
||||
* (Any standards relevant to the module are listed here.)
|
||||
*/
|
||||
|
||||
|
||||
/***
|
||||
*** Imports
|
||||
***/
|
||||
|
||||
|
||||
/* #includes here. */
|
||||
#include <isc/lang.h>
|
||||
|
||||
|
||||
/***
|
||||
*** Types
|
||||
***/
|
||||
|
||||
|
||||
/* (Type definitions here.) */
|
||||
|
||||
|
||||
/***
|
||||
*** Functions
|
||||
***/
|
||||
@@ -273,7 +273,7 @@ list is more than one line long:
|
||||
func1(int i) {
|
||||
/* whatever */
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
func2(int first_argument, int next_argument,
|
||||
int last_argument)
|
||||
@@ -371,7 +371,7 @@ Good:
|
||||
|
||||
os_result_t result;
|
||||
os_descriptor_t s;
|
||||
|
||||
|
||||
result = os_socket_create(AF_INET, SOCK_STREAM, 0, &s);
|
||||
if (result != OS_R_SUCCESS) {
|
||||
/* Do something about the error. */
|
||||
@@ -381,7 +381,7 @@ Good:
|
||||
Not so good:
|
||||
|
||||
int s;
|
||||
|
||||
|
||||
/*
|
||||
* Obviously using interfaces like socket() (below) is allowed
|
||||
* since otherwise you couldn't call operating system routines; the
|
||||
@@ -432,26 +432,26 @@ Good:
|
||||
|
||||
/* Test if flag set. */
|
||||
if ((flags & FOO) != 0) {
|
||||
|
||||
|
||||
}
|
||||
/* Test if flag clear. */
|
||||
if ((flags & BAR) == 0) {
|
||||
|
||||
|
||||
}
|
||||
/* Test if both flags set. */
|
||||
if ((flags & (FOO|BAR)) == (FOO|BAR)) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
Bad:
|
||||
|
||||
/* Test if flag set. */
|
||||
if (flags & FOO) {
|
||||
|
||||
|
||||
}
|
||||
/* Test if flag clear. */
|
||||
if (! (flags & BAR)) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
#### Testing for Zero or Non-zero
|
||||
@@ -462,9 +462,9 @@ variables.
|
||||
Good:
|
||||
|
||||
int i = 10;
|
||||
|
||||
|
||||
/* ... */
|
||||
|
||||
|
||||
if (i != 0) {
|
||||
/* Do something. */
|
||||
}
|
||||
@@ -472,9 +472,9 @@ Good:
|
||||
Bad:
|
||||
|
||||
int i = 10;
|
||||
|
||||
|
||||
/* ... */
|
||||
|
||||
|
||||
if (i) {
|
||||
/* Do something. */
|
||||
}
|
||||
@@ -489,9 +489,9 @@ comparison; do not treat a pointer variable as if it were a boolean.
|
||||
Good:
|
||||
|
||||
char *c = NULL;
|
||||
|
||||
|
||||
/* ... */
|
||||
|
||||
|
||||
if (c != NULL) {
|
||||
/* Do something. */
|
||||
}
|
||||
@@ -499,9 +499,9 @@ Good:
|
||||
Bad:
|
||||
|
||||
char *c = NULL;
|
||||
|
||||
|
||||
/* ... */
|
||||
|
||||
|
||||
if (c) {
|
||||
/* Do something. */
|
||||
}
|
||||
@@ -562,9 +562,9 @@ structure which is itself going to be freed immediately.
|
||||
Good:
|
||||
|
||||
char *text;
|
||||
|
||||
|
||||
/* text is initialized here. */
|
||||
|
||||
|
||||
isc_mem_free(mctx, text);
|
||||
text = NULL;
|
||||
|
||||
@@ -635,7 +635,7 @@ Good:
|
||||
int bar;
|
||||
int baz;
|
||||
};
|
||||
|
||||
|
||||
struct example x = { .foo = -1 };
|
||||
|
||||
Bad:
|
||||
@@ -644,9 +644,9 @@ Bad:
|
||||
int bar;
|
||||
int baz;
|
||||
};
|
||||
|
||||
|
||||
struct example x;
|
||||
|
||||
|
||||
x.foo = -1;
|
||||
x.bar = 0;
|
||||
x.baz = 0;
|
||||
@@ -657,9 +657,9 @@ Good:
|
||||
int bar;
|
||||
int baz;
|
||||
};
|
||||
|
||||
|
||||
struct example *x = isc_mem_get(mctx, sizeof(*x));
|
||||
|
||||
|
||||
*x = (struct example){ .foo = -1 };
|
||||
|
||||
Bad:
|
||||
@@ -668,9 +668,9 @@ Bad:
|
||||
int bar;
|
||||
int baz;
|
||||
};
|
||||
|
||||
|
||||
struct example *x = isc_mem_get(mctx, sizeof(*x));
|
||||
|
||||
|
||||
x->foo = -1;
|
||||
x->bar = 0;
|
||||
x->baz = 0;
|
||||
@@ -748,7 +748,7 @@ value of the format parameter:
|
||||
dns_zone_setfile(dns_zone_t *zone, const char *file) {
|
||||
return (dns_zone_setfile2(zone, file, dns_masterformat_text);
|
||||
}
|
||||
|
||||
|
||||
isc_result_t
|
||||
dns_zone_setfile2(dns_zone_t *zone, const char *file,
|
||||
dns_masterformat_t format)
|
||||
@@ -855,9 +855,9 @@ name server. However, BIND 9 may use it for its system test
|
||||
environment, and in some cases for generating source or documentation
|
||||
files which are then committed to to the git repository.
|
||||
|
||||
For Python coding, we abide by the Python style guidelines described
|
||||
in [PEP8](http://www.python.org/dev/peps/pep-0008/), with a few
|
||||
modifications:
|
||||
For Python coding, we enforce a common codestyle using the tool
|
||||
[black](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html)
|
||||
There are also a few other requirements:
|
||||
|
||||
* The `__init__()` method should always be the first one declared in a
|
||||
class definition, like so:
|
||||
|
178
doc/man/conf.py
178
doc/man/conf.py
@@ -32,13 +32,14 @@
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'BIND 9'
|
||||
project = "BIND 9"
|
||||
# pylint: disable=wrong-import-position
|
||||
import datetime
|
||||
|
||||
year = datetime.datetime.now().year
|
||||
# pylint: disable=redefined-builtin
|
||||
copyright = "%d, Internet Systems Consortium" % year
|
||||
author = 'Internet Systems Consortium'
|
||||
author = "Internet Systems Consortium"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -52,56 +53,146 @@ man_make_section_directory = False
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['../arm/_templates']
|
||||
templates_path = ["../arm/_templates"]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = [
|
||||
'_build',
|
||||
'Thumbs.db',
|
||||
'.DS_Store',
|
||||
]
|
||||
"_build",
|
||||
"Thumbs.db",
|
||||
".DS_Store",
|
||||
]
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
man_pages = [
|
||||
('arpaname', 'arpaname', 'translate IP addresses to the corresponding ARPA names', author, 1),
|
||||
('ddns-confgen', 'ddns-confgen', 'ddns key generation tool', author, 8),
|
||||
('delv', 'delv', 'DNS lookup and validation utility', author, 1),
|
||||
('dig', 'dig', 'DNS lookup utility', author, 1),
|
||||
('dnssec-cds', 'dnssec-cds', 'change DS records for a child zone based on CDS/CDNSKEY', author, 1),
|
||||
('dnssec-dsfromkey', 'dnssec-dsfromkey', 'DNSSEC DS RR generation tool', author, 1),
|
||||
('dnssec-importkey', 'dnssec-importkey', 'import DNSKEY records from external systems so they can be managed', author, 1),
|
||||
('dnssec-keyfromlabel', 'dnssec-keyfromlabel', 'DNSSEC key generation tool', author, 1),
|
||||
('dnssec-keygen', 'dnssec-keygen', 'DNSSEC key generation tool', author, 1),
|
||||
('dnssec-revoke', 'dnssec-revoke', 'set the REVOKED bit on a DNSSEC key', author, 1),
|
||||
('dnssec-settime', 'dnssec-settime', 'set the key timing metadata for a DNSSEC key', author, 1),
|
||||
('dnssec-signzone', 'dnssec-signzone', 'DNSSEC zone signing tool', author, 1),
|
||||
('dnssec-verify', 'dnssec-verify', 'DNSSEC zone verification tool', author, 1),
|
||||
('dnstap-read', 'dnstap-read', 'print dnstap data in human-readable form', author, 1),
|
||||
('filter-aaaa', 'filter-aaaa', 'filter AAAA in DNS responses when A is present', author, 8),
|
||||
('filter-a', 'filter-a', 'filter A in DNS responses when AAAA is present', author, 8),
|
||||
('host', 'host', 'DNS lookup utility', author, 1),
|
||||
('mdig', 'mdig', 'DNS pipelined lookup utility', author, 1),
|
||||
('named-checkconf', 'named-checkconf', 'named configuration file syntax checking tool', author, 1),
|
||||
('named-checkzone', 'named-checkzone', 'zone file validity checking or converting tool', author, 1),
|
||||
('named-compilezone', 'named-compilezone', 'zone file validity checking or converting tool', author, 1),
|
||||
('named-journalprint', 'named-journalprint', 'print zone journal in human-readable form', author, 1),
|
||||
('named-nzd2nzf', 'named-nzd2nzf', 'convert an NZD database to NZF text format', author, 1),
|
||||
('named-rrchecker', 'named-rrchecker', 'syntax checker for individual DNS resource records', author, 1),
|
||||
('named.conf', 'named.conf', 'configuration file for **named**', author, 5),
|
||||
('named', 'named', 'Internet domain name server', author, 8),
|
||||
('nsec3hash', 'nsec3hash', 'generate NSEC3 hash', author, 1),
|
||||
('nslookup', 'nslookup', 'query Internet name servers interactively', author, 1),
|
||||
('nsupdate', 'nsupdate', 'dynamic DNS update utility', author, 1),
|
||||
('rndc-confgen', 'rndc-confgen', 'rndc key generation tool', author, 8),
|
||||
('rndc.conf', 'rndc.conf', 'rndc configuration file', author, 5),
|
||||
('rndc', 'rndc', 'name server control utility', author, 8),
|
||||
('tsig-keygen', 'tsig-keygen', 'TSIG key generation tool', author, 8),
|
||||
]
|
||||
(
|
||||
"arpaname",
|
||||
"arpaname",
|
||||
"translate IP addresses to the corresponding ARPA names",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
("ddns-confgen", "ddns-confgen", "ddns key generation tool", author, 8),
|
||||
("delv", "delv", "DNS lookup and validation utility", author, 1),
|
||||
("dig", "dig", "DNS lookup utility", author, 1),
|
||||
(
|
||||
"dnssec-cds",
|
||||
"dnssec-cds",
|
||||
"change DS records for a child zone based on CDS/CDNSKEY",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
("dnssec-dsfromkey", "dnssec-dsfromkey", "DNSSEC DS RR generation tool", author, 1),
|
||||
(
|
||||
"dnssec-importkey",
|
||||
"dnssec-importkey",
|
||||
"import DNSKEY records from external systems so they can be managed",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"dnssec-keyfromlabel",
|
||||
"dnssec-keyfromlabel",
|
||||
"DNSSEC key generation tool",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
("dnssec-keygen", "dnssec-keygen", "DNSSEC key generation tool", author, 1),
|
||||
(
|
||||
"dnssec-revoke",
|
||||
"dnssec-revoke",
|
||||
"set the REVOKED bit on a DNSSEC key",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"dnssec-settime",
|
||||
"dnssec-settime",
|
||||
"set the key timing metadata for a DNSSEC key",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
("dnssec-signzone", "dnssec-signzone", "DNSSEC zone signing tool", author, 1),
|
||||
("dnssec-verify", "dnssec-verify", "DNSSEC zone verification tool", author, 1),
|
||||
(
|
||||
"dnstap-read",
|
||||
"dnstap-read",
|
||||
"print dnstap data in human-readable form",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"filter-aaaa",
|
||||
"filter-aaaa",
|
||||
"filter AAAA in DNS responses when A is present",
|
||||
author,
|
||||
8,
|
||||
),
|
||||
(
|
||||
"filter-a",
|
||||
"filter-a",
|
||||
"filter A in DNS responses when AAAA is present",
|
||||
author,
|
||||
8,
|
||||
),
|
||||
("host", "host", "DNS lookup utility", author, 1),
|
||||
("mdig", "mdig", "DNS pipelined lookup utility", author, 1),
|
||||
(
|
||||
"named-checkconf",
|
||||
"named-checkconf",
|
||||
"named configuration file syntax checking tool",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"named-checkzone",
|
||||
"named-checkzone",
|
||||
"zone file validity checking or converting tool",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"named-compilezone",
|
||||
"named-compilezone",
|
||||
"zone file validity checking or converting tool",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"named-journalprint",
|
||||
"named-journalprint",
|
||||
"print zone journal in human-readable form",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"named-nzd2nzf",
|
||||
"named-nzd2nzf",
|
||||
"convert an NZD database to NZF text format",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"named-rrchecker",
|
||||
"named-rrchecker",
|
||||
"syntax checker for individual DNS resource records",
|
||||
author,
|
||||
1,
|
||||
),
|
||||
("named.conf", "named.conf", "configuration file for **named**", author, 5),
|
||||
("named", "named", "Internet domain name server", author, 8),
|
||||
("nsec3hash", "nsec3hash", "generate NSEC3 hash", author, 1),
|
||||
("nslookup", "nslookup", "query Internet name servers interactively", author, 1),
|
||||
("nsupdate", "nsupdate", "dynamic DNS update utility", author, 1),
|
||||
("rndc-confgen", "rndc-confgen", "rndc key generation tool", author, 8),
|
||||
("rndc.conf", "rndc.conf", "rndc configuration file", author, 5),
|
||||
("rndc", "rndc", "name server control utility", author, 8),
|
||||
("tsig-keygen", "tsig-keygen", "TSIG key generation tool", author, 8),
|
||||
]
|
||||
|
||||
#
|
||||
# The rst_epilog will be completely overwritten from the Makefile,
|
||||
@@ -117,5 +208,6 @@ rst_epilog = """
|
||||
.. |session_key| replace:: ``@runstatedir@/session.key``
|
||||
"""
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_crossref_type('iscman', 'iscman', 'pair: %s; manual page')
|
||||
app.add_crossref_type("iscman", "iscman", "pair: %s; manual page")
|
||||
|
@@ -76,20 +76,20 @@ PATH = re.compile(TOP + "/")
|
||||
|
||||
S = State()
|
||||
|
||||
with open(sys.argv[1], "r", encoding='utf-8') as f:
|
||||
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
||||
for line in f.readlines():
|
||||
if line == "==================\n":
|
||||
if not S.inside:
|
||||
S.inside = True
|
||||
else:
|
||||
DNAME = sha256(S.last_line.encode('utf-8')).hexdigest()
|
||||
DNAME = sha256(S.last_line.encode("utf-8")).hexdigest()
|
||||
DNAME = os.path.join(OUT, DNAME)
|
||||
if not os.path.isdir(DNAME):
|
||||
os.mkdir(DNAME)
|
||||
FNAME = sha256(S.block.encode('utf-8')).hexdigest() + ".txt"
|
||||
FNAME = sha256(S.block.encode("utf-8")).hexdigest() + ".txt"
|
||||
FNAME = os.path.join(DNAME, FNAME)
|
||||
if not os.path.isfile(FNAME):
|
||||
with open(FNAME, "w", encoding='utf-8') as w:
|
||||
with open(FNAME, "w", encoding="utf-8") as w:
|
||||
w.write(S.block)
|
||||
S.reset()
|
||||
else:
|
||||
|
Reference in New Issue
Block a user