// Copyright (C) 2009 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "resolver.h" #include "resolver_log.h" using namespace std; using namespace isc; using namespace isc::util; using namespace isc::acl; using isc::acl::dns::RequestACL; using namespace isc::dns; using namespace isc::data; using namespace isc::config; using namespace isc::asiodns; using namespace isc::asiolink; using namespace isc::server_common; using namespace isc::server_common::portconfig; class ResolverImpl { private: // prohibit copy ResolverImpl(const ResolverImpl& source); ResolverImpl& operator=(const ResolverImpl& source); public: ResolverImpl() : config_session_(NULL), query_timeout_(2000), client_timeout_(4000), lookup_timeout_(30000), retries_(3), // we apply "reject all" (implicit default of the loader) ACL by // default: query_acl_(acl::dns::getRequestLoader().load(Element::fromJSON("[]"))), rec_query_(NULL) {} ~ResolverImpl() { queryShutdown(); } void querySetup(DNSServiceBase& dnss, isc::nsas::NameserverAddressStore& nsas, isc::cache::ResolverCache& cache) { assert(!rec_query_); // queryShutdown must be called first LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_QUERY_SETUP); rec_query_ = new RecursiveQuery(dnss, nsas, cache, upstream_, upstream_root_, query_timeout_, client_timeout_, lookup_timeout_, retries_); } void queryShutdown() { // only shut down if we have actually called querySetup before // (this is not a safety check, just to prevent logging of // actions that are not performed if (rec_query_) { LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_QUERY_SHUTDOWN); delete rec_query_; rec_query_ = NULL; } } void setForwardAddresses(const AddressList& upstream, DNSServiceBase* dnss) { upstream_ = upstream; if (dnss != NULL) { if (!upstream_.empty()) { BOOST_FOREACH(const AddressPair& address, upstream) { LOG_INFO(resolver_logger, RESOLVER_FORWARD_ADDRESS) .arg(address.first).arg(address.second); } } else { LOG_INFO(resolver_logger, RESOLVER_RECURSIVE); } } } void setRootAddresses(const AddressList& upstream_root, DNSServiceBase* dnss) { upstream_root_ = upstream_root; if (dnss != NULL) { if (!upstream_root_.empty()) { BOOST_FOREACH(const AddressPair& address, upstream_root) { LOG_INFO(resolver_logger, RESOLVER_SET_ROOT_ADDRESS) .arg(address.first).arg(address.second); } } else { LOG_WARN(resolver_logger, RESOLVER_NO_ROOT_ADDRESS); } } } void resolve(const isc::dns::QuestionPtr& question, const isc::resolve::ResolverInterface::CallbackPtr& callback); enum NormalQueryResult { RECURSION, DROPPED, ERROR }; NormalQueryResult processNormalQuery(const IOMessage& io_message, MessagePtr query_message, MessagePtr answer_message, OutputBufferPtr buffer, DNSServer* server); const RequestACL& getQueryACL() const { return (*query_acl_); } void setQueryACL(boost::shared_ptr new_acl) { query_acl_ = new_acl; } /// Currently non-configurable, but will be. static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096; /// These members are public because Resolver accesses them directly. ModuleCCSession* config_session_; /// Addresses of the root nameserver(s) AddressList upstream_root_; /// Addresses of the forward nameserver AddressList upstream_; /// Addresses we listen on AddressList listen_; /// 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_; private: /// ACL on incoming queries boost::shared_ptr query_acl_; /// Object to handle upstream queries RecursiveQuery* rec_query_; }; /* * std::for_each has a broken interface. It makes no sense in a language * without lambda functions/closures. These two classes emulate the lambda * functions so for_each can be used. */ class QuestionInserter { public: QuestionInserter(MessagePtr message) : message_(message) {} void operator()(const QuestionPtr question) { message_->addQuestion(question); } MessagePtr message_; }; // TODO: REMOVE, USE isc::resolve::MakeErrorMessage? void makeErrorMessage(MessagePtr message, MessagePtr answer_message, OutputBufferPtr buffer, const Rcode& rcode) { // extract the parameters that should be kept. // XXX: with the current implementation, it's not easy to set EDNS0 // depending on whether the query had it. So we'll simply omit it. const qid_t qid = message->getQid(); const bool rd = message->getHeaderFlag(Message::HEADERFLAG_RD); const bool cd = message->getHeaderFlag(Message::HEADERFLAG_CD); const Opcode& opcode = message->getOpcode(); vector questions; // answer_message is actually ignored right now, // see the comment in #607 answer_message->setRcode(rcode); answer_message->setOpcode(opcode); answer_message->setQid(qid); // If this is an error to a query or notify, we should also copy the // question section. if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) { questions.assign(message->beginQuestion(), message->endQuestion()); } message->clear(Message::RENDER); message->setQid(qid); message->setOpcode(opcode); message->setHeaderFlag(Message::HEADERFLAG_QR); if (rd) { message->setHeaderFlag(Message::HEADERFLAG_RD); } if (cd) { message->setHeaderFlag(Message::HEADERFLAG_CD); } for_each(questions.begin(), questions.end(), QuestionInserter(message)); message->setRcode(rcode); MessageRenderer renderer; renderer.setBuffer(buffer.get()); message->toWire(renderer); } // This is a derived class of \c DNSLookup, to serve as a // callback in the asiolink module. It calls // Resolver::processMessage() on a single DNS message. class MessageLookup : public DNSLookup { public: MessageLookup(Resolver* srv) : server_(srv) {} // \brief Handle the DNS Lookup virtual void operator()(const IOMessage& io_message, MessagePtr query_message, MessagePtr answer_message, OutputBufferPtr buffer, DNSServer* server) const { server_->processMessage(io_message, query_message, answer_message, buffer, server); } private: Resolver* server_; }; // This is a derived class of \c DNSAnswer, to serve as a // callback in the asiolink module. It takes a completed // set of answer data from the DNS lookup and assembles it // into a wire-format response. class MessageAnswer : public DNSAnswer { public: virtual void operator()(const IOMessage& io_message, MessagePtr query_message, MessagePtr answer_message, OutputBufferPtr buffer) const { 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); // 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->setHeaderFlag(Message::HEADERFLAG_QR); answer_message->setHeaderFlag(Message::HEADERFLAG_RA); answer_message->setHeaderFlag(Message::HEADERFLAG_RD, rd); answer_message->setHeaderFlag(Message::HEADERFLAG_CD, cd); // Now we can clear the buffer and render the new message into it buffer->clear(); MessageRenderer renderer; renderer.setBuffer(buffer.get()); ConstEDNSPtr edns(query_message->getEDNS()); const bool dnssec_ok = edns && edns->getDNSSECAwareness(); if (edns) { EDNSPtr edns_response(new EDNS()); edns_response->setDNSSECAwareness(dnssec_ok); // TODO: We should make our own edns bufsize length configurable edns_response->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE); answer_message->setEDNS(edns_response); } if (io_message.getSocket().getProtocol() == IPPROTO_UDP) { if (edns) { renderer.setLengthLimit(edns->getUDPSize()); } else { renderer.setLengthLimit(Message::DEFAULT_MAX_UDPSIZE); } } else { renderer.setLengthLimit(65535); } answer_message->toWire(renderer); renderer.setBuffer(NULL); LOG_DEBUG(resolver_logger, RESOLVER_DBG_DETAIL, RESOLVER_DNS_MESSAGE_SENT) .arg(renderer.getLength()).arg(*answer_message); } }; // This is a derived class of \c SimpleCallback, to serve // as a callback in the asiolink module. It checks for queued // configuration messages, and executes them if found. class ConfigCheck : public SimpleCallback { public: ConfigCheck(Resolver* srv) : server_(srv) {} virtual void operator()(const IOMessage&) const { if (server_->getConfigSession()->hasQueuedMsgs()) { server_->getConfigSession()->checkCommand(); } } private: Resolver* server_; }; Resolver::Resolver() : impl_(new ResolverImpl()), dnss_(NULL), checkin_(NULL), dns_lookup_(NULL), dns_answer_(new MessageAnswer), nsas_(NULL), cache_(NULL) { // Operations referring to "this" must be done in the constructor body // (some compilers will issue warnings if "this" is referred to in the // initialization list). checkin_ = new ConfigCheck(this); dns_lookup_ = new MessageLookup(this); } Resolver::~Resolver() { delete impl_; delete checkin_; delete dns_lookup_; delete dns_answer_; } void Resolver::setDNSService(isc::asiodns::DNSServiceBase& dnss) { dnss_ = &dnss; } void Resolver::setNameserverAddressStore(isc::nsas::NameserverAddressStore& nsas) { nsas_ = &nsas; } void Resolver::setCache(isc::cache::ResolverCache& cache) { cache_ = &cache; } void Resolver::setConfigSession(ModuleCCSession* config_session) { impl_->config_session_ = config_session; } ModuleCCSession* 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, MessagePtr answer_message, OutputBufferPtr buffer, DNSServer* server) { InputBuffer request_buffer(io_message.getData(), io_message.getDataSize()); // First, check the header part. If we fail even for the base header, // just drop the message. // In the following code, the debug output is such that there should only be // one debug message if packet processing failed. There could be two if // it succeeded. try { query_message->parseHeader(request_buffer); // Ignore all responses. if (query_message->getHeaderFlag(Message::HEADERFLAG_QR)) { LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_UNEXPECTED_RESPONSE); server->resume(false); return; } } catch (const Exception& ex) { LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_HEADER_PROCESSING_FAILED).arg(ex.what()); server->resume(false); return; } // Parse the message. On failure, return an appropriate error. try { query_message->fromWire(request_buffer); } catch (const DNSProtocolError& error) { LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_PROTOCOL_BODY_PARSE_FAILED) .arg(error.what()).arg(error.getRcode()); makeErrorMessage(query_message, answer_message, buffer, error.getRcode()); server->resume(true); return; } catch (const Exception& ex) { LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_MESSAGE_PROCESSING_FAILED) .arg(ex.what()).arg(Rcode::SERVFAIL()); makeErrorMessage(query_message, answer_message, buffer, Rcode::SERVFAIL()); server->resume(true); return; } // Other exceptions will be handled at a higher layer. // Note: there appears to be no LOG_DEBUG for a successfully-received // message. This is not an oversight - it is handled below. In the // meantime, output the full message for debug purposes (if requested). LOG_DEBUG(resolver_logger, RESOLVER_DBG_DETAIL, RESOLVER_DNS_MESSAGE_RECEIVED).arg(*query_message); // Perform further protocol-level validation. bool send_answer = true; if (query_message->getOpcode() == Opcode::NOTIFY()) { makeErrorMessage(query_message, answer_message, buffer, Rcode::NOTAUTH()); // Notify arrived, but we are not authoritative. LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NOTIFY_RECEIVED); } else if (query_message->getOpcode() != Opcode::QUERY()) { // Unsupported opcode. LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_UNSUPPORTED_OPCODE).arg(query_message->getOpcode()); makeErrorMessage(query_message, answer_message, buffer, Rcode::NOTIMP()); } else if (query_message->getRRCount(Message::SECTION_QUESTION) != 1) { // Not one question LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NOT_ONE_QUESTION) .arg(query_message->getRRCount(Message::SECTION_QUESTION)); makeErrorMessage(query_message, answer_message, buffer, Rcode::FORMERR()); } else { const ResolverImpl::NormalQueryResult result = impl_->processNormalQuery(io_message, query_message, answer_message, buffer, server); if (result == ResolverImpl::RECURSION) { // The RecursiveQuery object will post the "resume" event to the // DNSServer when an answer arrives, so we don't have to do it now. return; } else if (result == ResolverImpl::DROPPED) { send_answer = false; } } server->resume(send_answer); } void ResolverImpl::resolve(const QuestionPtr& question, const isc::resolve::ResolverInterface::CallbackPtr& callback) { rec_query_->resolve(question, callback); } ResolverImpl::NormalQueryResult ResolverImpl::processNormalQuery(const IOMessage& io_message, MessagePtr query_message, MessagePtr answer_message, OutputBufferPtr buffer, DNSServer* server) { const ConstQuestionPtr question = *query_message->beginQuestion(); const RRType qtype = question->getType(); const RRClass qclass = question->getClass(); // Apply query ACL const Client client(io_message); const BasicAction query_action( getQueryACL().execute(acl::dns::RequestContext( client.getRequestSourceIPAddress(), query_message->getTSIGRecord()))); if (query_action == isc::acl::REJECT) { LOG_INFO(resolver_logger, RESOLVER_QUERY_REJECTED) .arg(question->getName()).arg(qtype).arg(qclass).arg(client); makeErrorMessage(query_message, answer_message, buffer, Rcode::REFUSED()); return (ERROR); } else if (query_action == isc::acl::DROP) { LOG_INFO(resolver_logger, RESOLVER_QUERY_DROPPED) .arg(question->getName()).arg(qtype).arg(qclass).arg(client); return (DROPPED); } LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_QUERY_ACCEPTED) .arg(question->getName()).arg(qtype).arg(question->getClass()) .arg(client); // ACL passed. Reject inappropriate queries for the resolver. if (qtype == RRType::AXFR()) { if (io_message.getSocket().getProtocol() == IPPROTO_UDP) { // Can't process AXFR request received over UDP LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_AXFR_UDP); makeErrorMessage(query_message, answer_message, buffer, Rcode::FORMERR()); } else { // ... or over TCP for that matter LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_AXFR_TCP); makeErrorMessage(query_message, answer_message, buffer, Rcode::NOTIMP()); } return (ERROR); } else if (qtype == RRType::IXFR()) { // Can't process IXFR request LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_IXFR); makeErrorMessage(query_message, answer_message, buffer, Rcode::NOTIMP()); return (ERROR); } else if (qclass != RRClass::IN()) { // Non-IN message received, refuse it. LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NON_IN_PACKET) .arg(question->getClass()); makeErrorMessage(query_message, answer_message, buffer, Rcode::REFUSED()); return (ERROR); } // Everything is okay. Start resolver. if (upstream_.empty()) { // Processing normal query LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_NORMAL_QUERY); rec_query_->resolve(*question, answer_message, buffer, server); } else { // Processing forward query LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_FORWARD_QUERY); rec_query_->forward(query_message, answer_message, buffer, server); } return (RECURSION); } ConstElementPtr Resolver::updateConfig(ConstElementPtr config, bool startup) { LOG_DEBUG(resolver_logger, RESOLVER_DBG_CONFIG, RESOLVER_CONFIG_UPDATED) .arg(*config); try { // Parse forward_addresses ConstElementPtr rootAddressesE(config->get("root_addresses")); AddressList rootAddresses(parseAddresses(rootAddressesE, "root_addresses")); ConstElementPtr forwardAddressesE(config->get("forward_addresses")); AddressList forwardAddresses(parseAddresses(forwardAddressesE, "forward_addresses")); ConstElementPtr listenAddressesE(config->get("listen_on")); AddressList listenAddresses(parseAddresses(listenAddressesE, "listen_on")); const ConstElementPtr query_acl_cfg(config->get("query_acl")); const boost::shared_ptr query_acl = query_acl_cfg ? acl::dns::getRequestLoader().load(query_acl_cfg) : boost::shared_ptr(); bool set_timeouts(false); int qtimeout = impl_->query_timeout_; int ctimeout = impl_->client_timeout_; int ltimeout = impl_->lookup_timeout_; unsigned retries = impl_->retries_; 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 qtimeout = qtimeoutE->intValue(); if (qtimeout < -1) { LOG_ERROR(resolver_logger, RESOLVER_QUERY_TIME_SMALL) .arg(qtimeout); isc_throw(BadValue, "Query timeout too small"); } set_timeouts = true; } if (ctimeoutE) { ctimeout = ctimeoutE->intValue(); if (ctimeout < -1) { LOG_ERROR(resolver_logger, RESOLVER_CLIENT_TIME_SMALL) .arg(ctimeout); isc_throw(BadValue, "Client timeout too small"); } set_timeouts = true; } if (ltimeoutE) { ltimeout = ltimeoutE->intValue(); if (ltimeout < -1) { LOG_ERROR(resolver_logger, RESOLVER_LOOKUP_TIME_SMALL) .arg(ltimeout); isc_throw(BadValue, "Lookup timeout too small"); } set_timeouts = true; } if (retriesE) { // Do the assignment from "retriesE->intValue()" to "retries" // _after_ the comparison (as opposed to before it for the timeouts) // because "retries" is unsigned. if (retriesE->intValue() < 0) { LOG_ERROR(resolver_logger, RESOLVER_NEGATIVE_RETRIES) .arg(retriesE->intValue()); isc_throw(BadValue, "Negative number of retries"); } retries = retriesE->intValue(); set_timeouts = true; } // Everything OK, so commit the changes // listenAddresses can fail to bind, so try them first bool need_query_restart = false; if (!startup && listenAddressesE) { setListenAddresses(listenAddresses); need_query_restart = true; } if (forwardAddressesE) { setForwardAddresses(forwardAddresses); need_query_restart = true; } if (rootAddressesE) { setRootAddresses(rootAddresses); need_query_restart = true; } if (set_timeouts) { setTimeouts(qtimeout, ctimeout, ltimeout, retries); need_query_restart = true; } if (query_acl) { setQueryACL(query_acl); } if (startup && listenAddressesE) { setListenAddresses(listenAddresses); need_query_restart = true; } if (need_query_restart) { impl_->queryShutdown(); impl_->querySetup(*dnss_, *nsas_, *cache_); } return (isc::config::createAnswer()); } catch (const isc::Exception& error) { // Configuration error LOG_ERROR(resolver_logger, RESOLVER_CONFIG_ERROR).arg(error.what()); return (isc::config::createAnswer(1, error.what())); } } void Resolver::setForwardAddresses(const AddressList& addresses) { impl_->setForwardAddresses(addresses, dnss_); } void Resolver::setRootAddresses(const AddressList& addresses) { impl_->setRootAddresses(addresses, dnss_); } bool Resolver::isForwarding() const { return (!impl_->upstream_.empty()); } AddressList Resolver::getForwardAddresses() const { return (impl_->upstream_); } AddressList Resolver::getRootAddresses() const { return (impl_->upstream_root_); } void Resolver::setListenAddresses(const AddressList& addresses) { installListenAddresses(addresses, impl_->listen_, *dnss_); } void Resolver::setTimeouts(int query_timeout, int client_timeout, int lookup_timeout, unsigned retries) { LOG_DEBUG(resolver_logger, RESOLVER_DBG_CONFIG, RESOLVER_SET_PARAMS) .arg(query_timeout).arg(client_timeout).arg(lookup_timeout) .arg(retries); impl_->query_timeout_ = query_timeout; impl_->client_timeout_ = client_timeout; impl_->lookup_timeout_ = lookup_timeout; impl_->retries_ = 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_; } AddressList Resolver::getListenAddresses() const { return (impl_->listen_); } const RequestACL& Resolver::getQueryACL() const { return (impl_->getQueryACL()); } void Resolver::setQueryACL(boost::shared_ptr new_acl) { if (!new_acl) { isc_throw(InvalidParameter, "NULL pointer is passed to setQueryACL"); } LOG_INFO(resolver_logger, RESOLVER_SET_QUERY_ACL); impl_->setQueryACL(new_acl); }