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