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