update_test.pl 15.1 KB
Newer Older
1 2
#!/usr/bin/perl
#
Tinderbox User's avatar
Tinderbox User committed
3
# Copyright (C) 2004, 2007, 2012  Internet Systems Consortium, Inc. ("ISC")
Mark Andrews's avatar
Mark Andrews committed
4 5 6 7 8 9 10 11 12 13 14 15 16
# Copyright (C) 2000, 2001  Internet Software Consortium.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
17 18 19 20 21 22 23 24 25 26 27

#
# Dynamic update test suite.
#
# Usage:
#
#   perl update_test.pl [-s server] [-p port] zone
#
# The server defaults to 127.0.0.1.
# The port defaults to 53.
#
28
# The "Special NS rules" tests will only work correctly if the
Andreas Gustafsson's avatar
typo  
Andreas Gustafsson committed
29
# zone has no NS records to begin with, or alternatively has a
30 31 32 33 34 35 36 37 38 39
# single NS record pointing at the name "ns1" (relative to
# the zone name).
#
# Installation notes:
#
# This program uses the Net::DNS::Resolver module.
# You can install it by saying
#
#    perl -MCPAN -e "install Net::DNS"
#
Automatic Updater's avatar
Automatic Updater committed
40
# $Id: update_test.pl,v 1.10 2007/06/19 23:47:04 tbox Exp $
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
#

use Getopt::Std;
use Net::DNS;
use Net::DNS::Update;
use Net::DNS::Resolver;

$opt_s = "127.0.0.1";
$opt_p = 53;

getopt('s:p:');

$res = new Net::DNS::Resolver;
$res->nameservers($opt_s);
$res->port($opt_p);
$res->defnames(0); # Do not append default domain.

@ARGV == 1 or die
    "usage: perl update_test.pl [-s server] [-p port] zone\n";

$zone = shift @ARGV;

my $failures = 0;

sub assert {
    my ($cond, $explanation) = @_;
    if (!$cond) {
	print "I:Test Failed: $explanation ***\n";
	$failures++
    }
}

sub test {
    my ($expected, @records) = @_;

    my $update = new Net::DNS::Update("$zone");
77

78 79 80
    foreach $rec (@records) {
	$update->push(@$rec);
    }
81

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
    $reply = $res->send($update);

    # Did it work?
    if (defined $reply) {
	my $rcode = $reply->header->rcode;
        assert($rcode eq $expected, "expected $expected, got $rcode");
    } else {
	print "I:Update failed: ", $res->errorstring, "\n";
    }
}

sub section {
    my ($msg) = @_;
    print "I:$msg\n";
}

section("Delete any leftovers from previous tests");
test("NOERROR", ["update", rr_del("a.$zone")]);
test("NOERROR", ["update", rr_del("b.$zone")]);
test("NOERROR", ["update", rr_del("c.$zone")]);
test("NOERROR", ["update", rr_del("d.$zone")]);
test("NOERROR", ["update", rr_del("e.$zone")]);
test("NOERROR", ["update", rr_del("f.$zone")]);
test("NOERROR", ["update", rr_del("ns.s.$zone")]);
test("NOERROR", ["update", rr_del("s.$zone")]);
test("NOERROR", ["update", rr_del("t.$zone")]);
test("NOERROR", ["update", rr_del("*.$zone")]);
test("NOERROR", ["update", rr_del("u.$zone")]);
test("NOERROR", ["update", rr_del("a.u.$zone")]);
test("NOERROR", ["update", rr_del("b.u.$zone")]);

section("Simple prerequisites in the absence of data");
# Name is in Use
test("NXDOMAIN", ["pre", yxdomain("a.$zone")]);
# RRset exists (value independent)
test("NXRRSET", ["pre", yxrrset("a.$zone A")]);
# Name is not in use
test("NOERROR", ["pre", nxdomain("a.$zone")]);
# RRset does not exist
test("NOERROR", ["pre", nxrrset("a.$zone A")]);
# RRset exists (value dependent)
123
test("NXRRSET", ["pre", yxrrset("a.$zone A 73.80.65.49")]);
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138


section ("Simple creation of data");
test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.49")]);

section ("Simple prerequisites in the presence of data");
# Name is in use
test("NOERROR", ["pre", yxdomain("a.$zone")]);
# RRset exists (value independent)
test("NOERROR", ["pre", yxrrset("a.$zone A")]);
# Name is not in use
test("YXDOMAIN", ["pre", nxdomain("a.$zone")]);
# RRset does not exist
test("YXRRSET", ["pre", nxrrset("a.$zone A")]);
# RRset exists (value dependent)
139
test("NOERROR", ["pre", yxrrset("a.$zone A 73.80.65.49")]);
140 141 142 143 144 145 146 147

#
# Merging of RRsets
#
test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.50")]);

section("Detailed tests of \"RRset exists (value dependent)\" prerequisites");
test("NOERROR", ["pre",
148 149
		 yxrrset("a.$zone A 73.80.65.49"),
		 yxrrset("a.$zone A 73.80.65.50")]);
150
test("NOERROR", ["pre",
151 152 153 154
		 yxrrset("a.$zone A 73.80.65.50"),
		 yxrrset("a.$zone A 73.80.65.49")]);
test("NXRRSET", ["pre", yxrrset("a.$zone A 73.80.65.49")]);
test("NXRRSET", ["pre", yxrrset("a.$zone A 73.80.65.50")]);
155
test("NXRRSET", ["pre",
156 157 158
		 yxrrset("a.$zone A 73.80.65.49"),
		 yxrrset("a.$zone A 73.80.65.50"),
		 yxrrset("a.$zone A 73.80.65.51")]);
159 160 161 162 163 164 165 166 167 168


section("Torture test of \"RRset exists (value dependent)\" prerequisites.");

test("NOERROR", ["update",
		 rr_add("e.$zone 300 A 73.80.65.49"),
		 rr_add("e.$zone 300 TXT 'one'"),
		 rr_add("e.$zone 300 A 73.80.65.50")]);
test("NOERROR", ["update",
		 rr_add("e.$zone 300 A 73.80.65.52"),
169
		 rr_add("f.$zone 300 A 73.80.65.52"),
170 171 172 173 174 175 176 177
		 rr_add("e.$zone 300 A 73.80.65.51")]);
test("NOERROR", ["update",
		 rr_add("e.$zone 300 TXT 'three'"),
		 rr_add("e.$zone 300 TXT 'two'")]);
test("NOERROR", ["update",
		 rr_add("e.$zone 300 MX 10 mail.$zone")]);

test("NOERROR", ["pre",
178 179 180 181 182 183 184 185
		 yxrrset("e.$zone A 73.80.65.52"),
		 yxrrset("e.$zone TXT 'two'"),
		 yxrrset("e.$zone A 73.80.65.51"),
		 yxrrset("e.$zone TXT 'three'"),
		 yxrrset("e.$zone A 73.80.65.50"),
		 yxrrset("f.$zone A 73.80.65.52"),
		 yxrrset("e.$zone A 73.80.65.49"),
		 yxrrset("e.$zone TXT 'one'")]);
186 187 188


section("Subtraction of RRsets");
189
test("NOERROR", ["update", rr_del("a.$zone A 73.80.65.49")]);
190
test("NOERROR", ["pre",
191
		 yxrrset("a.$zone A 73.80.65.50")]);
192

193 194
test("NOERROR", ["update", rr_del("a.$zone A 73.80.65.50")]);
test("NOERROR", ["pre", nxrrset("a.$zone A")]);
195 196 197 198 199 200
test("NOERROR", ["pre", nxdomain("a.$zone")]);

section("Other forms of deletion");
test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.49")]);
test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.50")]);
test("NOERROR", ["update", rr_add("a.$zone 300 MX 10 mail.$zone")]);
201 202
test("NOERROR", ["update", rr_del("a.$zone A")]);
test("NOERROR", ["pre", nxrrset("a.$zone A")]);
203 204 205 206 207 208 209
test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.49")]);
test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.50")]);
test("NOERROR", ["update", rr_del("a.$zone")]);
test("NOERROR", ["pre", nxdomain("a.$zone")]);

section("Case insensitivity");
test("NOERROR", ["update", rr_add("a.$zone 300 PTR foo.net.")]);
210
test("NOERROR", ["pre", yxrrset("A.$zone PTR fOo.NeT.")]);
211 212 213 214

section("Special CNAME rules");
test("NOERROR", ["update", rr_add("b.$zone 300 CNAME foo.net.")]);
test("NOERROR", ["update", rr_add("b.$zone 300 A 73.80.65.49")]);
215
test("NOERROR", ["pre", yxrrset("b.$zone CNAME foo.net.")]);
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
test("NOERROR", ["pre", nxrrset("b.$zone A")]);

test("NOERROR", ["update", rr_add("c.$zone 300 A 73.80.65.49")]);
test("NOERROR", ["update", rr_add("c.$zone 300 CNAME foo.net.")]);
test("NOERROR", ["pre", yxrrset("c.$zone A")]);
test("NOERROR", ["pre", nxrrset("c.$zone CNAME")]);

# XXX should test with SIG, KEY, NXT, too.

#
# Currently commented out because Net::DNS does not properly
# support WKS records.
#
#section("Special WKS rules");
#test("NOERROR", ["update", rr_add("c.$zone 300 WKS 73.80.65.49 TCP telnet ftp")]);
#test("NOERROR", ["update", rr_add("c.$zone 300 WKS 73.80.65.49 UDP telnet ftp")]);
#test("NOERROR", ["update", rr_add("c.$zone 300 WKS 73.80.65.50 TCP telnet ftp")]);
#test("NOERROR", ["update", rr_add("c.$zone 300 WKS 73.80.65.49 TCP smtp")]);
#test("NOERROR", ["pre",
235 236 237
#		 yxrrset("c.$zone WKS 73.80.65.49 TCP smtp"),
#		 yxrrset("c.$zone WKS 73.80.65.49 UDP telnet ftp"),
#		 yxrrset("c.$zone WKS 73.80.65.50 TCP telnet ftp")]);
238 239 240 241 242 243 244 245 246 247 248 249 250


section("Special NS rules");

# Deleting the last NS record using "Delete an RR from an RRset"
# should fail at the zone apex and work elsewhere.  The pseudocode
# in RFC2136 says it should fail everywhere, but this is in conflict
# with the actual text.

# Apex
test("NOERROR", ["update",
		 rr_add("$zone 300 NS ns1.$zone"),
		 rr_add("$zone 300 NS ns2.$zone")]);
251 252
test("NOERROR", ["update", rr_del("$zone NS ns1.$zone")]);
test("NOERROR", ["update", rr_del("$zone NS ns2.$zone")]);
253
test("NOERROR", ["pre",
254
		 yxrrset("$zone NS ns2.$zone")]);
255 256 257

# Non-apex
test("NOERROR", ["update", rr_add("n.$zone 300 NS ns1.$zone")]);
258 259
test("NOERROR", ["update", rr_del("n.$zone NS ns1.$zone")]);
test("NOERROR", ["pre", nxrrset("n.$zone NS")]);
260 261 262 263 264 265

# Other ways of deleting NS records should also fail at the apex
# and work elsewhere.

# Non-apex
test("NOERROR", ["update", rr_add("n.$zone 300 NS ns1.$zone")]);
266 267
test("NOERROR", ["update", rr_del("n.$zone NS")]);
test("NOERROR", ["pre", nxrrset("n.$zone NS")]);
268 269

test("NOERROR", ["update", rr_add("n.$zone 300 NS ns1.$zone")]);
270
test("NOERROR", ["pre", yxrrset("n.$zone NS")]);
271
test("NOERROR", ["update", rr_del("n.$zone")]);
272
test("NOERROR", ["pre", nxrrset("n.$zone NS")]);
273 274 275 276

# Apex
test("NOERROR", ["update", rr_del("$zone NS")]);
test("NOERROR", ["pre",
277
		 yxrrset("$zone NS ns2.$zone")]);
278 279 280

test("NOERROR", ["update", rr_del("$zone")]);
test("NOERROR", ["pre",
281
		 yxrrset("$zone NS ns2.$zone")]);
282 283 284 285 286 287 288 289 290 291

# They should not touch the SOA, either.

test("NOERROR", ["update", rr_del("$zone SOA")]);
test("NOERROR", ["pre", yxrrset("$zone SOA")]);


section("Idempotency");

test("NOERROR", ["update", rr_add("d.$zone 300 A 73.80.65.49")]);
292
test("NOERROR", ["pre", yxrrset("d.$zone A 73.80.65.49")]);
293 294 295
test("NOERROR", ["update",
		 rr_add("d.$zone 300 A 73.80.65.49"),
		 rr_del("d.$zone A")]);
296
test("NOERROR", ["pre", nxrrset("d.$zone A 73.80.65.49")]);
297

298 299
test("NOERROR", ["update", rr_del("d.$zone A 73.80.65.49")]);
test("NOERROR", ["pre", nxrrset("d.$zone A")]);
300
test("NOERROR", ["update",
301
		   rr_del("d.$zone A"),
302 303
		   rr_add("d.$zone 300 A 73.80.65.49")]);

304
test("NOERROR", ["pre", yxrrset("d.$zone A")]);
305 306

section("Out-of-zone prerequisites and updates");
307
test("NOTZONE", ["pre", yxrrset("a.somewhere.else. A 73.80.65.49")]);
308 309 310 311 312 313
test("NOTZONE", ["update", rr_add("a.somewhere.else. 300 A 73.80.65.49")]);


section("Glue");
test("NOERROR", ["update", rr_add("s.$zone 300 NS ns.s.$zone")]);
test("NOERROR", ["update", rr_add("ns.s.$zone 300 A 73.80.65.49")]);
314
test("NOERROR", ["pre", yxrrset("ns.s.$zone A 73.80.65.49")]);
315 316 317

section("Wildcards");
test("NOERROR", ["update", rr_add("*.$zone 300 MX 10 mail.$zone")]);
318 319
test("NOERROR", ["pre", yxrrset("*.$zone MX 10 mail.$zone")]);
test("NXRRSET", ["pre", yxrrset("w.$zone MX 10 mail.$zone")]);
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
test("NOERROR", ["pre", nxrrset("w.$zone MX")]);
test("NOERROR", ["pre", nxdomain("w.$zone")]);


section("SOA serial handling");

my $soatimers = "20 20 1814400 3600";

# Get the current SOA serial number.
my $query = $res->query($zone, "SOA");
my ($old_soa) = $query->answer;

my $old_serial = $old_soa->serial;

# Increment it by 10.
my $new_serial = $old_serial + 10;
if ($new_serial > 0xFFFFFFFF) {
    $new_serial -= 0x80000000;
    $new_serial -= 0x80000000;
}

# Replace the SOA with a new one.
test("NOERROR", ["update", rr_add("$zone 300 SOA mname1. . $new_serial $soatimers")]);

# Check that the SOA really got replaced.
($db_soa) = $res->query($zone, "SOA")->answer;
assert($db_soa->mname eq "mname1");

# Check that attempts to decrement the serial number are ignored.
$new_serial = $old_serial - 10;
if ($new_serial < 0) {
    $new_serial += 0x80000000;
    $new_serial += 0x80000000;
}
test("NOERROR", ["update", rr_add("$zone 300 SOA mname2. . $new_serial $soatimers")]);
assert($db_soa->mname eq "mname1");

# Check that attempts to leave the serial number unchanged are ignored.
($old_soa) = $res->query($zone, "SOA")->answer;
$old_serial = $old_soa->serial;
test("NOERROR", ["update", rr_add("$zone 300 SOA mname3. . $old_serial " .
				  $soatimers)]);
($db_soa) = $res->query($zone, "SOA")->answer;
assert($db_soa->mname eq "mname1");

#
# Currently commented out because Net::DNS does not properly
# support multiple strings in TXT records.
#
#section("Big data");
#test("NOERROR", ["update", rr_add("a.$zone 300 TXT aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")]);
371
#test("NOERROR", ["update", rr_del("a.$zone TXT aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")]);
372 373 374 375 376 377
test("NOERROR", ["update", rr_add("a.$zone 300 TXT " . ("foo " x 3))]);

section("Updating TTLs only");

test("NOERROR", ["update", rr_add("t.$zone 300 A 73.80.65.49")]);
($a) = $res->query("t.$zone", "A")->answer;
378 379
$ttl = $a->ttl;
assert($ttl == 300, "incorrect TTL value $ttl != 300");
380
test("NOERROR", ["update",
381
		 rr_del("t.$zone A 73.80.65.49"),
382 383
		 rr_add("t.$zone 301 A 73.80.65.49")]);
($a) = $res->query("t.$zone", "A")->answer;
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
$ttl = $a->ttl;
assert($ttl == 301, "incorrect TTL value $ttl != 301");

# Add an RR that is identical to an existing one except for the TTL.
# RFC2136 is not clear about what this should do; it says "duplicate RRs
# will be silently ignored" but is an RR differing only in TTL
# to be considered a duplicate or not?  The test assumes that it
# should not be considered a duplicate.
test("NOERROR", ["update", rr_add("t.$zone 302 A 73.80.65.50")]);
($a) = $res->query("t.$zone", "A")->answer;
$ttl = $a->ttl;
assert($ttl == 302, "incorrect TTL value $ttl != 302");

section("TTL normalization");

# The desired behaviour is that the old RRs get their TTL
# changed to match the new one.  RFC2136 does not explicitly
# specify this, but I think it makes more sense than the
# alternatives.

test("NOERROR", ["update", rr_add("t.$zone 303 A 73.80.65.51")]);
(@answers) = $res->query("t.$zone", "A")->answer;
$nanswers = scalar @answers;
assert($nanswers == 3, "wrong number of answers $nanswers != 3");
foreach $a (@answers) {
    $ttl = $a->ttl;
    assert($ttl == 303, "incorrect TTL value $ttl != 303");
}
412 413 414 415 416 417 418

section("Obscuring existing data by zone cut");
test("NOERROR", ["update", rr_add("a.u.$zone 300 A 73.80.65.49")]);
test("NOERROR", ["update", rr_add("b.u.$zone 300 A 73.80.65.49")]);
test("NOERROR", ["update", rr_add("u.$zone 300 TXT txt-not-in-nxt")]);
test("NOERROR", ["update", rr_add("u.$zone 300 NS ns.u.$zone")]);

419
test("NOERROR", ["update", rr_del("u.$zone NS ns.u.$zone")]);
420 421

if ($failures) {
422
    print "I:$failures tests failed.\n";
423
} else {
424
    print "I:All tests successful.\n";
425 426
}
exit $failures;