From: Mukund Sivaraman Date: Sun, 2 Feb 2014 22:17:52 +0000 (+0530) Subject: [2430] Support basic $GENERATE in dns::MasterLoader X-Git-Tag: bind10-1.2.0beta1-release~50^2~23 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4760ac62312e9b5bf24ba73d67b859e6cb3fd61d;p=thirdparty%2Fkea.git [2430] Support basic $GENERATE in dns::MasterLoader It doesn't support format specifiers given in {} yet. This will be added in follow-up work on this branch. Relative names are not permitted in the RHS of $GENERATE statements. This is because the string version of createRdata() is used to construct RDATA, which does not take an origin. --- diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc index 6b6e091489..f8035df334 100644 --- a/src/lib/dns/master_loader.cc +++ b/src/lib/dns/master_loader.cc @@ -21,14 +21,16 @@ #include #include -#include +#include #include // for iequals +#include +#include #include #include #include -#include // for iequals -#include + +#include // for sscanf() using std::string; using std::auto_ptr; @@ -148,6 +150,8 @@ private: } MasterToken handleInitialToken(); + std::string generateForIter(const std::string& str, const int it); + void doGenerate(); void doOrigin(bool is_optional) { // Parse and create the new origin. It is relative to the previous @@ -348,6 +352,9 @@ private: } else if (iequals(directive, "ORIGIN")) { doOrigin(false); eatUntilEOL(true); + } else if (iequals(directive, "GENERATE")) { + doGenerate(); + eatUntilEOL(true); } else if (iequals(directive, "TTL")) { setDefaultTTL(RRTTL(getString()), false); eatUntilEOL(true); @@ -437,6 +444,130 @@ public: size_t rr_count_; // number of RRs successfully loaded }; +std::string +MasterLoader::MasterLoaderImpl::generateForIter(const std::string& str, + const int i) +{ + std::string rstr; + + for (std::string::const_iterator it = str.begin(); it != str.end();) { + switch (*it) { + case '$': + ++it; + if ((it != str.end()) && (*it == '$')) { + rstr.push_back('$'); + ++it; + continue; + } + + // TODO: This doesn't handle format specifiers in {} yet. + rstr += boost::str(boost::format("%d") % i); + break; + + case '\\': + rstr.push_back(*it); + ++it; + if (it == str.end()) { + continue; + } + rstr.push_back(*it); + ++it; + break; + + default: + rstr.push_back(*it); + ++it; + break; + } + } + + return (rstr); +} + +void +MasterLoader::MasterLoaderImpl::doGenerate() { + const MasterToken& range_token = lexer_.getNextToken(MasterToken::STRING); + if (range_token.getType() != MasterToken::STRING) { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + "Invalid $GENERATE syntax"); + return; + } + const std::string range = range_token.getString(); + + const MasterToken& lhs_token = lexer_.getNextToken(MasterToken::STRING); + if (lhs_token.getType() != MasterToken::STRING) { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + "Invalid $GENERATE syntax"); + return; + } + const std::string lhs = lhs_token.getString(); + + const MasterToken& param_token = lexer_.getNextToken(MasterToken::STRING); + if (param_token.getType() != MasterToken::STRING) { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + "Invalid $GENERATE syntax"); + return; + } + bool explicit_ttl = false; + const RRType rrtype = parseRRParams(explicit_ttl, param_token); + + const MasterToken& rhs_token = lexer_.getNextToken(MasterToken::QSTRING); + if ((rhs_token.getType() != MasterToken::QSTRING) && + (rhs_token.getType() != MasterToken::STRING)) + { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + "Invalid $GENERATE syntax"); + return; + } + const std::string rhs = rhs_token.getString(); + + unsigned int start; + unsigned int stop; + unsigned int step; + + const int n = sscanf(range.c_str(), "%u-%u/%u", &start, &stop, &step); + if ((n < 2) || (stop < start)) { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + "$GENERATE: invalid range: " + range); + return; + } + + if (n == 2) { + step = 1; + } + + for (int i = start; i <= stop; i += step) { + const std::string generated_name = generateForIter(lhs, i); + const std::string generated_rdata = generateForIter(rhs, i); + const size_t name_length = generated_name.size(); + last_name_.reset(new Name(generated_name.c_str(), name_length, + &active_origin_)); + previous_name_ = true; + + const rdata::RdataPtr rdata = + rdata::createRdata(rrtype, zone_class_, generated_rdata); + // In case we get NULL, it means there was error creating the + // Rdata. The errors should have been reported by callbacks_ + // already. We need to decide if we want to continue or not. + if (rdata) { + add_callback_(*last_name_, zone_class_, rrtype, + getCurrentTTL(explicit_ttl, rrtype, rdata), + rdata); + // Good, we added another one + ++rr_count_; + } else { + seen_error_ = true; + if (!many_errors_) { + ok_ = false; + complete_ = true; + // We don't have the exact error here, but it was + // reported by the error callback. + isc_throw(MasterLoaderError, "Invalid RR data"); + } + } + } +} + // A helper method of loadIncremental, parsing the first token of a new line. // If it looks like an RR, detect its owner name and return a string token for // the next field of the RR. diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc index ce9b8f7844..eedb130080 100644 --- a/src/lib/dns/tests/master_loader_unittest.cc +++ b/src/lib/dns/tests/master_loader_unittest.cc @@ -307,6 +307,338 @@ TEST_F(MasterLoaderTest, origin) { } } +TEST_F(MasterLoaderTest, generate) { + // Various forms of the directive + const char* generates[] = { + "$generate", + "$GENERATE", + "$Generate", + "$GeneratE", + "\"$GENERATE\"", + NULL + }; + for (const char** generate = generates; *generate != NULL; ++generate) { + SCOPED_TRACE(*generate); + + clear(); + const string directive = *generate; + const string input = + "$ORIGIN example.org.\n" + "before.example.org. 3600 IN A 192.0.2.0\n" + + directive + " 3-5 host$ A 192.0.2.$\n" + + "after.example.org. 3600 IN A 192.0.2.255\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("before.example.org", RRType::A(), "192.0.2.0"); + checkRR("host3.example.org", RRType::A(), "192.0.2.3"); + checkRR("host4.example.org", RRType::A(), "192.0.2.4"); + checkRR("host5.example.org", RRType::A(), "192.0.2.5"); + checkRR("after.example.org", RRType::A(), "192.0.2.255"); + } +} + +TEST_F(MasterLoaderTest, generateRelativeLHS) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 1-2 @ 3600 NS ns$.example.org.\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("example.org", RRType::NS(), "ns1.example.org."); + checkRR("example.org", RRType::NS(), "ns2.example.org."); +} + +TEST_F(MasterLoaderTest, generateInFront) { + // $ is in the front + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 9-10 $host 3600 TXT \"$ pomegranate\"\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("9host.example.org", RRType::TXT(), "9 pomegranate"); + checkRR("10host.example.org", RRType::TXT(), "10 pomegranate"); +} + +TEST_F(MasterLoaderTest, generateInMiddle) { + // $ is in the middle + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 9-10 num$-host 3600 TXT \"This is $ pomegranate\"\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("num9-host.example.org", RRType::TXT(), "This is 9 pomegranate"); + checkRR("num10-host.example.org", RRType::TXT(), "This is 10 pomegranate"); +} + +TEST_F(MasterLoaderTest, generateStripsQuotes) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 1-2 @ 3600 MX \"$ mx$.example.org.\"\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("example.org", RRType::MX(), "1 mx1.example.org."); + checkRR("example.org", RRType::MX(), "2 mx2.example.org."); +} + +TEST_F(MasterLoaderTest, generateWithDoublePlaceholder) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 9-10 host$ 3600 TXT \"This is $$ pomegranate\"\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("host9.example.org", RRType::TXT(), "This is $ pomegranate"); + checkRR("host10.example.org", RRType::TXT(), "This is $ pomegranate"); +} + +TEST_F(MasterLoaderTest, generateWithEscape) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 9-10 host$ 3600 TXT \"This is \\$\\pomegranate\"\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("host9.example.org", RRType::TXT(), "This is \\$\\pomegranate"); + checkRR("host10.example.org", RRType::TXT(), "This is \\$\\pomegranate"); +} + +TEST_F(MasterLoaderTest, generateWithParams) { + const string input = + "$ORIGIN example.org.\n" + "$TTL 3600\n" + "$GENERATE 2-3 host$ A 192.0.2.$\n" + "$GENERATE 5-6 host$ 3600 A 192.0.2.$\n" + "$GENERATE 8-9 host$ IN A 192.0.2.$\n" + "$GENERATE 11-12 host$ IN 3600 A 192.0.2.$\n" + "$GENERATE 14-15 host$ 3600 IN A 192.0.2.$\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("host2.example.org", RRType::A(), "192.0.2.2"); + checkRR("host3.example.org", RRType::A(), "192.0.2.3"); + + checkRR("host5.example.org", RRType::A(), "192.0.2.5"); + checkRR("host6.example.org", RRType::A(), "192.0.2.6"); + + checkRR("host8.example.org", RRType::A(), "192.0.2.8"); + checkRR("host9.example.org", RRType::A(), "192.0.2.9"); + + checkRR("host11.example.org", RRType::A(), "192.0.2.11"); + checkRR("host12.example.org", RRType::A(), "192.0.2.12"); + + checkRR("host14.example.org", RRType::A(), "192.0.2.14"); + checkRR("host15.example.org", RRType::A(), "192.0.2.15"); +} + +TEST_F(MasterLoaderTest, generateWithStep) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 2-9/2 host$ 3600 A 192.0.2.$\n" + "$GENERATE 12-21/3 host$ 3600 A 192.0.2.$\n" + "$GENERATE 30-31/1 host$ 3600 A 192.0.2.$\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSucessfully()); + EXPECT_TRUE(errors_.empty()); + + checkRR("host2.example.org", RRType::A(), "192.0.2.2"); + checkRR("host4.example.org", RRType::A(), "192.0.2.4"); + checkRR("host6.example.org", RRType::A(), "192.0.2.6"); + checkRR("host8.example.org", RRType::A(), "192.0.2.8"); + + checkRR("host12.example.org", RRType::A(), "192.0.2.12"); + checkRR("host15.example.org", RRType::A(), "192.0.2.15"); + checkRR("host18.example.org", RRType::A(), "192.0.2.18"); + checkRR("host21.example.org", RRType::A(), "192.0.2.21"); + + checkRR("host30.example.org", RRType::A(), "192.0.2.30"); + checkRR("host31.example.org", RRType::A(), "192.0.2.31"); +} + +TEST_F(MasterLoaderTest, generateMissingRange) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE\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()); // For the broken GENERATE + EXPECT_TRUE(warnings_.empty()); + + checkCallbackMessage(errors_.at(0), + "unexpected end of input", 2); +} + +TEST_F(MasterLoaderTest, generateMissingLHS) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 2-4\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()); // For the broken GENERATE + EXPECT_TRUE(warnings_.empty()); + + checkCallbackMessage(errors_.at(0), + "unexpected end of input", 2); +} + +TEST_F(MasterLoaderTest, generateMissingType) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 2-4 host$\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()); // For the broken GENERATE + EXPECT_TRUE(warnings_.empty()); + + checkCallbackMessage(errors_.at(0), + "unexpected end of input", 2); +} + +TEST_F(MasterLoaderTest, generateMissingRHS) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 2-4 host$ A\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()); // For the broken GENERATE + EXPECT_TRUE(warnings_.empty()); + + checkCallbackMessage(errors_.at(0), + "unexpected end of input", 2); +} + +TEST_F(MasterLoaderTest, generateWithBadRangeSyntax) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE ABCD host$ 3600 A 192.0.2.$\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()); // For the broken GENERATE + EXPECT_TRUE(warnings_.empty()); + + checkCallbackMessage(errors_.at(0), + "$GENERATE: invalid range: ABCD", 2); +} + +TEST_F(MasterLoaderTest, generateWithInvalidRange) { + // start > stop + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 2-1 host$ 3600 A 192.0.2.$\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()); // For the broken GENERATE + EXPECT_TRUE(warnings_.empty()); + + checkCallbackMessage(errors_.at(0), + "$GENERATE: invalid range: 2-1", 2); +} + +TEST_F(MasterLoaderTest, generateWithInvalidClass) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 1-2 host$ 3600 CH A 192.0.2.$\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()); // For the broken GENERATE + EXPECT_TRUE(warnings_.empty()); + + checkCallbackMessage(errors_.at(0), + "Class mismatch: CH vs. IN", 2); +} + +TEST_F(MasterLoaderTest, generateWithNoAvailableTTL) { + const string input = + "$ORIGIN example.org.\n" + "$GENERATE 1-2 host$ A 192.0.2.$\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()); // For the broken GENERATE + EXPECT_TRUE(warnings_.empty()); + + checkCallbackMessage(errors_.at(0), + "no TTL specified; load rejected", 2); +} + // Test the source is correctly popped even after error TEST_F(MasterLoaderTest, popAfterError) { const string include_str = "$include " TEST_DATA_SRCDIR