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.rdatatype 39 40pytest.importorskip("hypothesis") 41from hypothesis import given 42from hypothesis.strategies import binary, integers 43 44 45# labels of a zone with * A 192.0.2.1 wildcard 46WILDCARD_ZONE = ('allwild', 'test', '') 47WILDCARD_RDTYPE = dns.rdatatype.A 48WILDCARD_RDATA = '192.0.2.1' 49IPADDR = '10.53.0.1' 50TIMEOUT = 5 # seconds, just a sanity check 51 52 53# Helpers 54def is_nonexpanding_rdtype(rdtype): 55 """skip meta types to avoid weird rcodes caused by AXFR etc.; RFC 6895""" 56 return not(rdtype == WILDCARD_RDTYPE 57 or dns.rdatatype.is_metatype(rdtype) # known metatypes: OPT ... 58 or 128 <= rdtype <= 255) # unknown meta types 59 60 61def tcp_query(where, port, qname, qtype): 62 querymsg = dns.message.make_query(qname, qtype) 63 assert len(querymsg.question) == 1 64 return querymsg, dns.query.tcp(querymsg, where, port=port, timeout=TIMEOUT) 65 66 67def query(where, port, label, rdtype): 68 labels = (label, ) + WILDCARD_ZONE 69 qname = dns.name.Name(labels) 70 return tcp_query(where, port, qname, rdtype) 71 72 73# Tests 74@given(label=binary(min_size=1, max_size=63), 75 rdtype=integers(min_value=0, max_value=65535).filter( 76 is_nonexpanding_rdtype)) 77def test_wildcard_rdtype_mismatch(label, rdtype, named_port): 78 """any label non-matching rdtype must result in to NODATA""" 79 check_answer_nodata(*query(IPADDR, named_port, label, rdtype)) 80 81 82def check_answer_nodata(querymsg, answer): 83 assert querymsg.is_response(answer), str(answer) 84 assert answer.rcode() == dns.rcode.NOERROR, str(answer) 85 assert answer.answer == [], str(answer) 86 87 88@given(label=binary(min_size=1, max_size=63)) 89def test_wildcard_match(label, named_port): 90 """any label with maching rdtype must result in wildcard data in answer""" 91 check_answer_noerror(*query(IPADDR, named_port, label, WILDCARD_RDTYPE)) 92 93 94def check_answer_noerror(querymsg, answer): 95 assert querymsg.is_response(answer), str(answer) 96 assert answer.rcode() == dns.rcode.NOERROR, str(answer) 97 assert len(querymsg.question) == 1, str(answer) 98 expected_answer = [dns.rrset.from_text( 99 querymsg.question[0].name, 100 300, # TTL, ignored by dnspython comparison 101 dns.rdataclass.IN, 102 WILDCARD_RDTYPE, 103 WILDCARD_RDATA)] 104 assert answer.answer == expected_answer, str(answer) 105