1#!/usr/bin/perl 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# 15# Dynamic update test suite. 16# 17# Usage: 18# 19# perl update_test.pl [-s server] [-p port] zone 20# 21# The server defaults to 127.0.0.1. 22# The port defaults to 53. 23# 24# The "Special NS rules" tests will only work correctly if the 25# zone has no NS records to begin with, or alternatively has a 26# single NS record pointing at the name "ns1" (relative to 27# the zone name). 28# 29# Installation notes: 30# 31# This program uses the Net::DNS::Resolver module. 32# You can install it by saying 33# 34# perl -MCPAN -e "install Net::DNS" 35# 36 37use Getopt::Std; 38use Net::DNS; 39use Net::DNS::Update; 40use Net::DNS::Resolver; 41 42$opt_s = "127.0.0.1"; 43$opt_p = 53; 44 45getopt('s:p:'); 46 47$res = new Net::DNS::Resolver; 48$res->nameservers($opt_s); 49$res->port($opt_p); 50$res->defnames(0); # Do not append default domain. 51 52@ARGV == 1 or die 53 "usage: perl update_test.pl [-s server] [-p port] zone\n"; 54 55$zone = shift @ARGV; 56 57my $failures = 0; 58 59sub assert { 60 my ($cond, $explanation) = @_; 61 if (!$cond) { 62 print "Test Failed: $explanation ***\n"; 63 $failures++; 64 } 65} 66 67sub test { 68 my ($expected, @records) = @_; 69 70 my $update = new Net::DNS::Update("$zone"); 71 72 foreach $rec (@records) { 73 $update->push(@$rec); 74 } 75 76 $reply = $res->send($update); 77 78 # Did it work? 79 if (defined $reply) { 80 my $rcode = $reply->header->rcode; 81 assert($rcode eq $expected, "expected $expected, got $rcode"); 82 } else { 83 print "Update failed: ", $res->errorstring, "\n"; 84 $failures++; 85 } 86} 87 88sub section { 89 my ($msg) = @_; 90 print "$msg\n"; 91} 92 93section("Delete any leftovers from previous tests"); 94test("NOERROR", ["update", rr_del("a.$zone")]); 95test("NOERROR", ["update", rr_del("b.$zone")]); 96test("NOERROR", ["update", rr_del("c.$zone")]); 97test("NOERROR", ["update", rr_del("d.$zone")]); 98test("NOERROR", ["update", rr_del("e.$zone")]); 99test("NOERROR", ["update", rr_del("f.$zone")]); 100test("NOERROR", ["update", rr_del("ns.s.$zone")]); 101test("NOERROR", ["update", rr_del("s.$zone")]); 102test("NOERROR", ["update", rr_del("t.$zone")]); 103test("NOERROR", ["update", rr_del("*.$zone")]); 104test("NOERROR", ["update", rr_del("u.$zone")]); 105test("NOERROR", ["update", rr_del("a.u.$zone")]); 106test("NOERROR", ["update", rr_del("b.u.$zone")]); 107 108section("Simple prerequisites in the absence of data"); 109# Name is in Use 110test("NXDOMAIN", ["pre", yxdomain("a.$zone")]); 111# RRset exists (value independent) 112test("NXRRSET", ["pre", yxrrset("a.$zone A")]); 113# Name is not in use 114test("NOERROR", ["pre", nxdomain("a.$zone")]); 115# RRset does not exist 116test("NOERROR", ["pre", nxrrset("a.$zone A")]); 117# RRset exists (value dependent) 118test("NXRRSET", ["pre", yxrrset("a.$zone A 73.80.65.49")]); 119 120 121section ("Simple creation of data"); 122test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.49")]); 123 124section ("Simple prerequisites in the presence of data"); 125# Name is in use 126test("NOERROR", ["pre", yxdomain("a.$zone")]); 127# RRset exists (value independent) 128test("NOERROR", ["pre", yxrrset("a.$zone A")]); 129# Name is not in use 130test("YXDOMAIN", ["pre", nxdomain("a.$zone")]); 131# RRset does not exist 132test("YXRRSET", ["pre", nxrrset("a.$zone A")]); 133# RRset exists (value dependent) 134test("NOERROR", ["pre", yxrrset("a.$zone A 73.80.65.49")]); 135 136# 137# Merging of RRsets 138# 139test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.50")]); 140 141section("Detailed tests of \"RRset exists (value dependent)\" prerequisites"); 142test("NOERROR", ["pre", 143 yxrrset("a.$zone A 73.80.65.49"), 144 yxrrset("a.$zone A 73.80.65.50")]); 145test("NOERROR", ["pre", 146 yxrrset("a.$zone A 73.80.65.50"), 147 yxrrset("a.$zone A 73.80.65.49")]); 148test("NXRRSET", ["pre", yxrrset("a.$zone A 73.80.65.49")]); 149test("NXRRSET", ["pre", yxrrset("a.$zone A 73.80.65.50")]); 150test("NXRRSET", ["pre", 151 yxrrset("a.$zone A 73.80.65.49"), 152 yxrrset("a.$zone A 73.80.65.50"), 153 yxrrset("a.$zone A 73.80.65.51")]); 154 155 156section("Torture test of \"RRset exists (value dependent)\" prerequisites."); 157 158test("NOERROR", ["update", 159 rr_add("e.$zone 300 A 73.80.65.49"), 160 rr_add("e.$zone 300 TXT 'one'"), 161 rr_add("e.$zone 300 A 73.80.65.50")]); 162test("NOERROR", ["update", 163 rr_add("e.$zone 300 A 73.80.65.52"), 164 rr_add("f.$zone 300 A 73.80.65.52"), 165 rr_add("e.$zone 300 A 73.80.65.51")]); 166test("NOERROR", ["update", 167 rr_add("e.$zone 300 TXT 'three'"), 168 rr_add("e.$zone 300 TXT 'two'")]); 169test("NOERROR", ["update", 170 rr_add("e.$zone 300 MX 10 mail.$zone")]); 171 172test("NOERROR", ["pre", 173 yxrrset("e.$zone A 73.80.65.52"), 174 yxrrset("e.$zone TXT 'two'"), 175 yxrrset("e.$zone A 73.80.65.51"), 176 yxrrset("e.$zone TXT 'three'"), 177 yxrrset("e.$zone A 73.80.65.50"), 178 yxrrset("f.$zone A 73.80.65.52"), 179 yxrrset("e.$zone A 73.80.65.49"), 180 yxrrset("e.$zone TXT 'one'")]); 181 182 183section("Subtraction of RRsets"); 184test("NOERROR", ["update", rr_del("a.$zone A 73.80.65.49")]); 185test("NOERROR", ["pre", 186 yxrrset("a.$zone A 73.80.65.50")]); 187 188test("NOERROR", ["update", rr_del("a.$zone A 73.80.65.50")]); 189test("NOERROR", ["pre", nxrrset("a.$zone A")]); 190test("NOERROR", ["pre", nxdomain("a.$zone")]); 191 192section("Other forms of deletion"); 193test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.49")]); 194test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.50")]); 195test("NOERROR", ["update", rr_add("a.$zone 300 MX 10 mail.$zone")]); 196test("NOERROR", ["update", rr_del("a.$zone A")]); 197test("NOERROR", ["pre", nxrrset("a.$zone A")]); 198test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.49")]); 199test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.50")]); 200test("NOERROR", ["update", rr_del("a.$zone")]); 201test("NOERROR", ["pre", nxdomain("a.$zone")]); 202 203section("Case insensitivity"); 204test("NOERROR", ["update", rr_add("a.$zone 300 PTR foo.net.")]); 205test("NOERROR", ["pre", yxrrset("A.$zone PTR fOo.NeT.")]); 206 207section("Special CNAME rules"); 208test("NOERROR", ["update", rr_add("b.$zone 300 CNAME foo.net.")]); 209test("NOERROR", ["update", rr_add("b.$zone 300 A 73.80.65.49")]); 210test("NOERROR", ["pre", yxrrset("b.$zone CNAME foo.net.")]); 211test("NOERROR", ["pre", nxrrset("b.$zone A")]); 212 213test("NOERROR", ["update", rr_add("c.$zone 300 A 73.80.65.49")]); 214test("NOERROR", ["update", rr_add("c.$zone 300 CNAME foo.net.")]); 215test("NOERROR", ["pre", yxrrset("c.$zone A")]); 216test("NOERROR", ["pre", nxrrset("c.$zone CNAME")]); 217 218# XXX should test with SIG, KEY, NXT, too. 219 220# 221# Currently commented out because Net::DNS does not properly 222# support WKS records. 223# 224#section("Special WKS rules"); 225#test("NOERROR", ["update", rr_add("c.$zone 300 WKS 73.80.65.49 TCP telnet ftp")]); 226#test("NOERROR", ["update", rr_add("c.$zone 300 WKS 73.80.65.49 UDP telnet ftp")]); 227#test("NOERROR", ["update", rr_add("c.$zone 300 WKS 73.80.65.50 TCP telnet ftp")]); 228#test("NOERROR", ["update", rr_add("c.$zone 300 WKS 73.80.65.49 TCP smtp")]); 229#test("NOERROR", ["pre", 230# yxrrset("c.$zone WKS 73.80.65.49 TCP smtp"), 231# yxrrset("c.$zone WKS 73.80.65.49 UDP telnet ftp"), 232# yxrrset("c.$zone WKS 73.80.65.50 TCP telnet ftp")]); 233 234 235section("Special NS rules"); 236 237# Deleting the last NS record using "Delete an RR from an RRset" 238# should fail at the zone apex and work elsewhere. The pseudocode 239# in RFC2136 says it should fail everywhere, but this is in conflict 240# with the actual text. 241 242# Apex 243test("NOERROR", ["update", 244 rr_add("$zone 300 NS ns1.$zone"), 245 rr_add("$zone 300 NS ns2.$zone")]); 246test("NOERROR", ["update", rr_del("$zone NS ns1.$zone")]); 247test("NOERROR", ["update", rr_del("$zone NS ns2.$zone")]); 248test("NOERROR", ["pre", 249 yxrrset("$zone NS ns2.$zone")]); 250 251# Non-apex 252test("NOERROR", ["update", rr_add("n.$zone 300 NS ns1.$zone")]); 253test("NOERROR", ["update", rr_del("n.$zone NS ns1.$zone")]); 254test("NOERROR", ["pre", nxrrset("n.$zone NS")]); 255 256# Other ways of deleting NS records should also fail at the apex 257# and work elsewhere. 258 259# Non-apex 260test("NOERROR", ["update", rr_add("n.$zone 300 NS ns1.$zone")]); 261test("NOERROR", ["update", rr_del("n.$zone NS")]); 262test("NOERROR", ["pre", nxrrset("n.$zone NS")]); 263 264test("NOERROR", ["update", rr_add("n.$zone 300 NS ns1.$zone")]); 265test("NOERROR", ["pre", yxrrset("n.$zone NS")]); 266test("NOERROR", ["update", rr_del("n.$zone")]); 267test("NOERROR", ["pre", nxrrset("n.$zone NS")]); 268 269# Apex 270test("NOERROR", ["update", rr_del("$zone NS")]); 271test("NOERROR", ["pre", 272 yxrrset("$zone NS ns2.$zone")]); 273 274test("NOERROR", ["update", rr_del("$zone")]); 275test("NOERROR", ["pre", 276 yxrrset("$zone NS ns2.$zone")]); 277 278# They should not touch the SOA, either. 279 280test("NOERROR", ["update", rr_del("$zone SOA")]); 281test("NOERROR", ["pre", yxrrset("$zone SOA")]); 282 283 284section("Idempotency"); 285 286test("NOERROR", ["update", rr_add("d.$zone 300 A 73.80.65.49")]); 287test("NOERROR", ["pre", yxrrset("d.$zone A 73.80.65.49")]); 288test("NOERROR", ["update", 289 rr_add("d.$zone 300 A 73.80.65.49"), 290 rr_del("d.$zone A")]); 291test("NOERROR", ["pre", nxrrset("d.$zone A")]); 292 293test("NOERROR", ["update", rr_del("d.$zone A 73.80.65.49")]); 294test("NOERROR", ["pre", nxrrset("d.$zone A")]); 295test("NOERROR", ["update", 296 rr_del("d.$zone A"), 297 rr_add("d.$zone 300 A 73.80.65.49")]); 298 299test("NOERROR", ["pre", yxrrset("d.$zone A")]); 300 301section("Out-of-zone prerequisites and updates"); 302test("NOTZONE", ["pre", yxrrset("a.somewhere.else. A 73.80.65.49")]); 303test("NOTZONE", ["update", rr_add("a.somewhere.else. 300 A 73.80.65.49")]); 304 305 306section("Glue"); 307test("NOERROR", ["update", rr_add("s.$zone 300 NS ns.s.$zone")]); 308test("NOERROR", ["update", rr_add("ns.s.$zone 300 A 73.80.65.49")]); 309test("NOERROR", ["pre", yxrrset("ns.s.$zone A 73.80.65.49")]); 310 311section("Wildcards"); 312test("NOERROR", ["update", rr_add("*.$zone 300 MX 10 mail.$zone")]); 313test("NOERROR", ["pre", yxrrset("*.$zone MX 10 mail.$zone")]); 314test("NXRRSET", ["pre", yxrrset("w.$zone MX 10 mail.$zone")]); 315test("NOERROR", ["pre", nxrrset("w.$zone MX")]); 316test("NOERROR", ["pre", nxdomain("w.$zone")]); 317 318 319section("SOA serial handling"); 320 321my $soatimers = "20 20 1814400 3600"; 322 323# Get the current SOA serial number. 324my $query = $res->query($zone, "SOA"); 325my ($old_soa) = $query->answer; 326 327my $old_serial = $old_soa->serial; 328 329# Increment it by 10. 330my $new_serial = $old_serial + 10; 331if ($new_serial > 0xFFFFFFFF) { 332 $new_serial -= 0x80000000; 333 $new_serial -= 0x80000000; 334} 335 336# Replace the SOA with a new one. 337test("NOERROR", ["update", rr_add("$zone 300 SOA mname1. . $new_serial $soatimers")]); 338 339# Check that the SOA really got replaced. 340($db_soa) = $res->query($zone, "SOA")->answer; 341assert($db_soa->mname eq "mname1"); 342 343# Check that attempts to decrement the serial number are ignored. 344$new_serial = $old_serial - 10; 345if ($new_serial < 0) { 346 $new_serial += 0x80000000; 347 $new_serial += 0x80000000; 348} 349test("NOERROR", ["update", rr_add("$zone 300 SOA mname2. . $new_serial $soatimers")]); 350assert($db_soa->mname eq "mname1"); 351 352# Check that attempts to leave the serial number unchanged are ignored. 353($old_soa) = $res->query($zone, "SOA")->answer; 354$old_serial = $old_soa->serial; 355test("NOERROR", ["update", rr_add("$zone 300 SOA mname3. . $old_serial " . 356 $soatimers)]); 357($db_soa) = $res->query($zone, "SOA")->answer; 358assert($db_soa->mname eq "mname1"); 359 360# 361# Currently commented out because Net::DNS does not properly 362# support multiple strings in TXT records. 363# 364#section("Big data"); 365#test("NOERROR", ["update", rr_add("a.$zone 300 TXT aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")]); 366#test("NOERROR", ["update", rr_del("a.$zone TXT aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")]); 367test("NOERROR", ["update", rr_add("a.$zone 300 TXT " . ("foo " x 3))]); 368 369section("Updating TTLs only"); 370 371test("NOERROR", ["update", rr_add("t.$zone 300 A 73.80.65.49")]); 372($a) = $res->query("t.$zone", "A")->answer; 373$ttl = $a->ttl; 374assert($ttl == 300, "incorrect TTL value $ttl != 300"); 375test("NOERROR", ["update", 376 rr_del("t.$zone A 73.80.65.49"), 377 rr_add("t.$zone 301 A 73.80.65.49")]); 378($a) = $res->query("t.$zone", "A")->answer; 379$ttl = $a->ttl; 380assert($ttl == 301, "incorrect TTL value $ttl != 301"); 381 382# Add an RR that is identical to an existing one except for the TTL. 383# RFC2136 is not clear about what this should do; it says "duplicate RRs 384# will be silently ignored" but is an RR differing only in TTL 385# to be considered a duplicate or not? The test assumes that it 386# should not be considered a duplicate. 387test("NOERROR", ["update", rr_add("t.$zone 302 A 73.80.65.50")]); 388($a) = $res->query("t.$zone", "A")->answer; 389$ttl = $a->ttl; 390assert($ttl == 302, "incorrect TTL value $ttl != 302"); 391 392section("TTL normalization"); 393 394# The desired behaviour is that the old RRs get their TTL 395# changed to match the new one. RFC2136 does not explicitly 396# specify this, but I think it makes more sense than the 397# alternatives. 398 399test("NOERROR", ["update", rr_add("t.$zone 303 A 73.80.65.51")]); 400(@answers) = $res->query("t.$zone", "A")->answer; 401$nanswers = scalar @answers; 402assert($nanswers == 3, "wrong number of answers $nanswers != 3"); 403foreach $a (@answers) { 404 $ttl = $a->ttl; 405 assert($ttl == 303, "incorrect TTL value $ttl != 303"); 406} 407 408section("Obscuring existing data by zone cut"); 409test("NOERROR", ["update", rr_add("a.u.$zone 300 A 73.80.65.49")]); 410test("NOERROR", ["update", rr_add("b.u.$zone 300 A 73.80.65.49")]); 411test("NOERROR", ["update", rr_add("u.$zone 300 TXT txt-not-in-nxt")]); 412test("NOERROR", ["update", rr_add("u.$zone 300 NS ns.u.$zone")]); 413 414test("NOERROR", ["update", rr_del("u.$zone NS ns.u.$zone")]); 415 416if ($Net::DNS::VERSION < 1.01) { 417 print "skipped Excessive NSEC3PARAM iterations; Net::DNS too old.\n"; 418} else { 419 section("Excessive NSEC3PARAM iterations"); 420 test("REFUSED", ["update", rr_add("$zone 300 NSEC3PARAM 1 0 151 -")]); 421 test("NOERROR", ["update", rr_add("$zone 300 NSEC3PARAM 1 0 150 -")]); 422} 423 424if ($failures) { 425 print "$failures tests failed.\n"; 426} else { 427 print "All tests successful.\n"; 428} 429exit $failures; 430