xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/wildcard/tests_wildcard.py (revision 4b004442778f1201b2161e87fd65ba87aae6601a)
1#!/usr/bin/python3
2
3# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
4#
5# SPDX-License-Identifier: MPL-2.0
6#
7# This Source Code Form is subject to the terms of the Mozilla Public
8# License, v. 2.0.  If a copy of the MPL was not distributed with this
9# file, you can obtain one at https://mozilla.org/MPL/2.0/.
10#
11# See the COPYRIGHT file distributed with this work for additional
12# information regarding copyright ownership.
13
14"""
15Example property-based test for wildcard synthesis.
16Verifies that otherwise-empty zone with single wildcard record * A 192.0.2.1
17produces synthesized answers for <random_label>.test. A, and returns NODATA for
18<random_label>.test. when rdtype is not A.
19
20Limitations - untested properties:
21    - expansion works with multiple labels
22    - asterisk in qname does not cause expansion
23    - empty non-terminals prevent expansion
24    - or more generally any existing node prevents expansion
25    - DNSSEC record inclusion
26    - possibly others, see RFC 4592 and company
27    - content of authority & additional sections
28    - flags beyond RCODE
29    - special behavior of rdtypes like CNAME
30"""
31import pytest
32
33pytest.importorskip("dns")
34import dns.message
35import dns.name
36import dns.query
37import dns.rcode
38import dns.rdataclass
39import dns.rdatatype
40import dns.rrset
41
42pytest.importorskip("hypothesis")
43from hypothesis import given
44from hypothesis.strategies import binary, integers
45
46
47# labels of a zone with * A 192.0.2.1 wildcard
48WILDCARD_ZONE = ("allwild", "test", "")
49WILDCARD_RDTYPE = dns.rdatatype.A
50WILDCARD_RDATA = "192.0.2.1"
51IPADDR = "10.53.0.1"
52TIMEOUT = 5  # seconds, just a sanity check
53
54
55# Helpers
56def is_nonexpanding_rdtype(rdtype):
57    """skip meta types to avoid weird rcodes caused by AXFR etc.; RFC 6895"""
58    return not (
59        rdtype == WILDCARD_RDTYPE
60        or dns.rdatatype.is_metatype(rdtype)  # known metatypes: OPT ...
61        or 128 <= rdtype <= 255
62    )  # unknown meta types
63
64
65def tcp_query(where, port, qname, qtype):
66    querymsg = dns.message.make_query(qname, qtype)
67    assert len(querymsg.question) == 1
68    return querymsg, dns.query.tcp(querymsg, where, port=port, timeout=TIMEOUT)
69
70
71def query(where, port, label, rdtype):
72    labels = (label,) + WILDCARD_ZONE
73    qname = dns.name.Name(labels)
74    return tcp_query(where, port, qname, rdtype)
75
76
77# Tests
78@given(
79    label=binary(min_size=1, max_size=63),
80    rdtype=integers(min_value=0, max_value=65535).filter(is_nonexpanding_rdtype),
81)
82def test_wildcard_rdtype_mismatch(label, rdtype, named_port):
83    """any label non-matching rdtype must result in to NODATA"""
84    check_answer_nodata(*query(IPADDR, named_port, label, rdtype))
85
86
87def check_answer_nodata(querymsg, answer):
88    assert querymsg.is_response(answer), str(answer)
89    assert answer.rcode() == dns.rcode.NOERROR, str(answer)
90    assert answer.answer == [], str(answer)
91
92
93@given(label=binary(min_size=1, max_size=63))
94def test_wildcard_match(label, named_port):
95    """any label with maching rdtype must result in wildcard data in answer"""
96    check_answer_noerror(*query(IPADDR, named_port, label, WILDCARD_RDTYPE))
97
98
99def check_answer_noerror(querymsg, answer):
100    assert querymsg.is_response(answer), str(answer)
101    assert answer.rcode() == dns.rcode.NOERROR, str(answer)
102    assert len(querymsg.question) == 1, str(answer)
103    expected_answer = [
104        dns.rrset.from_text(
105            querymsg.question[0].name,
106            300,  # TTL, ignored by dnspython comparison
107            dns.rdataclass.IN,
108            WILDCARD_RDTYPE,
109            WILDCARD_RDATA,
110        )
111    ]
112    assert answer.answer == expected_answer, str(answer)
113