master_loader_unittest.cc 35.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
//
// 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.

#include <dns/master_loader_callbacks.h>
#include <dns/master_loader.h>
17
18
#include <dns/rrtype.h>
#include <dns/rrset.h>
19
#include <dns/rrclass.h>
20
#include <dns/rrttl.h>
21
#include <dns/name.h>
22
#include <dns/rdata.h>
23
24

#include <gtest/gtest.h>
25

26
#include <boost/bind.hpp>
27
#include <boost/lexical_cast.hpp>
28
29
#include <boost/scoped_ptr.hpp>

30
31
32
#include <string>
#include <vector>
#include <list>
33
#include <sstream>
34

35
using namespace isc::dns;
36
37
38
using std::vector;
using std::string;
using std::list;
39
using std::stringstream;
40
using std::endl;
41
using boost::lexical_cast;
42

43
namespace {
44
45
46
47
class MasterLoaderTest : public ::testing::Test {
public:
    MasterLoaderTest() :
        callbacks_(boost::bind(&MasterLoaderTest::callback, this,
48
                               &errors_, _1, _2, _3),
49
                   boost::bind(&MasterLoaderTest::callback, this,
50
                               &warnings_, _1, _2, _3))
51
52
    {}

53
54
55
56
57
    void TearDown() {
        // Check there are no more RRs we didn't expect
        EXPECT_TRUE(rrsets_.empty());
    }

58
59
    /// Concatenate file, line, and reason, and add it to either errors
    /// or warnings
60
    void callback(vector<string>* target, const std::string& file, size_t line,
61
                  const std::string& reason)
62
63
    {
        std::stringstream ss;
64
        ss << reason << " [" << file << ":" << line << "]";
65
        target->push_back(ss.str());
66
67
    }

68
69
70
71
72
    void addRRset(const Name& name, const RRClass& rrclass,
                  const RRType& rrtype, const RRTTL& rrttl,
                  const rdata::RdataPtr& data) {
        const RRsetPtr rrset(new BasicRRset(name, rrclass, rrtype, rrttl));
        rrset->addRdata(data);
73
        rrsets_.push_back(rrset);
74
75
    }

76
77
    void setLoader(const char* file, const Name& origin,
                   const RRClass& rrclass, const MasterLoader::Options options)
78
    {
79
        loader_.reset(new MasterLoader(file, origin, rrclass, callbacks_,
80
                                       boost::bind(&MasterLoaderTest::addRRset,
81
82
                                                   this, _1, _2, _3, _4, _5),
                                       options));
83
84
    }

85
86
87
88
89
90
91
92
93
    void setLoader(std::istream& stream, const Name& origin,
                   const RRClass& rrclass, const MasterLoader::Options options)
    {
        loader_.reset(new MasterLoader(stream, origin, rrclass, callbacks_,
                                       boost::bind(&MasterLoaderTest::addRRset,
                                                   this, _1, _2, _3, _4, _5),
                                       options));
    }

94
    static string prepareZone(const string& line, bool include_last) {
95
96
97
        string result;
        result += "example.org. 3600 IN SOA ns1.example.org. "
            "admin.example.org. 1234 3600 1800 2419200 7200\n";
98
99
100
101
102
        result += line;
        if (include_last) {
            result += "\n";
            result += "correct 3600    IN  A 192.0.2.2\n";
        }
103
        return (result);
104
105
106
107
108
109
110
111
    }

    void clear() {
        warnings_.clear();
        errors_.clear();
        rrsets_.clear();
    }

112
113
    // Check the next RR in the ones produced by the loader
    // Other than passed arguments are checked to be the default for the tests
114
115
    void checkRR(const string& name, const RRType& type, const string& data,
                 const RRTTL& rrttl = RRTTL(3600)) {
116
117
118
119
120
        ASSERT_FALSE(rrsets_.empty());
        RRsetPtr current = rrsets_.front();
        rrsets_.pop_front();

        EXPECT_EQ(Name(name), current->getName());
121
        EXPECT_EQ(type, current->getType());
122
        EXPECT_EQ(RRClass::IN(), current->getClass());
123
        EXPECT_EQ(rrttl, current->getTTL());
124
        ASSERT_EQ(1, current->getRdataCount());
125
126
        EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)->
                  compare(current->getRdataIterator()->getCurrent()));
127
128
    }

129
130
131
132
133
134
    void checkBasicRRs() {
        checkRR("example.org", RRType::SOA(),
                "ns1.example.org. admin.example.org. "
                "1234 3600 1800 2419200 7200");
        checkRR("example.org", RRType::NS(), "ns1.example.org.");
        checkRR("www.example.org", RRType::A(), "192.0.2.1");
135
        checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
136
137
    }

138
139
    void checkARR(const string& name) {
        checkRR(name, RRType::A(), "192.0.2.1");
140
141
    }

142
    MasterLoaderCallbacks callbacks_;
143
144
145
146
    boost::scoped_ptr<MasterLoader> loader_;
    vector<string> errors_;
    vector<string> warnings_;
    list<RRsetPtr> rrsets_;
147
148
};

149
150
// Test simple loading. The zone file contains no tricky things, and nothing is
// omitted. No RRset contains more than one RR Also no errors or warnings.
151
TEST_F(MasterLoaderTest, basicLoad) {
152
153
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);
154

155
    EXPECT_FALSE(loader_->loadedSucessfully());
156
    loader_->load();
157
    EXPECT_TRUE(loader_->loadedSucessfully());
158
159
160
161

    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());

162
163
164
165
166
167
168
    checkBasicRRs();
}

// Test the $INCLUDE directive
TEST_F(MasterLoaderTest, include) {
    // Test various cases of include
    const char* includes[] = {
169
170
171
172
173
        "$include",
        "$INCLUDE",
        "$Include",
        "$InCluDe",
        "\"$INCLUDE\"",
174
175
176
177
        NULL
    };
    for (const char** include = includes; *include != NULL; ++include) {
        SCOPED_TRACE(*include);
178
179

        clear();
180
181
        // Prepare input source that has the include and some more data
        // below (to see it returns back to the original source).
182
        const string include_str = string(*include) + " " +
183
            TEST_DATA_SRCDIR + "/example.org\nwww 3600 IN AAAA 2001:db8::1\n";
184
185
186
187
188
189
        stringstream ss(include_str);
        setLoader(ss, Name("example.org."), RRClass::IN(),
                  MasterLoader::MANY_ERRORS);

        loader_->load();
        EXPECT_TRUE(loader_->loadedSucessfully());
190
        EXPECT_TRUE(errors_.empty());
191
192
193
        EXPECT_TRUE(warnings_.empty());

        checkBasicRRs();
194
        checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
195
    }
196
}
197

198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// A commonly used helper to check callback message.
void
checkCallbackMessage(const string& actual_msg, const string& expected_msg,
                     size_t expected_line) {
    // The actual message should begin with the expected message.
    EXPECT_EQ(0, actual_msg.find(expected_msg)) << "actual message: " <<
                                                actual_msg << " expected: " <<
                                                expected_msg;

    // and it should end with "...:<line_num>]"
    const string line_desc = ":" + lexical_cast<string>(expected_line) + "]";
    EXPECT_EQ(actual_msg.size() - line_desc.size(),
              actual_msg.find(line_desc)) << "Expected on line " <<
        expected_line;
}

214
TEST_F(MasterLoaderTest, origin) {
215
    // Various forms of the directive
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
    const char* origins[] = {
        "$origin",
        "$ORIGIN",
        "$Origin",
        "$OrigiN",
        "\"$ORIGIN\"",
        NULL
    };
    for (const char** origin = origins; *origin != NULL; ++origin) {
        SCOPED_TRACE(*origin);

        clear();
        const string directive = *origin;
        const string input =
            "@  1H  IN  A   192.0.2.1\n" +
            directive + " sub.example.org.\n"
232
            "\"www\"    1H  IN  A   192.0.2.1\n" +
233
234
235
236
237
238
239
240
241
242
243
244
            // Relative name in the origin
            directive + " relative\n"
            "@  1H  IN  A   192.0.2.1\n"
            // Origin is _not_ used here (absolute name)
            "noorigin.example.org.  60M IN  A   192.0.2.1\n";
        stringstream ss(input);
        setLoader(ss, Name("example.org."), RRClass::IN(),
                  MasterLoader::MANY_ERRORS);

        loader_->load();
        EXPECT_TRUE(loader_->loadedSucessfully());
        EXPECT_TRUE(errors_.empty());
245
246
247
248
249
        // There's a relative origin in it, we warn about that.
        EXPECT_EQ(1, warnings_.size());
        checkCallbackMessage(warnings_.at(0),
                             "The new origin is relative, did you really mean "
                             "relative.sub.example.org.?", 4);
250
251
252
253
254
255
256
257

        checkARR("example.org");
        checkARR("www.sub.example.org");
        checkARR("relative.sub.example.org");
        checkARR("noorigin.example.org");
    }
}

258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// Test the source is correctly popped even after error
TEST_F(MasterLoaderTest, popAfterError) {
    const string include_str = "$include " TEST_DATA_SRCDIR
        "/broken.zone\nwww 3600 IN AAAA 2001:db8::1\n";
    stringstream ss(include_str);
    // We don't test without MANY_ERRORS, we want to see what happens
    // after the error.
    setLoader(ss, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);

    loader_->load();
    EXPECT_FALSE(loader_->loadedSucessfully());
    EXPECT_EQ(1, errors_.size()); // For the broken RR
    EXPECT_EQ(1, warnings_.size()); // For missing EOLN

    // The included file doesn't contain anything usable, but the
    // line after the include should be there.
    checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
276
}
277

278
279
// Check it works the same when created based on a stream, not filename
TEST_F(MasterLoaderTest, streamConstructor) {
280
    stringstream zone_stream(prepareZone("", true));
281
282
283
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);

284
    EXPECT_FALSE(loader_->loadedSucessfully());
285
    loader_->load();
286
    EXPECT_TRUE(loader_->loadedSucessfully());
287
288
289
290
291
292
293
294

    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());
    checkRR("example.org", RRType::SOA(), "ns1.example.org. "
            "admin.example.org. 1234 3600 1800 2419200 7200");
    checkRR("correct.example.org", RRType::A(), "192.0.2.2");
}

295
296
// Try loading data incrementally.
TEST_F(MasterLoaderTest, incrementalLoad) {
297
298
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);
299

300
    EXPECT_FALSE(loader_->loadedSucessfully());
301
    EXPECT_FALSE(loader_->loadIncremental(2));
302
    EXPECT_FALSE(loader_->loadedSucessfully());
303
304
305
306

    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());

307
308
    checkRR("example.org", RRType::SOA(),
            "ns1.example.org. admin.example.org. "
309
310
311
312
313
314
315
316
            "1234 3600 1800 2419200 7200");
    checkRR("example.org", RRType::NS(), "ns1.example.org.");

    // The third one is not loaded yet
    EXPECT_TRUE(rrsets_.empty());

    // Load the rest.
    EXPECT_TRUE(loader_->loadIncremental(20));
317
    EXPECT_TRUE(loader_->loadedSucessfully());
318
319
320
321
322

    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());

    checkRR("www.example.org", RRType::A(), "192.0.2.1");
323
    checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
324
325
}

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// Try loading from file that doesn't exist. There should be single error
// saying so.
TEST_F(MasterLoaderTest, invalidFile) {
    setLoader("This file doesn't exist at all",
              Name("exmaple.org."), RRClass::IN(), MasterLoader::MANY_ERRORS);

    // Nothing yet. The loader is dormant until invoked.
    // Is it really what we want?
    EXPECT_TRUE(errors_.empty());

    loader_->load();

    EXPECT_TRUE(warnings_.empty());
    EXPECT_TRUE(rrsets_.empty());
    ASSERT_EQ(1, errors_.size());
    EXPECT_EQ(0, errors_[0].find("Error opening the input source file: ")) <<
        "Different error: " << errors_[0];
}
344
345

struct ErrorCase {
346
    const char* const line;    // The broken line in master file
347
    const char* const reason;  // If non NULL, the reason string
348
    const char* const problem; // Description of the problem for SCOPED_TRACE
349
} const error_cases[] = {
350
351
352
353
    { "www...   3600    IN  A   192.0.2.1", NULL, "Invalid name" },
    { "www      FORTNIGHT   IN  A   192.0.2.1", NULL, "Invalid TTL" },
    { "www      3600    XX  A   192.0.2.1", NULL, "Invalid class" },
    { "www      3600    IN  A   bad_ip", NULL, "Invalid Rdata" },
354
355

    // Parameter ordering errors
356
357
358
359
360
361
362
363
364
365
366
367
    { "www      IN      A   3600 192.168.2.7",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Incorrect order of class, TTL and type" },
    { "www      A       IN  3600 192.168.2.8",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Incorrect order of class, TTL and type" },
    { "www      3600    A   IN   192.168.2.7",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Incorrect order of class, TTL and type" },
    { "www      A       3600 IN  192.168.2.8",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Incorrect order of class, TTL and type" },
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392

    // Missing type and Rdata
    { "www", "unexpected end of input", "Missing type and Rdata" },
    { "www 3600", "unexpected end of input", "Missing type and Rdata" },
    { "www IN", "unexpected end of input", "Missing type and Rdata" },
    { "www 3600 IN", "unexpected end of input", "Missing type and Rdata" },
    { "www IN 3600", "unexpected end of input", "Missing type and Rdata" },

    // Missing Rdata
    { "www A",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Missing Rdata" },
    { "www 3600 A",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Missing Rdata" },
    { "www IN A",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Missing Rdata" },
    { "www 3600 IN A",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Missing Rdata" },
    { "www IN 3600 A",
      "createRdata from text failed: IN/A RDATA construction from text failed",
      "Missing Rdata" },

393
394
395
396
397
398
399
400
    { "www      3600    IN", NULL, "Unexpected EOLN" },
    { "www      3600    CH  TXT nothing", NULL, "Class mismatch" },
    { "www      \"3600\"  IN  A   192.0.2.1", NULL, "Quoted TTL" },
    { "www      3600    \"IN\"  A   192.0.2.1", NULL, "Quoted class" },
    { "www      3600    IN  \"A\"   192.0.2.1", NULL, "Quoted type" },
    { "unbalanced)paren 3600    IN  A   192.0.2.1", NULL, "Token error 1" },
    { "www  3600    unbalanced)paren    A   192.0.2.1", NULL,
      "Token error 2" },
401
402
    // Check the unknown directive. The rest looks like ordinary RR,
    // so we see the $ is actually special.
403
    { "$UNKNOWN 3600    IN  A   192.0.2.1", NULL, "Unknown $ directive" },
404
405
406
407
408
409
    { "$INCLUD " TEST_DATA_SRCDIR "/example.org", "Unknown directive 'INCLUD'",
        "Include too short" },
    { "$INCLUDES " TEST_DATA_SRCDIR "/example.org",
        "Unknown directive 'INCLUDES'", "Include too long" },
    { "$INCLUDE", "unexpected end of input", "Missing include path" },
    // The following two error messages are system dependant, omitting
410
    { "$INCLUDE /file/not/found", NULL, "Include file not found" },
411
    { "$INCLUDE /file/not/found example.org. and here goes bunch of garbage",
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
412
        NULL, "Include file not found and garbage at the end of line" },
413
414
415
416
417
418
419
420
421
    { "$ORIGIN", "unexpected end of input", "Missing origin name" },
    { "$ORIGIN invalid...name", "duplicate period in invalid...name",
        "Invalid name for origin" },
    { "$ORIGIN )brokentoken", "unbalanced parentheses",
        "Broken token in origin" },
    { "$ORIGIN example.org. garbage", "Extra tokens at the end of line",
        "Garbage after origin" },
    { "$ORIGI name.", "Unknown directive 'ORIGI'", "$ORIGIN too short" },
    { "$ORIGINAL name.", "Unknown directive 'ORIGINAL'", "$ORIGIN too long" },
422
423
    { "$TTL 100 extra-garbage", "Extra tokens at the end of line",
      "$TTL with extra token" },
424
425
426
427
428
    { "$TTL", "unexpected end of input", "missing TTL" },
    { "$TTL No-ttl", "Unknown unit used: N in: No-ttl", "bad TTL" },
    { "$TTL \"100\"", "invalid TTL: \"100\"", "bad TTL, quoted" },
    { "$TT 100", "Unknown directive 'TT'", "bad directive, too short" },
    { "$TTLLIKE 100", "Unknown directive 'TTLLIKE'", "bad directive, extra" },
429
    { NULL, NULL, NULL }
430
431
432
433
434
435
436
};

// Test a broken zone is handled properly. We test several problems,
// both in strict and lenient mode.
TEST_F(MasterLoaderTest, brokenZone) {
    for (const ErrorCase* ec = error_cases; ec->line != NULL; ++ec) {
        SCOPED_TRACE(ec->problem);
437
        const string zone(prepareZone(ec->line, true));
438
439
440
441

        {
            SCOPED_TRACE("Strict mode");
            clear();
442
443
            stringstream zone_stream(zone);
            setLoader(zone_stream, Name("example.org."), RRClass::IN(),
444
                      MasterLoader::DEFAULT);
445
            EXPECT_FALSE(loader_->loadedSucessfully());
446
            EXPECT_THROW(loader_->load(), MasterLoaderError);
447
            EXPECT_FALSE(loader_->loadedSucessfully());
448
            EXPECT_EQ(1, errors_.size());
449
            if (ec->reason != NULL) {
450
                checkCallbackMessage(errors_.at(0), ec->reason, 2);
451
            }
452
453
454
455
456
457
458
459
460
461
462
463
            EXPECT_TRUE(warnings_.empty());

            checkRR("example.org", RRType::SOA(), "ns1.example.org. "
                    "admin.example.org. 1234 3600 1800 2419200 7200");
            // In the strict mode, it is aborted. The last RR is not
            // even attempted.
            EXPECT_TRUE(rrsets_.empty());
        }

        {
            SCOPED_TRACE("Lenient mode");
            clear();
464
465
            stringstream zone_stream(zone);
            setLoader(zone_stream, Name("example.org."), RRClass::IN(),
466
                      MasterLoader::MANY_ERRORS);
467
            EXPECT_FALSE(loader_->loadedSucessfully());
468
            EXPECT_NO_THROW(loader_->load());
469
            EXPECT_FALSE(loader_->loadedSucessfully());
470
471
472
473
474
475
476
477
            EXPECT_EQ(1, errors_.size());
            EXPECT_TRUE(warnings_.empty());
            checkRR("example.org", RRType::SOA(), "ns1.example.org. "
                    "admin.example.org. 1234 3600 1800 2419200 7200");
            // This one is below the error one.
            checkRR("correct.example.org", RRType::A(), "192.0.2.2");
            EXPECT_TRUE(rrsets_.empty());
        }
478
479
480
481
482

        {
            SCOPED_TRACE("Error at EOF");
            // This case is interesting only in the lenient mode.
            clear();
483
            const string zoneEOF(prepareZone(ec->line, false));
484
485
486
            stringstream zone_stream(zoneEOF);
            setLoader(zone_stream, Name("example.org."), RRClass::IN(),
                      MasterLoader::MANY_ERRORS);
487
            EXPECT_FALSE(loader_->loadedSucessfully());
488
            EXPECT_NO_THROW(loader_->load());
489
            EXPECT_FALSE(loader_->loadedSucessfully());
490
            EXPECT_EQ(1, errors_.size()) << errors_[0] << "\n" << errors_[1];
491
492
            // The unexpected EOF warning
            EXPECT_EQ(1, warnings_.size());
493
494
495
496
            checkRR("example.org", RRType::SOA(), "ns1.example.org. "
                    "admin.example.org. 1234 3600 1800 2419200 7200");
            EXPECT_TRUE(rrsets_.empty());
        }
497
498
    }
}
499

500
501
502
503
504
505
// Check that a garbage after the include generates an error, but not fatal
// one (in lenient mode) and we can recover.
TEST_F(MasterLoaderTest, includeWithGarbage) {
    // Include an origin (example.org) because we expect it to be handled
    // soon and we don't want it to break here.
    const string include_str("$INCLUDE " TEST_DATA_SRCDIR
506
                             "/example.org example.org. bunch of other stuff\n"
507
508
509
510
511
512
513
514
                             "www 3600 IN AAAA 2001:db8::1\n");
    stringstream zone_stream(include_str);
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);

    EXPECT_NO_THROW(loader_->load());
    EXPECT_FALSE(loader_->loadedSucessfully());
    ASSERT_EQ(1, errors_.size());
515
    checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1);
516
517
518
519
520
521
522
    // It says something about extra tokens at the end
    EXPECT_NE(string::npos, errors_[0].find("Extra"));
    EXPECT_TRUE(warnings_.empty());
    checkBasicRRs();
    checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
}

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
523
524
// Check we error about garbage at the end of $ORIGIN line (but the line
// works).
525
TEST_F(MasterLoaderTest, originWithGarbage) {
526
    const string origin_str = "$ORIGIN www.example.org. More garbage here\n"
527
528
529
530
531
532
533
        "@  1H  IN  A   192.0.2.1\n";
    stringstream ss(origin_str);
    setLoader(ss, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    EXPECT_NO_THROW(loader_->load());
    EXPECT_FALSE(loader_->loadedSucessfully());
    ASSERT_EQ(1, errors_.size());
534
    checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1);
535
536
537
538
    EXPECT_TRUE(warnings_.empty());
    checkARR("www.example.org");
}

539
540
541
542
// Test we can pass both file to include and the origin to switch
TEST_F(MasterLoaderTest, includeAndOrigin) {
    // First, switch origin to something else, so we can check it is
    // switched back.
543
    const string include_string = "$ORIGIN www.example.org.\n"
544
545
546
        "@  1H  IN  A   192.0.2.1\n"
        // Then include the file with data and switch origin back
        "$INCLUDE " TEST_DATA_SRCDIR "/example.org example.org.\n"
547
        // Another RR to see we fall back to the previous origin.
548
549
550
551
552
553
554
555
556
557
558
559
        "www    1H  IN  A   192.0.2.1\n";
    stringstream ss(include_string);
    setLoader(ss, Name("example.org"), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    // Successfully load the data
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
    EXPECT_TRUE(errors_.empty());
    EXPECT_TRUE(warnings_.empty());
    // And check it's the correct data
    checkARR("www.example.org");
    checkBasicRRs();
560
    checkARR("www.www.example.org");
561
562
}

563
564
565
566
567
568
569
570
571
572
573
574
575
// Like above, but the origin after include is bogus. The whole line should
// be rejected.
TEST_F(MasterLoaderTest, includeAndBadOrigin) {
    const string include_string =
        "$INCLUDE " TEST_DATA_SRCDIR "/example.org example..org.\n"
        // Another RR to see the switch survives after we exit include
        "www    1H  IN  A   192.0.2.1\n";
    stringstream ss(include_string);
    setLoader(ss, Name("example.org"), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    loader_->load();
    EXPECT_FALSE(loader_->loadedSucessfully());
    EXPECT_EQ(1, errors_.size());
576
577
    checkCallbackMessage(errors_.at(0), "duplicate period in example..org.",
                         1);
578
579
580
581
582
    EXPECT_TRUE(warnings_.empty());
    // And check it's the correct data
    checkARR("www.example.org");
}

583
584
// Check the origin doesn't get outside of the included file.
TEST_F(MasterLoaderTest, includeOriginRestore) {
585
586
    const string include_string =
        "$INCLUDE " TEST_DATA_SRCDIR "/origincheck.txt\n"
587
588
589
590
591
592
593
        "@  1H  IN  A   192.0.2.1\n";
    stringstream ss(include_string);
    setLoader(ss, Name("example.org"), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    // Successfully load the data
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
594
    EXPECT_TRUE(errors_.empty());
595
596
597
598
599
600
    EXPECT_TRUE(warnings_.empty());
    // And check it's the correct data
    checkARR("www.example.org");
    checkARR("example.org");
}

601
602
603
604
605
606
607
608
609
610
611
612
613
614
// Check we restore the last name for initial whitespace when returning from
// include. But we do produce a warning if there's one just ofter the include.
TEST_F(MasterLoaderTest, includeAndInitialWS) {
    const string include_string = "xyz  1H  IN  A   192.0.2.1\n"
        "$INCLUDE " TEST_DATA_SRCDIR "/example.org\n"
        "   1H  IN  A   192.0.2.1\n";
    stringstream ss(include_string);
    setLoader(ss, Name("example.org"), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    // Successfully load the data
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
    EXPECT_TRUE(errors_.empty());
    EXPECT_EQ(1, warnings_.size());
615
    checkCallbackMessage(warnings_.at(0),
616
617
                         "Owner name omitted around $INCLUDE, the result might "
                         "not be as expected", 3);
618
619
620
621
622
    checkARR("xyz.example.org");
    checkBasicRRs();
    checkARR("xyz.example.org");
}

623
624
// Test for "$TTL"
TEST_F(MasterLoaderTest, ttlDirective) {
625
626
    stringstream zone_stream;

627
    // Set the default TTL with $TTL followed by an RR omitting the TTL
628
629
    zone_stream << "$TTL 1800\nexample.org. IN A 192.0.2.1\n";
    // $TTL can be quoted.  Also testing the case of $TTL being changed.
630
    zone_stream << "\"$TTL\" 100\na.example.org. IN A 192.0.2.2\n";
631
    // Extended TTL form is accepted.
632
633
    zone_stream << "$TTL 1H\nb.example.org. IN A 192.0.2.3\n";
    // Matching is case insensitive.
634
635
636
    zone_stream << "$tTl 360\nc.example.org. IN A 192.0.2.4\n";
    // Maximum allowable TTL
    zone_stream << "$TTL 2147483647\nd.example.org. IN A 192.0.2.5\n";
637

638
639
640
641
642
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::DEFAULT);
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
    checkRR("example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
643
644
    checkRR("a.example.org", RRType::A(), "192.0.2.2", RRTTL(100));
    checkRR("b.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
645
646
    checkRR("c.example.org", RRType::A(), "192.0.2.4", RRTTL(360));
    checkRR("d.example.org", RRType::A(), "192.0.2.5", RRTTL(2147483647));
647
648
}

649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
TEST_F(MasterLoaderTest, ttlFromSOA) {
    // No $TTL, and the SOA doesn't have an explicit TTL field.  Its minimum
    // TTL field will be used as the RR's TTL, and it'll be used as the
    // default TTL for others.
    stringstream zone_stream("example.org. IN SOA . . 0 0 0 0 1800\n"
                             "a.example.org. IN A 192.0.2.1\n");
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::DEFAULT);
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
    checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(1800));
    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));

    // The use of SOA minimum TTL should have caused a warning.
    EXPECT_EQ(1, warnings_.size());
664
665
    checkCallbackMessage(warnings_.at(0),
                         "no TTL specified; using SOA MINTTL instead", 1);
666
667
}

668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
TEST_F(MasterLoaderTest, ttlFromPrevious) {
    // No available default TTL.  2nd and 3rd RR will use the TTL of the
    // 1st RR.  This will result in a warning, but only for the first time.
    stringstream zone_stream("a.example.org. 1800 IN A 192.0.2.1\n"
                             "b.example.org. IN A 192.0.2.2\n"
                             "c.example.org. IN A 192.0.2.3\n");
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::DEFAULT);
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
    checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
    checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(1800));

    EXPECT_EQ(1, warnings_.size());
683
684
685
    checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
}

Mukund Sivaraman's avatar
Mukund Sivaraman committed
686
TEST_F(MasterLoaderTest, RRParamsOrdering) {
687
688
    // We test the order and existence of TTL, class and type. See
    // MasterLoader::MasterLoaderImpl::parseRRParams() for ordering.
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715

    stringstream zone_stream;
    // <TTL> <class> <type> <RDATA>
    zone_stream << "a.example.org. 1800 IN A 192.0.2.1\n";
    // <type> <RDATA>
    zone_stream << "b.example.org. A 192.0.2.2\n";
    // <class> <TTL> <type> <RDATA>
    zone_stream << "c.example.org. IN 3600 A 192.0.2.3\n";
    // <TTL> <type> <RDATA>
    zone_stream << "d.example.org. 7200 A 192.0.2.4\n";
    // <class> <type> <RDATA>
    zone_stream << "e.example.org. IN A 192.0.2.5\n";

    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::DEFAULT);
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
    checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
    checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
    checkRR("d.example.org", RRType::A(), "192.0.2.4", RRTTL(7200));
    checkRR("e.example.org", RRType::A(), "192.0.2.5", RRTTL(7200));

    EXPECT_EQ(1, warnings_.size());
    checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
}

716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
TEST_F(MasterLoaderTest, ttlFromPreviousSOA) {
    // Mixture of the previous two cases: SOA has explicit TTL, followed by
    // an RR without an explicit TTL.  In this case the minimum TTL won't be
    // recognized as the "default TTL".
    stringstream zone_stream("example.org. 100 IN SOA . . 0 0 0 0 1800\n"
                             "a.example.org. IN A 192.0.2.1\n");
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::DEFAULT);
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());

    checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(100));
    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(100));

    EXPECT_EQ(1, warnings_.size());
    checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
}

TEST_F(MasterLoaderTest, ttlUnknown) {
    // No available TTL is known for the first RR.
    stringstream zone_stream("a.example.org. IN A 192.0.2.1\n");
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::DEFAULT);
    EXPECT_THROW(loader_->load(), MasterLoaderError);
}

TEST_F(MasterLoaderTest, ttlUnknownAndContinue) {
    stringstream zone_stream("a.example.org. IN A 192.0.2.1\n"
                             "b.example.org. 1800 IN A 192.0.2.2\n");
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    loader_->load();
    EXPECT_FALSE(loader_->loadedSucessfully());
    checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));

    EXPECT_TRUE(warnings_.empty());
    EXPECT_EQ(1, errors_.size());
    checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1);
}

TEST_F(MasterLoaderTest, ttlUnknownAndEOF) {
    // Similar to the previous case, but the input will be abruptly terminated
    // after the offending RR.  This will cause an additional warning.
    stringstream zone_stream("a.example.org. IN A 192.0.2.1");
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    loader_->load();
    EXPECT_FALSE(loader_->loadedSucessfully());
    EXPECT_TRUE(rrsets_.empty());

    EXPECT_EQ(1, errors_.size());
    checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1);

    // RDATA implementation can complain about it, too.  To be independent of
    // its details, we focus on the very last warning.
    EXPECT_FALSE(warnings_.empty());
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
772
773
    checkCallbackMessage(*warnings_.rbegin(), "File does not end with newline",
                         1);
774
775
}

776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
TEST_F(MasterLoaderTest, ttlOverflow) {
    stringstream zone_stream;
    zone_stream << "example.org. IN SOA . . 0 0 0 0 2147483648\n";
    zone_stream << "$TTL 3600\n"; // reset to an in-range value
    zone_stream << "$TTL 2147483649\n" << "a.example.org. IN A 192.0.2.1\n";
    zone_stream << "$TTL 3600\n"; // reset to an in-range value
    zone_stream << "b.example.org. 2147483650 IN A 192.0.2.2\n";
    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
              MasterLoader::DEFAULT);

    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
    EXPECT_EQ(3, rrsets_.size());

    checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 2147483648", RRTTL(0));
    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(0));
    checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(0));

    EXPECT_EQ(4, warnings_.size());
    checkCallbackMessage(warnings_.at(1),
                         "TTL 2147483648 > MAXTTL, setting to 0 per RFC2181",
                         1);
    checkCallbackMessage(warnings_.at(2),
                         "TTL 2147483649 > MAXTTL, setting to 0 per RFC2181",
                         3);
    checkCallbackMessage(warnings_.at(3),
                         "TTL 2147483650 > MAXTTL, setting to 0 per RFC2181",
                         6);
}

806
807
808
809
810
// Test the constructor rejects empty add callback.
TEST_F(MasterLoaderTest, emptyCallback) {
    EXPECT_THROW(MasterLoader(TEST_DATA_SRCDIR "/example.org",
                              Name("example.org"), RRClass::IN(), callbacks_,
                              AddRRCallback()), isc::InvalidParameter);
811
812
813
814
815
    // And the same with the second constructor
    stringstream ss("");
    EXPECT_THROW(MasterLoader(ss, Name("example.org"), RRClass::IN(),
                              callbacks_, AddRRCallback()),
                 isc::InvalidParameter);
816
817
818
819
820
821
822
823
824
}

// Check it throws when we try to load after loading was complete.
TEST_F(MasterLoaderTest, loadTwice) {
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);

    loader_->load();
    EXPECT_THROW(loader_->load(), isc::InvalidOperation);
825
826
827
    // Don't check them, they are not interesting, so suppress the error
    // at TearDown
    rrsets_.clear();
828
}
829
830
831
832
833
834
835
836

// Load 0 items should be rejected
TEST_F(MasterLoaderTest, loadZero) {
    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
              RRClass::IN(), MasterLoader::MANY_ERRORS);
    EXPECT_THROW(loader_->loadIncremental(0), isc::InvalidParameter);
}

837
838
839
840
841
842
843
844
845
846
847
848
// Test there's a warning when the file terminates without end of
// line.
TEST_F(MasterLoaderTest, noEOLN) {
    // No \n at the end
    const string input("example.org. 3600 IN SOA ns1.example.org. "
                       "admin.example.org. 1234 3600 1800 2419200 7200");
    stringstream ss(input);
    setLoader(ss, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);

    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
849
    EXPECT_TRUE(errors_.empty());
850
851
852
853
854
855
    // There should be one warning about the EOLN
    EXPECT_EQ(1, warnings_.size());
    checkRR("example.org", RRType::SOA(), "ns1.example.org. "
            "admin.example.org. 1234 3600 1800 2419200 7200");
}

856
857
858
859
860
861
862
863
864
865
// Test it rejects when we don't have the previous name to use in place of
// initial whitespace
TEST_F(MasterLoaderTest, noPreviousName) {
    const string input("    1H  IN  A   192.0.2.1\n");
    stringstream ss(input);
    setLoader(ss, Name("example.org."), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    loader_->load();
    EXPECT_FALSE(loader_->loadedSucessfully());
    EXPECT_EQ(1, errors_.size());
866
867
    checkCallbackMessage(errors_.at(0), "No previous name to use in place of "
                         "initial whitespace", 1);
868
869
870
    EXPECT_TRUE(warnings_.empty());
}

871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
// Check we warn if the first RR in an included file has omitted name
TEST_F(MasterLoaderTest, previousInInclude) {
    const string input("www 1H  IN  A   192.0.2.1\n"
                       "$INCLUDE " TEST_DATA_SRCDIR "/omitcheck.txt\n");
    stringstream ss(input);
    setLoader(ss, Name("example.org"), RRClass::IN(),
              MasterLoader::MANY_ERRORS);
    loader_->load();
    EXPECT_TRUE(loader_->loadedSucessfully());
    EXPECT_TRUE(errors_.empty());
    // There should be one warning about the EOLN
    EXPECT_EQ(1, warnings_.size());
    checkCallbackMessage(warnings_.at(0), "Owner name omitted around "
                         "$INCLUDE, the result might not be as expected", 1);
    checkARR("www.example.org");
    checkARR("www.example.org");
}

889
}