Add new system test for wildcard expansion

This is almost minimal prototype to show how to use python-hypothesis
library in a system test. It does not fully replace existing shell-based
system test for wildcards.
This commit is contained in:
Petr Špaček 2021-06-21 14:51:43 +02:00
parent 8ce4759c5c
commit 49da19c353
No known key found for this signature in database
GPG key ID: ABD587CDF06581AE
7 changed files with 130 additions and 0 deletions

View file

@ -1,4 +1,5 @@
.cache
.hypothesis
__pycache__
dig.out*
rndc.out*

View file

@ -0,0 +1,18 @@
############################################################################
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
############################################################################
import os
import pytest
@pytest.fixture(scope='module')
def named_port():
return int(os.environ.get("PORT", default=5300))

View file

@ -0,0 +1,4 @@
$ORIGIN allwild.test.
allwild.test. 3600 IN SOA . . 0 0 0 0 0
allwild.test. 3600 NS ns.example.test.
*.allwild.test. 3600 A 192.0.2.1

View file

@ -27,6 +27,7 @@ zone "." { type primary; file "root.db.signed"; };
/*
* RFC 4592 example zone.
*/
zone "allwild.test" { type primary; file "allwild.db"; };
zone "example" { type primary; file "example.db"; };
zone "nsec" { type primary; file "nsec.db.signed"; };
zone "private.nsec" { type primary; file "private.nsec.db.signed"; };

View file

@ -16,6 +16,7 @@ SYSTESTDIR=wildcard
dssets=
# RFC 4592 example zone.
cp allwild.db.in allwild.db
cp example.db.in example.db
zone=nsec

View file

@ -0,0 +1,103 @@
#!/usr/bin/python3
############################################################################
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
############################################################################
"""
Example property-based test for wildcard synthesis.
Verifies that otherwise-empty zone with single wildcard record * A 192.0.2.1
produces synthesized answers for <random_label>.test. A, and returns NODATA for
<random_label>.test. when rdtype is not A.
Limitations - untested properties:
- expansion works with multiple labels
- asterisk in qname does not cause expansion
- empty non-terminals prevent expansion
- or more generally any existing node prevents expansion
- DNSSEC record inclusion
- possibly others, see RFC 4592 and company
- content of authority & additional sections
- flags beyond RCODE
- special behavior of rdtypes like CNAME
"""
import pytest
pytest.importorskip("dns")
import dns.message
import dns.name
import dns.query
import dns.rcode
import dns.rdatatype
pytest.importorskip("hypothesis")
from hypothesis import given
from hypothesis.strategies import binary, integers
# labels of a zone with * A 192.0.2.1 wildcard
WILDCARD_ZONE = ('allwild', 'test', '')
WILDCARD_RDTYPE = dns.rdatatype.A
WILDCARD_RDATA = '192.0.2.1'
IPADDR = '10.53.0.1'
TIMEOUT = 5 # seconds, just a sanity check
# Helpers
def is_nonexpanding_rdtype(rdtype):
"""skip meta types to avoid weird rcodes caused by AXFR etc.; RFC 6895"""
return not(rdtype == WILDCARD_RDTYPE
or dns.rdatatype.is_metatype(rdtype) # known metatypes: OPT ...
or 128 <= rdtype <= 255) # unknown meta types
def tcp_query(where, port, qname, qtype):
querymsg = dns.message.make_query(qname, qtype)
assert len(querymsg.question) == 1
return querymsg, dns.query.tcp(querymsg, where, port=port, timeout=TIMEOUT)
def query(where, port, label, rdtype):
labels = (label, ) + WILDCARD_ZONE
qname = dns.name.Name(labels)
return tcp_query(where, port, qname, rdtype)
# Tests
@given(label=binary(min_size=1, max_size=63),
rdtype=integers(min_value=0, max_value=65535).filter(
is_nonexpanding_rdtype))
def test_wildcard_rdtype_mismatch(label, rdtype, named_port):
"""any label non-matching rdtype must result in to NODATA"""
check_answer_nodata(*query(IPADDR, named_port, label, rdtype))
def check_answer_nodata(querymsg, answer):
assert querymsg.is_response(answer), str(answer)
assert answer.rcode() == dns.rcode.NOERROR, str(answer)
assert answer.answer == [], str(answer)
@given(label=binary(min_size=1, max_size=63))
def test_wildcard_match(label, named_port):
"""any label with maching rdtype must result in wildcard data in answer"""
check_answer_noerror(*query(IPADDR, named_port, label, WILDCARD_RDTYPE))
def check_answer_noerror(querymsg, answer):
assert querymsg.is_response(answer), str(answer)
assert answer.rcode() == dns.rcode.NOERROR, str(answer)
assert len(querymsg.question) == 1, str(answer)
expected_answer = [dns.rrset.from_text(
querymsg.question[0].name,
300, # TTL, ignored by dnspython comparison
dns.rdataclass.IN,
WILDCARD_RDTYPE,
WILDCARD_RDATA)]
assert answer.answer == expected_answer, str(answer)

View file

@ -868,8 +868,10 @@
./bin/tests/system/views/setup.sh SH 2000,2001,2004,2007,2012,2014,2016,2017,2018,2019,2020,2021
./bin/tests/system/views/tests.sh SH 2000,2001,2004,2007,2012,2013,2014,2016,2018,2019,2020,2021
./bin/tests/system/wildcard/clean.sh SH 2012,2013,2014,2016,2018,2019,2020,2021
./bin/tests/system/wildcard/conftest.py PYTHON 2021
./bin/tests/system/wildcard/ns1/sign.sh SH 2012,2013,2014,2016,2018,2019,2020,2021
./bin/tests/system/wildcard/setup.sh SH 2012,2014,2016,2017,2018,2019,2020,2021
./bin/tests/system/wildcard/tests-wildcard.py PYTHON-BIN 2021
./bin/tests/system/wildcard/tests.sh SH 2012,2013,2016,2018,2019,2020,2021
./bin/tests/system/xfer/ans5/badkeydata X 2011,2018,2019,2020,2021
./bin/tests/system/xfer/ans5/badmessageid X 2020,2021