Home | History | Annotate | Line # | Download | only in digdelv
      1 # Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      2 #
      3 # SPDX-License-Identifier: MPL-2.0
      4 #
      5 # This Source Code Form is subject to the terms of the Mozilla Public
      6 # License, v. 2.0.  If a copy of the MPL was not distributed with this
      7 # file, you can obtain one at https://mozilla.org/MPL/2.0/.
      8 #
      9 # See the COPYRIGHT file distributed with this work for additional
     10 # information regarding copyright ownership.
     11 
     12 """
     13 Tests for the delv tool.
     14 """
     15 
     16 from re import compile as Re
     17 
     18 import re
     19 
     20 import pytest
     21 
     22 from digdelv.common import ARTIFACTS, check_ttl_range, parse_yaml
     23 from isctest.util import param
     24 
     25 import isctest
     26 import isctest.mark
     27 
     28 pytestmark = [
     29     pytest.mark.extra_artifacts(ARTIFACTS),
     30 ]
     31 
     32 
     33 @pytest.fixture(name="delv")
     34 def delv_fixture(named_port):
     35     # use delv insecure mode by default, as we're mostly not testing dnssec
     36     return isctest.run.EnvCmd("DELV", f"+noroot -p {named_port}")
     37 
     38 
     39 @pytest.mark.parametrize("option", ["+short", "+sh"])
     40 def test_short(delv, ns3, option):
     41     """Check that delv +short (and its +sh abbreviation) returns a
     42     single-line answer."""
     43     result = delv(f"@{ns3.ip} {option} a a.example")
     44     assert len(result.out.splitlines()) == 1
     45 
     46 
     47 @pytest.mark.parametrize("option", ["+split=4", "+sp=4"])
     48 def test_split_width(delv, ns3, option):
     49     """Check that delv +split (and its +sp abbreviation) splits hex data
     50     into fields of the requested width."""
     51     result = delv(f"@{ns3.ip} {option} -t sshfp foo.example")
     52     assert " 9ABC DEF6 7890 " in result.out
     53     assert check_ttl_range(result.out, "SSHFP", 300)
     54 
     55 
     56 def test_unknownformat(delv, ns3):
     57     """Check that delv +unknownformat prints RFC 3597 format."""
     58     result = delv(f"@{ns3.ip} +unknownformat a a.example")
     59     assert Re(r"CLASS1\s+TYPE1\s+\\# 4 0A000001") in result.out
     60     assert check_ttl_range(result.out, "TYPE1", 300)
     61 
     62 
     63 def test_4_and_6_mutually_exclusive(delv, ns3):
     64     """Check that delv rejects -4 combined with -6."""
     65     result = delv(f"@{ns3.ip} -4 -6 A a.example", raise_on_exception=False)
     66     assert result.rc != 0
     67     assert "only one of -4 and -6 allowed" in result.err
     68 
     69 
     70 def test_malformed_query_name(delv, ns3):
     71     """Check that delv exits cleanly on a malformed query name instead of
     72     aborting in the dns_client_detach(NULL) cleanup path."""
     73     longlabel = "a" * 64
     74     result = delv(f"@{ns3.ip} -t a {longlabel}.example.com", raise_on_exception=False)
     75     assert result.rc >= 0
     76     assert "label too long" in result.err
     77 
     78 
     79 @isctest.mark.with_ipv6
     80 @pytest.mark.parametrize(
     81     "server_args,message",
     82     [
     83         param(
     84             "@fd92:7065:b8e:ffff::3 @{ns3} -6",
     85             "Use of IPv4 disabled by -6",
     86             id="ipv4-server-with-6",
     87         ),
     88         param(
     89             "@{ns3} @fd92:7065:b8e:ffff::3 -4",
     90             "Use of IPv6 disabled by -4",
     91             id="ipv6-server-with-4",
     92         ),
     93     ],
     94 )
     95 def test_address_family_mismatch(delv, ns3, server_args, message):
     96     """Check that the last @server option overrides earlier ones and that
     97     the forced address family makes such a lookup fail."""
     98     result = delv(
     99         server_args.format(ns3=ns3.ip) + " -t txt foo.example",
    100         raise_on_exception=False,
    101     )
    102     assert result.rc != 0
    103     # it should have no results but error output
    104     assert "testing" not in result.out
    105     assert message in result.err
    106 
    107 
    108 def test_reverse_lookup(delv, ns3):
    109     """Check that delv -x works."""
    110     result = delv(f"@{ns3.ip} -x 127.0.0.1")
    111     # doesn't matter if has answer
    112     assert Re(r"127\.in-addr\.arpa\.", re.IGNORECASE) in result.out
    113     assert check_ttl_range(result.out, r"\-ANY", 10800)
    114 
    115 
    116 def test_tcp(delv, ns3):
    117     """Check that delv over TCP works."""
    118     result = delv(f"+tcp @{ns3.ip} a a.example")
    119     assert Re(r"10\.0\.0\.1$") in result.out
    120     assert check_ttl_range(result.out, "A", 300)
    121 
    122 
    123 @pytest.mark.parametrize(
    124     "args,expect_rrcomment,ttl_rrtype",
    125     [
    126         param(
    127             "+multi +norrcomments DNSKEY example",
    128             False,
    129             "DNSKEY",
    130             id="multi-norrcomments-dnskey",
    131         ),
    132         param(
    133             "+multi +norrcomments SOA example",
    134             False,
    135             "SOA",
    136             id="multi-norrcomments-soa",
    137         ),
    138         param("+rrcomments DNSKEY example", True, "DNSKEY", id="rrcomments"),
    139         param("+short +rrcomments DNSKEY example", True, None, id="short-rrcomments"),
    140     ],
    141 )
    142 def test_rrcomments(delv, ns3, zsk, args, expect_rrcomment, ttl_rrtype):
    143     """Check that +[no]rrcomments controls the DNSKEY comment
    144     (the default is rrcomments, even with +multi)."""
    145     result = delv(f"+tcp @{ns3.ip} {args}")
    146     assert (zsk.rrcomment in result.out) == expect_rrcomment
    147     if ttl_rrtype:
    148         assert check_ttl_range(result.out, ttl_rrtype, 300)
    149 
    150 
    151 def test_short_rrcomments_line(delv, ns3, zsk):
    152     """Check the exact delv +short +rrcomments output line."""
    153     result = delv(f"+tcp @{ns3.ip} +short +rrcomments DNSKEY example")
    154     assert f"{zsk.keydata}  {zsk.rrcomment}" in result.out
    155 
    156 
    157 def test_short_nosplit(delv, ns3, zsk):
    158     """Check that delv +short +nosplit does not split the key data."""
    159     result = delv(f"+tcp @{ns3.ip} +short +nosplit DNSKEY example")
    160     assert zsk.keydata.replace(" ", "") in result.out
    161     assert len(result.out.splitlines()) == 1
    162     assert len(result.out.split()) == 14
    163 
    164 
    165 def test_short_nosplit_norrcomments(delv, ns3, zsk):
    166     """Check that delv +short +nosplit +norrcomments prints the bare
    167     unsplit rdata."""
    168     result = delv(f"+tcp @{ns3.ip} +short +nosplit +norrcomments DNSKEY example")
    169     nosplit = zsk.keydata.replace(" ", "")
    170     assert Re(re.escape(nosplit) + "$") in result.out
    171     assert len(result.out.splitlines()) == 1
    172     assert len(result.out.split()) == 4
    173 
    174 
    175 @pytest.mark.parametrize(
    176     "qclass",
    177     [
    178         param("IN", id="in"),
    179         param("CH", id="ch-ignored"),
    180     ],
    181 )
    182 def test_class_option(delv, ns3, qclass):
    183     """Check that delv -c IN works and that -c CH is ignored and treated
    184     like IN."""
    185     result = delv(f"@{ns3.ip} -c {qclass} -t a a.example")
    186     assert "a.example." in result.out
    187     assert check_ttl_range(result.out, "A", 300)
    188 
    189 
    190 def test_q_m(delv, ns3):
    191     """Check that -q -m treats -m as a query name, not as the memory
    192     debugging flag."""
    193     result = delv(f"@{ns3.ip} -q -m")
    194     assert Re(r"^; -m\..*\d*.*IN.*ANY.*;") in result.out
    195     for stream in (result.out, result.err):
    196         assert Re(r"^add ") not in stream
    197         assert Re(r"^del ") not in stream
    198     assert check_ttl_range(result.out, r"\-ANY", 300)
    199 
    200 
    201 def test_any_query(delv, ns3):
    202     """Check that delv -t ANY works."""
    203     result = delv(f"@{ns3.ip} -t ANY example")
    204     assert Re(r"^example\.") in result.out
    205     assert check_ttl_range(result.out, "NS", 300)
    206     assert check_ttl_range(result.out, "SOA", 300)
    207 
    208 
    209 @pytest.mark.parametrize(
    210     "anchor",
    211     [
    212         param("anchor.dnskey", id="key-style"),
    213         param("anchor.ds", id="ds-style"),
    214     ],
    215 )
    216 def test_trust_anchors(delv, ns3, anchor):
    217     """Check that delv loads key-style and DS-style trust anchors and
    218     validates with them."""
    219     result = delv(f"-a ns3/{anchor} +root=example @{ns3.ip} -t DNSKEY example")
    220     assert "fully validated" in result.out
    221 
    222 
    223 def test_refused_chasing_ds(delv, ns2):
    224     """Check that delv handles REFUSED when chasing DS records."""
    225     result = delv(f"@{ns2.ip} +root xxx.example.tld A")
    226     assert ";; resolution failed: broken trust chain" in result.err
    227 
    228 
    229 def test_yaml_any(delv, ns3):
    230     """Check the structure of delv +yaml output."""
    231     result = delv(f"+yaml @{ns3.ip} any ns2.example")
    232     data = parse_yaml(result.out)
    233     assert data["status"] == "success"
    234     assert data["query_name"] == "ns2.example"
    235     answer = data["records"][0]["answer_not_validated"][0]
    236     assert len(str(answer).split()) == 5
    237 
    238 
    239 @pytest.mark.parametrize(
    240     "qtype,qname,status",
    241     [
    242         param("type500", "ns2.example", "ncache nxrrset", id="nodata"),
    243         param("a", "this-does-not-exist.ns2.example", "ncache nxdomain", id="nxdomain"),
    244     ],
    245 )
    246 def test_yaml_negative(delv, ns3, qtype, qname, status):
    247     """Check the structure of delv +yaml output for negative responses."""
    248     result = delv(f"+yaml @{ns3.ip} {qtype} {qname}")
    249     data = parse_yaml(result.out)
    250     assert data["status"] == status
    251     assert data["query_name"] == qname
    252     answer = data["records"][0]["negative_response_answer_not_validated"][0]
    253     assert len(str(answer).split()) == 5
    254 
    255 
    256 @pytest.mark.usefixtures("ns1")
    257 def test_ns_output(delv):
    258     """Check the NS records in delv +ns output."""
    259     result = delv(
    260         "-i +ns +nortrace +nostrace +nomtrace +novtrace +hint=root.hint ns example"
    261     )
    262     ns_lines = [
    263         fields
    264         for fields in (line.split() for line in result.out.splitlines())
    265         if len(fields) >= 4 and fields[0] == "example." and fields[3] == "NS"
    266     ]
    267     assert len(ns_lines) == 2
    268 
    269 
    270 @pytest.mark.parametrize(
    271     "args,marker,expect_no_qmin_labels",
    272     [
    273         param(
    274             "-i +ns +hint=root.hint",
    275             "; authoritative",
    276             True,
    277             id="no-validation",
    278         ),
    279         param(
    280             "-i +ns +qmin +hint=root.hint",
    281             "; authoritative",
    282             False,
    283             id="no-validation-qmin",
    284         ),
    285         param(
    286             "-a ns1/anchor.dnskey +root +ns +hint=root.hint",
    287             "; fully validated",
    288             True,
    289             id="validation",
    290         ),
    291         param(
    292             "-a ns1/anchor.dnskey +root +ns +qmin +hint=root.hint",
    293             "; fully validated",
    294             False,
    295             id="validation-qmin",
    296         ),
    297     ],
    298 )
    299 @pytest.mark.usefixtures("ns1")
    300 def test_ns_lookup(delv, args, marker, expect_no_qmin_labels):
    301     """Check delv +ns lookups with and without validation and query name
    302     minimization."""
    303     result = delv(f"{args} a a.example")
    304     assert marker in result.out
    305     if expect_no_qmin_labels:
    306         assert "_.example" not in result.out
    307 
    308 
    309 @isctest.mark.with_ipv6
    310 @pytest.mark.parametrize("family", ["-4", "-6"])
    311 @pytest.mark.usefixtures("ns1")
    312 def test_ns_address_family(delv, family):
    313     """Check that delv +ns with -4/-6 uses only the selected address
    314     family."""
    315     ipv4_packet = "sending packet to 10.53"
    316     ipv6_packet = "sending packet to fd92:7065"
    317     result = delv(
    318         f"-a ns1/anchor.dnskey +root {family} +ns +hint=root.hint a a.example"
    319     )
    320     assert (ipv4_packet in result.out) == (family == "-4")
    321     assert (ipv6_packet in result.out) == (family == "-6")
    322