2021-06-21 14:51:43 +02:00
|
|
|
#!/usr/bin/python3
|
2021-06-03 08:37:05 +02:00
|
|
|
|
2021-06-21 14:51:43 +02:00
|
|
|
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
|
|
#
|
2021-06-03 08:37:05 +02:00
|
|
|
# SPDX-License-Identifier: MPL-2.0
|
|
|
|
#
|
2021-06-21 14:51:43 +02:00
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
2021-06-03 08:37:05 +02:00
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
2021-06-21 14:51:43 +02:00
|
|
|
# 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
|
2022-03-14 08:59:32 +01:00
|
|
|
import dns.rdataclass
|
2021-06-21 14:51:43 +02:00
|
|
|
import dns.rdatatype
|
2022-03-14 08:59:32 +01:00
|
|
|
import dns.rrset
|
2021-06-21 14:51:43 +02:00
|
|
|
|
|
|
|
pytest.importorskip("hypothesis")
|
|
|
|
from hypothesis import given
|
|
|
|
from hypothesis.strategies import binary, integers
|
|
|
|
|
|
|
|
|
|
|
|
# labels of a zone with * A 192.0.2.1 wildcard
|
2022-06-07 16:27:23 +02:00
|
|
|
WILDCARD_ZONE = ("allwild", "test", "")
|
2021-06-21 14:51:43 +02:00
|
|
|
WILDCARD_RDTYPE = dns.rdatatype.A
|
2022-06-07 16:27:23 +02:00
|
|
|
WILDCARD_RDATA = "192.0.2.1"
|
|
|
|
IPADDR = "10.53.0.1"
|
2021-06-21 14:51:43 +02:00
|
|
|
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"""
|
2022-06-07 16:27:23 +02:00
|
|
|
return not (
|
|
|
|
rdtype == WILDCARD_RDTYPE
|
|
|
|
or dns.rdatatype.is_metatype(rdtype) # known metatypes: OPT ...
|
|
|
|
or 128 <= rdtype <= 255
|
|
|
|
) # unknown meta types
|
2021-06-21 14:51:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
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):
|
2022-06-07 16:27:23 +02:00
|
|
|
labels = (label,) + WILDCARD_ZONE
|
2021-06-21 14:51:43 +02:00
|
|
|
qname = dns.name.Name(labels)
|
|
|
|
return tcp_query(where, port, qname, rdtype)
|
|
|
|
|
|
|
|
|
|
|
|
# Tests
|
2022-06-07 16:27:23 +02:00
|
|
|
@given(
|
|
|
|
label=binary(min_size=1, max_size=63),
|
|
|
|
rdtype=integers(min_value=0, max_value=65535).filter(is_nonexpanding_rdtype),
|
|
|
|
)
|
2021-06-21 14:51:43 +02:00
|
|
|
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)
|
2022-06-07 16:27:23 +02:00
|
|
|
expected_answer = [
|
|
|
|
dns.rrset.from_text(
|
|
|
|
querymsg.question[0].name,
|
|
|
|
300, # TTL, ignored by dnspython comparison
|
|
|
|
dns.rdataclass.IN,
|
|
|
|
WILDCARD_RDTYPE,
|
|
|
|
WILDCARD_RDATA,
|
|
|
|
)
|
|
|
|
]
|
2021-06-21 14:51:43 +02:00
|
|
|
assert answer.answer == expected_answer, str(answer)
|