BIND is the popular implementation of a DNS server, developer
@@ -13,7 +13,7 @@
and provides a modular environment for serving and maintaining DNS.
Note
BIND 10 provides a EDNS0- and DNSSEC-capable
authoritative DNS server and a forwarding DNS server.
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 70f6a30af97bdddf412f80cc168e5ca9ed141722..3670c460ad7e1eb88adf64c3eeb85d1b52274a4f 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -982,6 +982,8 @@ accounts_file
The control commands are:
print_settings
+
+
shutdown
diff --git a/src/lib/asiolink/internal/coroutine.h b/ext/coroutine/coroutine.h
similarity index 100%
rename from src/lib/asiolink/internal/coroutine.h
rename to ext/coroutine/coroutine.h
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index e9097f21df09ca95ad0f4fadeeb2ab1c085e40ae..36de53dc6eabcff497e1e904c8b297a36ce1042e 100644
--- a/src/bin/auth/Makefile.am
+++ b/src/bin/auth/Makefile.am
@@ -50,6 +50,7 @@ b10_auth_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
b10_auth_LDADD += $(top_builddir)/src/lib/cc/libcc.la
b10_auth_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
b10_auth_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+b10_auth_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
b10_auth_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
b10_auth_LDADD += $(SQLITE_LIBS)
diff --git a/src/bin/auth/auth.spec.pre.in b/src/bin/auth/auth.spec.pre.in
index 8a77455d3ef64ad4658cea060169b353a1588b26..7cb571c95943f6091632ce81b674007ab2904729 100644
--- a/src/bin/auth/auth.spec.pre.in
+++ b/src/bin/auth/auth.spec.pre.in
@@ -12,45 +12,44 @@
"item_type": "list",
"item_optional": true,
"item_default": [],
- "list_item_spec": {
- "item_name": "list_element",
+ "list_item_spec":
+ { "item_name": "list_element",
"item_type": "map",
"item_optional": false,
"item_default": {},
- "map_item_spec": [
- { "item_name": "type",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- },
- { "item_name": "class",
- "item_type": "string",
- "item_optional": false,
- "item_default": "IN"
- },
- { "item_name": "zones",
- "item_type": "list",
- "item_optional": false,
- "item_default": [],
- "list_item_spec": {
- "item_name": "list_element",
- "item_type": "map",
- "item_optional": true,
- "map_item_spec": [
- { "item_name": "origin",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- },
- { "item_name": "file",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- }
- ]
- }
- }
- ]
+ "map_item_spec": [
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "class",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "IN"
+ },
+ { "item_name": "zones",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "list_element",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": { "origin": "", "file": "" },
+ "map_item_spec": [
+ { "item_name": "origin",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "file",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }]
+ }
+ }]
}
},
{ "item_name": "statistics-interval",
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index b8d57302e941c20c872e8d8ea9c1cbf6298b285d..045fe7f65f1434e8c03545bedbb3612012b10bfe 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -354,7 +354,7 @@ AuthSrv::setMemoryDataSrc(const isc::dns::RRClass& rrclass,
uint32_t
AuthSrv::getStatisticsTimerInterval() const {
- return (impl_->statistics_timer_.getInterval());
+ return (impl_->statistics_timer_.getInterval() / 1000);
}
void
@@ -362,11 +362,17 @@ AuthSrv::setStatisticsTimerInterval(uint32_t interval) {
if (interval == impl_->statistics_timer_.getInterval()) {
return;
}
+ if (interval > 86400) {
+ // It can't occur since the value is checked in
+ // statisticsIntervalConfig::build().
+ isc_throw(InvalidParameter, "Too long interval: " << interval);
+ }
if (interval == 0) {
impl_->statistics_timer_.cancel();
} else {
- impl_->statistics_timer_.setupTimer(
- boost::bind(&AuthSrv::submitStatistics, this), interval);
+ impl_->statistics_timer_.setup(boost::bind(&AuthSrv::submitStatistics,
+ this),
+ interval * 1000);
}
if (impl_->verbose_mode_) {
if (interval == 0) {
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index 7806be9667da84b9a0f73997e2b2202b3bbd0afa..4772a02d64ec428f3c1abac85a5f072749305cc6 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -318,7 +318,8 @@ public:
/// If the specified value is non 0, the \c AuthSrv object will submit
/// its statistics to the statistics module every \c interval seconds.
/// If it's 0, and \c AuthSrv currently submits statistics, the submission
- /// will be disabled.
+ /// will be disabled. \c interval must be equal to or shorter than 86400
+ /// seconds (1 day).
///
/// This method should normally not throw an exception; however, its
/// underlying library routines may involve resource allocation, and
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index 12e24bd98f8e6ebecd8aed26f79213f968f70d44..b22d24dc9cd9c0be1d6e5011d85eae6cf4ecea35 100644
--- a/src/bin/auth/b10-auth.xml
+++ b/src/bin/auth/b10-auth.xml
@@ -134,7 +134,7 @@
The port number it listens on.
The default is 5300.
- The Y1 prototype runs on all interfaces
+ This prototype runs on all interfaces
and on this nonstandard port.
diff --git a/src/bin/auth/benchmarks/Makefile.am b/src/bin/auth/benchmarks/Makefile.am
index 165bb4c025179b8d960dd9e1810d4eb10d172c23..653d5025f34ca14561a405d249d9cefa975db5a9 100644
--- a/src/bin/auth/benchmarks/Makefile.am
+++ b/src/bin/auth/benchmarks/Makefile.am
@@ -20,5 +20,7 @@ query_bench_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
query_bench_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
query_bench_LDADD += $(top_builddir)/src/lib/cc/libcc.la
query_bench_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+query_bench_LDADD += $(top_builddir)/src/lib/log/liblog.la
+query_bench_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
query_bench_LDADD += $(SQLITE_LIBS)
diff --git a/src/bin/auth/config.cc b/src/bin/auth/config.cc
index 1f258e32ad4d76d3604961e11825accc8b5b8c38..5befc6e2c31b809458aa087d4e9fd8480d57d296 100644
--- a/src/bin/auth/config.cc
+++ b/src/bin/auth/config.cc
@@ -179,9 +179,14 @@ public:
virtual void build(ConstElementPtr config_value) {
const int32_t config_interval = config_value->intValue();
if (config_interval < 0) {
- isc_throw(AuthConfigError, "negative statistics-interval value: "
+ isc_throw(AuthConfigError, "Negative statistics interval value: "
<< config_interval);
}
+ if (config_interval > 86400) {
+ isc_throw(AuthConfigError, "Statistics interval value "
+ << config_interval
+ << " must be equal to or shorter than 86400");
+ }
interval_ = config_interval;
}
virtual void commit() {
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index e270500da76f21dcbfc0c91b1bfdaf6ffa1dbeb0..e936c97f5a5a88deb19379370e9a31d70883696f 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -141,15 +141,70 @@ Query::process() const {
// Found a zone which is the nearest ancestor to QNAME, set the AA bit
response_.setHeaderFlag(Message::HEADERFLAG_AA);
+ response_.setRcode(Rcode::NOERROR());
while (keep_doing) {
keep_doing = false;
std::auto_ptr target(qtype_is_any ? new RRsetList : NULL);
- Zone::FindResult db_result =
- result.zone->find(qname_, qtype_, target.get());
+ const Zone::FindResult db_result(result.zone->find(qname_, qtype_,
+ target.get()));
switch (db_result.code) {
+ case Zone::DNAME: {
+ // First, put the dname into the answer
+ response_.addRRset(Message::SECTION_ANSWER,
+ boost::const_pointer_cast(db_result.rrset));
+ /*
+ * Empty DNAME should never get in, as it is impossible to
+ * create one in master file.
+ *
+ * FIXME: Other way to prevent this should be done
+ */
+ assert(db_result.rrset->getRdataCount() > 0);
+ // Get the data of DNAME
+ const rdata::generic::DNAME& dname(
+ dynamic_cast(
+ db_result.rrset->getRdataIterator()->getCurrent()));
+ // The yet unmatched prefix dname
+ const Name prefix(qname_.split(0, qname_.getLabelCount() -
+ db_result.rrset->getName().getLabelCount()));
+ // If we put it together, will it be too long?
+ // (The prefix contains trailing ., which will be removed
+ if (prefix.getLength() - Name::ROOT_NAME().getLength() +
+ dname.getDname().getLength() > Name::MAX_WIRE) {
+ /*
+ * In case the synthesized name is too long, section 4.1
+ * of RFC 2672 mandates we return YXDOMAIN.
+ */
+ response_.setRcode(Rcode::YXDOMAIN());
+ return;
+ }
+ // The new CNAME we are creating (it will be unsigned even
+ // with DNSSEC, the DNAME is signed and it can be validated
+ // by that)
+ RRsetPtr cname(new RRset(qname_, db_result.rrset->getClass(),
+ RRType::CNAME(), db_result.rrset->getTTL()));
+ // Construct the new target by replacing the end
+ cname->addRdata(rdata::generic::CNAME(qname_.split(0,
+ qname_.getLabelCount() -
+ db_result.rrset->getName().getLabelCount()).
+ concatenate(dname.getDname())));
+ response_.addRRset(Message::SECTION_ANSWER, cname);
+ break;
+ }
+ case Zone::CNAME:
+ /*
+ * We don't do chaining yet. Therefore handling a CNAME is
+ * mostly the same as handling SUCCESS, but we didn't get
+ * what we expected. It means no exceptions in ANY or NS
+ * on the origin (though CNAME in origin is probably
+ * forbidden anyway).
+ *
+ * So, just put it there.
+ */
+ response_.addRRset(Message::SECTION_ANSWER,
+ boost::const_pointer_cast(db_result.rrset));
+ break;
case Zone::SUCCESS:
- response_.setRcode(Rcode::NOERROR());
if (qtype_is_any) {
// If quety type is ANY, insert all RRs under the domain
// into answer section.
@@ -167,6 +222,7 @@ Query::process() const {
// and AAAA/A RRS of each of the NS RDATA into the additional
// section.
if (qname_ != result.zone->getOrigin() ||
+ db_result.code != Zone::SUCCESS ||
(qtype_ != RRType::NS() && !qtype_is_any))
{
getAuthAdditional(*result.zone);
@@ -174,7 +230,6 @@ Query::process() const {
break;
case Zone::DELEGATION:
response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
- response_.setRcode(Rcode::NOERROR());
response_.addRRset(Message::SECTION_AUTHORITY,
boost::const_pointer_cast(db_result.rrset));
getAdditional(*result.zone, *db_result.rrset);
@@ -186,13 +241,8 @@ Query::process() const {
break;
case Zone::NXRRSET:
// Just empty answer with SOA in authority section
- response_.setRcode(Rcode::NOERROR());
putSOA(*result.zone);
break;
- case Zone::CNAME:
- case Zone::DNAME:
- // TODO : replace qname, continue lookup
- break;
}
}
}
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 72984982b036620cba2e0978f453b1616b5dc4e9..def99b05641a8bc2f655550c467a300c8140d9e1 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -44,6 +44,8 @@ run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/auth/tests/command_unittest.cc b/src/bin/auth/tests/command_unittest.cc
index 0ba5e861f0f26fd5cab8a585d398dd6542a6b934..f788d9e4919e9e1cb0ab03b1ddebfd5b619e5703 100644
--- a/src/bin/auth/tests/command_unittest.cc
+++ b/src/bin/auth/tests/command_unittest.cc
@@ -98,7 +98,7 @@ AuthConmmandTest::stopServer() {
TEST_F(AuthConmmandTest, shutdown) {
asiolink::IntervalTimer itimer(server.getIOService());
- itimer.setupTimer(boost::bind(&AuthConmmandTest::stopServer, this), 1);
+ itimer.setup(boost::bind(&AuthConmmandTest::stopServer, this), 1);
server.getIOService().run();
EXPECT_EQ(0, rcode);
}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index 0e0aee9199fce93c1214a8f0113361a4e81f3895..b8b379e0c56ca12a4835a80b03caf726bd0926fd 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -365,5 +365,9 @@ TEST_F(StatisticsIntervalConfigTest, badInterval) {
EXPECT_THROW(parser->build(Element::fromJSON("2.5")),
isc::data::TypeError);
EXPECT_THROW(parser->build(Element::fromJSON("-1")), AuthConfigError);
+ // bounds check: interval value must be equal to or shorter than
+ // 86400 seconds (1 day)
+ EXPECT_NO_THROW(parser->build(Element::fromJSON("86400")));
+ EXPECT_THROW(parser->build(Element::fromJSON("86401")), AuthConfigError);
}
}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 6ea0ac379b4230b75cc386792c9652f682167a08..2d3cf03a621ea6d00a3f453d9898b802ef2ba66a 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -68,9 +68,26 @@ const char* const mx_txt =
"mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
"mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
+const char* const cname_txt =
+ "cname.example.com. 3600 IN CNAME www.example.com.\n";
+const char* const cname_nxdom_txt =
+ "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
+// CNAME Leading out of zone
+const char* const cname_out_txt =
+ "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
+// The DNAME to do tests against
+const char* const dname_txt =
+ "dname.example.com. 3600 IN DNAME "
+ "somethinglong.dnametarget.example.com.\n";
+// Some data at the dname node (allowed by RFC 2672)
+const char* const dname_a_txt =
+ "dname.example.com. 3600 IN A 192.0.2.5\n";
+// This is not inside the zone, this is created at runtime
+const char* const synthetized_cname_txt =
+ "www.dname.example.com. 3600 IN CNAME "
+ "www.somethinglong.dnametarget.example.com.\n";
// The rest of data won't be referenced from the test cases.
const char* const other_zone_rrs =
- "cname.example.com. 3600 IN CNAME www.example.com.\n"
"cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
"cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
"mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
@@ -82,20 +99,25 @@ const char* const other_zone_rrs =
// behavior.
// For simplicity, most names are assumed to be "in zone"; there's only
// one zone cut at the point of name "delegation.example.com".
-// It doesn't handle empty non terminal nodes (if we need to test such cases
-// find() should have specialized code for it).
+// Another special name is "dname.example.com". Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
class MockZone : public Zone {
public:
MockZone() :
origin_(Name("example.com")),
delegation_name_("delegation.example.com"),
+ dname_name_("dname.example.com"),
has_SOA_(true),
has_apex_NS_(true),
rrclass_(RRClass::IN())
{
stringstream zone_stream;
zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
- delegation_txt << mx_txt << www_a_txt << other_zone_rrs;
+ delegation_txt << mx_txt << www_a_txt << cname_txt <<
+ cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
+ other_zone_rrs;
masterLoad(zone_stream, origin_, rrclass_,
boost::bind(&MockZone::loadRRset, this, _1));
@@ -124,14 +146,20 @@ private:
if (rrset->getName() == delegation_name_ &&
rrset->getType() == RRType::NS()) {
delegation_rrset_ = rrset;
+ } else if (rrset->getName() == dname_name_ &&
+ rrset->getType() == RRType::DNAME()) {
+ dname_rrset_ = rrset;
}
}
const Name origin_;
+ // Names where we delegate somewhere else
const Name delegation_name_;
+ const Name dname_name_;
bool has_SOA_;
bool has_apex_NS_;
ConstRRsetPtr delegation_rrset_;
+ ConstRRsetPtr dname_rrset_;
const RRClass rrclass_;
};
@@ -153,6 +181,10 @@ MockZone::find(const Name& name, const RRType& type,
name.compare(delegation_name_).getRelation() ==
NameComparisonResult::SUBDOMAIN)) {
return (FindResult(DELEGATION, delegation_rrset_));
+ // And under DNAME
+ } else if (name.compare(dname_name_).getRelation() ==
+ NameComparisonResult::SUBDOMAIN) {
+ return (FindResult(DNAME, dname_rrset_));
}
// normal cases. names are searched for only per exact-match basis
@@ -169,8 +201,7 @@ MockZone::find(const Name& name, const RRType& type,
// If not found but we have a target, fill it with all RRsets here
if (!found_domain->second.empty() && target != NULL) {
for (found_rrset = found_domain->second.begin();
- found_rrset != found_domain->second.end(); found_rrset++)
- {
+ found_rrset != found_domain->second.end(); found_rrset++) {
// Insert RRs under the domain name into target
target->addRRset(
boost::const_pointer_cast(found_rrset->second));
@@ -422,4 +453,226 @@ TEST_F(QueryTest, MXAlias) {
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
NULL, NULL, ns_addrs_txt);
}
+
+/*
+ * Tests encountering a cname.
+ *
+ * There are tests leading to successful answers, NXRRSET, NXDOMAIN and
+ * out of the zone.
+ *
+ * TODO: We currently don't do chaining, so only the CNAME itself should be
+ * returned.
+ */
+TEST_F(QueryTest, CNAME) {
+ Query(memory_datasrc, Name("cname.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_txt, NULL, NULL);
+}
+
+TEST_F(QueryTest, explicitCNAME) {
+ // same owner name as the CNAME test but explicitly query for CNAME RR.
+ // expect the same response as we don't provide a full chain yet.
+ Query(memory_datasrc, Name("cname.example.com"), RRType::CNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ cname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_NX_RRSET) {
+ // Leads to www.example.com, it doesn't have TXT
+ // note: with chaining, what should be expected is not trivial:
+ // BIND 9 returns the CNAME in answer and SOA in authority, no additional.
+ // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
+ Query(memory_datasrc, Name("cname.example.com"), RRType::TXT(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_txt, NULL, NULL);
+}
+
+TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
+ // same owner name as the NXRRSET test but explicitly query for CNAME RR.
+ Query(memory_datasrc, Name("cname.example.com"), RRType::CNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ cname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_NX_DOMAIN) {
+ // Leads to nxdomain.example.com
+ // note: with chaining, what should be expected is not trivial:
+ // BIND 9 returns the CNAME in answer and SOA in authority, no additional,
+ // RCODE being NXDOMAIN.
+ // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
+ // RCODE being NOERROR.
+ Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_nxdom_txt, NULL, NULL);
+}
+
+TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
+ // same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
+ Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::CNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_OUT) {
+ /*
+ * This leads out of zone. This should have only the CNAME even
+ * when we do chaining.
+ *
+ * TODO: We should be able to have two zones in the mock data source.
+ * Then the same test should be done with .org included there and
+ * see what it does (depends on what we want to do)
+ */
+ Query(memory_datasrc, Name("cnameout.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_out_txt, NULL, NULL);
+}
+
+TEST_F(QueryTest, explicitCNAME_OUT) {
+ // same owner name as the OUT test but explicitly query for CNAME RR.
+ Query(memory_datasrc, Name("cnameout.example.com"), RRType::CNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ cname_out_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Test a query under a domain with DNAME. We should get a synthetized CNAME
+ * as well as the DNAME.
+ *
+ * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
+ * as well. This includes tests pointing inside the zone, outside the zone,
+ * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
+ */
+TEST_F(QueryTest, DNAME) {
+ Query(memory_datasrc, Name("www.dname.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ (string(dname_txt) + synthetized_cname_txt).c_str(),
+ NULL, NULL);
+}
+
+/*
+ * Ask an ANY query below a DNAME. Should return the DNAME and synthetized
+ * CNAME.
+ *
+ * ANY is handled specially sometimes. We check it is not the case with
+ * DNAME.
+ */
+TEST_F(QueryTest, DNAME_ANY) {
+ Query(memory_datasrc, Name("www.dname.example.com"), RRType::ANY(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ (string(dname_txt) + synthetized_cname_txt).c_str(), NULL, NULL);
+}
+
+// Test when we ask for DNAME explicitly, it does no synthetizing.
+TEST_F(QueryTest, explicitDNAME) {
+ Query(memory_datasrc, Name("dname.example.com"), RRType::DNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ dname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME. It should not synthetize
+ * the CNAME, it should return the RRset.
+ */
+TEST_F(QueryTest, DNAME_A) {
+ Query(memory_datasrc, Name("dname.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ dname_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME that is not there (NXRRSET).
+ * It should not synthetize the CNAME.
+ */
+TEST_F(QueryTest, DNAME_NX_RRSET) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("dname.example.com"),
+ RRType::TXT(), response).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
+ NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
+/*
+ * Constructing the CNAME will result in a name that is too long. This,
+ * however, should not throw (and crash the server), but respond with
+ * YXDOMAIN.
+ */
+TEST_F(QueryTest, LongDNAME) {
+ // A name that is as long as it can be
+ Name longname(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "dname.example.com.");
+ EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+ response).process());
+
+ responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
+ dname_txt, NULL, NULL);
+}
+
+/*
+ * Constructing the CNAME will result in a name of maximal length.
+ * This tests that we don't reject valid one by some kind of off by
+ * one mistake.
+ */
+TEST_F(QueryTest, MaxLenDNAME) {
+ Name longname(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "dname.example.com.");
+ EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+ response).process());
+
+ // Check the answer is OK
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ NULL, NULL, NULL);
+
+ // Check that the CNAME has the maximal length.
+ bool ok(false);
+ for (RRsetIterator i(response.beginSection(Message::SECTION_ANSWER));
+ i != response.endSection(Message::SECTION_ANSWER); ++ i) {
+ if ((*i)->getType() == RRType::CNAME()) {
+ ok = true;
+ RdataIteratorPtr ci((*i)->getRdataIterator());
+ ASSERT_FALSE(ci->isLast()) << "The CNAME is empty";
+ /*
+ * Does anybody have a clue why, if the Name::MAX_WIRE is put
+ * directly inside ASSERT_EQ, it fails to link and complains
+ * it is unresolved external?
+ */
+ const size_t max_len(Name::MAX_WIRE);
+ ASSERT_EQ(max_len, dynamic_cast(
+ ci->getCurrent()).getCname().getLength());
+ }
+ }
+ EXPECT_TRUE(ok) << "The synthetized CNAME not found";
+}
+
}
diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in
old mode 100644
new mode 100755
index 7594b77e1c864b9ffeecca792d330c2e9bdc3d00..5685c0547f6ed94df26b9d5c5173265ee796aa19
--- a/src/bin/bind10/bind10.py.in
+++ b/src/bin/bind10/bind10.py.in
@@ -195,8 +195,7 @@ class BoB:
"""Boss of BIND class."""
def __init__(self, msgq_socket_file=None, dns_port=5300, address=None,
- forward=None, nocache=False, verbose=False, setuid=None,
- username=None):
+ nocache=False, verbose=False, setuid=None, username=None):
"""
Initialize the Boss of BIND. This is a singleton (only one can run).
@@ -206,11 +205,6 @@ class BoB:
"""
self.address = address
self.dns_port = dns_port
- self.forward = forward
- if forward:
- self.resolver = True
- else:
- self.resolver = False
self.cc_session = None
self.ccs = None
self.cfg_start_auth = True
@@ -320,8 +314,8 @@ class BoB:
sys.stdout.write("\n")
# The next few methods start the individual processes of BIND-10. They
- # are called via start_all_process(). If any fail, an exception is raised
- # which is caught by the caller of start_all_processes(); this kills
+ # are called via start_all_processes(). If any fail, an exception is
+ # raised which is caught by the caller of start_all_processes(); this kills
# processes started up to that point before terminating the program.
def start_msgq(self, c_channel_env):
@@ -422,26 +416,19 @@ class BoB:
"""
Start the Authoritative server
"""
- # XXX: this must be read from the configuration manager in the future
- if self.resolver:
- dns_prog = 'b10-resolver'
- else:
- dns_prog = 'b10-auth'
- dnsargs = [dns_prog]
- if not self.resolver:
- # The resolver uses configuration manager for these
- dnsargs += ['-p', str(self.dns_port)]
- if self.address:
- dnsargs += ['-a', str(self.address)]
- if self.nocache:
- dnsargs += ['-n']
+ authargs = ['b10-auth']
+ authargs += ['-p', str(self.dns_port)]
+ if self.address:
+ authargs += ['-a', str(self.address)]
+ if self.nocache:
+ authargs += ['-n']
if self.uid:
- dnsargs += ['-u', str(self.uid)]
+ authargs += ['-u', str(self.uid)]
if self.verbose:
- dnsargs += ['-v']
+ authargs += ['-v']
# ... and start
- self.start_process("b10-auth", dnsargs, c_channel_env,
+ self.start_process("b10-auth", authargs, c_channel_env,
self.dns_port, self.address)
def start_resolver(self, c_channel_env):
@@ -739,8 +726,6 @@ def check_addr(option, opt_str, value, parser):
try:
if opt_str in ['-a', '--address']:
parser.values.address = isc.net.parse.addr_parse(value)
- elif opt_str in ['-f', '--forward']:
- parser.values.forward = isc.net.parse.addr_parse(value)
else:
raise OptionValueError("Unknown option " + opt_str)
except ValueError:
@@ -761,9 +746,6 @@ def main():
parser.add_option("-a", "--address", dest="address", type="string",
action="callback", callback=check_addr, default=None,
help="address the DNS server will use (default: listen on all addresses)")
- parser.add_option("-f", "--forward", dest="forward", type="string",
- action="callback", callback=check_addr, default=None,
- help="nameserver to which DNS queries should be forwarded")
parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
type="string", default=None,
help="UNIX domain socket file the b10-msgq daemon will use")
@@ -833,8 +815,8 @@ def main():
# Go bob!
boss_of_bind = BoB(options.msgq_socket_file, options.dns_port,
- options.address, options.forward, options.nocache,
- options.verbose, setuid, username)
+ options.address, options.nocache, options.verbose,
+ setuid, username)
startup_result = boss_of_bind.startup()
if startup_result:
sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index e672a42de4859731fb220e3c953b8255721636ee..dfc8acf229a3e9873323f3e590c8f8967c2de433 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -131,7 +131,7 @@
daemon to listen on.
The default is 5300.
- The Y1 prototype release uses a non-default
+ This prototype release uses a non-default
port for domain service.
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index fb6a89235f6d802c1233c1d114408aea0f5fd40d..683dda9e4c1a53b57bb6fba5649ce2b5986ef63c 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -51,7 +51,6 @@ except ImportError:
my_readline = sys.stdin.readline
CSV_FILE_NAME = 'default_user.csv'
-FAIL_TO_CONNECT_WITH_CMDCTL = "Fail to connect with b10-cmdctl module, is it running?"
CONFIG_MODULE_NAME = 'config'
CONST_BINDCTL_HELP = """
usage: [param1 = value1 [, param2 = value2]]
@@ -92,10 +91,13 @@ class BindCmdInterpreter(Cmd):
Cmd.__init__(self)
self.location = ""
self.prompt_end = '> '
- self.prompt = self.prompt_end
+ if sys.stdin.isatty():
+ self.prompt = self.prompt_end
+ else:
+ self.prompt = ""
self.ruler = '-'
self.modules = OrderedDict()
- self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl"))
+ self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl."))
self.server_port = server_port
self.conn = ValidatedHTTPSConnection(self.server_port,
ca_certs=pem_file)
@@ -119,8 +121,8 @@ class BindCmdInterpreter(Cmd):
self.cmdloop()
except FailToLogin as err:
- print(err)
- print(FAIL_TO_CONNECT_WITH_CMDCTL)
+ # error already printed when this was raised, ignoring
+ pass
except KeyboardInterrupt:
print('\nExit from bindctl')
@@ -270,8 +272,10 @@ class BindCmdInterpreter(Cmd):
return line
def postcmd(self, stop, line):
- '''Update the prompt after every command'''
- self.prompt = self.location + self.prompt_end
+ '''Update the prompt after every command, but only if we
+ have a tty as output'''
+ if sys.stdin.isatty():
+ self.prompt = self.location + self.prompt_end
return stop
def _prepare_module_commands(self, module_spec):
@@ -375,7 +379,14 @@ class BindCmdInterpreter(Cmd):
if cmd.command == "help" or ("help" in cmd.params.keys()):
self._handle_help(cmd)
elif cmd.module == CONFIG_MODULE_NAME:
- self.apply_config_cmd(cmd)
+ try:
+ self.apply_config_cmd(cmd)
+ except isc.cc.data.DataTypeError as dte:
+ print("Error: " + str(dte))
+ except isc.cc.data.DataNotFoundError as dnfe:
+ print("Error: " + str(dnfe))
+ except KeyError as ke:
+ print("Error: missing " + str(ke))
else:
self.apply_cmd(cmd)
@@ -396,9 +407,24 @@ class BindCmdInterpreter(Cmd):
def do_help(self, name):
print(CONST_BINDCTL_HELP)
- for k in self.modules.keys():
- print("\t", self.modules[k])
-
+ for k in self.modules.values():
+ n = k.get_name()
+ if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH:
+ print(" %s" % n)
+ print(textwrap.fill(k.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
+ else:
+ print(textwrap.fill("%s%s%s" %
+ (k.get_name(),
+ " "*(CONST_BINDCTL_HELP_INDENT_WIDTH - len(k.get_name())),
+ k.get_desc()),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
def onecmd(self, line):
if line == 'EOF' or line.lower() == "quit":
@@ -411,7 +437,19 @@ class BindCmdInterpreter(Cmd):
Cmd.onecmd(self, line)
def remove_prefix(self, list, prefix):
- return [(val[len(prefix):]) for val in list]
+ """Removes the prefix already entered, and all elements from the
+ list that don't match it"""
+ if prefix.startswith('/'):
+ prefix = prefix[1:]
+
+ new_list = []
+ for val in list:
+ if val.startswith(prefix):
+ new_val = val[len(prefix):]
+ if new_val.startswith("/"):
+ new_val = new_val[1:]
+ new_list.append(new_val)
+ return new_list
def complete(self, text, state):
if 0 == state:
@@ -502,8 +540,7 @@ class BindCmdInterpreter(Cmd):
self._validate_cmd(cmd)
self._handle_cmd(cmd)
except (IOError, http.client.HTTPException) as err:
- print('Error!', err)
- print(FAIL_TO_CONNECT_WITH_CMDCTL)
+ print('Error: ', err)
except BindCtlException as err:
print("Error! ", err)
self._print_correct_usage(err)
@@ -541,87 +578,115 @@ class BindCmdInterpreter(Cmd):
Raises a KeyError if the command was not complete
'''
identifier = self.location
- try:
- if 'identifier' in cmd.params:
- if not identifier.endswith("/"):
- identifier += "/"
- if cmd.params['identifier'].startswith("/"):
- identifier = cmd.params['identifier']
- else:
- identifier += cmd.params['identifier']
-
- # Check if the module is known; for unknown modules
- # we currently deny setting preferences, as we have
- # no way yet to determine if they are ok.
- module_name = identifier.split('/')[1]
- if self.config_data is None or \
- not self.config_data.have_specification(module_name):
- print("Error: Module '" + module_name + "' unknown or not running")
- return
+ if 'identifier' in cmd.params:
+ if not identifier.endswith("/"):
+ identifier += "/"
+ if cmd.params['identifier'].startswith("/"):
+ identifier = cmd.params['identifier']
+ else:
+ if cmd.params['identifier'].startswith('['):
+ identifier = identifier[:-1]
+ identifier += cmd.params['identifier']
+
+ # Check if the module is known; for unknown modules
+ # we currently deny setting preferences, as we have
+ # no way yet to determine if they are ok.
+ module_name = identifier.split('/')[1]
+ if module_name != "" and (self.config_data is None or \
+ not self.config_data.have_specification(module_name)):
+ print("Error: Module '" + module_name + "' unknown or not running")
+ return
- if cmd.command == "show":
- values = self.config_data.get_value_maps(identifier)
- for value_map in values:
- line = value_map['name']
- if value_map['type'] in [ 'module', 'map', 'list' ]:
- line += "/"
- else:
- line += ":\t" + json.dumps(value_map['value'])
- line += "\t" + value_map['type']
- line += "\t"
- if value_map['default']:
- line += "(default)"
- if value_map['modified']:
- line += "(modified)"
- print(line)
- elif cmd.command == "add":
- self.config_data.add_value(identifier, cmd.params['value'])
- elif cmd.command == "remove":
- if 'value' in cmd.params:
- self.config_data.remove_value(identifier, cmd.params['value'])
+ if cmd.command == "show":
+ # check if we have the 'all' argument
+ show_all = False
+ if 'argument' in cmd.params:
+ if cmd.params['argument'] == 'all':
+ show_all = True
+ elif 'identifier' not in cmd.params:
+ # no 'all', no identifier, assume this is the
+ #identifier
+ identifier += cmd.params['argument']
else:
- self.config_data.remove_value(identifier, None)
- elif cmd.command == "set":
- if 'identifier' not in cmd.params:
- print("Error: missing identifier or value")
+ print("Error: unknown argument " + cmd.params['argument'] + ", or multiple identifiers given")
+ return
+ values = self.config_data.get_value_maps(identifier, show_all)
+ for value_map in values:
+ line = value_map['name']
+ if value_map['type'] in [ 'module', 'map' ]:
+ line += "/"
+ elif value_map['type'] == 'list' \
+ and value_map['value'] != []:
+ # do not print content of non-empty lists if
+ # we have more data to show
+ line += "/"
else:
- parsed_value = None
- try:
- parsed_value = json.loads(cmd.params['value'])
- except Exception as exc:
- # ok could be an unquoted string, interpret as such
- parsed_value = cmd.params['value']
- self.config_data.set_value(identifier, parsed_value)
- elif cmd.command == "unset":
- self.config_data.unset(identifier)
- elif cmd.command == "revert":
- self.config_data.clear_local_changes()
- elif cmd.command == "commit":
- self.config_data.commit()
- elif cmd.command == "diff":
- print(self.config_data.get_local_changes());
- elif cmd.command == "go":
- self.go(identifier)
- except isc.cc.data.DataTypeError as dte:
- print("Error: " + str(dte))
- except isc.cc.data.DataNotFoundError as dnfe:
- print("Error: " + identifier + " not found")
- except KeyError as ke:
- print("Error: missing " + str(ke))
- raise ke
+ line += "\t" + json.dumps(value_map['value'])
+ line += "\t" + value_map['type']
+ line += "\t"
+ if value_map['default']:
+ line += "(default)"
+ if value_map['modified']:
+ line += "(modified)"
+ print(line)
+ elif cmd.command == "show_json":
+ if identifier == "":
+ print("Need at least the module to show the configuration in JSON format")
+ else:
+ data, default = self.config_data.get_value(identifier)
+ print(json.dumps(data))
+ elif cmd.command == "add":
+ if 'value' in cmd.params:
+ self.config_data.add_value(identifier, cmd.params['value'])
+ else:
+ self.config_data.add_value(identifier)
+ elif cmd.command == "remove":
+ if 'value' in cmd.params:
+ self.config_data.remove_value(identifier, cmd.params['value'])
+ else:
+ self.config_data.remove_value(identifier, None)
+ elif cmd.command == "set":
+ if 'identifier' not in cmd.params:
+ print("Error: missing identifier or value")
+ else:
+ parsed_value = None
+ try:
+ parsed_value = json.loads(cmd.params['value'])
+ except Exception as exc:
+ # ok could be an unquoted string, interpret as such
+ parsed_value = cmd.params['value']
+ self.config_data.set_value(identifier, parsed_value)
+ elif cmd.command == "unset":
+ self.config_data.unset(identifier)
+ elif cmd.command == "revert":
+ self.config_data.clear_local_changes()
+ elif cmd.command == "commit":
+ self.config_data.commit()
+ elif cmd.command == "diff":
+ print(self.config_data.get_local_changes());
+ elif cmd.command == "go":
+ self.go(identifier)
def go(self, identifier):
'''Handles the config go command, change the 'current' location
- within the configuration tree'''
- # this is just to see if it exists
- self.config_data.get_value(identifier)
- # some sanitizing
- identifier = identifier.replace("//", "/")
- if not identifier.startswith("/"):
- identifier = "/" + identifier
- if identifier.endswith("/"):
- identifier = identifier[:-1]
- self.location = identifier
+ within the configuration tree. '..' will be interpreted as
+ 'up one level'.'''
+ id_parts = isc.cc.data.split_identifier(identifier)
+
+ new_location = ""
+ for id_part in id_parts:
+ if (id_part == ".."):
+ # go 'up' one level
+ new_location, a, b = new_location.rpartition("/")
+ else:
+ new_location += "/" + id_part
+ # check if exists, if not, revert and error
+ v,d = self.config_data.get_value(new_location)
+ if v is None:
+ print("Error: " + identifier + " not found")
+ return
+
+ self.location = new_location
def apply_cmd(self, cmd):
'''Handles a general module command'''
diff --git a/src/bin/bindctl/bindctl-source.py.in b/src/bin/bindctl/bindctl-source.py.in
index 83059d243f5d49e5774053e79b834f02060e9d4c..2e9d513c0a9087ebb92b18f23c849e172a67ba96 100644
--- a/src/bin/bindctl/bindctl-source.py.in
+++ b/src/bin/bindctl/bindctl-source.py.in
@@ -33,51 +33,60 @@ isc.util.process.rename()
# number, and the overall BIND 10 version number (set in configure.ac).
VERSION = "bindctl 20101201 (BIND 10 @PACKAGE_VERSION@)"
+DEFAULT_IDENTIFIER_DESC = "The identifier specifies the config item. Child elements are separated with the '/' character. List indices can be specified with '[i]', where i is an integer specifying the index, starting with 0. Examples: 'Boss/start_auth', 'Recurse/listen_on[0]/address'. If no identifier is given, shows the item at the current location."
+
def prepare_config_commands(tool):
'''Prepare fixed commands for local configuration editing'''
- module = ModuleInfo(name = CONFIG_MODULE_NAME, desc = "Configuration commands")
- cmd = CommandInfo(name = "show", desc = "Show configuration")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
+ module = ModuleInfo(name = CONFIG_MODULE_NAME, desc = "Configuration commands.")
+ cmd = CommandInfo(name = "show", desc = "Show configuration.")
+ param = ParamInfo(name = "argument", type = "string", optional=True, desc = "If you specify the argument 'all' (before the identifier), recursively show all child elements for the given identifier.")
+ cmd.add_param(param)
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "show_json", desc = "Show full configuration in JSON format.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
module.add_command(cmd)
- cmd = CommandInfo(name = "add", desc = "Add entry to configuration list")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
+ cmd = CommandInfo(name = "add", desc = "Add an entry to configuration list. If no value is given, a default value is added.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=False)
+ param = ParamInfo(name = "value", type = "string", optional=True, desc = "Specifies a value to add to the list. It must be in correct JSON format and complete.")
cmd.add_param(param)
module.add_command(cmd)
- cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
+ cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=True)
+ param = ParamInfo(name = "value", type = "string", optional=True, desc = "Specifies a value to remove from the list. It must be in correct JSON format and complete.")
cmd.add_param(param)
module.add_command(cmd)
- cmd = CommandInfo(name = "set", desc = "Set a configuration value")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
+ cmd = CommandInfo(name = "set", desc = "Set a configuration value.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=False)
+ param = ParamInfo(name = "value", type = "string", optional=False, desc = "Specifies a value to set. It must be in correct JSON format and complete.")
cmd.add_param(param)
module.add_command(cmd)
- cmd = CommandInfo(name = "unset", desc = "Unset a configuration value")
- param = ParamInfo(name = "identifier", type = "string", optional=False)
+ cmd = CommandInfo(name = "unset", desc = "Unset a configuration value (i.e. revert to the default, if any).")
+ param = ParamInfo(name = "identifier", type = "string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
module.add_command(cmd)
- cmd = CommandInfo(name = "diff", desc = "Show all local changes")
+ cmd = CommandInfo(name = "diff", desc = "Show all local changes that have not been committed.")
module.add_command(cmd)
- cmd = CommandInfo(name = "revert", desc = "Revert all local changes")
+ cmd = CommandInfo(name = "revert", desc = "Revert all local changes.")
module.add_command(cmd)
- cmd = CommandInfo(name = "commit", desc = "Commit all local changes")
+ cmd = CommandInfo(name = "commit", desc = "Commit all local changes.")
module.add_command(cmd)
- cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part")
- param = ParamInfo(name = "identifier", type="string", optional=False)
+ cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part.")
+ param = ParamInfo(name = "identifier", type="string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
cmd.add_param(param)
module.add_command(cmd)
@@ -115,15 +124,12 @@ def set_bindctl_options(parser):
help = 'PEM formatted server certificate validation chain file')
if __name__ == '__main__':
- try:
- parser = OptionParser(version = VERSION)
- set_bindctl_options(parser)
- (options, args) = parser.parse_args()
- server_addr = options.addr + ':' + str(options.port)
- tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain)
- prepare_config_commands(tool)
- tool.run()
- except Exception as e:
- print(e, "\nFailed to connect with b10-cmdctl module, is it running?")
+ parser = OptionParser(version = VERSION)
+ set_bindctl_options(parser)
+ (options, args) = parser.parse_args()
+ server_addr = options.addr + ':' + str(options.port)
+ tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain)
+ prepare_config_commands(tool)
+ tool.run()
diff --git a/src/bin/bindctl/cmdparse.py b/src/bin/bindctl/cmdparse.py
index ab891d7c89bbb00b3e84845ae164e459161ab111..c624cba5d1efcb5f966e8161977d9c98a8acf1ac 100644
--- a/src/bin/bindctl/cmdparse.py
+++ b/src/bin/bindctl/cmdparse.py
@@ -33,6 +33,7 @@ param_value_str = "(?P[^\'\" ][^, ]+)"
param_value_with_quota_str = "[\"\'](?P.+?)(?\s*)(?P,?)(?P.*)$"
+
PARAM_WITH_QUOTA_PATTERN = re.compile(param_name_str +
param_value_with_quota_str +
next_params_str)
@@ -40,8 +41,58 @@ PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str)
# Used for module and command name
NAME_PATTERN = re.compile("^\s*(?P[\w]+)(?P\s*)(?P.*)$")
+# this removes all whitespace in the given string, except when
+# between " quotes
+_remove_unquoted_whitespace = \
+ lambda text:'"'.join( it if i%2 else ''.join(it.split())
+ for i,it in enumerate(text.split('"')) )
+
+
+def _remove_list_and_map_whitespace(text):
+ """Returns a string where the whitespace between matching [ and ]
+ is removed, unless quoted"""
+ # regular expression aren't really the right tool, since we may have
+ # nested structures
+ result = []
+ start_pos = 0
+ pos = 0
+ list_count = 0
+ map_count = 0
+ cur_start_list_pos = None
+ cur_start_map_pos = None
+ for i in text:
+ if i == '[' and map_count == 0:
+ if list_count == 0:
+ result.append(text[start_pos:pos + 1])
+ cur_start_list_pos = pos + 1
+ list_count = list_count + 1
+ elif i == ']' and map_count == 0:
+ if list_count > 0:
+ list_count = list_count - 1
+ if list_count == 0:
+ result.append(_remove_unquoted_whitespace(text[cur_start_list_pos:pos + 1]))
+ start_pos = pos + 1
+ if i == '{' and list_count == 0:
+ if map_count == 0:
+ result.append(text[start_pos:pos + 1])
+ cur_start_map_pos = pos + 1
+ map_count = map_count + 1
+ elif i == '}' and list_count == 0:
+ if map_count > 0:
+ map_count = map_count - 1
+ if map_count == 0:
+ result.append(_remove_unquoted_whitespace(text[cur_start_map_pos:pos + 1]))
+ start_pos = pos + 1
+
+
+ pos = pos + 1
+ if start_pos <= len(text):
+ result.append(text[start_pos:len(text)])
+ return "".join(result)
+
+
class BindCmdParse:
- """ This class will parse the command line usr input into three part
+ """ This class will parse the command line user input into three parts:
module name, command, parameters
the first two parts are strings and parameter is one hash,
parameters part is optional
@@ -86,9 +137,12 @@ class BindCmdParse:
self._parse_params(param_str)
+ def _remove_list_whitespace(self, text):
+ return ""
def _parse_params(self, param_text):
"""convert a=b,c=d into one hash """
+ param_text = _remove_list_and_map_whitespace(param_text)
# Check parameter name "help"
param = NAME_PATTERN.match(param_text)
diff --git a/src/bin/bindctl/moduleinfo.py b/src/bin/bindctl/moduleinfo.py
index 015ef16cf6e986d4ce2577d7bf9d50de3dce6a45..6e41dcea17ee510d926a881f429667a6f45f35f4 100644
--- a/src/bin/bindctl/moduleinfo.py
+++ b/src/bin/bindctl/moduleinfo.py
@@ -16,6 +16,8 @@
"""This module holds classes representing modules, commands and
parameters for use in bindctl"""
+import textwrap
+
try:
from collections import OrderedDict
except ImportError:
@@ -30,6 +32,9 @@ MODULE_NODE_NAME = 'module'
COMMAND_NODE_NAME = 'command'
PARAM_NODE_NAME = 'param'
+# this is used to align the descriptions in help output
+CONST_BINDCTL_HELP_INDENT_WIDTH=12
+
class ParamInfo:
"""One parameter of one command.
@@ -52,6 +57,12 @@ class ParamInfo:
def __str__(self):
return str("\t%s \t(%s)" % (self.name, self.type, self.desc))
+ def get_name(self):
+ return "%s " % (self.name, self.type)
+
+ def get_desc(self):
+ return self.desc
+
class CommandInfo:
"""One command which is provided by one bind10 module, it has zero
or more parameters
@@ -63,13 +74,18 @@ class CommandInfo:
self.params = OrderedDict()
# Set default parameter "help"
self.add_param(ParamInfo("help",
- desc = "Get help for command",
+ desc = "Get help for command.",
optional = True))
def __str__(self):
return str("%s \t(%s)" % (self.name, self.desc))
-
+ def get_name(self):
+ return self.name
+
+ def get_desc(self):
+ return self.desc;
+
def add_param(self, paraminfo):
"""Add a ParamInfo object to this CommandInfo"""
self.params[paraminfo.name] = paraminfo
@@ -144,22 +160,30 @@ class CommandInfo:
del params["help"]
if len(params) == 0:
- print("\tNo parameters for the command")
+ print("No parameters for the command")
return
- print("\n\tMandatory parameters:")
+ print("\nMandatory parameters:")
mandatory_infos = []
for info in params.values():
if not info.is_optional:
- print("\t", info)
+ print(" %s" % info.get_name())
+ print(textwrap.fill(info.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" ",
+ width=70))
mandatory_infos.append(info)
optional_infos = [info for info in params.values()
if info not in mandatory_infos]
if len(optional_infos) > 0:
- print("\n\tOptional parameters:")
+ print("\nOptional parameters:")
for info in optional_infos:
- print("\t", info)
+ print(" %s" % info.get_name())
+ print(textwrap.fill(info.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" ",
+ width=70))
class ModuleInfo:
@@ -172,11 +196,17 @@ class ModuleInfo:
self.desc = desc
self.commands = OrderedDict()
self.add_command(CommandInfo(name = "help",
- desc = "Get help for module"))
+ desc = "Get help for module."))
def __str__(self):
return str("%s \t%s" % (self.name, self.desc))
-
+
+ def get_name(self):
+ return self.name
+
+ def get_desc(self):
+ return self.desc
+
def add_command(self, command_info):
"""Add a CommandInfo to this ModuleInfo."""
self.commands[command_info.name] = command_info
@@ -201,8 +231,24 @@ class ModuleInfo:
def module_help(self):
"""Prints the help info for this module to stdout"""
print("Module ", self, "\nAvailable commands:")
- for k in self.commands.keys():
- print("\t", self.commands[k])
+ for k in self.commands.values():
+ n = k.get_name()
+ if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH:
+ print(" %s" % n)
+ print(textwrap.fill(k.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
+ else:
+ print(textwrap.fill("%s%s%s" %
+ (k.get_name(),
+ " "*(CONST_BINDCTL_HELP_INDENT_WIDTH - len(k.get_name())),
+ k.get_desc()),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
def command_help(self, command):
"""Prints the help info for the command with the given name.
diff --git a/src/bin/bindctl/tests/Makefile.am b/src/bin/bindctl/tests/Makefile.am
index 5f936443ca81445c458d96316b02a8e6be3216ae..8a7a6237b97bcfc61c9e8bf8f454df4c344e075d 100644
--- a/src/bin/bindctl/tests/Makefile.am
+++ b/src/bin/bindctl/tests/Makefile.am
@@ -1,5 +1,5 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = bindctl_test.py
+PYTESTS = bindctl_test.py cmdparse_test.py
EXTRA_DIST = $(PYTESTS)
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index 653c908549d20260210d5038dd400d91f1c3f30a..490dd7a09c2042d0e22bd2ae22f36710be04ba89 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -17,6 +17,8 @@
import unittest
import isc.cc.data
import os
+from isc.config.config_data import ConfigData, MultiConfigData
+from isc.config.module_spec import ModuleSpec
from bindctl import cmdparse
from bindctl import bindcmd
from bindctl.moduleinfo import *
@@ -238,11 +240,101 @@ class TestNameSequence(unittest.TestCase):
assert self.random_names[i] == module_names[i+1]
i = i + 1
- def test_apply_cfg_command(self):
+# tine class to fake a UIModuleCCSession, but only the config data
+# parts for the next set of tests
+class FakeCCSession(MultiConfigData):
+ def __init__(self):
+ self._local_changes = {}
+ self._current_config = {}
+ self._specifications = {}
+ self.add_foo_spec()
+
+ def add_foo_spec(self):
+ spec = { "module_name": "foo",
+ "config_data": [
+ { "item_name": "an_int",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 1
+ },
+ { "item_name": "a_list",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "a_string",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "bar"
+ }
+ }
+ ]
+ }
+ self.set_specification(ModuleSpec(spec))
+
+
+class TestConfigCommands(unittest.TestCase):
+ def setUp(self):
+ self.tool = bindcmd.BindCmdInterpreter()
+ mod_info = ModuleInfo(name = "foo")
+ self.tool.add_module_info(mod_info)
+ self.tool.config_data = FakeCCSession()
+
+ def test_apply_cfg_command_int(self):
self.tool.location = '/'
- cmd = cmdparse.BindCmdParse("config set identifier=\"foo/bar\" value=\"5\"")
+
+ self.assertEqual((1, MultiConfigData.DEFAULT),
+ self.tool.config_data.get_value("/foo/an_int"))
+
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/an_int\" value=\"5\"")
self.tool.apply_config_cmd(cmd)
+ self.assertEqual((5, MultiConfigData.LOCAL),
+ self.tool.config_data.get_value("/foo/an_int"))
+
+ # this should raise a NotFoundError
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/bar\" value=\"[]\"")
+ self.assertRaises(isc.cc.data.DataNotFoundError, self.tool.apply_config_cmd, cmd)
+
+ # this should raise a TypeError
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/an_int\" value=\"[]\"")
+ self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+
+ # this is a very specific one for use with a set of list tests
+ # to try out the flexibility of the parser (only in the next test)
+ def clt(self, full_cmd_string, item_value):
+ cmd = cmdparse.BindCmdParse(full_cmd_string)
+ self.tool.apply_config_cmd(cmd)
+ self.assertEqual(([item_value], MultiConfigData.LOCAL),
+ self.tool.config_data.get_value("/foo/a_list"))
+
+ def test_apply_cfg_command_list(self):
+ self.tool.location = '/'
+
+ self.assertEqual(([], MultiConfigData.DEFAULT),
+ self.tool.config_data.get_value("/foo/a_list"))
+
+ self.clt("config set identifier=\"foo/a_list\" value=[\"a\"]", "a")
+ self.clt("config set identifier=\"foo/a_list\" value =[\"b\"]", "b")
+ self.clt("config set identifier=\"foo/a_list\" value= [\"c\"]", "c")
+ self.clt("config set identifier=\"foo/a_list\" value = [\"d\"]", "d")
+ self.clt("config set identifier =\"foo/a_list\" value=[\"e\"]", "e")
+ self.clt("config set identifier= \"foo/a_list\" value=[\"f\"]", "f")
+ self.clt("config set identifier = \"foo/a_list\" value=[\"g\"]", "g")
+ self.clt("config set identifier = \"foo/a_list\" value = [\"h\"]", "h")
+ self.clt("config set identifier = \"foo/a_list\" value=[\"i\" ]", "i")
+ self.clt("config set identifier = \"foo/a_list\" value=[ \"j\"]", "j")
+ self.clt("config set identifier = \"foo/a_list\" value=[ \"k\" ]", "k")
+
+ # this should raise a TypeError
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=\"a\"")
+ self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=[1]")
+ self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+
+
+
class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
def __init__(self):
pass
diff --git a/src/bin/bindctl/tests/cmdparse_test.py b/src/bin/bindctl/tests/cmdparse_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..9150ed3250146c2010714e3e95897c4a6e663dd9
--- /dev/null
+++ b/src/bin/bindctl/tests/cmdparse_test.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2009 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+
+import unittest
+from bindctl import cmdparse
+
+class TestCmdParse(unittest.TestCase):
+
+ def test_remove_unquoted_whitespace(self):
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a"), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" a"), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a "), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" a "), "a")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace("a"), "a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a"), " a")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace("a "), "a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a "), " a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a "), "b")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"abc\""), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc\""), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"abc\" "), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc\" "), "\"abc\"")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\" abc\""), "\" abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"a bc\""), "\"a bc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"ab c\" "), "\"ab c\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc \" "), "\"abc \"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \" a b c \" "), "\" a b c \"")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a\" abc\"a"), "a\" abc\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \"a bc\"a"), "a\"a bc\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a\"ab c\" a"), "a\"ab c\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \"abc \" a"), "a\"abc \"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \" a b c \" a"), "a\" a b c \"a")
+
+ # short-hand function to make the set of tests more readable
+ def rws(self, a, b):
+ self.assertEqual(cmdparse._remove_list_and_map_whitespace(a), b)
+
+ def test_remove_list_whitespace(self):
+ self.rws("a", "a")
+ self.rws(" a ", " a ")
+ self.rws(" [a] ", " [a] ")
+ self.rws(" [ a] ", " [a] ")
+ self.rws(" [ a ] ", " [a] ")
+ self.rws(" [ a b c ] ", " [abc] ")
+ self.rws(" [ a \"b c\" ] ", " [a\"b c\"] ")
+ self.rws("a [ a \"b c\" ] a", "a [a\"b c\"] a")
+ self.rws("a] [ a \"b c\" ] a", "a] [a\"b c\"] a")
+ self.rws(" [ a [b c] ] ", " [a[bc]] ")
+ self.rws(" [ a b][ c d ] ", " [ab][cd] ")
+ self.rws(" [ a b] [ c d ] ", " [ab] [cd] ")
+
+ self.rws("a", "a")
+ self.rws(" a ", " a ")
+ self.rws(" {a} ", " {a} ")
+ self.rws(" { a} ", " {a} ")
+ self.rws(" { a } ", " {a} ")
+ self.rws(" { a b c } ", " {abc} ")
+ self.rws(" { a \"b c\" } ", " {a\"b c\"} ")
+ self.rws("a { a \"b c\" } a", "a {a\"b c\"} a")
+ self.rws("a} { a \"b c\" } a", "a} {a\"b c\"} a")
+ self.rws(" { a {b c} } ", " {a{bc}} ")
+ self.rws(" { a b}{ c d } ", " {ab}{cd} ")
+ self.rws(" { a b} { c d } ", " {ab} {cd} ")
+
+ self.rws(" [ a b]{ c d } ", " [ab]{cd} ")
+ self.rws(" [ a b{ c d }] ", " [ab{cd}] ")
+ self.rws(" [ a b{ \"c d\" }] ", " [ab{\"c d\"}] ")
+
+
+if __name__== "__main__":
+ unittest.main()
+
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 1b962e02bf62805becdf1eacff74f67cd91f21e4..8a8362ae70be4a5d759f54dd07492c756bde3bdb 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -205,7 +205,7 @@ class MsgQ:
# TODO: When we have logging, we might want
# to add a debug message here that a new connection
# was made
- self.register_socket(self, newsocket)
+ self.register_socket(newsocket)
def register_socket(self, newsocket):
"""
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index efae15183c926476575745446013d80f19ffc46b..59fcf41b5f72c84620443270de7c17d87481a11a 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -132,7 +132,7 @@ class SendNonblock(unittest.TestCase):
task()
# If we got here, then everything worked well and in time
# In that case, we terminate successfully
- sys.exit()
+ sys.exit(0) # needs exit code
else:
(pid, status) = os.waitpid(task_pid, 0)
self.assertEqual(0, status,
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index dc6deed6ee495e8e21cc4f829572affd50e3e375..75d6249ffc51c4998331bef19a744f3a143a5a75 100644
--- a/src/bin/resolver/Makefile.am
+++ b/src/bin/resolver/Makefile.am
@@ -37,7 +37,7 @@ spec_config.h: spec_config.h.pre
BUILT_SOURCES = spec_config.h
pkglibexec_PROGRAMS = b10-resolver
b10_resolver_SOURCES = resolver.cc resolver.h
-b10_resolver_SOURCES += response_classifier.cc response_classifier.h
+b10_resolver_SOURCES += response_scrubber.cc response_scrubber.h
b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/change_user.h
b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/common.h
b10_resolver_SOURCES += main.cc
@@ -48,6 +48,8 @@ b10_resolver_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
b10_resolver_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
b10_resolver_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
b10_resolver_LDADD += $(top_builddir)/src/lib/log/liblog.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/cache/libcache.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
b10_resolver_LDADD += $(top_builddir)/src/bin/auth/change_user.o
b10_resolver_LDFLAGS = -pthread
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index b07916b49ba31cdc4fa715f5974fe6be1572d42b..95a417d285045c4727f9d9d152028c1c90b0c31b 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -21,7 +21,6 @@
#include
#include
-#include
#include
#include
@@ -64,7 +63,9 @@ private:
public:
ResolverImpl() :
config_session_(NULL),
- timeout_(2000),
+ query_timeout_(2000),
+ client_timeout_(4000),
+ lookup_timeout_(30000),
retries_(3),
rec_query_(NULL)
{}
@@ -76,7 +77,12 @@ public:
void querySetup(DNSService& dnss) {
assert(!rec_query_); // queryShutdown must be called first
dlog("Query setup");
- rec_query_ = new RecursiveQuery(dnss, upstream_, upstream_root_, timeout_, retries_);
+ rec_query_ = new RecursiveQuery(dnss, upstream_,
+ upstream_root_,
+ query_timeout_,
+ client_timeout_,
+ lookup_timeout_,
+ retries_);
}
void queryShutdown() {
@@ -110,7 +116,6 @@ public:
void setRootAddresses(const vector& upstream_root,
DNSService *dnss)
{
- queryShutdown();
upstream_root_ = upstream_root;
if (dnss) {
if (!upstream_root_.empty()) {
@@ -122,10 +127,12 @@ public:
} else {
dlog("No root addresses");
}
- querySetup(*dnss);
}
}
+ void resolve(const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr& callback);
+
void processNormalQuery(const Question& question,
MessagePtr answer_message,
OutputBufferPtr buffer,
@@ -143,8 +150,13 @@ public:
/// Addresses we listen on
vector listen_;
- /// Time in milliseconds, to timeout
- int timeout_;
+ /// Timeout for outgoing queries in milliseconds
+ int query_timeout_;
+ /// Timeout for incoming client queries in milliseconds
+ int client_timeout_;
+ /// Timeout for lookup processing in milliseconds
+ int lookup_timeout_;
+
/// Number of retries after timeout
unsigned retries_;
@@ -170,6 +182,8 @@ public:
MessagePtr message_;
};
+
+// TODO: REMOVE, USE isc::resolve::MakeErrorMessage?
void
makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
const Rcode& rcode)
@@ -244,25 +258,16 @@ public:
const qid_t qid = query_message->getQid();
const bool rd = query_message->getHeaderFlag(Message::HEADERFLAG_RD);
const bool cd = query_message->getHeaderFlag(Message::HEADERFLAG_CD);
- const Opcode& opcode = query_message->getOpcode();
-
- // Fill in the final details of the answer message
+
+ // The opcode and question section should have already been set,
+ // fill in the final details of the answer message
answer_message->setQid(qid);
- answer_message->setOpcode(opcode);
answer_message->setHeaderFlag(Message::HEADERFLAG_QR);
answer_message->setHeaderFlag(Message::HEADERFLAG_RA);
- if (rd) {
- answer_message->setHeaderFlag(Message::HEADERFLAG_RD);
- }
- if (cd) {
- answer_message->setHeaderFlag(Message::HEADERFLAG_CD);
- }
+ answer_message->setHeaderFlag(Message::HEADERFLAG_RD, rd);
+ answer_message->setHeaderFlag(Message::HEADERFLAG_CD, cd);
- vector questions;
- questions.assign(query_message->beginQuestion(), query_message->endQuestion());
- for_each(questions.begin(), questions.end(), QuestionInserter(answer_message));
-
// Now we can clear the buffer and render the new message into it
buffer->clear();
MessageRenderer renderer(*buffer);
@@ -323,7 +328,6 @@ Resolver::~Resolver() {
delete checkin_;
delete dns_lookup_;
delete dns_answer_;
- dlog("Deleting the Resolver",true);
}
void
@@ -341,6 +345,14 @@ Resolver::getConfigSession() const {
return (impl_->config_session_);
}
+void
+Resolver::resolve(const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr& callback)
+{
+ impl_->resolve(question, callback);
+}
+
+
void
Resolver::processMessage(const IOMessage& io_message,
MessagePtr query_message,
@@ -424,6 +436,13 @@ Resolver::processMessage(const IOMessage& io_message,
}
}
+void
+ResolverImpl::resolve(const QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr& callback)
+{
+ rec_query_->resolve(question, callback);
+}
+
void
ResolverImpl::processNormalQuery(const Question& question,
MessagePtr answer_message,
@@ -431,7 +450,7 @@ ResolverImpl::processNormalQuery(const Question& question,
DNSServer* server)
{
dlog("Processing normal query");
- rec_query_->sendQuery(question, answer_message, buffer, server);
+ rec_query_->resolve(question, answer_message, buffer, server);
}
namespace {
@@ -487,16 +506,34 @@ Resolver::updateConfig(ConstElementPtr config) {
ConstElementPtr listenAddressesE(config->get("listen_on"));
vector listenAddresses(parseAddresses(listenAddressesE));
bool set_timeouts(false);
- int timeout = impl_->timeout_;
+ int qtimeout = impl_->query_timeout_;
+ int ctimeout = impl_->client_timeout_;
+ int ltimeout = impl_->lookup_timeout_;
unsigned retries = impl_->retries_;
- ConstElementPtr timeoutE(config->get("timeout")),
- retriesE(config->get("retries"));
- if (timeoutE) {
+ ConstElementPtr qtimeoutE(config->get("timeout_query")),
+ ctimeoutE(config->get("timeout_client")),
+ ltimeoutE(config->get("timeout_lookup")),
+ retriesE(config->get("retries"));
+ if (qtimeoutE) {
// It should be safe to just get it, the config manager should
// check for us
- timeout = timeoutE->intValue();
- if (timeout < -1) {
- isc_throw(BadValue, "Timeout too small");
+ qtimeout = qtimeoutE->intValue();
+ if (qtimeout < -1) {
+ isc_throw(BadValue, "Query timeout too small");
+ }
+ set_timeouts = true;
+ }
+ if (ctimeoutE) {
+ ctimeout = ctimeoutE->intValue();
+ if (ctimeout < -1) {
+ isc_throw(BadValue, "Client timeout too small");
+ }
+ set_timeouts = true;
+ }
+ if (ltimeoutE) {
+ ltimeout = ltimeoutE->intValue();
+ if (ltimeout < -1) {
+ isc_throw(BadValue, "Lookup timeout too small");
}
set_timeouts = true;
}
@@ -521,9 +558,10 @@ Resolver::updateConfig(ConstElementPtr config) {
}
if (rootAddressesE) {
setRootAddresses(rootAddresses);
+ need_query_restart = true;
}
if (set_timeouts) {
- setTimeouts(timeout, retries);
+ setTimeouts(qtimeout, ctimeout, ltimeout, retries);
need_query_restart = true;
}
@@ -610,15 +648,36 @@ Resolver::setListenAddresses(const vector& addresses) {
}
void
-Resolver::setTimeouts(int timeout, unsigned retries) {
- dlog("Setting timeout to " + boost::lexical_cast(timeout) +
- " and retry count to " + boost::lexical_cast(retries));
- impl_->timeout_ = timeout;
+Resolver::setTimeouts(int query_timeout, int client_timeout,
+ int lookup_timeout, unsigned retries) {
+ dlog("Setting query timeout to " + boost::lexical_cast(query_timeout) +
+ ", client timeout to " + boost::lexical_cast(client_timeout) +
+ ", lookup timeout to " + boost::lexical_cast(lookup_timeout) +
+ " and retry count to " + boost::lexical_cast(retries));
+ impl_->query_timeout_ = query_timeout;
+ impl_->client_timeout_ = client_timeout;
+ impl_->lookup_timeout_ = lookup_timeout;
impl_->retries_ = retries;
}
-pair
-Resolver::getTimeouts() const {
- return (pair(impl_->timeout_, impl_->retries_));
+
+int
+Resolver::getQueryTimeout() const {
+ return impl_->query_timeout_;
+}
+
+int
+Resolver::getClientTimeout() const {
+ return impl_->client_timeout_;
+}
+
+int
+Resolver::getLookupTimeout() const {
+ return impl_->lookup_timeout_;
+}
+
+int
+Resolver::getRetries() const {
+ return impl_->retries_;
}
vector
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index cc006c35bb4b97321f53537ef7305fbf6f5afccf..2ae807930cc3419de3f6b566f458cb8c28ebd31b 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -24,6 +24,8 @@
#include
+#include
+
class ResolverImpl;
/**
@@ -35,7 +37,7 @@ class ResolverImpl;
* answer. It doesn't really know about chasing referrals and similar, it
* simply plugs the parts that know into the network handling code.
*/
-class Resolver {
+class Resolver : public isc::resolve::ResolverInterface {
///
/// \name Constructors, Assignment Operator and Destructor.
///
@@ -51,6 +53,10 @@ public:
~Resolver();
//@}
+ virtual void resolve(
+ const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr& callback);
+
/// \brief Process an incoming DNS message, then signal 'server' to resume
///
/// A DNS query (or other message) has been received by a \c DNSServer
@@ -59,7 +65,10 @@ public:
/// send the reply.
///
/// \param io_message The raw message received
- /// \param message Pointer to the \c Message object
+ /// \param query_message Pointer to the query Message object we
+ /// received from the client
+ /// \param answer_message Pointer to the anwer Message object we
+ /// shall return to the client
/// \param buffer Pointer to an \c OutputBuffer for the resposne
/// \param server Pointer to the \c DNSServer
void processMessage(const asiolink::IOMessage& io_message,
@@ -140,11 +149,18 @@ public:
* \short Set options related to timeouts.
*
* This sets the time of timeout and number of retries.
- * \param timeout The time in milliseconds. The value -1 disables timeouts.
+ * \param query_timeout The timeout we use for queries we send
+ * \param client_timeout The timeout at which point we send back a
+ * SERVFAIL (while continuing to resolve the query)
+ * \param lookup_timeout The timeout at which point we give up and
+ * stop.
* \param retries The number of retries (0 means try the first time only,
* do not retry).
*/
- void setTimeouts(int timeout = -1, unsigned retries = 0);
+ void setTimeouts(int query_timeout = 2000,
+ int client_timeout = 4000,
+ int lookup_timeout = 30000,
+ unsigned retries = 3);
/**
* \short Get info about timeouts.
@@ -153,6 +169,39 @@ public:
*/
std::pair getTimeouts() const;
+ /**
+ * \brief Get the timeout for outgoing queries
+ *
+ * \returns Timeout for outgoing queries
+ */
+ int getQueryTimeout() const;
+
+ /**
+ * \brief Get the timeout for incoming client queries
+ *
+ * After this timeout, a SERVFAIL shall be sent back
+ * (internal resolving on the query will continue, see
+ * \c getLookupTimeout())
+ *
+ * \returns Timeout for outgoing queries
+ */
+ int getClientTimeout() const;
+
+ /**
+ * \brief Get the timeout for lookups
+ *
+ * After this timeout, internal processing shall stop
+ */
+ int getLookupTimeout() const;
+
+ /**
+ * \brief Get the number of retries for outgoing queries
+ *
+ * If a query times out (value of \c getQueryTimeout()), we
+ * will retry this number of times
+ */
+ int getRetries() const;
+
private:
ResolverImpl* impl_;
asiolink::DNSService* dnss_;
diff --git a/src/bin/resolver/resolver.spec.pre.in b/src/bin/resolver/resolver.spec.pre.in
index 53c67a60665ee062e5ba6427bf3456674d9340c6..48e1eb6aa1b77b52881d4a20647e82b6e6121872 100644
--- a/src/bin/resolver/resolver.spec.pre.in
+++ b/src/bin/resolver/resolver.spec.pre.in
@@ -4,16 +4,28 @@
"module_description": "Recursive service",
"config_data": [
{
- "item_name": "timeout",
+ "item_name": "timeout_query",
"item_type": "integer",
"item_optional": False,
"item_default": 2000
},
+ {
+ "item_name": "timeout_client",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 4000
+ },
+ {
+ "item_name": "timeout_lookup",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 30000
+ },
{
"item_name": "retries",
"item_type": "integer",
"item_optional": False,
- "item_default": 0
+ "item_default": 3
},
{
"item_name": "forward_addresses",
diff --git a/src/bin/resolver/response_scrubber.cc b/src/bin/resolver/response_scrubber.cc
new file mode 100644
index 0000000000000000000000000000000000000000..060a8b1a92b4ea397eb95b228327ea7f72244bb9
--- /dev/null
+++ b/src/bin/resolver/response_scrubber.cc
@@ -0,0 +1,189 @@
+
+// Copyright (C) 2011 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
+#include
+#include
+#include
+#include
+#include "response_scrubber.h"
+
+using namespace isc::dns;
+using namespace std;
+
+// Compare addresses etc.
+
+ResponseScrubber::Category ResponseScrubber::addressCheck(
+ const asiolink::IOEndpoint& to, const asiolink::IOEndpoint& from)
+{
+ if (from.getProtocol() == to.getProtocol()) {
+ if (from.getAddress() == to.getAddress()) {
+ if (from.getPort() == to.getPort()) {
+ return (ResponseScrubber::SUCCESS);
+ } else {
+ return (ResponseScrubber::PORT);
+ }
+ } else {
+ return (ResponseScrubber::ADDRESS);
+ }
+ }
+ return (ResponseScrubber::PROTOCOL);
+}
+
+// Do a general scrubbing. The QNAMES of RRsets in the specified section are
+// compared against the list of name given and if they are not equal and not in
+// the specified relationship (generally superdomain or subdomain) to at least
+// of of the given names, they are removed.
+
+unsigned int
+ResponseScrubber::scrubSection(Message& message,
+ const vector& names,
+ const NameComparisonResult::NameRelation connection,
+ const Message::Section section)
+{
+ unsigned int count = 0; // Count of RRsets removed
+ unsigned int kept = 0; // Count of RRsets kept
+ bool removed = true; // Set true if RRset removed in a pass
+
+ // Need to go through the section multiple times as when an RRset is
+ // removed, all iterators into the section are invalidated. This condition
+ // is flagged by "remove" being set true when an RRset is removed.
+
+ while (removed) {
+ RRsetIterator i = message.beginSection(section);
+
+ // Skips the ones that have been checked (and retained) in a previous
+ // pass through the "while" loop. (Although RRset removal invalidates
+ // iterators, it does not change the relative order of the retained
+ // RRsets in the section.)
+ for (int j = 0; j < kept; ++j) {
+ ++i;
+ }
+
+ // Start looking at the remaining entries in the section.
+ removed = false;
+ for (; (i != message.endSection(section)) && (!removed); ++i) {
+
+ // Loop through the list of names given and see if any are in the
+ // given relationship with the QNAME of this RRset
+ bool nomatch = true;
+ for (vector::const_iterator n = names.begin();
+ ((n != names.end()) && nomatch); ++n) {
+ NameComparisonResult result = (*i)->getName().compare(**n);
+ NameComparisonResult::NameRelation relationship =
+ result.getRelation();
+ if ((relationship == NameComparisonResult::EQUAL) ||
+ (relationship == connection)) {
+
+ // RRset in the specified relationship, so a match has
+ // been found
+ nomatch = false;
+ }
+ }
+
+ // Remove the RRset if there was no match to one of the given names.
+ if (nomatch) {
+ message.removeRRset(section, i);
+ ++count; // One more RRset removed
+ removed = true; // Something was removed
+ } else {
+
+ // There was a match so this is one more entry we can skip next
+ // time.
+ ++kept;
+ }
+ }
+ }
+
+ return count;
+}
+
+// Perform the scrubbing of all sections of the message.
+
+unsigned int
+ResponseScrubber::scrubAllSections(Message& message, const Name& bailiwick) {
+
+ // Leave the question section alone. Just go through the RRsets in the
+ // answer, authority and additional sections.
+ unsigned int count = 0;
+ const vector bailiwick_names(1, &bailiwick);
+ count += scrubSection(message, bailiwick_names,
+ NameComparisonResult::SUBDOMAIN, Message::SECTION_ANSWER);
+ count += scrubSection(message, bailiwick_names,
+ NameComparisonResult::SUBDOMAIN, Message::SECTION_AUTHORITY);
+ count += scrubSection(message, bailiwick_names,
+ NameComparisonResult::SUBDOMAIN, Message::SECTION_ADDITIONAL);
+
+ return count;
+}
+
+// Scrub across sections.
+
+unsigned int
+ResponseScrubber::scrubCrossSections(isc::dns::Message& message) {
+
+ // Get a list of the names in the answer section or, failing this, the
+ // question section. Note that pointers to the names within "message" are
+ // stored; this is OK as the relevant sections in "message" will not change
+ // during the lifetime of this method (it only affects the authority
+ // section).
+ vector source;
+ if (message.getRRCount(Message::SECTION_ANSWER) != 0) {
+ for (RRsetIterator i = message.beginSection(Message::SECTION_ANSWER);
+ i != message.endSection(Message::SECTION_ANSWER); ++i) {
+ const Name& qname = (*i)->getName();
+ source.push_back(&qname);
+ }
+
+ } else {
+ for (QuestionIterator i = message.beginQuestion();
+ i != message.endQuestion(); ++i) {
+ const Name& qname = (*i)->getName();
+ source.push_back(&qname);
+ }
+ }
+
+ if (source.empty()) {
+ // TODO: Log the fact - should be at least a question present
+ return (0);
+ }
+
+ // Could be duplicates, especially in the answer section, so sort the
+ // names and remove them.
+ sort(source.begin(), source.end(), ResponseScrubber::compareNameLt);
+ vector::iterator endunique =
+ unique(source.begin(), source.end(), ResponseScrubber::compareNameEq);
+ source.erase(endunique, source.end());
+
+ // Now purge the authority section of RRsets that are not equal to or a
+ // superdomain of the names in the question/answer section.
+ return (scrubSection(message, source,
+ NameComparisonResult::SUPERDOMAIN, Message::SECTION_AUTHORITY));
+
+}
+
+// Scrub a message
+
+unsigned int
+ResponseScrubber::scrub(const isc::dns::MessagePtr& message,
+ const isc::dns::Name& bailiwick)
+{
+ unsigned int sections_removed = scrubAllSections(*message, bailiwick);
+ sections_removed += scrubCrossSections(*message);
+
+ return sections_removed;
+}
+
+
diff --git a/src/bin/resolver/response_scrubber.h b/src/bin/resolver/response_scrubber.h
new file mode 100644
index 0000000000000000000000000000000000000000..c3fce57df90f0b16535415850a45d0e8044d1f57
--- /dev/null
+++ b/src/bin/resolver/response_scrubber.h
@@ -0,0 +1,422 @@
+// Copyright (C) 2011 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.
+
+// $Id$
+
+#ifndef __RESPONSE_SCRUBBER_H
+#define __RESPONSE_SCRUBBER_H
+
+/// \page DataScrubbing Data Scrubbing
+/// \section DataScrubbingIntro Introduction
+/// When a response is received from an authoritative server, it should be
+/// checked to ensure that the data contained in it is valid. Signed data is
+/// not a problem - validating the signatures is a sufficient check. But
+/// unsigned data in a response is more of a problem. (Note that even data from
+/// signed zones may be not be signed, e.g. delegations are not signed.) In
+/// particular, how do we know that the server from which the response was
+/// received was authoritive for the data it returned?
+///
+/// The part of the code that checks for this is the "Data Scrubbing" module.
+/// Although it includes the checking of IP addresses and ports, it is called
+/// "Scrubbing" because it "scrubs" the returned message and removes doubtful
+/// information.
+///
+/// \section DataScrubbingBasic Basic Checks
+/// The first part - how do we know that the response comes from the correct
+/// server - is relatively trivial, albeit not foolproof (which is why DNSSEC
+/// was developed). The following are checked:
+///
+/// - The IP address from which the response was received is the same as the
+/// one to which the query was sent.
+/// - The port on which the response was received is the same as the one from
+/// which the query was sent.
+///
+/// (These tests need not not done for a TCP connection - if data is received
+/// over the TCP stream, it is assumed that it comes from the address and port
+/// to which a connection was made.)
+///
+/// - The protocol used to send the question is the same as the protocol on
+/// which an answer was received.
+///
+/// (Strictly speaking, if this check fails it is a programming error - the
+/// code should not mix up UPD and TCP messages.)
+///
+/// - The QID in the response message is the same as the QID in the query
+/// message sent.
+///
+/// If the conditions are met, then the data - in all three response sections -
+/// is scanned and out of bailiwick data is removed ("scrubbed").
+///
+/// \section DataScrubbingBailiwick Bailiwick
+/// Bailiwick means "district or jurisdiction of bailie or bailiff" (Concise
+/// Oxford Dictionary, 7th Edition). It is not a term mentioned in any RFC
+/// (or at least, any RFC up to RFC 5997) but is widely used in DNS literature.
+/// In this context it is taken to mean the data for which a DNS server has
+/// authority. So when we speak of the information being "in bailiwick", we
+/// mean that the the server is the ultimate source of authority for that data.
+///
+/// In practice, determining this from the response alone is difficult. In
+/// particular, as a server may be authoritative for many zones, it could in
+/// theory be authoritative for any combination of RRsets that appear in a
+/// response.
+///
+/// For this reason, bailiwick is dependent on the query. If, for example, a
+/// query for www.example.com is sent to the nameservers for example.com
+/// (because of a referral of from the com. servers), the bailiwick for the
+/// query is example.com. This means that any information returned on domains
+/// other than example.com may not be authoritative. More exactly, it may be
+/// authoritative (because the server is also authoritative for the zone
+/// concerned), but based on the information available (in this example, that
+/// the response originated from a nameserver for the zone example.com) it is
+/// not possible to be certain.
+///
+/// Ideally, out of bailiwick data should be excluded from further processing
+/// as it may be incorrect and corrupt the cache. In practice, there are
+/// two cases to consider:
+///
+/// The first is when the data has a qname that is not example.com or a
+/// subdomain of it (e.g. xyz.com, www.example.net). In this case the data can
+/// be retrieved by an independent query - no path from the root zone to the
+/// data goes through the current bailiwick, so there is no chance of ending up
+/// in a loop. In this case, data that appears to be out of bailiwick can be
+/// dropped from the response.
+///
+/// The second case is when the QNAME of the data is a subdomain of the
+/// bailiwick. Here the server may or may not be authoritative for the data.
+/// For example, if the name queried for were www.sub.example.com and the
+/// example.com nameservers supplied an answer:
+///
+/// - The answer could be authoritative - www.sub.example.com could be
+/// in the example.com zone.
+/// - The answer might not be authoritative - the zone sub.example.com may have
+/// been delegated, so the authoritative answer should come from
+/// sub.example.com's nameservers.
+/// - The answer might be authoritative even though zone sub.example.com has
+/// been delegated, because the nameserver for example.com is the same as
+/// that for sub.example.com.
+///
+/// Unlike the previous case, it is not possible to err on the side of caution
+/// and drop such data. Any independent query for it will pass through the
+/// current bailiwick and the same question will be asked again. For this
+/// reason, any data in the response that has a QNAME equal to a subdomain of
+/// the bailiwick has to be accepted.
+///
+/// In summary then, data in a response that has a QNAME equal to or a subdomain
+/// of the bailiwick is considered in-bailiwick. Anything else is out of of
+/// bailiwick.
+///
+/// \subsection DataScrubbingCrossSection Cross-Section Scrubbing
+/// Even with the bailiwick checks above, there are some additional cleaning
+/// that can be done with the packet. In particular:
+///
+/// - The QNAMEs of the RRsets in the authority section must be equal to or
+/// superdomains of a QNAME of an RRset in the answer. Any that are not
+/// should be removed.
+/// - If there is no answer section, the QNAMES of RRsets in the authority
+/// section must be equal to or superdomains of the QNAME of the RRset in the
+/// question.
+///
+/// Although previous checks should have removed some inconsistencies, it
+/// will not trap obscure cases (e.g. bailiwick: "example.com", answer:
+/// "www.example.com", authority: sub.example.com). These checks do just that.
+///
+/// (Note that not included here is QNAME of question not equal to or a
+/// superdomain of the answer; that check is made in the ResponseClassifier
+/// class.)
+///
+/// \section DataScrubbingExample Examples
+/// Some examples should make this clear: they all use the notation
+/// Qu = Question, Zo = Zone being queried, An = Answer, Au = Authority,
+/// Ad = Additional.
+///
+/// \subsection DataScrubbingEx1 Example 1: Simple Query
+/// Querying a nameserver for the zone "example.com" for www.example.com and
+/// receiving the answer "www.example.com A 1.2.3.4" with two nameservers quoted
+/// as authority and both their addresses in the additional section:
+///
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): example.com NS ns0.example.com\n
+/// Au(2): example.com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// This answer could be returned by a properly configured server. All resource
+/// records in the answer - with the exception of Ad(2) - are in bailiwick
+/// because the QNAME is equal to or a subdomain of the zone being queried.
+///
+/// It is permissible for Ad(2) to be returned by a properly configured server
+/// as a hint to resolvers. However the example.com nameservers are not
+/// authoritative for addresses of domains in example.net; that record could
+/// be out of date or incorrect. Indeed, it might even be a deliberate attempt
+/// at a spoof by getting us to cache an invalid address for ns1.example.net.
+/// The safest thing to do is to drop the A record and to get the address of
+/// ns1.example.net by querying for that name through the .net nameservers.
+///
+/// \subsection DataScrubbingEx2 Example 2: Multiple Zones on Same Nameserver
+/// Assume now that example.com and sub.example.com are hosted on the same
+/// nameserver and that from the .com zone the resolver has received a referral
+/// to example.com. Suppose that the query is for www.sub.example.com and that
+/// the following response is received:
+///
+/// Qu: www.sub.example.com\n
+/// Zo: example.com
+///
+/// An:
+///
+/// Au(1): sub.example.com NS ns0.sub.example.com\n
+/// Au(2): sub.example.com NS ns1.example.net
+///
+/// Ad(1): ns0.sub.example.com A 192.0.2.101\n
+/// Ad(2): ns1.example.net A 192.0.2.201
+///
+/// Although we asked the example.com nameservers for information, we got the
+/// nameservers for sub.example.com in the authority section. This is valid
+/// because if BIND-10 hosts multiple zones, it will look up the data in the
+/// zone that most closely matches the query.
+///
+/// Using the criteria above, the data in the additional section can therefore
+/// be regarded as in bailiwick because sub.example.com is a subdomain of
+/// example.com. As before though, the address for ns1.example.net in the
+/// additional section is not in bailiwick because ns1.example.net is now a
+/// subdomain of example.com.
+///
+/// \subsection DataScrubbingEx3 Example 3: Deliberate Spoof Attempt
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): com NS ns0.example.com\n
+/// Au(2): com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// This is a deliberately invalid response. The query is being sent to the
+/// nameservers for example.com (presumably because a referral to example.com
+/// was received from the com nameservers), but the response is an attempt
+/// to get the specified nameservers cached as the nameservers for com - for
+/// which example.com is not authoritative.
+///
+/// Note though that this response is only invalid because, due to the previous
+/// referral, the query was sent to the example.com nameservers. Had the
+/// referral been to the com nameservers, it would be a valid response; the com
+/// zone could well be serving all the data for example.com. Having said that,
+/// the A record for ns1.example.net would still be regarded as being out of
+/// bailiwick becase the nameserver is not authoritative for the .net zone.
+///
+/// \subsection DataScrubbingEx4 Example 4: Inconsistent Answer Section
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): alpha.example.com NS ns0.example.com\n
+/// Au(2): alpha.example.com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// Here, everything in the answer and authority sections is in bailiwick for
+/// the example.com server. And although the zone example.com was queried, it
+/// is permissible for the authority section to contain nameservers with a
+/// qname that is a subdomain of example.com (e.g. see \ref DataScrubbingEx2).
+/// However, only servers with a qname that is equal to or a superdomain of
+/// the answer are authoritative for the answer. So in this case, both
+/// Au(1) and Au(2) (as well as Ad(2), for reasons given earlier) will be
+/// scrubbed.
+
+#include
+#include
+#include
+#include
+
+/// \brief Response Data Scrubbing
+///
+/// This is the class that implements the data scrubbing. Given a response
+/// message and some additional information, it checks the information using
+/// the rules given in \ref DataScrubbing and either rejects the packet or
+/// modifies it to remove non-conforming RRsets.
+///
+/// TODO: Examine the additional records and remove all cases where the
+/// QNAME does not match the RDATA of records in the authority section.
+
+class ResponseScrubber {
+public:
+
+ /// \brief Response Code for Address Check
+ enum Category {
+ SUCCESS = 0, ///< Packet is OK
+
+ // Error categories
+
+ ADDRESS = 1, ///< Mismatching IP address
+ PORT = 2, ///< Mismatching port
+ PROTOCOL = 3 ///< Mismatching protocol
+ };
+
+ /// \brief Check IP Address
+ ///
+ /// Compares the address to which the query was sent, the port it was
+ /// sent from, and the protocol used for communication with the (address,
+ /// port, protocol) from which the response was received.
+ ///
+ /// \param to Endpoint representing the address to which the query was sent.
+ /// \param from Endpoint from which the response was received.
+ ///
+ /// \return SUCCESS if the two endpoints match, otherwise an error status
+ /// indicating what was incorrect.
+ static Category addressCheck(const asiolink::IOEndpoint& to,
+ const asiolink::IOEndpoint& from);
+
+ /// \brief Check QID
+ ///
+ /// Compares the QID in the sent message with the QID in the response.
+ ///
+ /// \param sent Message sent to the authoritative server
+ /// \param received Message received from the authoritative server
+ ///
+ /// \return true if the QIDs match, false otherwise.
+ static bool qidCheck(const isc::dns::Message& sent,
+ const isc::dns::Message& received) {
+ return (sent.getQid() == received.getQid());
+ }
+
+ /// \brief Generalised Scrub Message Section
+ ///
+ /// When scrubbing a message given the bailiwick of the server, RRsets are
+ /// retained in the message section if the QNAME is equal to or a subdomain
+ /// of the bailiwick. However, when checking QNAME of RRsets in the
+ /// authority section against the QNAME of the question or answers, RRsets
+ /// are retained only if their QNAME is equal to or a superdomain of the
+ /// name in question.
+ ///
+ /// This method provides the generalised scrubbing whereby the RRsets in
+ /// a section are tested against a given name, and RRsets kept if their
+ /// QNAME is equal to or in the supplied relationship with the given name.
+ ///
+ /// \param section Section of the message to be scrubbed.
+ /// \param zone Names against which RRsets should be checked. Note that
+ /// this is a vector of pointers to Name objects; they are assumed to
+ /// independently exist, and the caller retains ownership of them and is
+ /// assumed to destroy them when needed.
+ /// \param connection Relationship required for retention, i.e. the QNAME of
+ /// an RRset in the specified section must be equal to or a "connection"
+ /// (SUPERDOMAIN/SUBDOMAIN) of "name" for the RRset to be retained.
+ /// \param message Message to be scrubbed.
+ ///
+ /// \return Count of the number of RRsets removed from the section.
+ static unsigned int scrubSection(isc::dns::Message& message,
+ const std::vector& names,
+ const isc::dns::NameComparisonResult::NameRelation connection,
+ const isc::dns::Message::Section section);
+
+ /// \brief Scrub All Sections of a Message
+ ///
+ /// Scrubs each of the answer, authority and additional sections of the
+ /// message.
+ ///
+ /// No distinction is made between RRsets legitimately in the message (e.g.
+ /// glue for authorities that are not in bailiwick) and ones that could be
+ /// considered as attempts of spoofing (e.g. non-bailiwick RRsets in the
+ /// additional section that are not related to the query).
+ ///
+ /// The resultant packet returned to the caller may be invalid. If so, it
+ /// is up to the caller to detect that.
+ ///
+ /// \param message Message to be scrubbed.
+ /// \param bailiwick Name of the zone whose authoritative servers were
+ /// queried.
+ ///
+ /// \return Count of the number of RRsets removed from the message.
+ static unsigned int scrubAllSections(isc::dns::Message& message,
+ const isc::dns::Name& bailiwick);
+
+ /// \brief Scrub Across Message Sections
+ ///
+ /// Does some cross-section comparisons and removes inconsistent RRs. In
+ /// particular it:
+ ///
+ /// - If an answer is present, checks that the qname of the authority RRs
+ /// are equal to or superdomain of the qname answer RRsets. Any that are
+ /// not are removed.
+ /// - If an answer is not present, checks that the authority RRs are
+ /// equal to or superdomains of the question. If not, the authority RRs
+ /// are removed.
+ ///
+ /// Note that the scrubbing does not check:
+ ///
+ /// - that the question is in the bailiwick of the server; that check is
+ /// assumed to have been done prior to the query being sent (else why
+ /// was the query sent there in the first place?)
+ /// - that the qname of one of the RRsets in the answer (if present) is
+ /// equal to the qname of the question (that check is done in the
+ /// response classification code).
+ ///
+ /// \param message Message to be scrubbed.
+ ///
+ /// \return Count of the number of RRsets removed from the section.
+ static unsigned int scrubCrossSections(isc::dns::Message& message);
+
+ /// \brief Main Scrubbing Entry Point
+ ///
+ /// The single entry point to the module to sanitise the message. All
+ /// it does is call the various other scrubbing methods.
+ ///
+ /// \param message Pointer to the message to be scrubbed. (This is a
+ /// pointer - as opposed to a Message as in other methods in this class -
+ /// as the external code is expected to be mainly using message pointers
+ /// to access messages.)
+ /// \param bailiwick Name of the zone whose authoritative servers were
+ /// queried.
+ ///
+ /// \return Count of the number of RRsets removed from the message.
+ static unsigned int scrub(const isc::dns::MessagePtr& message,
+ const isc::dns::Name& bailiwick);
+
+ /// \brief Comparison Function for Sorting Name Pointers
+ ///
+ /// Utility method called to sorts pointers to names in lexical order.
+ ///
+ /// \param n1 Pointer to first Name object
+ /// \param n2 Pointer to second Name object
+ ///
+ /// \return true if n1 is less than n2, false otherwise.
+ static bool compareNameLt(const isc::dns::Name* n1,
+ const isc::dns::Name* n2)
+ {
+ return (*n1 < *n2);
+ }
+
+ /// \brief Function for Comparing Name Pointers
+ ///
+ /// Utility method called to sorts pointers to names in lexical order.
+ ///
+ /// \param n1 Pointer to first Name object
+ /// \param n2 Pointer to second Name object
+ ///
+ /// \return true if n1 is equal to n2, false otherwise.
+ static bool compareNameEq(const isc::dns::Name* n1,
+ const isc::dns::Name* n2)
+ {
+ return (*n1 == *n2);
+ }
+};
+
+#endif // __RESPONSE_SCRUBBER_H
diff --git a/src/bin/resolver/tests/Makefile.am b/src/bin/resolver/tests/Makefile.am
index 85524395211991c551217b93ea751582c55fe733..3dc6b3b5a1e7b6265e3216bddf549d3f91bf1ed1 100644
--- a/src/bin/resolver/tests/Makefile.am
+++ b/src/bin/resolver/tests/Makefile.am
@@ -4,7 +4,6 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(top_srcdir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -20,24 +19,35 @@ TESTS += run_unittests
run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
run_unittests_SOURCES += ../resolver.h ../resolver.cc
-run_unittests_SOURCES += ../response_classifier.h ../response_classifier.cc
+run_unittests_SOURCES += ../response_scrubber.h ../response_scrubber.cc
run_unittests_SOURCES += resolver_unittest.cc
run_unittests_SOURCES += resolver_config_unittest.cc
-run_unittests_SOURCES += response_classifier_unittest.cc
+run_unittests_SOURCES += response_scrubber_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
-run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
+run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+
+# Note the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
endif
+
+
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/resolver/tests/resolver_config_unittest.cc b/src/bin/resolver/tests/resolver_config_unittest.cc
index ca0c9b67bf62b66e35b5a6d88b88ef44ef28050e..916396a4c2f530a176a6858b165cceb09c118f94 100644
--- a/src/bin/resolver/tests/resolver_config_unittest.cc
+++ b/src/bin/resolver/tests/resolver_config_unittest.cc
@@ -237,31 +237,51 @@ TEST_F(ResolverConfig, invalidListenAddresses) {
// Just test it sets and gets the values correctly
TEST_F(ResolverConfig, timeouts) {
- server.setTimeouts(0, 1);
- EXPECT_EQ(0, server.getTimeouts().first);
- EXPECT_EQ(1, server.getTimeouts().second);
+ server.setTimeouts(0, 1, 2, 3);
+ EXPECT_EQ(0, server.getQueryTimeout());
+ EXPECT_EQ(1, server.getClientTimeout());
+ EXPECT_EQ(2, server.getLookupTimeout());
+ EXPECT_EQ(3, server.getRetries());
server.setTimeouts();
- EXPECT_EQ(-1, server.getTimeouts().first);
- EXPECT_EQ(0, server.getTimeouts().second);
+ EXPECT_EQ(2000, server.getQueryTimeout());
+ EXPECT_EQ(4000, server.getClientTimeout());
+ EXPECT_EQ(30000, server.getLookupTimeout());
+ EXPECT_EQ(3, server.getRetries());
}
TEST_F(ResolverConfig, timeoutsConfig) {
ElementPtr config = Element::fromJSON("{"
- "\"timeout\": 1000,"
- "\"retries\": 3"
+ "\"timeout_query\": 1000,"
+ "\"timeout_client\": 2000,"
+ "\"timeout_lookup\": 3000,"
+ "\"retries\": 4"
"}");
ConstElementPtr result(server.updateConfig(config));
EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
- EXPECT_EQ(1000, server.getTimeouts().first);
- EXPECT_EQ(3, server.getTimeouts().second);
+ EXPECT_EQ(1000, server.getQueryTimeout());
+ EXPECT_EQ(2000, server.getClientTimeout());
+ EXPECT_EQ(3000, server.getLookupTimeout());
+ EXPECT_EQ(4, server.getRetries());
}
TEST_F(ResolverConfig, invalidTimeoutsConfig) {
invalidTest("{"
- "\"timeout\": \"error\""
+ "\"timeout_query\": \"error\""
"}");
invalidTest("{"
- "\"timeout\": -2"
+ "\"timeout_query\": -2"
+ "}");
+ invalidTest("{"
+ "\"timeout_client\": \"error\""
+ "}");
+ invalidTest("{"
+ "\"timeout_client\": -2"
+ "}");
+ invalidTest("{"
+ "\"timeout_lookup\": \"error\""
+ "}");
+ invalidTest("{"
+ "\"timeout_lookup\": -2"
"}");
invalidTest("{"
"\"retries\": \"error\""
diff --git a/src/bin/resolver/tests/response_scrubber_unittest.cc b/src/bin/resolver/tests/response_scrubber_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1dc66395eff1eba05a5f8cba575cd94fd0e7a8ab
--- /dev/null
+++ b/src/bin/resolver/tests/response_scrubber_unittest.cc
@@ -0,0 +1,542 @@
+// Copyright (C) 2011 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.
+
+// $Id$
+
+#include
+#include
+
+#include
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+// Class for endpoint checks. The family of the endpoint is set in the
+// constructor; the address family by the string provided for the address.
+
+namespace asiolink {
+
+class GenericEndpoint : public IOEndpoint {
+public:
+ GenericEndpoint(const std::string& address, uint16_t port, short protocol) :
+ address_(address), port_(port), protocol_(protocol)
+ {}
+ virtual ~GenericEndpoint()
+ {}
+
+ virtual IOAddress getAddress() const {
+ return address_;
+ }
+
+ virtual uint16_t getPort() const {
+ return port_;
+ }
+
+ virtual short getProtocol() const {
+ return protocol_;
+ }
+
+ virtual short getFamily() const {
+ return address_.getFamily();
+ }
+
+private:
+ IOAddress address_; // Address of endpoint
+ uint16_t port_; // Port number of endpoint
+ short protocol_; // Protocol of the endpoint
+ };
+}
+
+using namespace asio::ip;
+using namespace isc::dns;
+using namespace rdata;
+using namespace isc::dns::rdata::generic;
+using namespace isc::dns::rdata::in;
+using namespace asiolink;
+
+// Test class
+
+namespace {
+class ResponseScrubberTest : public ::testing::Test {
+public:
+ ResponseScrubberTest() :
+ bailiwick("example.com"),
+
+ qu_in_any_www(Name("www.example.com"), RRClass::IN(), RRType::ANY()),
+ qu_in_a_www(Name("www.example.com"), RRClass::IN(), RRType::A()),
+ qu_in_ns(Name("example.com"), RRClass::IN(), RRType::NS()),
+ qu_in_txt_www(Name("www.example.com"), RRClass::IN(), RRType::TXT()),
+ rrs_in_a_org(new RRset(Name("mail.example.org"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+
+ rrs_in_a_net(new RRset(Name("mail.example.net"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_cname_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::CNAME(), RRTTL(300))),
+ rrs_in_a_wwwnet(new RRset(Name("www.example.net"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_ns(new RRset(Name("example.com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_ns_com(new RRset(Name("com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_ns_net(new RRset(Name("example.net"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_ns_sub(new RRset(Name("subdomain.example.com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_ns_sub2(new RRset(Name("subdomain2.example.com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_a_ns0(new RRset(Name("ns0.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_ns1(new RRset(Name("ns1.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_ns2(new RRset(Name("ns2.example.net"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_ns3(new RRset(Name("ns3.subdomain.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::TXT(), RRTTL(300)))
+ {}
+ Name bailiwick; // Bailiwick of the server queried
+ Question qu_in_any_www; // www.example.com IN ANY
+ Question qu_in_a_www; // www.example.com IN A
+ Question qu_in_ns; // example.com IN NS
+ Question qu_in_txt_www; // www.example.com IN TXT
+ RRsetPtr rrs_in_a_org; // mail.example.org IN A
+ RRsetPtr rrs_in_a_net; // mail.example.org IN A
+ RRsetPtr rrs_in_a_www; // www.example.com IN A
+ RRsetPtr rrs_in_cname_www; // www.example.com IN CNAME
+ RRsetPtr rrs_in_a_wwwnet; // www.example.net IN A
+ RRsetPtr rrs_in_ns; // example.com IN NS
+ RRsetPtr rrs_in_ns_com; // com IN NS
+ RRsetPtr rrs_in_ns_net; // example.net IN NS
+ RRsetPtr rrs_in_ns_sub; // subdomain.example.com IN NS
+ RRsetPtr rrs_in_ns_sub2; // subdomain2.example.com IN NS
+ RRsetPtr rrs_in_a_ns0; // ns0.example.com IN A
+ RRsetPtr rrs_in_a_ns1; // ns1.com IN A
+ RRsetPtr rrs_in_a_ns2; // ns2.example.net IN A
+ RRsetPtr rrs_in_a_ns3; // ns3.subdomain.example.net IN A
+ RRsetPtr rrs_in_txt_www; // www.example.com IN TXT
+};
+
+
+// Check that the IP addresses/ports/protocol for the packets sent and received
+// both match if both types are IP V4.
+
+TEST_F(ResponseScrubberTest, UDPv4) {
+
+ // Basic UDP Endpoint
+ GenericEndpoint udp_a("192.0.2.1", 12345, IPPROTO_UDP);
+
+ // Same address, port
+ GenericEndpoint udp_b("192.0.2.1", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::SUCCESS,
+ ResponseScrubber::addressCheck(udp_a, udp_b));
+
+ // Different address, same port
+ GenericEndpoint udp_c("192.0.2.2", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_c));
+
+ // Same address, different port
+ GenericEndpoint udp_d("192.0.2.1", 12346, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::PORT,
+ ResponseScrubber::addressCheck(udp_a, udp_d));
+
+ // Different address, different port
+ GenericEndpoint udp_e("192.0.2.3", 12347, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_e));
+
+}
+
+// Repeat the tests for TCP
+
+TEST_F(ResponseScrubberTest, TCPv4) {
+
+ // Basic TCP Endpoint
+ GenericEndpoint tcp_a("192.0.2.1", 12345, IPPROTO_TCP);
+
+ // Same address, port
+ GenericEndpoint tcp_b("192.0.2.1", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::SUCCESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_b));
+
+ // Different address, same port
+ GenericEndpoint tcp_c("192.0.2.2", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_c));
+
+ // Same address, different port
+ GenericEndpoint tcp_d("192.0.2.1", 12346, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::PORT,
+ ResponseScrubber::addressCheck(tcp_a, tcp_d));
+
+ // Different address, different port
+ GenericEndpoint tcp_e("192.0.2.3", 12347, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_e));
+
+}
+
+// Repeat the tests for UDP/IPv6
+
+TEST_F(ResponseScrubberTest, UDPv6) {
+
+ // Basic UDP Endpoint
+ GenericEndpoint udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+
+ // Same address and port
+ GenericEndpoint udp_b("2001:db8::1", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::SUCCESS,
+ ResponseScrubber::addressCheck(udp_a, udp_b));
+
+ // Different address, same port
+ GenericEndpoint udp_c("2001:db8::3", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_c));
+
+ // Same address, different port
+ GenericEndpoint udp_d("2001:db8::1", 12346, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::PORT,
+ ResponseScrubber::addressCheck(udp_a, udp_d));
+
+ // Different address, different port
+ GenericEndpoint udp_e("2001:db8::3", 12347, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_e));
+
+}
+
+// Same again for TCP/IPv6
+
+TEST_F(ResponseScrubberTest, TCPv6) {
+
+ // Basic TCP Endpoint
+ GenericEndpoint tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+
+ // Same address and port
+ GenericEndpoint tcp_b("2001:db8::1", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::SUCCESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_b));
+
+ // Different address, same port
+ GenericEndpoint tcp_c("2001:db8::3", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_c));
+
+ // Same address, different port
+ GenericEndpoint tcp_d("2001:db8::1", 12346, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::PORT,
+ ResponseScrubber::addressCheck(tcp_a, tcp_d));
+
+ // Different address, different port
+ GenericEndpoint tcp_e("2001:db8::3", 12347, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_e));
+
+}
+
+// Ensure that mixed IPv4/6 addresses don't match.
+
+TEST_F(ResponseScrubberTest, v4v6) {
+
+ // UDP
+ GenericEndpoint udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+ GenericEndpoint udp_b("192.0.2.1", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_b));
+
+ // TCP
+ GenericEndpoint tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+ GenericEndpoint tcp_b("192.0.2.1", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_b));
+}
+
+// Check mixed protocols are detected
+
+TEST_F(ResponseScrubberTest, Protocol) {
+ GenericEndpoint udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+ GenericEndpoint tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::PROTOCOL,
+ ResponseScrubber::addressCheck(udp_a, tcp_a));
+}
+
+// Check that the QIDs check OK
+
+TEST_F(ResponseScrubberTest, Qid) {
+ Message a(Message::RENDER);
+ a.setQid(27);
+
+ Message b(Message::RENDER);
+ b.setQid(27);
+ EXPECT_TRUE(ResponseScrubber::qidCheck(a, b));
+
+ Message c(Message::RENDER);
+ c.setQid(28);
+ EXPECT_FALSE(ResponseScrubber::qidCheck(a, c));
+}
+
+// Check the scrubAllSections() method. As this operates by calling the
+// scrubSection() method (with a SUBDOMAIN argument), this is also a check of
+// the latter.
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsValid) {
+ Message valid(Message::RENDER);
+
+ // Valid message with nothing out of bailiwick
+ valid.addQuestion(qu_in_a_www);
+ valid.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ valid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+ valid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0);
+
+ // Scrub the message and expect nothing to have been removed.
+ int removed = ResponseScrubber::scrubAllSections(valid, bailiwick);
+ EXPECT_EQ(0, removed);
+
+ // ... and check that this is the case
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+
+ // Add out-of-bailiwick glue to the additional section (pretend that the
+ // NS RRset contained an out-of-domain server.
+ valid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+
+ // ... and check that it is removed when scrubbed
+ removed = ResponseScrubber::scrubAllSections(valid, bailiwick);
+ EXPECT_EQ(1, removed);
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+ EXPECT_FALSE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+ }
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsInvalid) {
+ Message invalid(Message::RENDER);
+
+ // Invalid message, with various things in and out of bailiwick.
+
+ invalid.addQuestion(qu_in_a_www);
+
+ // Answer section
+ //
+ // rrs_in_a_www - "www.example.com A", in bailiwick
+ // rrs_in_txt_www - "www.example.com TXT", in bailiwick
+ // rrs_in_a_org - "mail.example.org A", out of bailiwick - the qname is
+ // related to the bailiwick name by having a common ancestor at the root
+ // rrs_in_a_net - "mail.example.net A", out of bailiwick - the qname is
+ // related to the bailiwick name by having a common ancestor at the root
+ invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ invalid.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_org);
+ invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_net);
+
+ // Authority section
+ //
+ // rrs_in_ns - "example.com NS", in bailiwick (qname is bailiwick name)
+ // rrs_in_ns_com - "com NS", out of bailiwick as the qname is a superdomain
+ // (direct ancestor) of the bailiwick name
+ // rrs_in_ns_net - "example.net NS", out of bailiwick - the qname is related
+ // to the bailiwick name by having a common ancestor at the root
+ // rrs_in_ns_sub - "subdomain.example.com", in bailiwick as the qname is
+ // a subdomain of the bailiwick name
+ invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+ invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com);
+ invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net);
+ invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+
+ // Additional section
+ //
+ // rrs_in_a_ns0 - "ns0.example.com", in bailiwick because the qname is
+ // a subdomain of the bailiwick name
+ // rrs_in_a_ns1 - "ns1.com", out of bailiwick because the qname is a
+ // sibling to the bailiwick name
+ // rrs_in_a_ns2 - "ns2.example.net", out of bailiwick because qname is
+ // related by having a common ancestor and the root.
+ // rrs_in_a_ns3 - "ns3.subdomain.example.com", in bailiwick because the
+ // qname is a direct descendent of the bailiwick name.
+ invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0);
+ invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns1);
+ invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+ invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+
+ // Scrub the message
+ int removed = ResponseScrubber::scrubAllSections(invalid, bailiwick);
+ EXPECT_EQ(6, removed);
+
+ // ... and check the sections. Answer...
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_txt_www));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_org));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_net));
+
+ // ... authority...
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net));
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+
+ // ... additional.
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns1));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+}
+
+// An empty message
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsEmpty) {
+ Message empty(Message::RENDER);
+
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ADDITIONAL));
+
+ int removed = ResponseScrubber::scrubAllSections(empty, bailiwick);
+ EXPECT_EQ(0, removed);
+
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ADDITIONAL));
+
+}
+
+// Check the cross-section scrubbing (checks the general scrubSection()
+// method with a SUPERDOMAIN argument.)
+
+// Empty message (apart from question)
+
+TEST_F(ResponseScrubberTest, CrossSectionEmpty) {
+
+ Message message1(Message::RENDER);
+ message1.addQuestion(qu_in_a_www);
+ int removed = ResponseScrubber::scrubCrossSections(message1);
+ EXPECT_EQ(0, removed);
+}
+
+// Valid answer section
+
+TEST_F(ResponseScrubberTest, CrossSectionAnswer) {
+
+ // Valid message with nothing out of bailiwick, but the authority
+ // (subdomain.example.com) is not authoritative for the answer.
+ //
+ // TODO: Test the case where the additional section does not match
+ // with something in the authority section.
+ Message message1(Message::RENDER);
+ message1.addQuestion(qu_in_a_www);
+ message1.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message1.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+ message1.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+ int removed = ResponseScrubber::scrubCrossSections(message1);
+ EXPECT_EQ(1, removed);
+ EXPECT_TRUE(message1.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_FALSE(message1.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+ EXPECT_TRUE(message1.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+
+ // A repeat of the test, this time with a mixture of incorrect and correct
+ // authorities.
+ Message message2(Message::RENDER);
+ message2.addQuestion(qu_in_a_www);
+ message2.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+ message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+ message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub2);
+ message2.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+ removed = ResponseScrubber::scrubCrossSections(message2);
+ EXPECT_EQ(2, removed);
+ EXPECT_TRUE(message2.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_FALSE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+ EXPECT_TRUE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_FALSE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub2));
+ EXPECT_TRUE(message2.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+}
+
+// Test the main "scrub" method. This is a single to ensure that the
+// combination of methods
+
+TEST_F(ResponseScrubberTest, All) {
+ MessagePtr mptr(new Message(Message::RENDER));
+
+ // Question is "www.example.com IN A" sent to a nameserver with the
+ // bailiwick of "example.com".
+ mptr->addQuestion(qu_in_a_www);
+
+ // Answer section.
+
+ // "www.example.com IN CNAME www.example.net" - should be kept
+ mptr->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www);
+
+ // "www.example.net IN A a.b.c.d" - should be removed, out of bailiwick
+ mptr->addRRset(Message::SECTION_ANSWER, rrs_in_a_wwwnet);
+
+ // Authority section.
+
+ // "example.net IN NS xxxx" - should be removed, out of bailiwick.
+ mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net);
+
+ // "example.com IN NS xxx" - kept
+ mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+
+ // "com IN NS xxx" - removed, out of bailiwick
+ mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com);
+
+ // "subdomain.example.com IN NS xxx" - removed, not a superdomain of the
+ // answer.
+ mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+
+ // Additional section
+
+ // "ns2.example.net IN A a.b.c.d" - removed, out of bailiwick
+ mptr->addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+
+ // "ns3.subdomain.example.com IN A a.b.c.d" - retained.
+ mptr->addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+
+ unsigned int removed = ResponseScrubber::scrub(mptr, bailiwick);
+ EXPECT_EQ(5, removed);
+
+ EXPECT_TRUE(mptr->hasRRset(Message::SECTION_ANSWER, rrs_in_cname_www));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_ANSWER, rrs_in_a_wwwnet));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net));
+ EXPECT_TRUE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+ EXPECT_TRUE(mptr->hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+
+}
+} // Anonymous namespace
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 62051dfd150ea2f2063f336f98b8ae32f09744df..f622439a02e60139102de9ae90ed9ed1ba21b84e 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -89,7 +89,8 @@
/usr/local/share/bind10-devel/stats.spec
— This is a spec file for b10-stats. It
contains definitions of statistics items of BIND 10 and commands
- received vi bindctl.
+ received via
+ bindctl1.
diff --git a/src/bin/usermgr/b10-cmdctl-usermgr.py.in b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
index 645c05365e860a3ad830ec389162c8342a3a90b9..d62ad7200a824fa1ca455332e6711dd3ee329346 100644
--- a/src/bin/usermgr/b10-cmdctl-usermgr.py.in
+++ b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
@@ -24,7 +24,7 @@ from hashlib import sha1
import csv
import getpass
import getopt
-import sys
+import sys; sys.path.append ('@@PYTHONPATH@@')
import isc.util.process
isc.util.process.rename()
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 536ac83399d83cddee02834dccf14dbd98b13283..fdfe1ef3b00c3f93b384d1d97b985010fca00b29 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -63,7 +63,7 @@
- The Y1 prototype release only supports AXFR. IXFR is not implemented.
+ This prototype release only supports AXFR. IXFR is not implemented.
diff --git a/src/bin/xfrout/tests/xfrout_test.py b/src/bin/xfrout/tests/xfrout_test.py
index 2fb4463e99e178c753b5888d11bc085c03cc03e9..55a2e52c865afcf9a97d5d29299091e50f5a8ba5 100644
--- a/src/bin/xfrout/tests/xfrout_test.py
+++ b/src/bin/xfrout/tests/xfrout_test.py
@@ -121,6 +121,29 @@ class TestXfroutSession(unittest.TestCase):
get_msg = self.sock.read_msg()
self.assertEqual(get_msg.get_rcode().to_text(), "NXDOMAIN")
+ def test_send_message(self):
+ msg = self.getmsg()
+ msg.make_response()
+ # soa record data with different cases
+ soa_record = (4, 3, 'Example.com.', 'com.Example.', 3600, 'SOA', None, 'master.Example.com. admin.exAmple.com. 1234 3600 1800 2419200 7200')
+ rrset_soa = self.xfrsess._create_rrset_from_db_record(soa_record)
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
+ self.xfrsess._send_message(self.sock, msg)
+ send_out_data = self.sock.readsent()[2:]
+
+ # CASE_INSENSITIVE compression mode
+ render = MessageRenderer();
+ render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
+ msg.to_wire(render)
+ self.assertNotEqual(render.get_data(), send_out_data)
+
+ # CASE_SENSITIVE compression mode
+ render.clear()
+ render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
+ render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
+ msg.to_wire(render)
+ self.assertEqual(render.get_data(), send_out_data)
+
def test_clear_message(self):
msg = self.getmsg()
qid = msg.get_qid()
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index eb96b940c4c610b5166d915f3244ea6602bdde6f..a81964077d7683e59bd5dbe84e3bd36b0fa5e99e 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -170,6 +170,9 @@ class XfroutSession(BaseRequestHandler):
def _send_message(self, sock_fd, msg):
render = MessageRenderer()
+ # As defined in RFC5936 section3.4, perform case-preserving name
+ # compression for AXFR message.
+ render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
msg.to_wire(render)
header_len = struct.pack('H', socket.htons(render.get_length()))
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 866ca6509121497c403a6c0be379d7f08085102c..d5486a0f7d7988fb6ee613016a04ad9956960a01 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS = exceptions dns cc config datasrc python xfr bench log asiolink \
- testutils nsas
+SUBDIRS = exceptions dns cc config datasrc python xfr bench log \
+ resolve nsas cache asiolink testutils
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index c8790394b5b7baf94ce20195ed90d37429a98d43..b3968f0661a95efe6b31bf3e6e2c52c2baafb2c9 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . tests internal
+SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
@@ -12,15 +12,29 @@ CLEANFILES = *.gcno *.gcda
# have some code fragments that would hit gcc's unused-parameter warning,
# which would make the build fail with -Werror (our default setting).
lib_LTLIBRARIES = libasiolink.la
-libasiolink_la_SOURCES = asiolink.cc asiolink.h
-libasiolink_la_SOURCES += iosocket.cc iosocket.h
-libasiolink_la_SOURCES += iomessage.h
-libasiolink_la_SOURCES += ioaddress.cc ioaddress.h
-libasiolink_la_SOURCES += ioendpoint.cc ioendpoint.h
-libasiolink_la_SOURCES += udpdns.cc internal/udpdns.h
-libasiolink_la_SOURCES += tcpdns.cc internal/tcpdns.h
-libasiolink_la_SOURCES += internal/coroutine.h
-libasiolink_la_SOURCES += iofetch.cc internal/iofetch.h
+libasiolink_la_SOURCES = asiolink.h
+libasiolink_la_SOURCES += dns_answer.h
+libasiolink_la_SOURCES += dns_lookup.h
+libasiolink_la_SOURCES += dns_server.h
+libasiolink_la_SOURCES += dns_service.h dns_service.cc
+libasiolink_la_SOURCES += dummy_io_cb.h
+libasiolink_la_SOURCES += interval_timer.h interval_timer.cc
+libasiolink_la_SOURCES += io_address.h io_address.cc
+libasiolink_la_SOURCES += io_asio_socket.h
+libasiolink_la_SOURCES += io_endpoint.h io_endpoint.cc
+libasiolink_la_SOURCES += io_error.h
+libasiolink_la_SOURCES += io_fetch.h io_fetch.cc
+libasiolink_la_SOURCES += io_message.h
+libasiolink_la_SOURCES += io_service.h io_service.cc
+libasiolink_la_SOURCES += io_socket.h io_socket.cc
+libasiolink_la_SOURCES += recursive_query.h recursive_query.cc
+libasiolink_la_SOURCES += simple_callback.h
+libasiolink_la_SOURCES += tcp_endpoint.h
+libasiolink_la_SOURCES += tcp_server.h tcp_server.cc
+libasiolink_la_SOURCES += tcp_socket.h
+libasiolink_la_SOURCES += udp_endpoint.h
+libasiolink_la_SOURCES += udp_server.h udp_server.cc
+libasiolink_la_SOURCES += udp_socket.h
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
@@ -33,3 +47,6 @@ libasiolink_la_CXXFLAGS += -Wno-error
endif
libasiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
libasiolink_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la
+libasiolink_la_LIBADD += $(top_builddir)/src/lib/resolve/libresolve.la
+libasiolink_la_LIBADD += $(top_builddir)/src/lib/cache/libcache.la
+libasiolink_la_LIBADD += $(top_builddir)/src/lib/nsas/libnsas.la
diff --git a/src/lib/asiolink/README b/src/lib/asiolink/README
index b0f6a7d97933b7214222f0e16c933d8a75449661..6bd1a7383c28a33bf384193fb387ded30c562a24 100644
--- a/src/lib/asiolink/README
+++ b/src/lib/asiolink/README
@@ -33,7 +33,7 @@ This is intended to simplify development a bit, since it allows the
routines to be written in a straightfowrard step-step-step fashion rather
than as a complex chain of separate handler functions.
-Coroutine objects (i.e., UDPServer, TCPServer and UDPQuery) are objects
+Coroutine objects (i.e., UDPServer, TCPServer and IOFetch) are objects
with reenterable operator() members. When an instance of one of these
classes is called as a function, it resumes at the position where it left
off. Thus, a UDPServer can issue an asynchronous I/O call and specify
@@ -101,3 +101,82 @@ when the answer has arrived. In simplified form, the DNSQuery routine is:
Currently, DNSQuery is only implemented for UDP queries. In future work
it will be necessary to write code to fall back to TCP when circumstances
require it.
+
+
+Upstream Fetches
+================
+Upstream fetches (queries by the resolver on behalf of a client) are made
+using a slightly-modified version of the pattern described above.
+
+Sockets
+-------
+First, it will be useful to understand the class hierarchy used in the
+fetch logic:
+
+ IOSocket
+ |
+ IOAsioSocket
+ |
+ +-----+-----+
+ | |
+UDPSocket TCPSocket
+
+IOSocket is a wrapper class for a socket and is used by the authoritative
+server code. It is an abstract base class, providing little more that the ability to hold the socket and to return the protocol in use.
+
+Built on this is IOAsioSocket, which adds the open, close, asyncSend and
+asyncReceive methods. This is a template class, which takes as template
+argument the class of the object that will be used as the callback when the
+asynchronous operation completes. This object can be of any type, but must
+include an operator() method with the signature:
+
+ operator()(asio::error_code ec, size_t length)
+
+... the two arguments being the status of the completed I/O operation and
+the number of bytes transferred. (In the case of the open method, the second
+argument will be zero.)
+
+Finally, the TCPSocket and UDPSocket classes provide the body of the
+asynchronous operations.
+
+Fetch Sequence
+--------------
+The fetch is implemented by the IOFetch class, which takes as argument the
+protocol to use. The sequence is:
+
+ REENTER:
+ render the question into a wire-format query packet
+ open() // Open socket and optionally connect
+ if (! synchronous) {
+ YIELD;
+ }
+ YIELD asyncSend(query) // Send query
+ do {
+ YIELD asyncReceive(response) // Read response
+ } while (! complete(response))
+ close() // Drop connection and close socket
+ server->resume
+
+The open() method opens a socket for use. On TCP, it also makes a
+connection to the remote end. So under UDP the operation will complete
+immediately, but under TCP it could take a long time. One solution would be
+for the open operation to post an event to the I/O queue; then both cases
+could be regarded as being equivalent, with the completion being signalled
+by the posting of the completion event. However UDP is the most common case
+and that would involve extra overhead. So the open() returns a status
+indicating whether the operation completed asynchronously. If it did, the
+code yields back to the coroutine; if not the yield is bypassed.
+
+The asynchronous send is straightforward, invoking the underlying ASIO
+function. (Note that the address/port is supplied to both the open() and
+asyncSend() methods - it is used by the TCPSocket in open() and by the
+UDPSocket in asyncSend().)
+
+The asyncReceive() method issues an asynchronous read and waits for completion.
+The fetch object keeps track of the amount of data received so far and when
+the receive completes it calls a method on the socket to determine if the
+entire message has been received. (This will always be the case for UDP. On
+TCP though, the message is preceded by a count field as several reads may be
+required to read all the data.) The fetch loops until all the data is read.
+
+Finally, the socket is closed and the server called to resume operation.
diff --git a/src/lib/asiolink/asiolink.cc b/src/lib/asiolink/asiolink.cc
deleted file mode 100644
index 15235f323944237d2ec93bd8fe6f9cd32214b93a..0000000000000000000000000000000000000000
--- a/src/lib/asiolink/asiolink.cc
+++ /dev/null
@@ -1,680 +0,0 @@
-// Copyright (C) 2010 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
-
-#include // For rand(), temporary until better forwarding is done
-
-#include // for some IPC/network system calls
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-#include
-
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-
-using namespace std;
-using namespace isc::dns;
-using isc::log::dlog;
-using namespace boost;
-
-// Is this something we can use in libdns++?
-namespace {
- class SectionInserter {
- public:
- SectionInserter(MessagePtr message, const Message::Section sect) :
- message_(message), section_(sect)
- {}
- void operator()(const RRsetPtr rrset) {
- message_->addRRset(section_, rrset, true);
- }
- MessagePtr message_;
- const Message::Section section_;
- };
-
-
- /// \brief Copies the parts relevant for a DNS answer to the
- /// target message
- ///
- /// This adds all the RRsets in the answer, authority and
- /// additional sections to the target, as well as the response
- /// code
- void copyAnswerMessage(const Message& source, MessagePtr target) {
- target->setRcode(source.getRcode());
-
- for_each(source.beginSection(Message::SECTION_ANSWER),
- source.endSection(Message::SECTION_ANSWER),
- SectionInserter(target, Message::SECTION_ANSWER));
- for_each(source.beginSection(Message::SECTION_AUTHORITY),
- source.endSection(Message::SECTION_AUTHORITY),
- SectionInserter(target, Message::SECTION_AUTHORITY));
- for_each(source.beginSection(Message::SECTION_ADDITIONAL),
- source.endSection(Message::SECTION_ADDITIONAL),
- SectionInserter(target, Message::SECTION_ADDITIONAL));
- }
-}
-
-namespace asiolink {
-
-typedef pair addr_t;
-
-class IOServiceImpl {
-private:
- IOServiceImpl(const IOService& source);
- IOServiceImpl& operator=(const IOService& source);
-public:
- /// \brief The constructor
- IOServiceImpl() :
- io_service_(),
- work_(io_service_)
- {};
- /// \brief The destructor.
- ~IOServiceImpl() {};
- //@}
-
- /// \brief Start the underlying event loop.
- ///
- /// This method does not return control to the caller until
- /// the \c stop() method is called via some handler.
- void run() { io_service_.run(); };
-
- /// \brief Run the underlying event loop for a single event.
- ///
- /// This method return control to the caller as soon as the
- /// first handler has completed. (If no handlers are ready when
- /// it is run, it will block until one is.)
- void run_one() { io_service_.run_one();} ;
-
- /// \brief Stop the underlying event loop.
- ///
- /// This will return the control to the caller of the \c run() method.
- void stop() { io_service_.stop();} ;
-
- /// \brief Return the native \c io_service object used in this wrapper.
- ///
- /// This is a short term work around to support other BIND 10 modules
- /// that share the same \c io_service with the authoritative server.
- /// It will eventually be removed once the wrapper interface is
- /// generalized.
- asio::io_service& get_io_service() { return io_service_; };
-private:
- asio::io_service io_service_;
- asio::io_service::work work_;
-};
-
-IOService::IOService() {
- io_impl_ = new IOServiceImpl();
-}
-
-IOService::~IOService() {
- delete io_impl_;
-}
-
-void
-IOService::run() {
- io_impl_->run();
-}
-
-void
-IOService::run_one() {
- io_impl_->run_one();
-}
-
-void
-IOService::stop() {
- io_impl_->stop();
-}
-
-asio::io_service&
-IOService::get_io_service() {
- return (io_impl_->get_io_service());
-}
-
-class DNSServiceImpl {
-public:
- DNSServiceImpl(IOService& io_service, const char& port,
- const ip::address* v4addr, const ip::address* v6addr,
- SimpleCallback* checkin, DNSLookup* lookup,
- DNSAnswer* answer);
-
- IOService& io_service_;
-
- typedef boost::shared_ptr UDPServerPtr;
- typedef boost::shared_ptr TCPServerPtr;
- typedef boost::shared_ptr DNSServerPtr;
- vector servers_;
- SimpleCallback *checkin_;
- DNSLookup *lookup_;
- DNSAnswer *answer_;
-
- void addServer(uint16_t port, const ip::address& address) {
- try {
- dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast(port));
- TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
- address, port, checkin_, lookup_, answer_));
- (*tcpServer)();
- servers_.push_back(tcpServer);
- dlog(std::string("Initialize UDP server at ") + address.to_string() + ":" + boost::lexical_cast(port));
- UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
- address, port, checkin_, lookup_, answer_));
- (*udpServer)();
- servers_.push_back(udpServer);
- }
- catch (const asio::system_error& err) {
- // We need to catch and convert any ASIO level exceptions.
- // This can happen for unavailable address, binding a privilege port
- // without the privilege, etc.
- isc_throw(IOError, "Failed to initialize network servers: " <<
- err.what());
- }
- }
- void addServer(const char& port, const ip::address& address) {
- uint16_t portnum;
- try {
- // XXX: SunStudio with stlport4 doesn't reject some invalid
- // representation such as "-1" by lexical_cast, so
- // we convert it into a signed integer of a larger size and perform
- // range check ourselves.
- const int32_t portnum32 = boost::lexical_cast(&port);
- if (portnum32 < 0 || portnum32 > 65535) {
- isc_throw(IOError, "Invalid port number '" << &port);
- }
- portnum = portnum32;
- } catch (const boost::bad_lexical_cast& ex) {
- isc_throw(IOError, "Invalid port number '" << &port << "': " <<
- ex.what());
- }
- addServer(portnum, address);
- }
-};
-
-DNSServiceImpl::DNSServiceImpl(IOService& io_service,
- const char& port,
- const ip::address* const v4addr,
- const ip::address* const v6addr,
- SimpleCallback* checkin,
- DNSLookup* lookup,
- DNSAnswer* answer) :
- io_service_(io_service),
- checkin_(checkin),
- lookup_(lookup),
- answer_(answer)
-{
-
- if (v4addr) {
- addServer(port, *v4addr);
- }
- if (v6addr) {
- addServer(port, *v6addr);
- }
-}
-
-DNSService::DNSService(IOService& io_service,
- const char& port, const char& address,
- SimpleCallback* checkin,
- DNSLookup* lookup,
- DNSAnswer* answer) :
- impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
- answer)), io_service_(io_service)
-{
- addServer(port, &address);
-}
-
-DNSService::DNSService(IOService& io_service,
- const char& port,
- const bool use_ipv4, const bool use_ipv6,
- SimpleCallback* checkin,
- DNSLookup* lookup,
- DNSAnswer* answer) :
- impl_(NULL), io_service_(io_service)
-{
- const ip::address v4addr_any = ip::address(ip::address_v4::any());
- const ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL;
- const ip::address v6addr_any = ip::address(ip::address_v6::any());
- const ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
- impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer);
-}
-
-DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer *answer) :
- impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
- answer)), io_service_(io_service)
-{
-}
-
-DNSService::~DNSService() {
- delete impl_;
-}
-
-namespace {
-
-typedef std::vector > AddressVector;
-
-}
-
-RecursiveQuery::RecursiveQuery(DNSService& dns_service,
- const AddressVector& upstream,
- const AddressVector& upstream_root,
- int timeout, unsigned retries) :
- dns_service_(dns_service), upstream_(new AddressVector(upstream)),
- upstream_root_(new AddressVector(upstream_root)),
- timeout_(timeout), retries_(retries)
-{}
-
-namespace {
-
-ip::address
-convertAddr(const string& address) {
- error_code err;
- ip::address addr = ip::address::from_string(address, err);
- if (err) {
- isc_throw(IOError, "Invalid IP address '" << &address << "': "
- << err.message());
- }
- return (addr);
-}
-
-}
-
-void
-DNSService::addServer(const char& port, const string& address) {
- impl_->addServer(port, convertAddr(address));
-}
-
-void
-DNSService::addServer(uint16_t port, const string& address) {
- impl_->addServer(port, convertAddr(address));
-}
-
-void
-DNSService::clearServers() {
- // FIXME: This does not work, it does not close the socket.
- // How is it done?
- impl_->servers_.clear();
-}
-
-namespace {
-
-/*
- * This is a query in progress. When a new query is made, this one holds
- * the context information about it, like how many times we are allowed
- * to retry on failure, what to do when we succeed, etc.
- *
- * Used by RecursiveQuery::sendQuery.
- */
-class RunningQuery : public UDPQuery::Callback {
-private:
- // The io service to handle async calls
- asio::io_service& io_;
-
- // Info for (re)sending the query (the question and destination)
- Question question_;
-
- // This is where we build and store our final answer
- MessagePtr answer_message_;
-
- // currently we use upstream as the current list of NS records
- // we should differentiate between forwarding and resolving
- shared_ptr upstream_;
-
- // root servers...just copied over to the zone_servers_
- shared_ptr upstream_root_;
-
- // Buffer to store the result.
- OutputBufferPtr buffer_;
-
- // Server to notify when we succeed or fail
- shared_ptr server_;
-
- /*
- * TODO Do something more clever with timeouts. In the long term, some
- * computation of average RTT, increase with each retry, etc.
- */
- // Timeout information
- int timeout_;
- unsigned retries_;
-
- // normal query state
-
- // if we change this to running and add a sent, we can do
- // decoupled timeouts i think
- bool done;
-
- // Not using NSAS at this moment, so we keep a list
- // of 'current' zone servers
- vector zone_servers_;
-
- // Update the question that will be sent to the server
- void setQuestion(const Question& new_question) {
- question_ = new_question;
- }
-
- // (re)send the query to the server.
- void send() {
- const int uc = upstream_->size();
- const int zs = zone_servers_.size();
- buffer_->clear();
- if (uc > 0) {
- int serverIndex = rand() % uc;
- dlog("Sending upstream query (" + question_.toText() +
- ") to " + upstream_->at(serverIndex).first);
- UDPQuery query(io_, question_,
- upstream_->at(serverIndex).first,
- upstream_->at(serverIndex).second, buffer_, this,
- timeout_);
- io_.post(query);
- } else if (zs > 0) {
- int serverIndex = rand() % zs;
- dlog("Sending query to zone server (" + question_.toText() +
- ") to " + zone_servers_.at(serverIndex).first);
- UDPQuery query(io_, question_,
- zone_servers_.at(serverIndex).first,
- zone_servers_.at(serverIndex).second, buffer_, this,
- timeout_);
- io_.post(query);
- } else {
- dlog("Error, no upstream servers to send to.");
- }
- }
-
- // This function is called by operator() if there is an actual
- // answer from a server and we are in recursive mode
- // depending on the contents, we go on recursing or return
- //
- // Note that the footprint may change as this function may
- // need to append data to the answer we are building later.
- //
- // returns true if we are done
- // returns false if we are not done
- bool handleRecursiveAnswer(const Message& incoming) {
- //temporary code to grab TC enabled responses
- if(incoming.getHeaderFlag(Message::HEADERFLAG_TC)) {
- //TC (truncated) bit is set, which means we need to use TCP
- // need to check if TCP conn already open (RFC 5966)
- }
- if (incoming.getRRCount(Message::SECTION_ANSWER) > 0) {
- dlog("Got final result, copying answer.");
- copyAnswerMessage(incoming, answer_message_);
- return true;
- } else {
- dlog("Got delegation, continuing");
- // ok we need to do some more processing.
- // the ns list should contain all nameservers
- // while the additional may contain addresses for
- // them.
- // this needs to tie into NSAS of course
- // for this very first mockup, hope there is an
- // address in additional and just use that
-
- // send query to the addresses in the delegation
- bool found_ns_address = false;
- zone_servers_.clear();
-
- for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
- rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
- rrsi++) {
- ConstRRsetPtr rrs = *rrsi;
- if (rrs->getType() == RRType::A()) {
- // found address
- RdataIteratorPtr rdi = rrs->getRdataIterator();
- // just use the first for now
- if (!rdi->isLast()) {
- std::string addr_str = rdi->getCurrent().toText();
- dlog("[XX] first address found: " + addr_str);
- // now we have one address, simply
- // resend that exact same query
- // to that address and yield, when it
- // returns, loop again.
-
- // should use NSAS
- zone_servers_.push_back(addr_t(addr_str, 53));
- found_ns_address = true;
- }
- }
- }
- if (found_ns_address) {
- // next resolver round
- send();
- return false;
- } else {
- dlog("[XX] no ready-made addresses in additional. need nsas.");
- // this will result in answering with the delegation. oh well
- copyAnswerMessage(incoming, answer_message_);
- return true;
- }
- }
- }
-
-
-public:
- RunningQuery(asio::io_service& io, const Question &question,
- MessagePtr answer_message, shared_ptr upstream,
- shared_ptr upstream_root,
- OutputBufferPtr buffer, DNSServer* server, int timeout,
- unsigned retries) :
- io_(io),
- question_(question),
- answer_message_(answer_message),
- upstream_(upstream),
- upstream_root_(upstream_root),
- buffer_(buffer),
- server_(server->clone()),
- timeout_(timeout),
- retries_(retries),
- zone_servers_()
- {
- dlog("Started a new RunningQuery");
- done = false;
-
- // should use NSAS for root servers
- // Adding root servers if not a forwarder
- if (upstream_->empty()) {
- if (upstream_root_->empty()) { //if no root ips given, use this
- zone_servers_.push_back(addr_t("192.5.5.241", 53));
- }
- else
- {
- //copy the list
- dlog("Size is " +
- boost::lexical_cast(upstream_root_->size()) +
- "\n");
- //Use BOOST_FOREACH here? Is it faster?
- for(AddressVector::iterator it = upstream_root_->begin();
- it < upstream_root_->end(); it++) {
- zone_servers_.push_back(addr_t(it->first,it->second));
- dlog("Put " + zone_servers_.back().first + "into root list\n");
- }
- }
- }
- send();
- }
-
-
- // This function is used as callback from DNSQuery.
- virtual void operator()(UDPQuery::Result result) {
- // XXX is this the place for TCP retry?
- if (result != UDPQuery::TIME_OUT) {
- // we got an answer
- Message incoming(Message::PARSE);
- InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
- incoming.fromWire(ibuf);
-
- if (upstream_->size() == 0 &&
- incoming.getRcode() == Rcode::NOERROR()) {
- done = handleRecursiveAnswer(incoming);
- } else {
- copyAnswerMessage(incoming, answer_message_);
- done = true;
- }
-
- if (done) {
- server_->resume(result == UDPQuery::SUCCESS);
- delete this;
- }
- } else if (retries_--) {
- // We timed out, but we have some retries, so send again
- dlog("Timeout, resending query");
- send();
- } else {
- // out of retries, give up for now
- server_->resume(false);
- delete this;
- }
- }
-};
-
-}
-
-void
-RecursiveQuery::sendQuery(const Question& question,
- MessagePtr answer_message,
- OutputBufferPtr buffer,
- DNSServer* server)
-{
- // XXX: eventually we will need to be able to determine whether
- // the message should be sent via TCP or UDP, or sent initially via
- // UDP and then fall back to TCP on failure, but for the moment
- // we're only going to handle UDP.
- asio::io_service& io = dns_service_.get_io_service();
- // It will delete itself when it is done
- new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
- buffer, server, timeout_, retries_);
-}
-
-class IntervalTimerImpl {
-private:
- // prohibit copy
- IntervalTimerImpl(const IntervalTimerImpl& source);
- IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
-public:
- IntervalTimerImpl(IOService& io_service);
- ~IntervalTimerImpl();
- void setupTimer(const IntervalTimer::Callback& cbfunc,
- const uint32_t interval);
- void callback(const asio::error_code& error);
- void cancel() {
- timer_.cancel();
- interval_ = 0;
- }
- uint32_t getInterval() const { return (interval_); }
-private:
- // a function to update timer_ when it expires
- void updateTimer();
- // a function to call back when timer_ expires
- IntervalTimer::Callback cbfunc_;
- // interval in seconds
- uint32_t interval_;
- // asio timer
- asio::deadline_timer timer_;
-};
-
-IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
- interval_(0), timer_(io_service.get_io_service())
-{}
-
-IntervalTimerImpl::~IntervalTimerImpl()
-{}
-
-void
-IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
- const uint32_t interval)
-{
- // Interval should not be 0.
- if (interval == 0) {
- isc_throw(isc::BadValue, "Interval should not be 0");
- }
- // Call back function should not be empty.
- if (cbfunc.empty()) {
- isc_throw(isc::InvalidParameter, "Callback function is empty");
- }
- cbfunc_ = cbfunc;
- interval_ = interval;
- // Set initial expire time.
- // At this point the timer is not running yet and will not expire.
- // After calling IOService::run(), the timer will expire.
- updateTimer();
- return;
-}
-
-void
-IntervalTimerImpl::updateTimer() {
- if (interval_ == 0) {
- // timer has been canceled. Do nothing.
- return;
- }
- try {
- // Update expire time to (current time + interval_).
- timer_.expires_from_now(boost::posix_time::seconds(interval_));
- } catch (const asio::system_error& e) {
- isc_throw(isc::Unexpected, "Failed to update timer");
- }
- // Reset timer.
- timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
-}
-
-void
-IntervalTimerImpl::callback(const asio::error_code& cancelled) {
- // Do not call cbfunc_ in case the timer was cancelled.
- // The timer will be canelled in the destructor of asio::deadline_timer.
- if (!cancelled) {
- cbfunc_();
- // Set next expire time.
- updateTimer();
- }
-}
-
-IntervalTimer::IntervalTimer(IOService& io_service) {
- impl_ = new IntervalTimerImpl(io_service);
-}
-
-IntervalTimer::~IntervalTimer() {
- delete impl_;
-}
-
-void
-IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
- return (impl_->setupTimer(cbfunc, interval));
-}
-
-void
-IntervalTimer::cancel() {
- impl_->cancel();
-}
-
-uint32_t
-IntervalTimer::getInterval() const {
- return (impl_->getInterval());
-}
-
-}
diff --git a/src/lib/asiolink/asiolink.h b/src/lib/asiolink/asiolink.h
index 8bbd80d3440ffaa0d8ffd1bf31593a344fb52ce2..03951ae9df8db83b1675817aee5faa117d033ebb 100644
--- a/src/lib/asiolink/asiolink.h
+++ b/src/lib/asiolink/asiolink.h
@@ -18,31 +18,21 @@
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
// See the description of the namespace below.
-#include // for some network system calls
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include
-
-#include
-#include
-#include
-#include
-
-namespace asio {
-// forward declaration for IOService::get_io_service() below
-class io_service;
-}
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
/// \namespace asiolink
/// \brief A wrapper interface for the ASIO library.
@@ -94,605 +84,6 @@ class io_service;
/// the placeholder of callback handlers:
/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
-namespace asiolink {
-class DNSServiceImpl;
-struct IOServiceImpl;
-struct IntervalTimerImpl;
-
-/// \brief An exception that is thrown if an error occurs within the IO
-/// module. This is mainly intended to be a wrapper exception class for
-/// ASIO specific exceptions.
-class IOError : public isc::Exception {
-public:
- IOError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// \brief Forward declarations for classes used below
-class SimpleCallback;
-class DNSLookup;
-class DNSAnswer;
-
-/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
-/// class.
-///
-class IOService {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOService(const IOService& source);
- IOService& operator=(const IOService& source);
-public:
- /// \brief The constructor
- IOService();
- /// \brief The destructor.
- ~IOService();
- //@}
-
- /// \brief Start the underlying event loop.
- ///
- /// This method does not return control to the caller until
- /// the \c stop() method is called via some handler.
- void run();
-
- /// \brief Run the underlying event loop for a single event.
- ///
- /// This method return control to the caller as soon as the
- /// first handler has completed. (If no handlers are ready when
- /// it is run, it will block until one is.)
- void run_one();
-
- /// \brief Stop the underlying event loop.
- ///
- /// This will return the control to the caller of the \c run() method.
- void stop();
-
- /// \brief Return the native \c io_service object used in this wrapper.
- ///
- /// This is a short term work around to support other BIND 10 modules
- /// that share the same \c io_service with the authoritative server.
- /// It will eventually be removed once the wrapper interface is
- /// generalized.
- asio::io_service& get_io_service();
-
-private:
- IOServiceImpl* io_impl_;
-};
-
-///
-/// DNSService is the service that handles DNS queries and answers with
-/// a given IOService. This class is mainly intended to hold all the
-/// logic that is shared between the authoritative and the recursive
-/// server implementations. As such, it handles asio, including config
-/// updates (through the 'Checkinprovider'), and listening sockets.
-///
-class DNSService {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- DNSService(const DNSService& source);
- DNSService& operator=(const DNSService& source);
-
-public:
- /// \brief The constructor with a specific IP address and port on which
- /// the services listen on.
- ///
- /// \param io_service The IOService to work with
- /// \param port the port to listen on
- /// \param address the IP address to listen on
- /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
- /// \param lookup The lookup provider (see \c DNSLookup)
- /// \param answer The answer provider (see \c DNSAnswer)
- DNSService(IOService& io_service, const char& port,
- const char& address, SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer* answer);
- /// \brief The constructor with a specific port on which the services
- /// listen on.
- ///
- /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
- /// IPv4/IPv6 services will be available if and only if \c use_ipv4
- /// or \c use_ipv6 is \c true, respectively.
- ///
- /// \param io_service The IOService to work with
- /// \param port the port to listen on
- /// \param ipv4 If true, listen on ipv4 'any'
- /// \param ipv6 If true, listen on ipv6 'any'
- /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
- /// \param lookup The lookup provider (see \c DNSLookup)
- /// \param answer The answer provider (see \c DNSAnswer)
- DNSService(IOService& io_service, const char& port,
- const bool use_ipv4, const bool use_ipv6,
- SimpleCallback* checkin, DNSLookup* lookup,
- DNSAnswer* answer);
- /// \brief The constructor without any servers.
- ///
- /// Use addServer() to add some servers.
- DNSService(IOService& io_service, SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer* answer);
- /// \brief The destructor.
- ~DNSService();
- //@}
-
- /// \brief Add another server to the service
- void addServer(uint16_t port, const std::string &address);
- void addServer(const char &port, const std::string &address);
- /// \brief Remove all servers from the service
- void clearServers();
-
- /// \brief Return the native \c io_service object used in this wrapper.
- ///
- /// This is a short term work around to support other BIND 10 modules
- /// that share the same \c io_service with the authoritative server.
- /// It will eventually be removed once the wrapper interface is
- /// generalized.
- asio::io_service& get_io_service() { return io_service_.get_io_service(); }
-private:
- DNSServiceImpl* impl_;
- IOService& io_service_;
-};
-
-/// \brief The \c DNSServer class is a wrapper (and base class) for
-/// classes which provide DNS server functionality.
-///
-/// The classes derived from this one, \c TCPServer and \c UDPServer,
-/// act as the interface layer between clients sending queries, and
-/// functions defined elsewhere that provide answers to those queries.
-/// Those functions are described in more detail below under
-/// \c SimpleCallback, \c DNSLookup, and \c DNSAnswer.
-///
-/// Notes to developers:
-/// When constructed, this class (and its derived classes) will have its
-/// "self_" member set to point to "this". Objects of this class (as
-/// instantiated through a base class) are sometimes passed by
-/// reference (as this superclass); calls to methods in the base
-/// class are then rerouted via this pointer to methods in the derived
-/// class. This allows code from outside asiolink, with no specific
-/// knowledge of \c TCPServer or \c UDPServer, to access their methods.
-///
-/// This class is both assignable and copy-constructable. Its subclasses
-/// use the "stackless coroutine" pattern, meaning that it will copy itself
-/// when "forking", and that instances will be posted as ASIO handler
-/// objects, which are always copied.
-///
-/// Because these objects are frequently copied, it is recommended
-/// that derived classes be kept small to reduce copy overhead.
-class DNSServer {
-protected:
- ///
- /// \name Constructors and destructors
- ///
- /// This is intentionally defined as \c protected, as this base class
- /// should never be instantiated except as part of a derived class.
- //@{
- DNSServer() : self_(this) {}
-public:
- /// \brief The destructor
- virtual ~DNSServer() {}
- //@}
-
- ///
- /// \name Class methods
- ///
- /// These methods all make their calls indirectly via the "self_"
- /// pointer, ensuring that the functions ultimately invoked will be
- /// the ones in the derived class. This makes it possible to pass
- /// instances of derived classes as references to this base class
- /// without losing access to derived class data.
- ///
- //@{
- /// \brief The funtion operator
- virtual void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0)
- {
- (*self_)(ec, length);
- }
-
- /// \brief Resume processing of the server coroutine after an
- /// asynchronous call (e.g., to the DNS Lookup provider) has completed.
- ///
- /// \param done If true, this signals the system there is an answer
- /// to return.
- virtual void resume(const bool done) { self_->resume(done); }
-
- /// \brief Indicate whether the server is able to send an answer
- /// to a query.
- ///
- /// This is presently used only for testing purposes.
- virtual bool hasAnswer() { return (self_->hasAnswer()); }
-
- /// \brief Returns the current value of the 'coroutine' object
- ///
- /// This is a temporary method, intended to be used for debugging
- /// purposes during development and removed later. It allows
- /// callers from outside the coroutine object to retrieve information
- /// about its current state.
- ///
- /// \return The value of the 'coroutine' object
- virtual int value() { return (self_->value()); }
-
- /// \brief Returns a pointer to a clone of this DNSServer object.
- ///
- /// When a \c DNSServer object is copied or assigned, the result will
- /// normally be another \c DNSServer object containing a copy
- /// of the original "self_" pointer. Calling clone() guarantees
- /// that the underlying object is also correctly copied.
- ///
- /// \return A deep copy of this DNSServer object
- virtual DNSServer* clone() { return (self_->clone()); }
- //@}
-
-protected:
- /// \brief Lookup handler object.
- ///
- /// This is a protected class; it can only be instantiated
- /// from within a derived class of \c DNSServer.
- ///
- /// A server object that has received a query creates an instance
- /// of this class and scheudles it on the ASIO service queue
- /// using asio::io_service::post(). When the handler executes, it
- /// calls the asyncLookup() method in the server object to start a
- /// DNS lookup. When the lookup is complete, the server object is
- /// scheduled to resume, again using io_service::post().
- ///
- /// Note that the calling object is copied into the handler object,
- /// not referenced. This is because, once the calling object yields
- /// control to the handler, it falls out of scope and may disappear
- template
- class AsyncLookup {
- public:
- AsyncLookup(T& caller) : caller_(caller) {}
- void operator()() { caller_.asyncLookup(); }
- private:
- T caller_;
- };
-
- /// \brief Carries out a DNS lookup.
- ///
- /// This function calls the \c DNSLookup object specified by the
- /// DNS server when the \c IOService was created, passing along
- /// the details of the query and a pointer back to the current
- /// server object. It is called asynchronously via the AsyncLookup
- /// handler class.
- virtual void asyncLookup() { self_->asyncLookup(); }
-
-private:
- DNSServer* self_;
-};
-
-/// \brief The \c DNSLookup class is an abstract base class for a DNS
-/// Lookup provider function.
-///
-/// Specific derived class implementations are hidden within the
-/// implementation. Instances of the derived classes can be called
-/// as functions via the operator() interface. Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// A DNS Lookup provider function obtains the data needed to answer
-/// a DNS query (e.g., from authoritative data source, cache, or upstream
-/// query). After it has run, the OutputBuffer object passed to it
-/// should contain the answer to the query, in an internal representation.
-class DNSLookup {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- DNSLookup(const DNSLookup& source);
- DNSLookup& operator=(const DNSLookup& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class
- /// should never be instantiated (except as part of a derived class).
- DNSLookup() : self_(this) {}
-public:
- /// \brief The destructor
- virtual ~DNSLookup() {}
- //@}
- /// \brief The function operator
- ///
- /// This makes its call indirectly via the "self" pointer, ensuring
- /// that the function ultimately invoked will be the one in the derived
- /// class.
- ///
- /// \param io_message The event message to handle
- /// \param message The DNS MessagePtr that needs handling
- /// \param buffer The final answer is put here
- /// \param DNSServer DNSServer object to use
- virtual void operator()(const IOMessage& io_message,
- isc::dns::MessagePtr message,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer,
- DNSServer* server) const
- {
- (*self_)(io_message, message, answer_message, buffer, server);
- }
-private:
- DNSLookup* self_;
-};
-
-/// \brief The \c DNSAnswer class is an abstract base class for a DNS
-/// Answer provider function.
-///
-/// Specific derived class implementations are hidden within the
-/// implementation. Instances of the derived classes can be called
-/// as functions via the operator() interface. Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// A DNS Answer provider function takes answer data that has been obtained
-/// from a DNS Lookup provider functon and readies it to be sent to the
-/// client. After it has run, the OutputBuffer object passed to it should
-/// contain the answer to the query rendered into wire format.
-class DNSAnswer {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- DNSAnswer(const DNSAnswer& source);
- DNSAnswer& operator=(const DNSAnswer& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class
- /// should never be instantiated (except as part of a derived class).
- DNSAnswer() {}
-public:
- /// \brief The destructor
- virtual ~DNSAnswer() {}
- //@}
- /// \brief The function operator
- ///
- /// This makes its call indirectly via the "self" pointer, ensuring
- /// that the function ultimately invoked will be the one in the derived
- /// class.
- ///
- /// \param io_message The event message to handle
- /// \param message The DNS MessagePtr that needs handling
- /// \param buffer The result is put here
- virtual void operator()(const IOMessage& io_message,
- isc::dns::MessagePtr message,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer) const = 0;
-};
-
-/// \brief The \c SimpleCallback class is an abstract base class for a
-/// simple callback function with the signature:
-///
-/// void simpleCallback(const IOMessage& io_message) const;
-///
-/// Specific derived class implementations are hidden within the
-/// implementation. Instances of the derived classes can be called
-/// as functions via the operator() interface. Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// The \c SimpleCallback is expected to be used for basic, generic
-/// tasks such as checking for configuration changes. It may also be
-/// used for testing purposes.
-class SimpleCallback {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- SimpleCallback(const SimpleCallback& source);
- SimpleCallback& operator=(const SimpleCallback& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class
- /// should never be instantiated (except as part of a derived class).
- SimpleCallback() : self_(this) {}
-public:
- /// \brief The destructor
- virtual ~SimpleCallback() {}
- /// \brief The function operator
- //@}
- ///
- /// This makes its call indirectly via the "self" pointer, ensuring
- /// that the function ultimately invoked will be the one in the derived
- /// class.
- ///
- /// \param io_message The event message to handle
- virtual void operator()(const IOMessage& io_message) const {
- (*self_)(io_message);
- }
-private:
- SimpleCallback* self_;
-};
-
-/// \brief The \c RecursiveQuery class provides a layer of abstraction around
-/// the ASIO code that carries out an upstream query.
-///
-/// This design is very preliminary; currently it is only capable of
-/// handling simple forward requests to a single resolver.
-class RecursiveQuery {
- ///
- /// \name Constructors
- ///
- //@{
-public:
- /// \brief Constructor for use when acting as a forwarder
- ///
- /// This is currently the only way to construct \c RecursiveQuery
- /// object. The addresses of the forward nameservers is specified,
- /// and every upstream query will be sent to one random address.
- /// \param dns_service The DNS Service to perform the recursive
- /// query on.
- /// \param upstream Addresses and ports of the upstream servers
- /// to forward queries to.
- /// \param upstream_root Addresses and ports of the root servers
- /// to use when resolving.
- /// \param timeout How long to timeout the query, in ms
- /// -1 means never timeout (but do not use that).
- /// TODO: This should be computed somehow dynamically in future
- /// \param retries how many times we try again (0 means just send and
- /// and return if it returs).
- RecursiveQuery(DNSService& dns_service,
- const std::vector >&
- upstream,
- const std::vector >&
- upstream_root,
- int timeout = -1, unsigned retries = 0);
- //@}
-
- /// \brief Initiates an upstream query in the \c RecursiveQuery object.
- ///
- /// When sendQuery() is called, a message is sent asynchronously to
- /// the upstream name server. When a reply arrives, 'server'
- /// is placed on the ASIO service queue via io_service::post(), so
- /// that the original \c DNSServer objct can resume processing.
- ///
- /// \param question The question being answered
- /// \param buffer An output buffer into which the response can be copied
- /// \param server A pointer to the \c DNSServer object handling the client
- void sendQuery(const isc::dns::Question& question,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer,
- DNSServer* server);
-private:
- DNSService& dns_service_;
- boost::shared_ptr > >
- upstream_;
- boost::shared_ptr > >
- upstream_root_;
- int timeout_;
- unsigned retries_;
-};
-
-/// \brief The \c IntervalTimer class is a wrapper for the ASIO
-/// \c asio::deadline_timer class.
-///
-/// This class is implemented to use \c asio::deadline_timer as
-/// interval timer.
-///
-/// \c setupTimer() sets a timer to expire on (now + interval) and
-/// a call back function.
-///
-/// \c IntervalTimerImpl::callback() is called by the timer when
-/// it expires.
-///
-/// The function calls the call back function set by \c setupTimer()
-/// and updates the timer to expire in (now + interval) seconds.
-/// The type of call back function is \c void(void).
-///
-/// The call back function will not be called if the instance of this
-/// class is destructed before the timer is expired.
-///
-/// Note: Destruction of an instance of this class while call back
-/// is pending causes throwing an exception from \c IOService.
-///
-/// Sample code:
-/// \code
-/// void function_to_call_back() {
-/// // this function will be called periodically
-/// }
-/// int interval_in_seconds = 1;
-/// IOService io_service;
-///
-/// IntervalTimer intervalTimer(io_service);
-/// intervalTimer.setupTimer(function_to_call_back, interval_in_seconds);
-/// io_service.run();
-/// \endcode
-///
-class IntervalTimer {
-public:
- /// \name The type of timer callback function
- typedef boost::function Callback;
-
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IntervalTimer(const IntervalTimer& source);
- IntervalTimer& operator=(const IntervalTimer& source);
-public:
- /// \brief The constructor with \c IOService.
- ///
- /// This constructor may throw a standard exception if
- /// memory allocation fails inside the method.
- /// This constructor may also throw \c asio::system_error.
- ///
- /// \param io_service A reference to an instance of IOService
- ///
- IntervalTimer(IOService& io_service);
-
- /// \brief The destructor.
- ///
- /// This destructor never throws an exception.
- ///
- /// On the destruction of this class the timer will be canceled
- /// inside \c asio::deadline_timer.
- ///
- ~IntervalTimer();
- //@}
-
- /// \brief Register timer callback function and interval.
- ///
- /// This function sets callback function and interval in seconds.
- /// Timer will actually start after calling \c IOService::run().
- ///
- /// \param cbfunc A reference to a function \c void(void) to call back
- /// when the timer is expired (should not be an empty functor)
- /// \param interval Interval in seconds (greater than 0)
- ///
- /// Note: IntervalTimer will not pass \c asio::error_code to
- /// call back function. In case the timer is cancelled, the function
- /// will not be called.
- ///
- /// \throw isc::InvalidParameter cbfunc is empty
- /// \throw isc::BadValue interval is 0
- /// \throw isc::Unexpected ASIO library error
- ///
- void setupTimer(const Callback& cbfunc, const uint32_t interval);
-
- /// Cancel the timer.
- ///
- /// If the timer has been set up, this method cancels any asynchronous
- /// events waiting on the timer and stops the timer itself.
- /// If the timer has already been canceled, this method effectively does
- /// nothing.
- ///
- /// This method never throws an exception.
- void cancel();
-
- /// Return the timer interval.
- ///
- /// This method returns the timer interval in seconds if it's running;
- /// if the timer has been canceled it returns 0.
- ///
- /// This method never throws an exception.
- ///
- /// Note: We may want to change the granularity of the timer to
- /// milliseconds or even finer. If and when this happens the semantics
- /// of the return value of this method will be changed accordingly.
- uint32_t getInterval() const;
-
-private:
- IntervalTimerImpl* impl_;
-};
-
-} // asiolink
#endif // __ASIOLINK_H
// Local Variables:
diff --git a/src/lib/asiolink/dns_answer.h b/src/lib/asiolink/dns_answer.h
new file mode 100644
index 0000000000000000000000000000000000000000..84e1f6fd954c6343837b9b6612b26e39c55e33ea
--- /dev/null
+++ b/src/lib/asiolink/dns_answer.h
@@ -0,0 +1,73 @@
+// Copyright (C) 2011 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.
+
+#ifndef __ASIOLINK_DNS_ANSWER_H
+#define __ASIOLINK_DNS_ANSWER_H 1
+
+#include
+
+namespace asiolink {
+
+/// \brief The \c DNSAnswer class is an abstract base class for a DNS
+/// Answer provider function.
+///
+/// Specific derived class implementations are hidden within the
+/// implementation. Instances of the derived classes can be called
+/// as functions via the operator() interface. Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// A DNS Answer provider function takes answer data that has been obtained
+/// from a DNS Lookup provider functon and readies it to be sent to the
+/// client. After it has run, the OutputBuffer object passed to it should
+/// contain the answer to the query rendered into wire format.
+class DNSAnswer {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSAnswer(const DNSAnswer& source);
+ DNSAnswer& operator=(const DNSAnswer& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ DNSAnswer() {}
+public:
+ /// \brief The destructor
+ virtual ~DNSAnswer() {}
+ //@}
+ /// \brief The function operator
+ ///
+ /// This makes its call indirectly via the "self" pointer, ensuring
+ /// that the function ultimately invoked will be the one in the derived
+ /// class.
+ ///
+ /// \param io_message The event message to handle
+ /// \param query_message The DNS MessagePtr of the original query
+ /// \param answer_message The DNS MessagePtr of the answer we are
+ /// building
+ /// \param buffer Intermediate data results are put here
+ virtual void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr query_message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer) const = 0;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_ANSWER_H
diff --git a/src/lib/asiolink/dns_lookup.h b/src/lib/asiolink/dns_lookup.h
new file mode 100644
index 0000000000000000000000000000000000000000..078885397a449ddeaf2b162e7974be49e0f76b51
--- /dev/null
+++ b/src/lib/asiolink/dns_lookup.h
@@ -0,0 +1,81 @@
+// Copyright (C) 2011 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.
+
+#ifndef __ASIOLINK_DNS_LOOKUP_H
+#define __ASIOLINK_DNS_LOOKUP_H 1
+
+#include
+#include
+#include
+#include
+
+namespace asiolink {
+
+/// \brief The \c DNSLookup class is an abstract base class for a DNS
+/// Lookup provider function.
+///
+/// Specific derived class implementations are hidden within the
+/// implementation. Instances of the derived classes can be called
+/// as functions via the operator() interface. Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// A DNS Lookup provider function obtains the data needed to answer
+/// a DNS query (e.g., from authoritative data source, cache, or upstream
+/// query). After it has run, the OutputBuffer object passed to it
+/// should contain the answer to the query, in an internal representation.
+class DNSLookup {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSLookup(const DNSLookup& source);
+ DNSLookup& operator=(const DNSLookup& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ DNSLookup() : self_(this) {}
+public:
+ /// \brief The destructor
+ virtual ~DNSLookup() {}
+ //@}
+ /// \brief The function operator
+ ///
+ /// This makes its call indirectly via the "self" pointer, ensuring
+ /// that the function ultimately invoked will be the one in the derived
+ /// class.
+ ///
+ /// \param io_message The event message to handle
+ /// \param message The DNS MessagePtr that needs handling
+ /// \param buffer The final answer is put here
+ /// \param DNSServer DNSServer object to use
+ virtual void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ DNSServer* server) const
+ {
+ (*self_)(io_message, message, answer_message, buffer, server);
+ }
+private:
+ DNSLookup* self_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_LOOKUP_H
diff --git a/src/lib/asiolink/dns_server.h b/src/lib/asiolink/dns_server.h
new file mode 100644
index 0000000000000000000000000000000000000000..65452755fbf6b131c4babe31a58a42f46afbdc48
--- /dev/null
+++ b/src/lib/asiolink/dns_server.h
@@ -0,0 +1,152 @@
+// Copyright (C) 2011 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.
+
+#ifndef __ASIOLINK_DNS_SERVER_H
+#define __ASIOLINK_DNS_SERVER_H 1
+
+#include
+
+namespace asiolink {
+
+/// \brief The \c DNSServer class is a wrapper (and base class) for
+/// classes which provide DNS server functionality.
+///
+/// The classes derived from this one, \c TCPServer and \c UDPServer,
+/// act as the interface layer between clients sending queries, and
+/// functions defined elsewhere that provide answers to those queries.
+/// Those functions are described in more detail below under
+/// \c SimpleCallback, \c DNSLookup, and \c DNSAnswer.
+///
+/// Notes to developers:
+/// When constructed, this class (and its derived classes) will have its
+/// "self_" member set to point to "this". Objects of this class (as
+/// instantiated through a base class) are sometimes passed by
+/// reference (as this superclass); calls to methods in the base
+/// class are then rerouted via this pointer to methods in the derived
+/// class. This allows code from outside asiolink, with no specific
+/// knowledge of \c TCPServer or \c UDPServer, to access their methods.
+///
+/// This class is both assignable and copy-constructable. Its subclasses
+/// use the "stackless coroutine" pattern, meaning that it will copy itself
+/// when "forking", and that instances will be posted as ASIO handler
+/// objects, which are always copied.
+///
+/// Because these objects are frequently copied, it is recommended
+/// that derived classes be kept small to reduce copy overhead.
+class DNSServer {
+protected:
+ ///
+ /// \name Constructors and destructors
+ ///
+ /// This is intentionally defined as \c protected, as this base class
+ /// should never be instantiated except as part of a derived class.
+ //@{
+ DNSServer() : self_(this) {}
+public:
+ /// \brief The destructor
+ virtual ~DNSServer() {}
+ //@}
+
+ ///
+ /// \name Class methods
+ ///
+ /// These methods all make their calls indirectly via the "self_"
+ /// pointer, ensuring that the functions ultimately invoked will be
+ /// the ones in the derived class. This makes it possible to pass
+ /// instances of derived classes as references to this base class
+ /// without losing access to derived class data.
+ ///
+ //@{
+ /// \brief The funtion operator
+ virtual void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0)
+ {
+ (*self_)(ec, length);
+ }
+
+ /// \brief Resume processing of the server coroutine after an
+ /// asynchronous call (e.g., to the DNS Lookup provider) has completed.
+ ///
+ /// \param done If true, this signals the system there is an answer
+ /// to return.
+ virtual void resume(const bool done) { self_->resume(done); }
+
+ /// \brief Indicate whether the server is able to send an answer
+ /// to a query.
+ ///
+ /// This is presently used only for testing purposes.
+ virtual bool hasAnswer() { return (self_->hasAnswer()); }
+
+ /// \brief Returns the current value of the 'coroutine' object
+ ///
+ /// This is a temporary method, intended to be used for debugging
+ /// purposes during development and removed later. It allows
+ /// callers from outside the coroutine object to retrieve information
+ /// about its current state.
+ ///
+ /// \return The value of the 'coroutine' object
+ virtual int value() { return (self_->value()); }
+
+ /// \brief Returns a pointer to a clone of this DNSServer object.
+ ///
+ /// When a \c DNSServer object is copied or assigned, the result will
+ /// normally be another \c DNSServer object containing a copy
+ /// of the original "self_" pointer. Calling clone() guarantees
+ /// that the underlying object is also correctly copied.
+ ///
+ /// \return A deep copy of this DNSServer object
+ virtual DNSServer* clone() { return (self_->clone()); }
+ //@}
+
+protected:
+ /// \brief Lookup handler object.
+ ///
+ /// This is a protected class; it can only be instantiated
+ /// from within a derived class of \c DNSServer.
+ ///
+ /// A server object that has received a query creates an instance
+ /// of this class and scheudles it on the ASIO service queue
+ /// using asio::io_service::post(). When the handler executes, it
+ /// calls the asyncLookup() method in the server object to start a
+ /// DNS lookup. When the lookup is complete, the server object is
+ /// scheduled to resume, again using io_service::post().
+ ///
+ /// Note that the calling object is copied into the handler object,
+ /// not referenced. This is because, once the calling object yields
+ /// control to the handler, it falls out of scope and may disappear
+ template
+ class AsyncLookup {
+ public:
+ AsyncLookup(T& caller) : caller_(caller) {}
+ void operator()() { caller_.asyncLookup(); }
+ private:
+ T caller_;
+ };
+
+ /// \brief Carries out a DNS lookup.
+ ///
+ /// This function calls the \c DNSLookup object specified by the
+ /// DNS server when the \c IOService was created, passing along
+ /// the details of the query and a pointer back to the current
+ /// server object. It is called asynchronously via the AsyncLookup
+ /// handler class.
+ virtual void asyncLookup() { self_->asyncLookup(); }
+
+private:
+ DNSServer* self_;
+};
+
+
+} // asiolink
+#endif // __ASIOLINK_DNS_SERVER_H
diff --git a/src/lib/asiolink/dns_service.cc b/src/lib/asiolink/dns_service.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e9688c79f99e037e71c1bbae6418d11bcb2a72d5
--- /dev/null
+++ b/src/lib/asiolink/dns_service.cc
@@ -0,0 +1,194 @@
+// Copyright (C) 2011 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
+#include
+#include // for some IPC/network system calls
+
+#include
+
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+using isc::log::dlog;
+
+namespace asiolink {
+
+class SimpleCallback;
+class DNSLookup;
+class DNSAnswer;
+
+namespace {
+
+asio::ip::address
+convertAddr(const std::string& address) {
+ asio::error_code err;
+ asio::ip::address addr = asio::ip::address::from_string(address, err);
+ if (err) {
+ isc_throw(IOError, "Invalid IP address '" << &address << "': "
+ << err.message());
+ }
+ return (addr);
+}
+
+}
+
+
+class DNSServiceImpl {
+public:
+ DNSServiceImpl(IOService& io_service, const char& port,
+ const asio::ip::address* v4addr,
+ const asio::ip::address* v6addr,
+ SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer);
+
+ IOService& io_service_;
+
+ typedef boost::shared_ptr UDPServerPtr;
+ typedef boost::shared_ptr TCPServerPtr;
+ typedef boost::shared_ptr DNSServerPtr;
+ std::vector servers_;
+ SimpleCallback *checkin_;
+ DNSLookup *lookup_;
+ DNSAnswer *answer_;
+
+ void addServer(uint16_t port, const asio::ip::address& address) {
+ try {
+ dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast(port));
+ TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
+ address, port, checkin_, lookup_, answer_));
+ (*tcpServer)();
+ servers_.push_back(tcpServer);
+ dlog(std::string("Initialize UDP server at ") + address.to_string() + ":" + boost::lexical_cast(port));
+ UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
+ address, port, checkin_, lookup_, answer_));
+ (*udpServer)();
+ servers_.push_back(udpServer);
+ }
+ catch (const asio::system_error& err) {
+ // We need to catch and convert any ASIO level exceptions.
+ // This can happen for unavailable address, binding a privilege port
+ // without the privilege, etc.
+ isc_throw(IOError, "Failed to initialize network servers: " <<
+ err.what());
+ }
+ }
+ void addServer(const char& port, const asio::ip::address& address) {
+ uint16_t portnum;
+ try {
+ // XXX: SunStudio with stlport4 doesn't reject some invalid
+ // representation such as "-1" by lexical_cast, so
+ // we convert it into a signed integer of a larger size and perform
+ // range check ourselves.
+ const int32_t portnum32 = boost::lexical_cast(&port);
+ if (portnum32 < 0 || portnum32 > 65535) {
+ isc_throw(IOError, "Invalid port number '" << &port);
+ }
+ portnum = portnum32;
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(IOError, "Invalid port number '" << &port << "': " <<
+ ex.what());
+ }
+ addServer(portnum, address);
+ }
+};
+
+DNSServiceImpl::DNSServiceImpl(IOService& io_service,
+ const char& port,
+ const asio::ip::address* const v4addr,
+ const asio::ip::address* const v6addr,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ io_service_(io_service),
+ checkin_(checkin),
+ lookup_(lookup),
+ answer_(answer)
+{
+
+ if (v4addr) {
+ addServer(port, *v4addr);
+ }
+ if (v6addr) {
+ addServer(port, *v6addr);
+ }
+}
+
+DNSService::DNSService(IOService& io_service,
+ const char& port, const char& address,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
+ answer)), io_service_(io_service)
+{
+ addServer(port, &address);
+}
+
+DNSService::DNSService(IOService& io_service,
+ const char& port,
+ const bool use_ipv4, const bool use_ipv6,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ impl_(NULL), io_service_(io_service)
+{
+ const asio::ip::address v4addr_any =
+ asio::ip::address(asio::ip::address_v4::any());
+ const asio::ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL;
+ const asio::ip::address v6addr_any =
+ asio::ip::address(asio::ip::address_v6::any());
+ const asio::ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
+ impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer);
+}
+
+DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer *answer) :
+ impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
+ answer)), io_service_(io_service)
+{
+}
+
+DNSService::~DNSService() {
+ delete impl_;
+}
+
+void
+DNSService::addServer(const char& port, const std::string& address) {
+ impl_->addServer(port, convertAddr(address));
+}
+
+void
+DNSService::addServer(uint16_t port, const std::string& address) {
+ impl_->addServer(port, convertAddr(address));
+}
+
+void
+DNSService::clearServers() {
+ // FIXME: This does not work, it does not close the socket.
+ // How is it done?
+ impl_->servers_.clear();
+}
+
+
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/dns_service.h b/src/lib/asiolink/dns_service.h
new file mode 100644
index 0000000000000000000000000000000000000000..84aa5fbfd8b4ba8e7c7ba23584d10512e8ae864c
--- /dev/null
+++ b/src/lib/asiolink/dns_service.h
@@ -0,0 +1,112 @@
+// Copyright (C) 2011 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.
+
+#ifndef __ASIOLINK_DNS_SERVICE_H
+#define __ASIOLINK_DNS_SERVICE_H 1
+
+#include
+
+#include
+
+namespace asiolink {
+
+class SimpleCallback;
+class DNSLookup;
+class DNSAnswer;
+class DNSServiceImpl;
+
+///
+/// DNSService is the service that handles DNS queries and answers with
+/// a given IOService. This class is mainly intended to hold all the
+/// logic that is shared between the authoritative and the recursive
+/// server implementations. As such, it handles asio, including config
+/// updates (through the 'Checkinprovider'), and listening sockets.
+///
+class DNSService {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSService(const DNSService& source);
+ DNSService& operator=(const DNSService& source);
+
+public:
+ /// \brief The constructor with a specific IP address and port on which
+ /// the services listen on.
+ ///
+ /// \param io_service The IOService to work with
+ /// \param port the port to listen on
+ /// \param address the IP address to listen on
+ /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
+ /// \param lookup The lookup provider (see \c DNSLookup)
+ /// \param answer The answer provider (see \c DNSAnswer)
+ DNSService(IOService& io_service, const char& port,
+ const char& address, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer);
+ /// \brief The constructor with a specific port on which the services
+ /// listen on.
+ ///
+ /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
+ /// IPv4/IPv6 services will be available if and only if \c use_ipv4
+ /// or \c use_ipv6 is \c true, respectively.
+ ///
+ /// \param io_service The IOService to work with
+ /// \param port the port to listen on
+ /// \param ipv4 If true, listen on ipv4 'any'
+ /// \param ipv6 If true, listen on ipv6 'any'
+ /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
+ /// \param lookup The lookup provider (see \c DNSLookup)
+ /// \param answer The answer provider (see \c DNSAnswer)
+ DNSService(IOService& io_service, const char& port,
+ const bool use_ipv4, const bool use_ipv6,
+ SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer);
+ /// \brief The constructor without any servers.
+ ///
+ /// Use addServer() to add some servers.
+ DNSService(IOService& io_service, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer);
+ /// \brief The destructor.
+ ~DNSService();
+ //@}
+
+ /// \brief Add another server to the service
+ void addServer(uint16_t port, const std::string &address);
+ void addServer(const char &port, const std::string &address);
+ /// \brief Remove all servers from the service
+ void clearServers();
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other BIND 10 modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ asio::io_service& get_io_service() { return io_service_.get_io_service(); }
+
+ /// \brief Return the IO Service Object
+ ///
+ /// \return IOService object for this DNS service.
+ asiolink::IOService& getIOService() { return (io_service_);}
+
+private:
+ DNSServiceImpl* impl_;
+ IOService& io_service_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_SERVICE_H
diff --git a/src/lib/asiolink/dummy_io_cb.h b/src/lib/asiolink/dummy_io_cb.h
new file mode 100644
index 0000000000000000000000000000000000000000..bde656c348cd38c77259e0ddf7764d88ec19f9da
--- /dev/null
+++ b/src/lib/asiolink/dummy_io_cb.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2011 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.
+
+#ifndef __DUMMY_IO_CB_H
+#define __DUMMY_IO_CB_H
+
+#include
+
+#include
+#include
+
+namespace asiolink {
+
+/// \brief Asynchronous I/O Completion Callback
+///
+/// The two socket classes (UDPSocket and TCPSocket) require that the I/O
+/// completion callback function have an operator() method with the appropriate
+/// signature. The classes are templates, any class with that method and
+/// signature can be passed as the callback object - there is no need for a
+/// base class defining the interface. However, some users of the socket
+/// classes do not use the asynchronous I/O operations, yet have to supply a
+/// template parameter. This is the reason for this class - it is the dummy
+/// template parameter.
+
+class DummyIOCallback {
+public:
+
+ /// \brief Asynchronous I/O callback method
+ ///
+ /// \param error Unused
+ /// \param length Unused
+ void operator()(asio::error_code, size_t)
+ {
+ // TODO: log an error if this method ever gets called.
+ }
+};
+
+} // namespace asiolink
+
+#endif // __DUMMY_IO_CB_H
diff --git a/src/lib/asiolink/internal/Makefile.am b/src/lib/asiolink/internal/Makefile.am
deleted file mode 100644
index 3c6155b9c6357b8312f1f8a770219237315349ce..0000000000000000000000000000000000000000
--- a/src/lib/asiolink/internal/Makefile.am
+++ /dev/null
@@ -1 +0,0 @@
-SUBDIRS = tests
diff --git a/src/lib/asiolink/internal/iofetch.h b/src/lib/asiolink/internal/iofetch.h
deleted file mode 100644
index 7c0a8a08261ea7f4da56f92c3e9edd97b6858bfa..0000000000000000000000000000000000000000
--- a/src/lib/asiolink/internal/iofetch.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (C) 2010 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.
-
-#ifndef __IOQUERY_H
-#define __IOQUERY_H 1
-
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-
-// This file contains UDP-specific implementations of generic classes
-// defined in asiolink.h. It is *not* intended to be part of the public
-// API.
-
-namespace asiolink {
-//
-// Asynchronous UDP coroutine for upstream queries
-//
-class UDPQuery : public coroutine {
-public:
- // TODO Maybe this should be more generic than just for UDPQuery?
- ///
- /// \brief Result of the query
- ///
- /// This is related only to contacting the remote server. If the answer
- ///indicates error, it is still counted as SUCCESS here, if it comes back.
- ///
- enum Result {
- SUCCESS,
- TIME_OUT,
- STOPPED
- };
- /// Abstract callback for the UDPQuery.
- class Callback {
- public:
- virtual ~Callback() {}
-
- /// This will be called when the UDPQuery is completed
- virtual void operator()(Result result) = 0;
- };
- ///
- /// \brief Constructor.
- ///
- /// It creates the query.
- /// @param callback will be called when we terminate. It is your task to
- /// delete it if allocated on heap.
- ///@param timeout in ms.
- ///
- explicit UDPQuery(asio::io_service& io_service,
- const isc::dns::Question& q,
- const IOAddress& addr, uint16_t port,
- isc::dns::OutputBufferPtr buffer,
- Callback* callback, int timeout = -1);
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
- /// Terminate the query.
- void stop(Result reason = STOPPED);
-private:
- enum { MAX_LENGTH = 4096 };
-
- ///
- /// \short Private data
- ///
- /// They are not private because of stability of the
- /// interface (this is private class anyway), but because this class
- /// will be copyed often (it is used as a coroutine and passed as callback
- /// to many async_*() functions) and we want keep the same data. Some of
- /// the data is not copyable too.
- ///
- struct PrivateData;
- boost::shared_ptr data_;
-};
-}
-
-
-#endif // __IOQUERY_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/internal/tests/Makefile.am b/src/lib/asiolink/internal/tests/Makefile.am
deleted file mode 100644
index 449cab740ac717d17c467bd388d7dd31c3f2414a..0000000000000000000000000000000000000000
--- a/src/lib/asiolink/internal/tests/Makefile.am
+++ /dev/null
@@ -1,37 +0,0 @@
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
-
-AM_CXXFLAGS = $(B10_CXXFLAGS)
-
-if USE_STATIC_LINK
-AM_LDFLAGS = -static
-endif
-
-CLEANFILES = *.gcno *.gcda
-
-TESTS =
-if HAVE_GTEST
-TESTS += run_unittests
-run_unittests_SOURCES = udpdns_unittest.cc
-run_unittests_SOURCES += run_unittests.cc
-run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
-run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
-# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
-# B10_CXXFLAGS)
-run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-run_unittests_CXXFLAGS += -Wno-unused-parameter
-endif
-if USE_CLANGPP
-# We need to disable -Werror for any test that uses internal definitions of
-# ASIO when using clang++
-run_unittests_CXXFLAGS += -Wno-error
-endif
-endif
-
-noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiolink/internal/tests/udpdns_unittest.cc b/src/lib/asiolink/internal/tests/udpdns_unittest.cc
deleted file mode 100644
index 1e36e4a188976f44db9145ca9ef35a449cb31ea5..0000000000000000000000000000000000000000
--- a/src/lib/asiolink/internal/tests/udpdns_unittest.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright (C) 2010 CZ.NIC
-//
-// 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
-#include
-#include
-#include
-
-#include
-
-#include
-#include
-
-using namespace asio;
-using namespace isc::dns;
-using asio::ip::udp;
-
-namespace {
-
-const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
-const uint16_t TEST_PORT(5301);
-// FIXME Shouldn't we send something that is real message?
-const char TEST_DATA[] = "TEST DATA";
-
-// Test fixture for the asiolink::UDPQuery.
-class UDPQueryTest : public ::testing::Test,
- public asiolink::UDPQuery::Callback
-{
- public:
- // Expected result of the callback
- asiolink::UDPQuery::Result expected_;
- // Did the callback run already?
- bool run_;
- // We use an io_service to run the query
- io_service service_;
- // Something to ask
- Question question_;
- // Buffer where the UDPQuery will store response
- OutputBufferPtr buffer_;
- // The query we are testing
- asiolink::UDPQuery query_;
-
- UDPQueryTest() :
- run_(false),
- question_(Name("example.net"), RRClass::IN(), RRType::A()),
- buffer_(new OutputBuffer(512)),
- query_(service_, question_, asiolink::IOAddress(TEST_HOST),
- TEST_PORT, buffer_, this, 100)
- { }
-
- // This is the callback's (), so it can be called.
- void operator()(asiolink::UDPQuery::Result result) {
- // We check the query returns the correct result
- EXPECT_EQ(expected_, result);
- // Check it is called only once
- EXPECT_FALSE(run_);
- // And mark the callback was called
- run_ = true;
- }
- // A response handler, pretending to be remote DNS server
- void respond(udp::endpoint* remote, udp::socket* socket) {
- // Some data came, just send something back.
- socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA),
- *remote);
- socket->close();
- }
-};
-
-/*
- * Test that when we run the query and stop it after it was run,
- * it returns "stopped" correctly.
- *
- * That is why stop() is posted to the service_ as well instead
- * of calling it.
- */
-TEST_F(UDPQueryTest, stop) {
- expected_ = asiolink::UDPQuery::STOPPED;
- // Post the query
- service_.post(query_);
- // Post query_.stop() (yes, the boost::bind thing is just
- // query_.stop()).
- service_.post(boost::bind(&asiolink::UDPQuery::stop, query_,
- asiolink::UDPQuery::STOPPED));
- // Run both of them
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that when we queue the query to service_ and call stop()
- * before it gets executed, it acts sanely as well (eg. has the
- * same result as running stop() after - calls the callback).
- */
-TEST_F(UDPQueryTest, prematureStop) {
- expected_ = asiolink::UDPQuery::STOPPED;
- // Stop before it is started
- query_.stop();
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that it will timeout when no answer will arrive.
- */
-TEST_F(UDPQueryTest, timeout) {
- expected_ = asiolink::UDPQuery::TIME_OUT;
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that it will succeed when we fake an answer and
- * stores the same data we send.
- *
- * This is done through a real socket on loopback address.
- */
-TEST_F(UDPQueryTest, receive) {
- expected_ = asiolink::UDPQuery::SUCCESS;
- udp::socket socket(service_, udp::v4());
- socket.set_option(socket_base::reuse_address(true));
- socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
- char inbuff[512];
- udp::endpoint remote;
- socket.async_receive_from(asio::buffer(inbuff, 512), remote, boost::bind(
- &UDPQueryTest::respond, this, &remote, &socket));
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
- ASSERT_EQ(sizeof TEST_DATA, buffer_->getLength());
- EXPECT_EQ(0, memcmp(TEST_DATA, buffer_->getData(), sizeof TEST_DATA));
-}
-
-}
diff --git a/src/lib/asiolink/internal/udpdns.h b/src/lib/asiolink/internal/udpdns.h
deleted file mode 100644
index 6c6a8864224f5fb71e202e7cadf317f9dc7331a6..0000000000000000000000000000000000000000
--- a/src/lib/asiolink/internal/udpdns.h
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright (C) 2010 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.
-
-#ifndef __UDPDNS_H
-#define __UDPDNS_H 1
-
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-
-// This file contains UDP-specific implementations of generic classes
-// defined in asiolink.h. It is *not* intended to be part of the public
-// API.
-
-namespace asiolink {
-/// \brief The \c UDPEndpoint class is a concrete derived class of
-/// \c IOEndpoint that represents an endpoint of a UDP packet.
-///
-/// Other notes about \c TCPEndpoint applies to this class, too.
-class UDPEndpoint : public IOEndpoint {
-public:
- ///
- /// \name Constructors and Destructor.
- ///
- //@{
- /// \brief Constructor from a pair of address and port.
- ///
- /// \param address The IP address of the endpoint.
- /// \param port The UDP port number of the endpoint.
- UDPEndpoint(const IOAddress& address, const unsigned short port) :
- asio_endpoint_placeholder_(
- new asio::ip::udp::endpoint(asio::ip::address::from_string(address.toText()),
- port)),
- asio_endpoint_(*asio_endpoint_placeholder_)
- {}
-
- /// \brief Constructor from an ASIO UDP endpoint.
- ///
- /// This constructor is designed to be an efficient wrapper for the
- /// corresponding ASIO class, \c udp::endpoint.
- ///
- /// \param asio_endpoint The ASIO representation of the UDP endpoint.
- UDPEndpoint(const asio::ip::udp::endpoint& asio_endpoint) :
- asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
- {}
-
- /// \brief The destructor.
- ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
- //@}
-
- inline IOAddress getAddress() const {
- return (asio_endpoint_.address());
- }
-
- inline uint16_t getPort() const {
- return (asio_endpoint_.port());
- }
-
- inline short getProtocol() const {
- return (asio_endpoint_.protocol().protocol());
- }
-
- inline short getFamily() const {
- return (asio_endpoint_.protocol().family());
- }
-
- // This is not part of the exosed IOEndpoint API but allows
- // direct access to the ASIO implementation of the endpoint
- inline const asio::ip::udp::endpoint& getASIOEndpoint() const {
- return (asio_endpoint_);
- }
-
-private:
- const asio::ip::udp::endpoint* asio_endpoint_placeholder_;
- const asio::ip::udp::endpoint& asio_endpoint_;
-};
-
-/// \brief The \c UDPSocket class is a concrete derived class of
-/// \c IOSocket that represents a UDP socket.
-///
-/// Other notes about \c TCPSocket applies to this class, too.
-class UDPSocket : public IOSocket {
-private:
- UDPSocket(const UDPSocket& source);
- UDPSocket& operator=(const UDPSocket& source);
-public:
- /// \brief Constructor from an ASIO UDP socket.
- ///
- /// \param socket The ASIO representation of the UDP socket.
- UDPSocket(asio::ip::udp::socket& socket) : socket_(socket) {}
-
- virtual int getNative() const { return (socket_.native()); }
- virtual int getProtocol() const { return (IPPROTO_UDP); }
-
-private:
- asio::ip::udp::socket& socket_;
-};
-
-//
-// Asynchronous UDP server coroutine
-//
-///
-/// \brief This class implements the coroutine to handle UDP
-/// DNS query event. As such, it is both a \c DNSServer and
-/// a \c coroutine
-///
-class UDPServer : public virtual DNSServer, public virtual coroutine {
-public:
- /// \brief Constructor
- /// \param io_service the asio::io_service to work with
- /// \param addr the IP address to listen for queries on
- /// \param port the port to listen for queries on
- /// \param checkin the callbackprovider for non-DNS events
- /// \param lookup the callbackprovider for DNS lookup events
- /// \param answer the callbackprovider for DNS answer events
- explicit UDPServer(asio::io_service& io_service,
- const asio::ip::address& addr, const uint16_t port,
- SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL,
- DNSAnswer* answer = NULL);
-
- /// \brief The function operator
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
-
- /// \brief Calls the lookup callback
- void asyncLookup();
-
- /// \brief Resume operation
- ///
- /// \param done Set this to true if the lookup action is done and
- /// we have an answer
- void resume(const bool done);
-
- /// \brief Check if we have an answer
- ///
- /// \return true if we have an answer
- bool hasAnswer() { return (done_); }
-
- /// \brief Returns the coroutine state value
- ///
- /// \return the coroutine state value
- int value() { return (get_value()); }
-
- /// \brief Clones the object
- ///
- /// \return a newly allocated copy of this object
- DNSServer* clone() {
- UDPServer* s = new UDPServer(*this);
- return (s);
- }
-
-private:
- enum { MAX_LENGTH = 4096 };
-
- // The ASIO service object
- asio::io_service& io_;
-
- // Class member variables which are dynamic, and changes to which
- // need to accessible from both sides of a coroutine fork or from
- // outside of the coroutine (i.e., from an asynchronous I/O call),
- // should be declared here as pointers and allocated in the
- // constructor or in the coroutine. This allows state information
- // to persist when an individual copy of the coroutine falls out
- // scope while waiting for an event, *so long as* there is another
- // object that is referencing the same data. As a side-benefit, using
- // pointers also reduces copy overhead for coroutine objects.
- //
- // Note: Currently these objects are allocated by "new" in the
- // constructor, or in the function operator while processing a query.
- // Repeated allocations from the heap for every incoming query is
- // clearly a performance issue; this must be optimized in the future.
- // The plan is to have a structure pre-allocate several "server state"
- // objects which can be pulled off a free list and placed on an in-use
- // list whenever a query comes in. This will serve the dual purpose
- // of improving performance and guaranteeing that state information
- // will *not* be destroyed when any one instance of the coroutine
- // falls out of scope while waiting for an event.
- //
- // Socket used to for listen for queries. Created in the
- // constructor and stored in a shared_ptr because socket objects
- // are not copyable.
- boost::shared_ptr socket_;
-
- // The ASIO-enternal endpoint object representing the client
- boost::shared_ptr sender_;
-
- // \c IOMessage and \c Message objects to be passed to the
- // DNS lookup and answer providers
- boost::shared_ptr io_message_;
-
- // The original query as sent by the client
- isc::dns::MessagePtr query_message_;
-
- // The response message we are building
- isc::dns::MessagePtr answer_message_;
-
- // The buffer into which the response is written
- isc::dns::OutputBufferPtr respbuf_;
-
- // The buffer into which the query packet is written
- boost::shared_array data_;
-
- // State information that is entirely internal to a given instance
- // of the coroutine can be declared here.
- size_t bytes_;
- bool done_;
-
- // Callback functions provided by the caller
- const SimpleCallback* checkin_callback_;
- const DNSLookup* lookup_callback_;
- const DNSAnswer* answer_callback_;
-
- boost::shared_ptr peer_;
- boost::shared_ptr iosock_;
-};
-}
-
-
-#endif // __UDPDNS_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/interval_timer.cc b/src/lib/asiolink/interval_timer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8efb102cd9efb67367b8da889afce7eec0482d45
--- /dev/null
+++ b/src/lib/asiolink/interval_timer.cc
@@ -0,0 +1,136 @@
+// Copyright (C) 2011 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
+
+#include // for some IPC/network system calls
+#include
+#include
+
+#include
+
+#include
+
+#include
+#include
+#include
+
+namespace asiolink {
+
+class IntervalTimerImpl {
+private:
+ // prohibit copy
+ IntervalTimerImpl(const IntervalTimerImpl& source);
+ IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
+public:
+ IntervalTimerImpl(IOService& io_service);
+ ~IntervalTimerImpl();
+ void setup(const IntervalTimer::Callback& cbfunc, const long interval);
+ void callback(const asio::error_code& error);
+ void cancel() {
+ timer_.cancel();
+ interval_ = 0;
+ }
+ long getInterval() const { return (interval_); }
+private:
+ // a function to update timer_ when it expires
+ void update();
+ // a function to call back when timer_ expires
+ IntervalTimer::Callback cbfunc_;
+ // interval in milliseconds
+ long interval_;
+ // asio timer
+ asio::deadline_timer timer_;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+ interval_(0), timer_(io_service.get_io_service())
+{}
+
+IntervalTimerImpl::~IntervalTimerImpl()
+{}
+
+void
+IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
+ const long interval)
+{
+ // Interval should not be less than or equal to 0.
+ if (interval <= 0) {
+ isc_throw(isc::BadValue, "Interval should not be less than or "
+ "equal to 0");
+ }
+ // Call back function should not be empty.
+ if (cbfunc.empty()) {
+ isc_throw(isc::InvalidParameter, "Callback function is empty");
+ }
+ cbfunc_ = cbfunc;
+ interval_ = interval;
+ // Set initial expire time.
+ // At this point the timer is not running yet and will not expire.
+ // After calling IOService::run(), the timer will expire.
+ update();
+ return;
+}
+
+void
+IntervalTimerImpl::update() {
+ if (interval_ == 0) {
+ // timer has been canceled. Do nothing.
+ return;
+ }
+ try {
+ // Update expire time to (current time + interval_).
+ timer_.expires_from_now(boost::posix_time::millisec(interval_));
+ } catch (const asio::system_error& e) {
+ isc_throw(isc::Unexpected, "Failed to update timer");
+ }
+ // Reset timer.
+ timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
+}
+
+void
+IntervalTimerImpl::callback(const asio::error_code& cancelled) {
+ // Do not call cbfunc_ in case the timer was cancelled.
+ // The timer will be canelled in the destructor of asio::deadline_timer.
+ if (!cancelled) {
+ cbfunc_();
+ // Set next expire time.
+ update();
+ }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) {
+ impl_ = new IntervalTimerImpl(io_service);
+}
+
+IntervalTimer::~IntervalTimer() {
+ delete impl_;
+}
+
+void
+IntervalTimer::setup(const Callback& cbfunc, const long interval) {
+ return (impl_->setup(cbfunc, interval));
+}
+
+void
+IntervalTimer::cancel() {
+ impl_->cancel();
+}
+
+long
+IntervalTimer::getInterval() const {
+ return (impl_->getInterval());
+}
+
+}
diff --git a/src/lib/asiolink/interval_timer.h b/src/lib/asiolink/interval_timer.h
new file mode 100644
index 0000000000000000000000000000000000000000..d805cd7c39efbc0dc3459c893544034d2987d088
--- /dev/null
+++ b/src/lib/asiolink/interval_timer.h
@@ -0,0 +1,133 @@
+// Copyright (C) 2011 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.
+
+#ifndef __ASIOLINK_INTERVAL_TIMER_H
+#define __ASIOLINK_INTERVAL_TIMER_H 1
+
+#include
+
+#include
+
+namespace asiolink {
+
+struct IntervalTimerImpl;
+
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c asio::deadline_timer class.
+///
+/// This class is implemented to use \c asio::deadline_timer as interval
+/// timer.
+///
+/// \c setup() sets a timer to expire on (now + interval) and a call back
+/// function.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when it expires.
+///
+/// The function calls the call back function set by \c setup() and updates
+/// the timer to expire in (now + interval) milliseconds.
+/// The type of call back function is \c void(void).
+///
+/// The call back function will not be called if the instance of this class is
+/// destroyed before the timer is expired.
+///
+/// Note: Destruction of an instance of this class while call back is pending
+/// causes throwing an exception from \c IOService.
+///
+/// Sample code:
+/// \code
+/// void function_to_call_back() {
+/// // this function will be called periodically
+/// }
+/// int interval_in_milliseconds = 1000;
+/// IOService io_service;
+///
+/// IntervalTimer intervalTimer(io_service);
+/// intervalTimer.setup(function_to_call_back, interval_in_milliseconds);
+/// io_service.run();
+/// \endcode
+class IntervalTimer {
+public:
+ /// \name The type of timer callback function
+ typedef boost::function Callback;
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IntervalTimer(const IntervalTimer& source);
+ IntervalTimer& operator=(const IntervalTimer& source);
+public:
+ /// \brief The constructor with \c IOService.
+ ///
+ /// This constructor may throw a standard exception if
+ /// memory allocation fails inside the method.
+ /// This constructor may also throw \c asio::system_error.
+ ///
+ /// \param io_service A reference to an instance of IOService
+ IntervalTimer(IOService& io_service);
+
+ /// \brief The destructor.
+ ///
+ /// This destructor never throws an exception.
+ ///
+ /// On the destruction of this class the timer will be canceled
+ /// inside \c asio::deadline_timer.
+ ~IntervalTimer();
+ //@}
+
+ /// \brief Register timer callback function and interval.
+ ///
+ /// This function sets callback function and interval in milliseconds.
+ /// Timer will actually start after calling \c IOService::run().
+ ///
+ /// \param cbfunc A reference to a function \c void(void) to call back
+ /// when the timer is expired (should not be an empty functor)
+ /// \param interval Interval in milliseconds (greater than 0)
+ ///
+ /// Note: IntervalTimer will not pass \c asio::error_code to
+ /// call back function. In case the timer is cancelled, the function
+ /// will not be called.
+ ///
+ /// \throw isc::InvalidParameter cbfunc is empty
+ /// \throw isc::BadValue interval is less than or equal to 0
+ /// \throw isc::Unexpected ASIO library error
+ void setup(const Callback& cbfunc, const long interval);
+
+ /// Cancel the timer.
+ ///
+ /// If the timer has been set up, this method cancels any asynchronous
+ /// events waiting on the timer and stops the timer itself.
+ /// If the timer has already been canceled, this method effectively does
+ /// nothing.
+ ///
+ /// This method never throws an exception.
+ void cancel();
+
+ /// Return the timer interval.
+ ///
+ /// This method returns the timer interval in milliseconds if it's running;
+ /// if the timer has been canceled it returns 0.
+ ///
+ /// This method never throws an exception.
+ long getInterval() const;
+
+private:
+ IntervalTimerImpl* impl_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_INTERVAL_TIMER_H
diff --git a/src/lib/asiolink/ioaddress.cc b/src/lib/asiolink/io_address.cc
similarity index 94%
rename from src/lib/asiolink/ioaddress.cc
rename to src/lib/asiolink/io_address.cc
index 990524acfb606ee301c9e37644385779b9eef281..70e837456d2e00d8665884e95f6d7a6ed2d2dce2 100644
--- a/src/lib/asiolink/ioaddress.cc
+++ b/src/lib/asiolink/io_address.cc
@@ -20,7 +20,10 @@
#include
-#include
+#include
+#include
+#include
+
using namespace asio;
using asio::ip::udp;
diff --git a/src/lib/asiolink/ioaddress.h b/src/lib/asiolink/io_address.h
similarity index 71%
rename from src/lib/asiolink/ioaddress.h
rename to src/lib/asiolink/io_address.h
index 5727041f66880030b7448b8963d6d77213dbd973..0d2787f95ffa9852086c0292d92dd892e76cba99 100644
--- a/src/lib/asiolink/ioaddress.h
+++ b/src/lib/asiolink/io_address.h
@@ -12,8 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#ifndef __IOADDRESS_H
-#define __IOADDRESS_H 1
+#ifndef __IO_ADDRESS_H
+#define __IO_ADDRESS_H 1
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
@@ -73,15 +73,54 @@ public:
/// \return A string representation of the address.
std::string toText() const;
- /// \brief Returns the address family.
+ /// \brief Returns the address family
+ ///
+ /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
short getFamily() const;
+ /// \brief Compare addresses for equality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return true if addresses are equal, false if not.
+ bool equals(const IOAddress& other) const {
+ return (asio_address_ == other.asio_address_);
+ }
+
+ /// \brief Compare addresses for equality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return true if addresses are equal, false if not.
+ bool operator==(const IOAddress& other) const {
+ return equals(other);
+ }
+
+ // \brief Compare addresses for inequality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return false if addresses are equal, true if not.
+ bool nequals(const IOAddress& other) const {
+ return (!equals(other));
+ }
+
+ // \brief Compare addresses for inequality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return false if addresses are equal, true if not.
+ bool operator!=(const IOAddress& other) const {
+ return (nequals(other));
+ }
+
+
private:
asio::ip::address asio_address_;
};
} // asiolink
-#endif // __IOADDRESS_H
+#endif // __IO_ADDRESS_H
// Local Variables:
// mode: c++
diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h
new file mode 100644
index 0000000000000000000000000000000000000000..eae9b32509b5eeb48fa3d542aef29b235318b753
--- /dev/null
+++ b/src/lib/asiolink/io_asio_socket.h
@@ -0,0 +1,309 @@
+// Copyright (C) 2010 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.
+
+#ifndef __IO_ASIO_SOCKET_H
+#define __IO_ASIO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include // for some network system calls
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+
+namespace asiolink {
+
+/// \brief Socket not open
+///
+/// Thrown on an attempt to do read/write to a socket that is not open.
+class SocketNotOpen : public IOError {
+public:
+ SocketNotOpen(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+
+
+/// Forward declaration of an IOEndpoint
+class IOEndpoint;
+
+
+/// \brief I/O Socket with asynchronous operations
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// This is the basic IOSocket with additional operations - open, send, receive
+/// and close. Depending on how the asiolink code develops, it may be a
+/// temporary class: its main use is to add the template parameter needed for
+/// the derived classes UDPSocket and TCPSocket but without changing the
+/// signature of the more basic IOSocket class.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it. Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+///
+/// TODO: Check if IOAsioSocket class is still needed
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template
+class IOAsioSocket : public IOSocket {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOAsioSocket(const IOAsioSocket& source);
+ IOAsioSocket& operator=(const IOAsioSocket& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ IOAsioSocket() {}
+public:
+ /// The destructor.
+ virtual ~IOAsioSocket() {}
+ //@}
+
+ /// \brief Return the "native" representation of the socket.
+ ///
+ /// In practice, this is the file descriptor of the socket for
+ /// UNIX-like systems so the current implementation simply uses
+ /// \c int as the type of the return value.
+ /// We may have to need revisit this decision later.
+ ///
+ /// In general, the application should avoid using this method;
+ /// it essentially discloses an implementation specific "handle" that
+ /// can change the internal state of the socket (consider the
+ /// application closes it, for example).
+ /// But we sometimes need to perform very low-level operations that
+ /// requires the native representation. Passing the file descriptor
+ /// to a different process is one example.
+ /// This method is provided as a necessary evil for such limited purposes.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The native representation of the socket. This is the socket
+ /// file descriptor for UNIX-like systems.
+ virtual int getNative() const = 0;
+
+ /// \brief Return the transport protocol of the socket.
+ ///
+ /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+ /// \c IPPROTO_TCP for TCP sockets.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return IPPROTO_UDP for UDP sockets
+ /// \return IPPROTO_TCP for TCP sockets
+ virtual int getProtocol() const = 0;
+
+ /// \brief Open AsioSocket
+ ///
+ /// Opens the socket for asynchronous I/O. On a UDP socket, this is merely
+ /// an "open()" on the underlying socket (so completes immediately), but on
+ /// a TCP socket it also connects to the remote end (which is done as an
+ /// asynchronous operation).
+ ///
+ /// For TCP, signalling of the completion of the operation is done by
+ /// by calling the callback function in the normal way. This could be done
+ /// for UDP (by posting en event on the event queue); however, that will
+ /// incur additional overhead in the most common case. Instead, the return
+ /// value indicates whether the operation was asynchronous or not. If yes,
+ /// (i.e. TCP) the callback has been posted to the event queue: if no (UDP),
+ /// no callback has been posted (in which case it is up to the caller as to
+ /// whether they want to manually post the callback themself.)
+ ///
+ /// \param endpoint Pointer to the endpoint object. This is ignored for
+ /// a UDP socket (the target is specified in the send call), but should
+ /// be of type TCPEndpoint for a TCP connection.
+ /// \param callback I/O Completion callback, called when the operation has
+ /// completed, but only if the operation was asynchronous.
+ ///
+ /// \return true if an asynchronous operation was started and the caller
+ /// should yield and wait for completion, false if the operation was
+ /// completed synchronously and no callback was queued.
+ virtual bool open(const IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Send Asynchronously
+ ///
+ /// This corresponds to async_send_to() for UDP sockets and async_send()
+ /// for TCP. In both cases an endpoint argument is supplied indicating the
+ /// target of the send - this is ignored for TCP.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param endpoint Target of the send
+ /// \param callback Callback object.
+ virtual void asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Receive Asynchronously
+ ///
+ /// This correstponds to async_receive_from() for UDP sockets and
+ /// async_receive() for TCP. In both cases, an endpoint argument is
+ /// supplied to receive the source of the communication. For TCP it will
+ /// be filled in with details of the connection.
+ ///
+ /// \param data Buffer to receive incoming message
+ /// \param length Length of the data buffer
+ /// \param cumulative Amount of data that should already be in the buffer.
+ /// \param endpoint Source of the communication
+ /// \param callback Callback object
+ virtual void asyncReceive(void* data, size_t length, size_t cumulative,
+ IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Checks if the data received is complete.
+ ///
+ /// This applies to TCP receives, where the data is a byte stream and a
+ /// receive is not guaranteed to receive the entire message. DNS messages
+ /// over TCP are prefixed by a two-byte count field. This method takes the
+ /// amount received so far and the amount received in this I/O and checks
+ /// if the message is complete, returning the appropriate indication. As
+ /// a side-effect, it also updates the amount received.
+ ///
+ /// For a UDP receive, all the data is received in one I/O, so this is
+ /// effectively a no-op (although it does update the amount received).
+ ///
+ /// \param data Data buffer containing data to date
+ /// \param length Amount of data received in last asynchronous I/O
+ /// \param cumulative On input, amount of data received before the last
+ /// I/O. On output, the total amount of data received to date.
+ ///
+ /// \return true if the receive is complete, false if another receive is
+ /// needed.
+ virtual bool receiveComplete(void* data, size_t length,
+ size_t& cumulative) = 0;
+
+ /// \brief Cancel I/O On AsioSocket
+ virtual void cancel() = 0;
+
+ /// \brief Close socket
+ virtual void close() = 0;
+};
+
+
+#include "io_socket.h"
+
+/// \brief The \c DummyAsioSocket class is a concrete derived class of
+/// \c IOAsioSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOAsioSocket object without involving system resource
+/// allocation such as real network sockets.
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template
+class DummyAsioSocket : public IOAsioSocket {
+private:
+ DummyAsioSocket(const DummyAsioSocket& source);
+ DummyAsioSocket& operator=(const DummyAsioSocket& source);
+public:
+ /// \brief Constructor from the protocol number.
+ ///
+ /// The protocol must validly identify a standard network protocol.
+ /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+ ///
+ /// \param protocol The network protocol number for the socket.
+ DummyAsioSocket(const int protocol) : protocol_(protocol) {}
+
+ /// \brief A dummy derived method of \c IOAsioSocket::getNative().
+ ///
+ /// \return Always returns -1 as the object is not associated with a real
+ /// (native) socket.
+ virtual int getNative() const { return (-1); }
+
+ /// \brief A dummy derived method of \c IOAsioSocket::getProtocol().
+ ///
+ /// \return Protocol socket was created with
+ virtual int getProtocol() const { return (protocol_); }
+
+
+ /// \brief Open AsioSocket
+ ///
+ /// A call that is a no-op on UDP sockets, this opens a connection to the
+ /// system identified by the given endpoint.
+ ///
+ /// \param endpoint Unused
+ /// \param callback Unused.
+ ///false indicating that the operation completed synchronously.
+ virtual bool open(const IOEndpoint*, C&) {
+ return (false);
+ }
+
+ /// \brief Send Asynchronously
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ ///
+ /// \param data Unused
+ /// \param length Unused
+ /// \param endpoint Unused
+ /// \param callback Unused
+ virtual void asyncSend(const void*, size_t, const IOEndpoint*, C&) {
+ }
+
+ /// \brief Receive Asynchronously
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ ///
+ /// \param data Unused
+ /// \param length Unused
+ /// \param cumulative Unused
+ /// \param endpoint Unused
+ /// \param callback Unused
+ virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) { }
+ /// \brief Checks if the data received is complete.
+ ///
+ /// \param data Unused
+ /// \param length Unused
+ /// \param cumulative Unused
+ ///
+ /// \return Always true
+ virtual bool receiveComplete(void*, size_t, size_t&) {
+ return (true);
+ }
+
+ /// \brief Cancel I/O On AsioSocket
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ virtual void cancel() {
+ }
+
+ /// \brief Close socket
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ virtual void close() {
+ }
+
+private:
+ const int protocol_;
+};
+
+} // namespace asiolink
+
+#endif // __IO_ASIO_SOCKET_H
diff --git a/src/lib/asiolink/ioendpoint.cc b/src/lib/asiolink/io_endpoint.cc
similarity index 85%
rename from src/lib/asiolink/ioendpoint.cc
rename to src/lib/asiolink/io_endpoint.cc
index 2807f8d8c58e2c1ca5abcdb538f610da32e6fe7c..bf79f61868ff0953dd18078b1de131d82f9d02bc 100644
--- a/src/lib/asiolink/ioendpoint.cc
+++ b/src/lib/asiolink/io_endpoint.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 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
@@ -18,9 +18,12 @@
#include
#include
-#include