Commit 1f051716 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge branch #1066

parents d916aef6 27b3488b
......@@ -175,7 +175,8 @@ std::pair<bool, isc::dns::RRsetPtr>
DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
const isc::dns::RRType* type,
bool want_cname, bool want_dname,
bool want_ns)
bool want_ns,
const isc::dns::Name* construct_name)
{
RRsigStore sig_store;
bool records_found = false;
......@@ -191,6 +192,9 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
}
std::string columns[DatabaseAccessor::COLUMN_COUNT];
if (construct_name == NULL) {
construct_name = &name;
}
while (context->getNext(columns)) {
if (!records_found) {
records_found = true;
......@@ -225,7 +229,8 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "NS found together with data"
" in non-apex domain " + name.toText());
}
addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
addOrCreate(result_rrset, *construct_name, getClass(),
cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
*database_);
} else if (type != NULL && cur_type == *type) {
......@@ -238,7 +243,8 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "NS found together with data"
" in non-apex domain " + name.toText());
}
addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
addOrCreate(result_rrset, *construct_name, getClass(),
cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
*database_);
} else if (want_cname && cur_type == isc::dns::RRType::CNAME()) {
......@@ -248,7 +254,8 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "CNAME found but it is not "
"the only record for " + name.toText());
}
addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
addOrCreate(result_rrset, *construct_name, getClass(),
cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
*database_);
} else if (want_dname && cur_type == isc::dns::RRType::DNAME()) {
......@@ -258,7 +265,8 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "DNAME with multiple RRs in " +
name.toText());
}
addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
addOrCreate(result_rrset, *construct_name, getClass(),
cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
*database_);
} else if (cur_type == isc::dns::RRType::RRSIG()) {
......@@ -292,6 +300,20 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
return (std::pair<bool, isc::dns::RRsetPtr>(records_found, result_rrset));
}
bool
DatabaseClient::Finder::hasSubdomains(const std::string& name) {
// Request the context
DatabaseAccessor::IteratorContextPtr
context(database_->getRecords(name, zone_id_, true));
// It must not return NULL, that's a bug of the implementation
if (!context) {
isc_throw(isc::Unexpected, "Iterator context null at " + name);
}
std::string columns[DatabaseAccessor::COLUMN_COUNT];
return (context->getNext(columns));
}
ZoneFinder::FindResult
DatabaseClient::Finder::find(const isc::dns::Name& name,
const isc::dns::RRType& type,
......@@ -307,20 +329,35 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
std::pair<bool, isc::dns::RRsetPtr> found;
logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
.arg(database_->getDBName()).arg(name).arg(type);
// In case we are in GLUE_OK mode and start matching wildcards,
// we can't do it under NS, so we store it here to check
isc::dns::RRsetPtr first_ns;
// First, do we have any kind of delegation (NS/DNAME) here?
const Name origin(getOrigin());
const size_t origin_label_count(origin.getLabelCount());
const size_t current_label_count(name.getLabelCount());
Name origin(getOrigin());
size_t origin_label_count(origin.getLabelCount());
// Number of labels in the last known non-empty domain
size_t last_known(origin_label_count);
size_t current_label_count(name.getLabelCount());
// This is how many labels we remove to get origin
const size_t remove_labels(current_label_count - origin_label_count);
size_t remove_labels(current_label_count - origin_label_count);
// Now go trough all superdomains from origin down
for (int i(remove_labels); i > 0; --i) {
const Name superdomain(name.split(i));
Name superdomain(name.split(i));
// Look if there's NS or DNAME (but ignore the NS in origin)
found = getRRset(superdomain, NULL, false, true,
i != remove_labels && !glue_ok);
if (found.first) {
// It contains some RRs, so it exists.
last_known = superdomain.getLabelCount();
// In case we are in GLUE_OK, we want to store the highest
// encountered RRset.
if (glue_ok && !first_ns && i != remove_labels) {
first_ns = getRRset(superdomain, NULL, false, false,
true).second;
}
}
if (found.second) {
// We found something redirecting somewhere else
// (it can be only NS or DNAME here)
......@@ -360,18 +397,73 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
}
if (!result_rrset && !records_found) {
// Request the context
DatabaseAccessor::IteratorContextPtr
context(database_->getRecords(name.toText(), zone_id_, true));
// It must not return NULL, that's a bug of the implementation
if (!context) {
isc_throw(isc::Unexpected, "Iterator context null at " +
name.toText());
}
std::string columns[DatabaseAccessor::COLUMN_COUNT];
if (context->getNext(columns)) {
// Nothing lives here.
// But check if something lives below this
// domain and if so, pretend something is here as well.
if (hasSubdomains(name.toText())) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
arg(database_->getDBName()).arg(name);
records_found = true;
} else {
// It's not empty non-terminal. So check for wildcards.
// We remove labels one by one and look for the wildcard there.
// Go up to first non-empty domain.
remove_labels = current_label_count - last_known;
Name star("*");
for (size_t i(1); i <= remove_labels; ++ i) {
// Construct the name with *
// TODO: Once the underlying DatabaseAccessor takes
// string, do the concatenation on strings, not
// Names
Name superdomain(name.split(i));
Name wildcard(star.concatenate(superdomain));
// TODO What do we do about DNAME here?
found = getRRset(wildcard, &type, true, false, true,
&name);
if (found.first) {
if (first_ns) {
// In case we are under NS, we don't
// wildcard-match, but return delegation
result_rrset = first_ns;
result_status = DELEGATION;
records_found = true;
// We pretend to switch to non-glue_ok mode
glue_ok = false;
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD_CANCEL_NS).
arg(database_->getDBName()).arg(wildcard).
arg(first_ns->getName());
} else if (!hasSubdomains(name.split(i - 1).toText()))
{
// Nothing we added as part of the * can exist
// directly, as we go up only to first existing
// domain, but it could be empty non-terminal. In
// that case, we need to cancel the match.
records_found = true;
result_rrset = found.second;
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD).
arg(database_->getDBName()).arg(wildcard).
arg(name);
} else {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
arg(database_->getDBName()).arg(wildcard).
arg(name).arg(superdomain);
}
break;
} else if (hasSubdomains(wildcard.toText())) {
// Empty non-terminal asterisk
records_found = true;
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD_EMPTY).
arg(database_->getDBName()).arg(wildcard).
arg(name);
break;
}
}
}
}
}
......
......@@ -375,6 +375,9 @@ public:
* DataSourceError.
* \param want_ns This allows redirection by NS to be returned. If
* any other data is met as well, DataSourceError is thrown.
* \param construct_name If set to non-NULL, the resulting RRset will
* be constructed for this name instead of the queried one. This
* is useful for wildcards.
* \note It may happen that some of the above error conditions are not
* detected in some circumstances. The goal here is not to validate
* the domain in DB, but to avoid bad behaviour resulting from
......@@ -392,7 +395,18 @@ public:
type,
bool want_cname,
bool want_dname,
bool want_ns);
bool want_ns, const
isc::dns::Name*
construct_name = NULL);
/**
* \brief Checks if something lives below this domain.
*
* This looks if there's any subdomain of the given name. It can be
* used to test if domain is empty non-terminal.
*
* \param name The domain to check.
*/
bool hasSubdomains(const std::string& name);
};
/**
* \brief Find a zone in the database
......
......@@ -87,6 +87,11 @@ When searching for a domain, the program met a DNAME redirection to a different
place in the domain space at the given domain name. It will return that one
instead.
% DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1
The domain name doesn't have any RRs, so it doesn't exist in the database.
However, it has a subdomain, so it exists in the DNS address space. So we
return NXRRSET instead of NXDOMAIN.
% DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4
The data returned by the database backend did not contain any data for the given
domain name, class and type.
......@@ -117,6 +122,28 @@ were found to be different. This isn't allowed on the wire and is considered
an error, so we set it to the lowest value we found (but we don't modify the
database). The data in database should be checked and fixed.
% DATASRC_DATABASE_WILDCARD constructing RRset %3 from wildcard %2 in %1
The database doesn't contain directly matching domain, but it does contain a
wildcard one which is being used to synthesize the answer.
% DATASRC_DATABASE_WILDCARD_CANCEL_NS canceled wildcard match on %2 because %3 contains NS in %1
The database was queried to provide glue data and it didn't find direct match.
It could create it from given wildcard, but matching wildcards is forbidden
under a zone cut, which was found. Therefore the delegation will be returned
instead.
% DATASRC_DATABASE_WILDCARD_CANCEL_SUB wildcard %2 can't be used to construct %3 because %4 exists in %1
The answer could be constructed using the wildcard, but the given subdomain
exists, therefore this name is something like empty non-terminal (actually,
from the protocol point of view, it is empty non-terminal, but the code
discovers it differently).
% DATASRC_DATABASE_WILDCARD_EMPTY implicit wildcard %2 used to construct %3 in %1
The given wildcard exists implicitly in the domainspace, as empty nonterminal
(eg. there's something like subdomain.*.example.org, so *.example.org exists
implicitly, but is empty). This will produce NXRRSET, because the constructed
domain is empty as well as the wildcard.
% DATASRC_DO_QUERY handling query for '%1/%2'
A debug message indicating that a query for the given name and RR type is being
processed.
......
......@@ -458,6 +458,20 @@ private:
// This is because of empty domain test
addRecord("A", "3600", "", "192.0.2.1");
addCurName("a.b.example.org.");
// Something for wildcards
addRecord("A", "3600", "", "192.0.2.5");
addCurName("*.wild.example.org.");
addRecord("AAAA", "3600", "", "2001:db8::5");
addCurName("cancel.here.wild.example.org.");
addRecord("NS", "3600", "", "ns.example.com.");
addCurName("delegatedwild.example.org.");
addRecord("A", "3600", "", "192.0.2.5");
addCurName("*.delegatedwild.example.org.");
addRecord("A", "3600", "", "192.0.2.5");
addCurName("wild.*.foo.example.org.");
addRecord("A", "3600", "", "192.0.2.5");
addCurName("wild.*.foo.*.bar.example.org.");
}
};
......@@ -670,12 +684,12 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
ZoneFinder::FindResult result =
finder->find(name, type, NULL, options);
ASSERT_EQ(expected_result, result.code) << name << " " << type;
if (expected_rdatas.size() > 0) {
if (!expected_rdatas.empty()) {
checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
name, finder->getClass(), expected_type, expected_ttl,
expected_rdatas);
if (expected_sig_rdatas.size() > 0) {
if (!expected_sig_rdatas.empty()) {
checkRRset(result.rrset->getRRsig(), expected_name != Name(".") ?
expected_name : name, finder->getClass(),
isc::dns::RRType::RRSIG(), expected_ttl,
......@@ -1074,6 +1088,16 @@ TEST_F(DatabaseClientTest, findDelegation) {
DataSourceError);
}
TEST_F(DatabaseClientTest, emptyDomain) {
shared_ptr<DatabaseClient::Finder> finder(getFinder());
// This domain doesn't exist, but a subdomain of it does.
// Therefore we should pretend the domain is there, but contains no RRsets
doFindTest(finder, isc::dns::Name("b.example.org."), isc::dns::RRType::A(),
isc::dns::RRType::A(), isc::dns::RRTTL(3600),
ZoneFinder::NXRRSET, expected_rdatas_, expected_sig_rdatas_);
}
// Glue-OK mode. Just go trough NS delegations down (but not trough
// DNAME) and pretend it is not there.
TEST_F(DatabaseClientTest, glueOK) {
......@@ -1133,15 +1157,127 @@ TEST_F(DatabaseClientTest, glueOK) {
ZoneFinder::FIND_GLUE_OK);
}
TEST_F(DatabaseClientTest, empty) {
TEST_F(DatabaseClientTest, wildcard) {
shared_ptr<DatabaseClient::Finder> finder(getFinder());
// Check empty domain
// This domain doesn't exist, but a subdomain of it does.
// Therefore we should pretend the domain is there, but contains no RRsets
doFindTest(finder, isc::dns::Name("b.example.org."), isc::dns::RRType::A(),
isc::dns::RRType::A(), isc::dns::RRTTL(3600),
ZoneFinder::NXRRSET, expected_rdatas_, expected_sig_rdatas_);
// First, simple wildcard match
expected_rdatas_.push_back("192.0.2.5");
doFindTest(finder, isc::dns::Name("a.wild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::A(),
isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
expected_sig_rdatas_);
doFindTest(finder, isc::dns::Name("b.a.wild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::A(),
isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
expected_sig_rdatas_);
expected_rdatas_.clear();
doFindTest(finder, isc::dns::Name("a.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
expected_sig_rdatas_);
doFindTest(finder, isc::dns::Name("b.a.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
expected_sig_rdatas_);
// Direct request for thi wildcard
expected_rdatas_.push_back("192.0.2.5");
doFindTest(finder, isc::dns::Name("*.wild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::A(),
isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
expected_sig_rdatas_);
expected_rdatas_.clear();
doFindTest(finder, isc::dns::Name("*.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
expected_sig_rdatas_);
// This is nonsense, but check it doesn't match by some stupid accident
doFindTest(finder, isc::dns::Name("a.*.wild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::A(),
isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
expected_rdatas_, expected_sig_rdatas_);
// These should be canceled, since it is below a domain which exitsts
doFindTest(finder, isc::dns::Name("nothing.here.wild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::A(),
isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
expected_rdatas_, expected_sig_rdatas_);
doFindTest(finder, isc::dns::Name("cancel.here.wild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::A(),
isc::dns::RRTTL(3600), ZoneFinder::NXRRSET,
expected_rdatas_, expected_sig_rdatas_);
doFindTest(finder,
isc::dns::Name("below.cancel.here.wild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::A(),
isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
expected_rdatas_, expected_sig_rdatas_);
// And this should be just plain empty non-terminal domain, check
// the wildcard doesn't hurt it
doFindTest(finder, isc::dns::Name("here.wild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::A(),
isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
expected_sig_rdatas_);
// Also make sure that the wildcard doesn't hurt the original data
// below the wildcard
expected_rdatas_.push_back("2001:db8::5");
doFindTest(finder, isc::dns::Name("cancel.here.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
expected_rdatas_, expected_sig_rdatas_);
expected_rdatas_.clear();
// How wildcard go together with delegation
expected_rdatas_.push_back("ns.example.com.");
doFindTest(finder, isc::dns::Name("below.delegatedwild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::NS(),
isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
expected_sig_rdatas_,
isc::dns::Name("delegatedwild.example.org"));
// FIXME: This doesn't look logically OK, GLUE_OK should make it transparent,
// so the match should either work or be canceled, but return NXDOMAIN
doFindTest(finder, isc::dns::Name("below.delegatedwild.example.org"),
isc::dns::RRType::A(), isc::dns::RRType::NS(),
isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
expected_sig_rdatas_,
isc::dns::Name("delegatedwild.example.org"),
ZoneFinder::FIND_GLUE_OK);
expected_rdatas_.clear();
expected_rdatas_.push_back("192.0.2.5");
// These are direct matches
const char* positive_names[] = {
"wild.*.foo.example.org.",
"wild.*.foo.*.bar.example.org.",
NULL
};
for (const char** name(positive_names); *name != NULL; ++ name) {
doFindTest(finder, isc::dns::Name(*name), isc::dns::RRType::A(),
isc::dns::RRType::A(), isc::dns::RRTTL(3600),
ZoneFinder::SUCCESS, expected_rdatas_,
expected_sig_rdatas_);
}
// These are wildcard matches against empty nonterminal asterisk
expected_rdatas_.clear();
const char* negative_names[] = {
"a.foo.example.org.",
"*.foo.example.org.",
"foo.example.org.",
"wild.bar.foo.example.org.",
"baz.foo.*.bar.example.org",
"baz.foo.baz.bar.example.org",
"*.foo.baz.bar.example.org",
"*.foo.*.bar.example.org",
"foo.*.bar.example.org",
"*.bar.example.org",
"bar.example.org",
NULL
};
for (const char** name(negative_names); *name != NULL; ++ name) {
doFindTest(finder, isc::dns::Name(*name), isc::dns::RRType::A(),
isc::dns::RRType::A(), isc::dns::RRTTL(3600),
ZoneFinder::NXRRSET, expected_rdatas_,
expected_sig_rdatas_);
}
}
TEST_F(DatabaseClientTest, getOrigin) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment