2
0
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:
Tom Krizek
2022-06-08 10:48:09 +00:00
38 changed files with 1582 additions and 975 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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])

View File

@@ -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.

View File

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

View File

@@ -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:

View File

@@ -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:

View File

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

View File

@@ -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

View File

@@ -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])

View File

@@ -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):

View File

@@ -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

View File

@@ -12,5 +12,6 @@
# information regarding copyright ownership.
import ssl
version = ssl.OPENSSL_VERSION_INFO
print(version[0], version[1], version[2])

View File

@@ -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():

View File

@@ -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,
)

View File

@@ -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:

View File

@@ -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

View File

@@ -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()

View File

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

View File

@@ -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])

View File

@@ -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])

View File

@@ -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])

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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:

View File

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

View File

@@ -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: