"q00jkcevqvmu85r014c7dkba38o0ji5r";
hash_map_[Name("nxdomain3.example.com")] =
"009mhaveqvm6t7vbl5lop2u3t2rp3tom";
-
+ hash_map_[Name("*.example.com")] =
+ "r53bq7cc2uvmubfu5ocmm6pers9tk9en";
+ hash_map_[Name("unsigned-delegation.example.com")] =
+ "q81r598950igr1eqvc60aedlq66425b5"; // a bit larger than H(www)
+ hash_map_[Name("*.uwild.example.com")] =
+ "b4um86eghhds6nea196smvmlo4ors995";
+ hash_map_[Name("unsigned-delegation-optout.example.com")] =
+ "vld46lphhasfapj8og1pglgiasa5o5gt";
+
+ // For closest encloser proof for www1.uwild.example.com:
+ hash_map_[Name("uwild.example.com")] =
+ "t644ebqk9bibcna874givr6joj62mlhv";
+ hash_map_[Name("www1.uwild.example.com")] =
+ "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
}
virtual isc::dns::Name getOrigin() const { return (origin_); }
virtual isc::dns::RRClass getClass() const { return (rrclass_); }
// Similar to the previous case, but next closer name is different
// (is the parent) of the non existent name.
- nsec3Check(true, expected_closest_labels,
- string(nsec3_apex_txt) + string(nsec3_www_txt),
- mock_finder->findNSEC3(Name("nx.domain.example.com"), true));
+ {
+ SCOPED_TRACE("nxdomain, next closer != qname");
+ nsec3Check(true, expected_closest_labels,
+ string(nsec3_apex_txt) + string(nsec3_www_txt),
+ mock_finder->findNSEC3(Name("nx.domain.example.com"),
+ true));
+ }
// In the rest of test we check hash comparison for wrap around cases.
- nsec3Check(false, 4, nsec3_apex_txt,
- mock_finder->findNSEC3(Name("nxdomain2.example.com"), false));
- nsec3Check(false, 4, nsec3_www_txt,
- mock_finder->findNSEC3(Name("nxdomain3.example.com"), false));
+ {
+ SCOPED_TRACE("largest");
+ nsec3Check(false, 4, nsec3_apex_txt,
+ mock_finder->findNSEC3(Name("nxdomain2.example.com"),
+ false));
+ }
+ {
+ SCOPED_TRACE("smallest");
+ nsec3Check(false, 4, nsec3_www_txt,
+ mock_finder->findNSEC3(Name("nxdomain3.example.com"),
+ false));
+ }
+ }
+
+ // This tests that the DS is returned above the delegation point as
+ // an authoritative answer, not a delegation. This is as described in
+ // RFC 4035, section 3.1.4.1.
+
+ // This mock finder is used for some DS-query tests to support the cases
+ // where the query is expected to be handled in a different zone than our
+ // main test zone, example.com. Only limited methods are expected to called
+ // (and for limited purposes) on this class object in these tests, which
+ // are overridden below.
+ class AlternateZoneFinder : public MockZoneFinder {
+ public:
+ // This zone is expected not to have a DS by default and return NXRRSET
+ // for a DS query. If have_ds is set to true on construction, it will
+ // return a faked DS answer.
+ AlternateZoneFinder(const Name& origin, bool have_ds = false) :
+ MockZoneFinder(), origin_(origin), have_ds_(have_ds)
+ {}
+ virtual isc::dns::Name getOrigin() const { return (origin_); }
+ virtual FindResult find(const isc::dns::Name&,
+ const isc::dns::RRType& type,
+ const FindOptions)
+ {
+ if (type == RRType::SOA()) {
+ RRsetPtr soa = textToRRset(origin_.toText() + " 3600 IN SOA . . "
+ "0 0 0 0 0\n", origin_);
+ soa->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText("SOA"))));
+ return (FindResult(SUCCESS, soa));
+ }
+ if (type == RRType::NS()) {
+ RRsetPtr ns = textToRRset(origin_.toText() + " 3600 IN NS " +
+ Name("ns").concatenate(origin_).toText());
+ ns->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText("NS"))));
+ return (FindResult(SUCCESS, ns));
+ }
+ if (type == RRType::DS()) {
+ if (have_ds_) {
+ RRsetPtr ds = textToRRset(origin_.toText() +
+ " 3600 IN DS 57855 5 1 " +
+ "49FD46E6C4B45C55D4AC69CBD"
+ "3CD34AC1AFE51DE");
+ ds->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText("DS"))));
+ return (FindResult(SUCCESS, ds));
+ } else {
+ RRsetPtr nsec = textToRRset(origin_.toText() +
+ " 3600 IN NSEC " +
+ origin_.toText() +
+ " SOA NSEC RRSIG");
+ nsec->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText("NSEC"))));
+ return (FindResult(NXRRSET, nsec, RESULT_NSEC_SIGNED));
+ }
+ }
+
+ // Returning NXDOMAIN is not correct, but doesn't matter for our tests.
+ return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+ }
+ private:
+ const Name origin_;
+ const bool have_ds_;
+ };
+
+ TEST_F(QueryTest, dsAboveDelegation) {
+ // Pretending to have authority for the child zone, too.
+ memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
+ Name("delegation.example.com"))));
+
+ // The following will succeed only if the search goes to the parent
+ // zone, not the child one we added above.
+ EXPECT_NO_THROW(Query(memory_client, Name("delegation.example.com"),
+ RRType::DS(), response, true).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
+ (string(delegation_ds_txt) + "\n" +
+ "delegation.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("DS")).c_str(),
+ (string(zone_ns_txt) + "\n" +
+ "example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NS")).c_str(),
+ ns_addrs_and_sig_txt.c_str());
+ }
+
+ TEST_F(QueryTest, dsAboveDelegationNoData) {
+ // Similar to the previous case, but the query is for an unsigned zone
+ // (which doesn't have a DS at the parent). The response should be a
+ // "no data" error. The query should still be handled at the parent.
+ memory_client.addZone(ZoneFinderPtr(
+ new AlternateZoneFinder(
+ Name("unsigned-delegation.example.com"))));
+
+ // The following will succeed only if the search goes to the parent
+ // zone, not the child one we added above.
+ EXPECT_NO_THROW(Query(memory_client,
+ Name("unsigned-delegation.example.com"),
+ RRType::DS(), response, true).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(soa_txt) +
+ string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(unsigned_delegation_nsec_txt) +
+ "unsigned-delegation.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC")).c_str(),
+ NULL, mock_finder->getOrigin());
+ }
+
+ // This one checks that type-DS query results in a "no data" response
+ // when it happens to be sent to the child zone, as described in RFC 4035,
+ // section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
+ TEST_F(QueryTest, dsBelowDelegation) {
+ EXPECT_NO_THROW(Query(memory_client, Name("example.com"),
+ RRType::DS(), response, true).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(nsec_apex_txt) + "\n" +
+ string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NSEC")).c_str(), NULL,
+ mock_finder->getOrigin());
+ }
+
+ // Similar to the previous case, but even more pathological: the DS somehow
+ // exists in the child zone. The Query module should still return SOA.
+ // In our implementation NSEC/NSEC3 isn't attached in this case.
+ TEST_F(QueryTest, dsBelowDelegationWithDS) {
+ mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
+ EXPECT_NO_THROW(Query(memory_client, Name("example.com"),
+ RRType::DS(), response, true).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA")).c_str(), NULL,
+ mock_finder->getOrigin());
+ }
+
+ // DS query received at a completely irrelevant (neither parent nor child)
+ // server. It should just like the "noZone" test case, but DS query involves
+ // special processing, so we test it explicitly.
+ TEST_F(QueryTest, dsNoZone) {
+ Query(memory_client, Name("example"), RRType::DS(), response,
+ true).process();
+ responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
+ }
+
+ // DS query for a "grandchild" zone. This should result in normal
+ // delegation (unless this server also has authority of the grandchild zone).
+ TEST_F(QueryTest, dsAtGrandParent) {
+ Query(memory_client, Name("grand.delegation.example.com"), RRType::DS(),
+ response, true).process();
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
+ (string(delegation_txt) + string(delegation_ds_txt) +
+ "delegation.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("DS")).c_str(),
+ ns_addrs_and_sig_txt.c_str());
+ }
+
+ // DS query sent to a "grandparent" server that also has authority for the
+ // child zone. In this case the query should be handled in the child
+ // side and should result in no data with SOA. Note that the server doesn't
+ // have authority for the "parent". Unlike the dsAboveDelegation test case
+ // the query should be handled in the child zone, not in the grandparent.
+ TEST_F(QueryTest, dsAtGrandParentAndChild) {
+ // Pretending to have authority for the child zone, too.
+ const Name childname("grand.delegation.example.com");
+ memory_client.addZone(ZoneFinderPtr(
+ new AlternateZoneFinder(childname)));
+ Query(memory_client, childname, RRType::DS(), response, true).process();
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
+ childname.toText() + " 3600 IN RRSIG " +
+ getCommonRRSIGText("SOA") + "\n" +
+ childname.toText() + " 3600 IN NSEC " +
+ childname.toText() + " SOA NSEC RRSIG\n" +
+ childname.toText() + " 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC")).c_str(), NULL, childname);
+ }
+
+ // DS query for the root name (quite pathological). Since there's no "parent",
+ // the query will be handled in the root zone anyway, and should (normally)
+ // result in no data.
+ TEST_F(QueryTest, dsAtRoot) {
+ // Pretend to be a root server.
+ memory_client.addZone(ZoneFinderPtr(
+ new AlternateZoneFinder(Name::ROOT_NAME())));
+ Query(memory_client, Name::ROOT_NAME(), RRType::DS(), response,
+ true).process();
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(". 3600 IN SOA . . 0 0 0 0 0\n") +
+ ". 3600 IN RRSIG " + getCommonRRSIGText("SOA") + "\n" +
+ ". 3600 IN NSEC " + ". SOA NSEC RRSIG\n" +
+ ". 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC")).c_str(), NULL);
+ }
+
+ // Even more pathological case: A faked root zone actually has its own DS
+ // query. How we respond wouldn't matter much in practice, but check if
+ // it behaves as it's intended. This implementation should return the DS.
+ TEST_F(QueryTest, dsAtRootWithDS) {
+ memory_client.addZone(ZoneFinderPtr(
+ new AlternateZoneFinder(Name::ROOT_NAME(),
+ true)));
+ Query(memory_client, Name::ROOT_NAME(), RRType::DS(), response,
+ true).process();
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
+ (string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
+ "3CD34AC1AFE51DE\n") +
+ ". 3600 IN RRSIG " + getCommonRRSIGText("DS")).c_str(),
+ (string(". 3600 IN NS ns.\n") +
+ ". 3600 IN RRSIG " + getCommonRRSIGText("NS")).c_str(),
+ NULL);
+ }
+
+ // Check the signature is present when an NXRRSET is returned
+ TEST_F(QueryTest, nxrrsetWithNSEC3) {
+ mock_finder->setNSEC3Flag(true);
+
+ // NXRRSET with DNSSEC proof. We should have SOA, NSEC3 that proves the
+ // NXRRSET and their RRSIGs.
+ Query(memory_client, Name("www.example.com"), RRType::TXT(), response,
+ true).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(nsec3_www_txt) + "\n" +
+ mock_finder->hash_map_[Name("www.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n").c_str(),
+ NULL, mock_finder->getOrigin());
+ }
+
+ // Check the exception is correctly raised when the NSEC3 thing isn't in the
+ // zone
+ TEST_F(QueryTest, nxrrsetMissingNSEC3) {
+ mock_finder->setNSEC3Flag(true);
+ // We just need it to return false for "matched". This indicates
+ // there's no exact match for NSEC3 on www.example.com.
+ ZoneFinder::FindNSEC3Result nsec3(false, 0, ConstRRsetPtr(),
+ ConstRRsetPtr());
+ mock_finder->setNSEC3Result(&nsec3);
+
+ EXPECT_THROW(Query(memory_client, Name("www.example.com"), RRType::TXT(),
+ response, true).process(), Query::BadNSEC3);
+ }
+
+ TEST_F(QueryTest, nxrrsetWithNSEC3_ds_exact) {
+ mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ mock_finder->setNSEC3Flag(true);
+
+ // This delegation has no DS, but does have a matching NSEC3 record
+ // (See RFC5155 section 7.2.4)
+ Query(memory_client, Name("unsigned-delegation.example.com."),
+ RRType::DS(), response, true).process();
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(unsigned_delegation_nsec3_txt) + "\n" +
+ mock_finder->
+ hash_map_[Name("unsigned-delegation.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n").c_str(),
+ NULL, mock_finder->getOrigin());
+ }
+
+ TEST_F(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
+ mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ mock_finder->setNSEC3Flag(true);
+
+ // This delegation has no DS, and no directly matching NSEC3 record
+ // So the response should contain closest encloser proof (and the
+ // 'next closer' should have opt-out set, though that is not
+ // actually checked)
+ // (See RFC5155 section 7.2.4)
+ Query(memory_client, Name("unsigned-delegation-optout.example.com."),
+ RRType::DS(), response, true).process();
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(nsec3_apex_txt) + "\n" +
+ mock_finder->hash_map_[Name("example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n" +
+ string(unsigned_delegation_nsec3_txt) + "\n" +
+ mock_finder->
+ hash_map_[Name("unsigned-delegation.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n").c_str(),
+ NULL, mock_finder->getOrigin());
}
-// The following are tentative tests until we really add tests for the
-// query logic for these cases. At that point it's probably better to
-// clean them up.
-TEST_F(QueryTest, nxdomainWithNSEC3) {
+TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
mock_finder->setNSEC3Flag(true);
- ZoneFinder::FindResult result = mock_finder->find(
- Name("nxdomain.example.com"), RRType::A(), ZoneFinder::FIND_DNSSEC);
- EXPECT_EQ(ZoneFinder::NXDOMAIN, result.code);
- EXPECT_FALSE(result.rrset);
- EXPECT_TRUE(result.isNSEC3Signed());
- EXPECT_FALSE(result.isWildcard());
+ ZoneFinder::FindNSEC3Result nsec3(true, 0, textToRRset(nsec3_apex_txt),
+ ConstRRsetPtr());
+ mock_finder->setNSEC3Result(&nsec3);
+
+ EXPECT_THROW(Query(memory_client, Name("nxdomain.example.com"),
+ RRType::TXT(), response, true).process(),
+ Query::BadNSEC3);
}
- TEST_F(QueryTest, nxrrsetWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
- ZoneFinder::FindResult result = mock_finder->find(
- Name("www.example.com"), RRType::TXT(), ZoneFinder::FIND_DNSSEC);
- EXPECT_EQ(ZoneFinder::NXRRSET, result.code);
- EXPECT_FALSE(result.rrset);
- EXPECT_TRUE(result.isNSEC3Signed());
- EXPECT_FALSE(result.isWildcard());
- }
-
+TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
+ mock_finder->setNSEC3Flag(true);
+ Query(memory_client, Name("nxdomain.example.com"), qtype,
+ response, true).process();
+ responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
+ NULL, (string(soa_txt) +
+ string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(nsec3_apex_txt) + "\n" +
+ string("0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NSEC3") + "\n" +
+ string(nsec3_www_txt) + "\n" +
+ string("q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NSEC3")).c_str(),
+ NULL, mock_finder->getOrigin());
+}
+
+// The following are tentative tests until we really add tests for the
+// query logic for these cases. At that point it's probably better to
+// clean them up.
TEST_F(QueryTest, emptyNameWithNSEC3) {
mock_finder->setNSEC3Flag(true);
ZoneFinder::FindResult result = mock_finder->find(