2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 21:45:37 +00:00

Merge branch 'trac2238' into trac2269

This commit is contained in:
Tomek Mrugalski
2012-09-21 19:11:58 +02:00
128 changed files with 7887 additions and 1159 deletions

View File

@@ -1,11 +1,21 @@
4XX. [func] tomek
A new library (libb10-dhcpsrv) has been create. Currently its
functionality is limited to a Configuration Manager. CfgMgr
currently only supports basic configuration storage for DHCPv6
server, but that capability is expected to be expanded in a near
future.
A new library (libb10-dhcpsrv) has been created. At present, it
only holds the code for the DHCP Configuration Manager. Currently
this object only supports basic configuration storage for the DHCPv6
server, but that capability will be expanded.
(Trac #2238, git TBD)
475. [func] naokikambe
Added Xfrout statistics counters: notifyoutv4, notifyoutv6, xfrrej, and
xfrreqdone. These are per-zone type counters. The value of these
counters can be seen with zone name by invoking "Stats show Xfrout" via
bindctl.
(Trac #2158, git e68c127fed52e6034ab5309ddd506da03c37a08a)
474. [func] stephen
DHCP servers now use the BIND 10 logging system for messages.
(Trac #1545, git de69a92613b36bd3944cb061e1b7c611c3c85506)
473. [bug] jelte
TCP connections now time out in b10-auth if no (or not all) query
data is sent by the client. The timeout value defaults to 5000

View File

@@ -30,7 +30,7 @@ endif
check-valgrind-suppress:
if HAVE_VALGRIND
@VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --error-exitcode=1 --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions.revisit --num-callers=48 --leak-check=full --fullpath-after=" \
@VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --track-origins=yes --error-exitcode=1 --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions.revisit --num-callers=48 --leak-check=full --fullpath-after=" \
make -C $(abs_top_builddir) check
else
@echo "*** Valgrind is required for check-valgrind-suppress ***"; exit 1;

View File

@@ -1188,6 +1188,7 @@ AC_CONFIG_FILES([Makefile
src/lib/datasrc/Makefile
src/lib/datasrc/memory/Makefile
src/lib/datasrc/memory/tests/Makefile
src/lib/datasrc/memory/tests/testdata/Makefile
src/lib/datasrc/memory/benchmarks/Makefile
src/lib/datasrc/tests/Makefile
src/lib/datasrc/tests/testdata/Makefile

View File

@@ -24,6 +24,7 @@
* - @subpage libdhcp
* - @subpage libdhcpIntro
* - @subpage libdhcpIfaceMgr
* - @subpage perfdhcpInternals
*
* @section misc Miscellaneous topics
* - @subpage LoggingApi
@@ -36,4 +37,4 @@
* @todo: Move this logo to the right (and possibly up). Not sure what
* is the best way to do it in Doxygen, without using CSS hacks.
* @image html isc-logo.png
*/
*/

View File

@@ -1,4 +1,6 @@
/b10-dhcp4
/b10-dhcp4.8
/dhcp4_messages.cc
/dhcp4_messages.h
/spec_config.h
/spec_config.h.pre
/b10-dhcp4.8

View File

@@ -12,7 +12,7 @@ endif
pkglibexecdir = $(libexecdir)/@PACKAGE@
CLEANFILES = spec_config.h
CLEANFILES = *.gcno *.gcda spec_config.h dhcp4_messages.h dhcp4_messages.cc
man_MANS = b10-dhcp4.8
DISTCLEANFILES = $(man_MANS)
@@ -35,11 +35,20 @@ endif
spec_config.h: spec_config.h.pre
$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
BUILT_SOURCES = spec_config.h
dhcp4_messages.h dhcp4_messages.cc: dhcp4_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/bin/dhcp4/dhcp4_messages.mes
BUILT_SOURCES = spec_config.h dhcp4_messages.h dhcp4_messages.cc
pkglibexec_PROGRAMS = b10-dhcp4
b10_dhcp4_SOURCES = main.cc dhcp4_srv.cc dhcp4_srv.h
b10_dhcp4_SOURCES = main.cc
b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
EXTRA_DIST += dhcp4_messages.mes
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
@@ -47,7 +56,7 @@ if USE_CLANGPP
b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
endif
b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la

View File

@@ -13,28 +13,30 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <cassert>
#include <iostream>
#include <cc/session.h>
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
#include <cc/session.h>
#include <cc/session.h>
#include <config/ccsession.h>
#include <util/buffer.h>
#include <dhcp4/spec_config.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
#include <dhcp/iface_mgr.h>
#include <asiolink/asiolink.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
using namespace std;
using namespace isc::util;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::data;
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::log;
using namespace isc::util;
using namespace std;
namespace isc {
namespace dhcp {
@@ -43,7 +45,8 @@ ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
ConstElementPtr
ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
cout << "b10-dhcp4: Received new config:" << new_config->str() << endl;
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
.arg(new_config->str());
ConstElementPtr answer = isc::config::createAnswer(0,
"Thank you for sending config.");
return (answer);
@@ -51,13 +54,14 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
ConstElementPtr
ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) {
cout << "b10-dhcp4: Received new command: [" << command << "], args="
<< args->str() << endl;
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
.arg(command).arg(args->str());
if (command == "shutdown") {
if (ControlledDhcpv4Srv::server_) {
ControlledDhcpv4Srv::server_->shutdown();
} else {
cout << "Server not initialized yet or already shut down." << endl;
LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
ConstElementPtr answer = isc::config::createAnswer(1,
"Shutdown failure.");
return (answer);
@@ -93,10 +97,9 @@ void ControlledDhcpv4Srv::establishSession() {
/// @todo: Check if session is not established already. Throw, if it is.
cout << "b10-dhcp4: my specfile is " << specfile << endl;
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
.arg(specfile);
cc_session_ = new Session(io_service_.get_io_service());
config_session_ = new ModuleCCSession(specfile, *cc_session_,
dhcp4ConfigHandler,
dhcp4CommandHandler, false);
@@ -106,8 +109,8 @@ void ControlledDhcpv4Srv::establishSession() {
/// control with the "select" model of the DHCP server. This is
/// fully explained in \ref dhcpv4Session.
int ctrl_socket = cc_session_->getSocketDesc();
cout << "b10-dhcp4: Control session started, socket="
<< ctrl_socket << endl;
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
.arg(ctrl_socket);
IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
}

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2012 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.
/// Defines the logger used by the top-level component of b10-dhcp4.
#include "dhcp4_log.h"
namespace isc {
namespace dhcp {
isc::log::Logger dhcp4_logger("dhcp4");
} // namespace dhcp
} // namespace isc

59
src/bin/dhcp4/dhcp4_log.h Normal file
View File

@@ -0,0 +1,59 @@
// Copyright (C) 2012 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 __DHCP4_LOG__H
#define __DHCP4_LOG__H
#include <log/macros.h>
#include <log/logger_support.h>
#include <dhcp4/dhcp4_messages.h>
namespace isc {
namespace dhcp {
/// \brief DHCP4 Logging
///
/// Defines the levels used to output debug messages in the non-library part of
/// the b10-dhcp4 program. Higher numbers equate to more verbose (and detailed)
/// output.
// Debug levels used to log information during startup and shutdown.
const int DBG_DHCP4_START = DBGLVL_START_SHUT;
const int DBG_DHCP4_SHUT = DBGLVL_START_SHUT;
// Debug level used to log setting information (such as configuration changes).
const int DBG_DHCP4_COMMAND = DBGLVL_COMMAND;
// Trace basic operations within the code.
const int DBG_DHCP4_BASIC = DBGLVL_TRACE_BASIC;
// Trace detailed operations, including errors raised when processing invalid
// packets. (These are not logged at severities of WARN or higher for fear
// that a set of deliberately invalid packets set to the server could overwhelm
// the logging.)
const int DBG_DHCP4_DETAIL = DBGLVL_TRACE_DETAIL;
// This level is used to log the contents of packets received and sent.
const int DBG_DHCP4_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
/// Define the logger for the "dhcp4" module part of b10-dhcp4. We could define
/// a logger in each file, but we would want to define a common name to avoid
/// spelling mistakes, so it is just one small step from there to define a
/// module-common logger.
extern isc::log::Logger dhcp4_logger;
} // namespace dhcp4
} // namespace isc
#endif // __DHCP4_LOG__H

View File

@@ -0,0 +1,98 @@
# Copyright (C) 2012 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.
$NAMESPACE isc::dhcp
% DHCP4_CCSESSION_STARTED control channel session started on socket %1
A debug message issued during startup after the IPv4 DHCP server has
successfully established a session with the BIND 10 control channel.
% DHCP4_CCSESSION_STARTING starting control channel session, specfile: %1
This debug message is issued just before the IPv4 DHCP server attempts
to establish a session with the BIND 10 control channel.
% DHCP4_COMMAND_RECEIVED received command %1, arguments: %2
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the IPv4 DHCP server.
% DHCP4_CONFIG_UPDATE updated configuration received: %1
A debug message indicating that the IPv4 DHCP server has received an
updated configuration from the BIND 10 configuration system.
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv4 DHCP server but it is not running.
% DHCP4_OPEN_SOCKET opening sockets on port %1
A debug message issued during startup, this indicates that the IPv4 DHCP
server is about to open sockets on the specified port.
% DHCP4_PACKET_PARSE_FAIL failed to parse incoming packet: %1
The IPv4 DHCP server has received a packet that it is unable to
interpret. The reason why the packet is invalid is included in the message.
% DHCP4_PACKET_RECEIVED %1 (type %2) packet received on interface %3
A debug message noting that the server has received the specified type of
packet on the specified interface. Note that a packet marked as UNKNOWN
may well be a valid DHCP packet, just a type not expected by the server
(e.g. it will report a received OFFER packet as UNKNOWN).
% DHCP4_PACK_FAIL failed to assemble response correctly
This error is output if the server failed to assemble the data to be
returned to the client into a valid packet. The cause is most likely
to be a programming error: please raise a bug report.
% DHCP4_QUERY_DATA received packet type %1, data is <%2>
A debug message listing the data received from the client.
% DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
A debug message listing the data returned to the client.
% DHCP4_SERVER_FAILED server failed: %1
The IPv4 DHCP server has encountered a fatal error and is terminating.
The reason for the failure is included in the message.
% DHCP4_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
The server has failed to establish communication with the rest of BIND
10 and is running in stand-alone mode. (This behavior will change once
the IPv4 DHCP server is properly integrated with the rest of BIND 10.)
% DHCP4_SHUTDOWN server shutdown
The IPv4 DHCP server has terminated normally.
% DHCP4_SHUTDOWN_REQUEST shutdown of server requested
This debug message indicates that a shutdown of the IPv4 server has
been requested via a call to the 'shutdown' method of the core Dhcpv4Srv
object.
% DHCP4_SRV_CONSTRUCT_ERROR error creating Dhcpv4Srv object, reason: %1
This error message indicates that during startup, the construction of a
core component within the IPv4 DHCP server (the Dhcpv4 server object)
has failed. As a result, the server will exit. The reason for the
failure is given within the message.
% DHCP4_STANDALONE skipping message queue, running standalone
This is a debug message indicating that the IPv4 server is running in
standalone mode, not connected to the message queue. Standalone mode
is only useful during program development, and should not be used in a
production environment.
% DHCP4_STARTING server starting
This informational message indicates that the IPv4 DHCP server has
processed any command-line switches and is starting.
% DHCP4_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
This is a debug message issued during the IPv4 DHCP server startup.
It lists some information about the parameters with which the server
is running.

View File

@@ -16,13 +16,15 @@
#include <dhcp/pkt4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <asiolink/io_address.h>
#include <dhcp/option4_addrlst.h>
using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::log;
using namespace std;
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
@@ -35,20 +37,19 @@ const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
const std::string HARDCODED_SERVER_ID = "192.0.2.1";
Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
cout << "Initialization: opening sockets on port " << port << endl;
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// first call to instance() will create IfaceMgr (it's a singleton)
// First call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
IfaceMgr::instance();
/// @todo: instantiate LeaseMgr here once it is imlpemented.
IfaceMgr::instance().openSockets4(port);
setServerID();
} catch (const std::exception &e) {
cerr << "Error during DHCPv4 server startup: " << e.what() << endl;
LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
shutdown_ = true;
return;
}
@@ -57,12 +58,11 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
}
Dhcpv4Srv::~Dhcpv4Srv() {
cout << "b10-dhcp4: DHCPv4 server terminating." << endl;
IfaceMgr::instance().closeSockets();
}
void Dhcpv4Srv::shutdown() {
cout << "b10-dhcp4: DHCPv4 server shutdown." << endl;
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_SHUTDOWN_REQUEST);
shutdown_ = true;
}
@@ -79,39 +79,48 @@ Dhcpv4Srv::run() {
if (query) {
try {
query->unpack();
} catch (const std::exception& e) {
/// TODO: Printout reasons of failed parsing
cout << "Failed to parse incoming packet " << endl;
// Failed to parse the packet.
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
DHCP4_PACKET_PARSE_FAIL).arg(e.what());
continue;
}
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
.arg(serverReceivedPacketName(query->getType()))
.arg(query->getType())
.arg(query->getIface());
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
.arg(query->toText());
switch (query->getType()) {
case DHCPDISCOVER:
rsp = processDiscover(query);
break;
case DHCPREQUEST:
rsp = processRequest(query);
break;
case DHCPRELEASE:
processRelease(query);
break;
case DHCPDECLINE:
processDecline(query);
break;
case DHCPINFORM:
processInform(query);
break;
default:
cout << "Unknown pkt type received:"
<< query->getType() << endl;
// Only action is to output a message if debug is enabled,
// and that will be covered by the debug statement before
// the "switch" statement.
;
}
cout << "Received message type " << int(query->getType()) << endl;
// TODO: print out received packets only if verbose (or debug)
// mode is enabled
cout << query->toText();
if (rsp) {
if (rsp->getRemoteAddr().toText() == "0.0.0.0") {
rsp->setRemoteAddr(query->getRemoteAddr());
@@ -127,14 +136,15 @@ Dhcpv4Srv::run() {
rsp->setIface(query->getIface());
rsp->setIndex(query->getIndex());
cout << "Replying with message type "
<< static_cast<int>(rsp->getType()) << ":" << endl;
cout << rsp->toText();
cout << "----" << endl;
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA,
DHCP4_RESPONSE_DATA)
.arg(rsp->getType()).arg(rsp->toText());
if (rsp->pack()) {
cout << "Packet assembled correctly." << endl;
IfaceMgr::instance().send(rsp);
} else {
LOG_ERROR(dhcp4_logger, DHCP4_PACK_FAIL);
}
IfaceMgr::instance().send(rsp);
}
}
@@ -266,15 +276,44 @@ Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
/// TODO: Implement this.
cout << "Received RELEASE on " << release->getIface() << " interface." << endl;
}
void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
/// TODO: Implement this.
cout << "Received DECLINE on " << decline->getIface() << " interface." << endl;
}
Pkt4Ptr Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
/// TODO: Currently implemented echo mode. Implement this for real
return (inform);
}
const char*
Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
static const char* DISCOVER = "DISCOVER";
static const char* REQUEST = "REQUEST";
static const char* RELEASE = "RELEASE";
static const char* DECLINE = "DECLINE";
static const char* INFORM = "INFORM";
static const char* UNKNOWN = "UNKNOWN";
switch (type) {
case DHCPDISCOVER:
return (DISCOVER);
case DHCPREQUEST:
return (REQUEST);
case DHCPRELEASE:
return (RELEASE);
case DHCPDECLINE:
return (DECLINE);
case DHCPINFORM:
return (INFORM);
default:
;
}
return (UNKNOWN);
}

View File

@@ -44,7 +44,7 @@ class Dhcpv4Srv : public boost::noncopyable {
public:
/// @brief Default constructor.
///
/// Instantiates necessary services, required to run DHCPv6 server.
/// Instantiates necessary services, required to run DHCPv4 server.
/// In particular, creates IfaceMgr that will be responsible for
/// network interaction. Will instantiate lease manager, and load
/// old or create new DUID. It is possible to specify alternate
@@ -54,7 +54,7 @@ class Dhcpv4Srv : public boost::noncopyable {
/// @param port specifies port number to listen on
Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT);
/// @brief Destructor. Used during DHCPv6 service shutdown.
/// @brief Destructor. Used during DHCPv4 service shutdown.
~Dhcpv4Srv();
/// @brief Main server processing loop.
@@ -70,6 +70,23 @@ class Dhcpv4Srv : public boost::noncopyable {
/// @brief Instructs the server to shut down.
void shutdown();
/// @brief Return textual type of packet received by server
///
/// Returns the name of valid packet received by the server (e.g. DISCOVER).
/// If the packet is unknown - or if it is a valid DHCP packet but not one
/// expected to be received by the server (such as an OFFER), the string
/// "UNKNOWN" is returned. This method is used in debug messages.
///
/// As the operation of the method does not depend on any server state, it
/// is declared static.
///
/// @param type DHCPv4 packet type
///
/// @return Pointer to "const" string containing the packet name.
/// Note that this string is statically allocated and MUST NOT
/// be freed by the caller.
static const char* serverReceivedPacketName(uint8_t type);
protected:
/// @brief Processes incoming DISCOVER and returns response.
///
@@ -89,11 +106,11 @@ protected:
/// is valid, not expired, not reserved, not used by other client and
/// that requesting client is allowed to use it.
///
/// Returns ACK message, NACK message, or NULL
/// Returns ACK message, NAK message, or NULL
///
/// @param request a message received from client
///
/// @return ACK or NACK message
/// @return ACK or NAK message
Pkt4Ptr processRequest(Pkt4Ptr& request);
/// @brief Stub function that will handle incoming RELEASE messages.

View File

@@ -14,13 +14,15 @@
#include <config.h>
#include <iostream>
#include <log/dummylog.h>
#include <log/logger_support.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <boost/lexical_cast.hpp>
using namespace std;
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <log/logger_support.h>
using namespace isc::dhcp;
using namespace std;
/// This file contains entry point (main() function) for standard DHCPv4 server
/// component for BIND10 framework. It parses command-line arguments and
@@ -37,11 +39,10 @@ const char* const DHCP4_NAME = "b10-dhcp4";
void
usage() {
cerr << "Usage: b10-dhcp4 [-v]"
<< endl;
cerr << "\t-v: verbose output" << endl;
cerr << "\t-s: stand-alone mode (don't connect to BIND10)" << endl;
cerr << "\t-p number: specify non-standard port number 1-65535 "
cerr << "Usage: " << DHCP4_NAME << " [-v] [-s] [-p number]" << endl;
cerr << " -v: verbose output" << endl;
cerr << " -s: stand-alone mode (don't connect to BIND10)" << endl;
cerr << " -p number: specify non-standard port number 1-65535 "
<< "(useful for testing only)" << endl;
exit(EXIT_FAILURE);
}
@@ -50,20 +51,21 @@ usage() {
int
main(int argc, char* argv[]) {
int ch;
bool verbose_mode = false; // should server be verbose?
int port_number = DHCP4_SERVER_PORT; // The default. any other values are
// useful for testing only.
bool stand_alone = false; // should be connect to BIND10 msgq?
bool stand_alone = false; // Should be connect to BIND10 msgq?
bool verbose_mode = false; // Should server be verbose?
while ((ch = getopt(argc, argv, "vsp:")) != -1) {
switch (ch) {
case 'v':
verbose_mode = true;
isc::log::denabled = true;
break;
case 's':
stand_alone = true;
break;
case 'p':
try {
port_number = boost::lexical_cast<int>(optarg);
@@ -78,50 +80,46 @@ main(int argc, char* argv[]) {
usage();
}
break;
case ':':
default:
usage();
}
}
// Check for extraneous parameters.
if (argc > optind) {
usage();
}
// Initialize logging. If verbose, we'll use maximum verbosity.
isc::log::initLogger(DHCP4_NAME,
(verbose_mode ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL);
LOG_INFO(dhcp4_logger, DHCP4_STARTING);
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
.arg(stand_alone ? "yes" : "no" );
cout << "b10-dhcp4: My pid=" << getpid() << ", binding to port "
<< port_number << ", verbose " << (verbose_mode?"yes":"no")
<< ", stand-alone=" << (stand_alone?"yes":"no") << endl;
if (argc - optind > 0) {
usage();
}
int ret = EXIT_SUCCESS;
try {
cout << "[b10-dhcp4] Initiating DHCPv4 server operation." << endl;
/// @todo: pass verbose to the actul server once logging is implemented
ControlledDhcpv4Srv server(port_number);
if (!stand_alone) {
try {
server.establishSession();
} catch (const std::exception& ex) {
cerr << "Failed to establish BIND10 session. "
"Running in stand-alone mode:" << ex.what() << endl;
LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
// Let's continue. It is useful to have the ability to run
// DHCP server in stand-alone mode, e.g. for testing
}
} else {
cout << "Skipping connection to the BIND10 msgq." << endl;
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);
}
server.run();
LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
} catch (const std::exception& ex) {
cerr << "[b10-dhcp4] Server failed: " << ex.what() << endl;
LOG_FATAL(dhcp4_logger, DHCP4_SERVER_FAILED).arg(ex.what());
ret = EXIT_FAILURE;
}

View File

@@ -30,7 +30,7 @@ AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
CLEANFILES = $(builddir)/interfaces.txt
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -47,9 +47,11 @@ if HAVE_GTEST
TESTS += dhcp4_unittests
dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
dhcp4_unittests_SOURCES += dhcp4_unittests.cc
dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
if USE_CLANGPP
# Disable unused parameter warning caused by some of the

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2012 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
@@ -271,4 +271,36 @@ TEST_F(Dhcpv4SrvTest, processInform) {
delete srv;
}
TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
// Check all possible packet types
for (int itype = 0; itype < 256; ++itype) {
uint8_t type = itype;
switch (type) {
case DHCPDECLINE:
EXPECT_STREQ("DECLINE", Dhcpv4Srv::serverReceivedPacketName(type));
break;
case DHCPDISCOVER:
EXPECT_STREQ("DISCOVER", Dhcpv4Srv::serverReceivedPacketName(type));
break;
case DHCPINFORM:
EXPECT_STREQ("INFORM", Dhcpv4Srv::serverReceivedPacketName(type));
break;
case DHCPRELEASE:
EXPECT_STREQ("RELEASE", Dhcpv4Srv::serverReceivedPacketName(type));
break;
case DHCPREQUEST:
EXPECT_STREQ("REQUEST", Dhcpv4Srv::serverReceivedPacketName(type));
break;
default:
EXPECT_STREQ("UNKNOWN", Dhcpv4Srv::serverReceivedPacketName(type));
}
}
}
} // end of anonymous namespace

View File

@@ -27,16 +27,27 @@ import fcntl
class TestDhcpv4Daemon(unittest.TestCase):
def setUp(self):
# don't redirect stdout/stderr here as we want to print out things
# Don't redirect stdout/stderr here as we want to print out things
# during the test
pass
#
# However, we do want to set the logging lock directory to somewhere
# to which we can write - use the current working directory. We then
# set the appropriate environment variable. os.putenv() may be not
# supported on some platforms as suggested in
# http://docs.python.org/release/3.2/library/os.html?highlight=putenv#os.environ:
# "If the platform supports the putenv() function...". It was checked
# that it does not work on Ubuntu. To overcome this problem we access
# os.environ directly.
lockdir_envvar = "B10_LOCKFILE_DIR_FROM_BUILD"
if lockdir_envvar not in os.environ:
os.environ[lockdir_envvar] = os.getcwd()
def tearDown(self):
pass
def runCommand(self, params, wait=1):
"""
This method runs dhcp4 and returns a touple: (returncode, stdout, stderr)
This method runs dhcp4 and returns a tuple: (returncode, stdout, stderr)
"""
## @todo: Convert this into generic method and reuse it in dhcp6
@@ -79,9 +90,9 @@ class TestDhcpv4Daemon(unittest.TestCase):
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
# There's potential problem if b10-dhcp4 prints out more
# than 4k of text
# than 16kB of text
try:
output = os.read(self.stdout_pipes[0], 4096)
output = os.read(self.stdout_pipes[0], 16384)
except OSError:
print("No data available from stdout")
output = ""
@@ -91,7 +102,7 @@ class TestDhcpv4Daemon(unittest.TestCase):
output = ""
try:
error = os.read(self.stderr_pipes[0], 4096)
error = os.read(self.stderr_pipes[0], 16384)
except OSError:
print("No data available on stderr")
error = ""
@@ -128,13 +139,13 @@ class TestDhcpv4Daemon(unittest.TestCase):
print(" not that is can bind sockets correctly. Please ignore binding errors.")
(returncode, output, error) = self.runCommand(["../b10-dhcp4", "-v"])
self.assertEqual( str(output).count("[b10-dhcp4] Initiating DHCPv4 server operation."), 1)
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP4_STARTING"), 1)
def test_portnumber_0(self):
print("Check that specifying port number 0 is not allowed.")
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p', '0'])
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-p', '0'])
# When invalid port number is specified, return code must not be success
self.assertTrue(returncode != 0)
@@ -178,28 +189,19 @@ class TestDhcpv4Daemon(unittest.TestCase):
def test_portnumber_nonroot(self):
print("Check that specifying unprivileged port number will work.")
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
# When invalid port number is specified, return code must not be success
# TODO: Temporarily commented out as socket binding on systems that do not have
# interface detection implemented currently fails.
# self.assertTrue(returncode == 0)
# Check that there is an error message about invalid port number printed on stderr
self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
# Check that there is a message about running with an unprivileged port
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP4_OPEN_SOCKET opening sockets on port 10057"), 1)
def test_skip_msgq(self):
print("Check that connection to BIND10 msgq can be disabled.")
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
# When invalid port number is specified, return code must not be success
# TODO: Temporarily commented out as socket binding on systems that do not have
# interface detection implemented currently fails.
# self.assertTrue(returncode == 0)
# Check that there is an error message about invalid port number printed on stderr
self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
# Check that the system outputs a message on one of its streams about running
# standalone.
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP4_STANDALONE"), 1)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,11 +1,6 @@
*~
Makefile
Makefile.in
*.o
.deps
.libs
b10-dhcp6
spec_config.h
spec_config.h.pre
tests/dhcp6_unittests
/b10-dhcp6
/b10-dhcp6.8
/dhcp6_messages.cc
/dhcp6_messages.h
/spec_config.h
/spec_config.h.pre

View File

@@ -13,7 +13,7 @@ endif
pkglibexecdir = $(libexecdir)/@PACKAGE@
CLEANFILES = spec_config.h
CLEANFILES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
man_MANS = b10-dhcp6.8
DISTCLEANFILES = $(man_MANS)
@@ -37,11 +37,20 @@ endif
spec_config.h: spec_config.h.pre
$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
BUILT_SOURCES = spec_config.h
dhcp6_messages.h dhcp6_messages.cc: dhcp6_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/bin/dhcp6/dhcp6_messages.mes
BUILT_SOURCES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
pkglibexec_PROGRAMS = b10-dhcp6
b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
b10_dhcp6_SOURCES = main.cc
b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
EXTRA_DIST += dhcp6_messages.mes
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
@@ -49,7 +58,7 @@ if USE_CLANGPP
b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
endif
b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la

View File

@@ -13,28 +13,30 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <cassert>
#include <iostream>
#include <cc/session.h>
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
#include <cc/session.h>
#include <cc/session.h>
#include <config/ccsession.h>
#include <util/buffer.h>
#include <dhcp6/spec_config.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/spec_config.h>
#include <dhcp/iface_mgr.h>
#include <asiolink/asiolink.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
using namespace std;
using namespace isc::util;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::data;
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::log;
using namespace isc::util;
using namespace std;
namespace isc {
namespace dhcp {
@@ -43,7 +45,8 @@ ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
ConstElementPtr
ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
cout << "b10-dhcp6: Received new config:" << new_config->str() << endl;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
.arg(new_config->str());
ConstElementPtr answer = isc::config::createAnswer(0,
"Thank you for sending config.");
return (answer);
@@ -51,13 +54,14 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
ConstElementPtr
ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr args) {
cout << "b10-dhcp6: Received new command: [" << command << "], args="
<< args->str() << endl;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)
.arg(command).arg(args->str());
if (command == "shutdown") {
if (ControlledDhcpv6Srv::server_) {
ControlledDhcpv6Srv::server_->shutdown();
} else {
cout << "Server not initialized yet or already shut down." << endl;
LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
ConstElementPtr answer = isc::config::createAnswer(1,
"Shutdown failure.");
return (answer);
@@ -93,10 +97,9 @@ void ControlledDhcpv6Srv::establishSession() {
/// @todo: Check if session is not established already. Throw, if it is.
cout << "b10-dhcp6: my specfile is " << specfile << endl;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTING)
.arg(specfile);
cc_session_ = new Session(io_service_.get_io_service());
config_session_ = new ModuleCCSession(specfile, *cc_session_,
dhcp6ConfigHandler,
dhcp6CommandHandler, false);
@@ -106,8 +109,8 @@ void ControlledDhcpv6Srv::establishSession() {
/// control with the "select" model of the DHCP server. This is
/// fully explained in \ref dhcpv6Session.
int ctrl_socket = cc_session_->getSocketDesc();
cout << "b10-dhcp6: Control session started, socket="
<< ctrl_socket << endl;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
.arg(ctrl_socket);
IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
}

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2012 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.
/// Defines the logger used by the top-level component of b10-dhcp6.
#include "dhcp6_log.h"
namespace isc {
namespace dhcp {
isc::log::Logger dhcp6_logger("dhcp6");
} // namespace dhcp
} // namespace isc

59
src/bin/dhcp6/dhcp6_log.h Normal file
View File

@@ -0,0 +1,59 @@
// Copyright (C) 2012 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 __DHCP6_LOG__H
#define __DHCP6_LOG__H
#include <log/macros.h>
#include <log/logger_support.h>
#include <dhcp6/dhcp6_messages.h>
namespace isc {
namespace dhcp {
/// \brief DHCP6 Logging
///
/// Defines the levels used to output debug messages in the non-library part of
/// the b10-dhcp6 program. Higher numbers equate to more verbose (and detailed)
/// output.
// Debug levels used to log information during startup and shutdown.
const int DBG_DHCP6_START = DBGLVL_START_SHUT;
const int DBG_DHCP6_SHUT = DBGLVL_START_SHUT;
// Debug level used to log setting information (such as configuration changes).
const int DBG_DHCP6_COMMAND = DBGLVL_COMMAND;
// Trace basic operations within the code.
const int DBG_DHCP6_BASIC = DBGLVL_TRACE_BASIC;
// Trace detailed operations, including errors raised when processing invalid
// packets. (These are not logged at severities of WARN or higher for fear
// that a set of deliberately invalid packets set to the server could overwhelm
// the logging.)
const int DBG_DHCP6_DETAIL = DBGLVL_TRACE_DETAIL;
// This level is used to log the contents of packets received and sent.
const int DBG_DHCP6_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
/// Define the logger for the "dhcp6" module part of b10-dhcp6. We could define
/// a logger in each file, but we would want to define a common name to avoid
/// spelling mistakes, so it is just one small step from there to define a
/// module-common logger.
extern isc::log::Logger dhcp6_logger;
} // namespace dhcp6
} // namespace isc
#endif // __DHCP6_LOG__H

View File

@@ -0,0 +1,101 @@
# Copyright (C) 2012 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.
$NAMESPACE isc::dhcp
% DHCP6_CCSESSION_STARTED control channel session started on socket %1
A debug message issued during startup after the IPv6 DHCP server has
successfully established a session with the BIND 10 control channel.
% DHCP6_CCSESSION_STARTING starting control channel session, specfile: %1
This debug message is issued just before the IPv6 DHCP server attempts
to establish a session with the BIND 10 control channel.
% DHCP6_COMMAND_RECEIVED received command %1, arguments: %2
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the IPv6 DHCP server.
% DHCP6_CONFIG_UPDATE updated configuration received: %1
A debug message indicating that the IPv6 DHCP server has received an
updated configuration from the BIND 10 configuration system.
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv6 DHCP server but it is not running.
% DHCP6_NO_INTERFACES failed to detect any network interfaces
During startup the IPv6 DHCP server failed to detect any network
interfaces and is therefore shutting down.
% DHCP6_OPEN_SOCKET opening sockets on port %1
A debug message issued during startup, this indicates that the IPv6 DHCP
server is about to open sockets on the specified port.
% DHCP6_PACKET_PARSE_FAIL failed to parse incoming packet
The IPv6 DHCP server has received a packet that it is unable to interpret.
% DHCP6_PACKET_RECEIVED %1 (type %2) packet received
A debug message noting that the server has received the specified type
of packet. Note that a packet marked as UNKNOWN may well be a valid
DHCP packet, just a type not expected by the server (e.g. it will report
a received OFFER packet as UNKNOWN).
% DHCP6_PACK_FAIL failed to assemble response correctly
This error is output if the server failed to assemble the data to be
returned to the client into a valid packet. The reason is most likely
to be to a programming error: please raise a bug report.
% DHCP6_QUERY_DATA received packet length %1, data length %2, data is <%3>
A debug message listing the data received from the client or relay.
% DHCP6_RESPONSE_DATA responding with packet type %1 data is <%2>
A debug message listing the data returned to the client.
% DHCP6_SERVER_FAILED server failed: %1
The IPv6 DHCP server has encountered a fatal error and is terminating.
The reason for the failure is included in the message.
% DHCP6_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
The server has failed to establish communication with the rest of BIND
10 and is running in stand-alone mode. (This behavior will change once
the IPv6 DHCP server is properly integrated with the rest of BIND 10.)
% DHCP6_SHUTDOWN server shutdown
The IPv6 DHCP server has terminated normally.
% DHCP6_SHUTDOWN_REQUEST shutdown of server requested
This debug message indicates that a shutdown of the IPv6 server has
been requested via a call to the 'shutdown' method of the core Dhcpv6Srv
object.
% DHCP6_SRV_CONSTRUCT_ERROR error creating Dhcpv6Srv object, reason: %1
This error message indicates that during startup, the construction of a
core component within the IPv6 DHCP server (the Dhcpv6 server object)
has failed. As a result, the server will exit. The reason for the
failure is given within the message.
% DHCP6_STANDALONE skipping message queue, running standalone
This is a debug message indicating that the IPv6 server is running in
standalone mode, not connected to the message queue. Standalone mode
is only useful during program development, and should not be used in a
production environment.
% DHCP6_STARTING server starting
This informational message indicates that the IPv6 DHCP server has
processed any command-line switches and is starting.
% DHCP6_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
This is a debug message issued during the IPv6 DHCP server startup.
It lists some information about the parameters with which the server
is running.

View File

@@ -14,23 +14,25 @@
#include <stdlib.h>
#include <time.h>
#include <dhcp/dhcp6.h>
#include <dhcp/pkt6.h>
#include <dhcp/iface_mgr.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_addrlst.h>
#include <asiolink/io_address.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_ia.h>
#include <dhcp/pkt6.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::util;
using namespace std;
const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
const uint32_t HARDCODED_T1 = 1500; // in seconds
@@ -40,14 +42,14 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
cout << "Initialization: opening sockets on port " << port << endl;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
// first call to instance() will create IfaceMgr (it's a singleton)
// First call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
try {
if (IfaceMgr::instance().countIfaces() == 0) {
cout << "Failed to detect any network interfaces. Aborting." << endl;
LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
shutdown_ = true;
return;
}
@@ -59,7 +61,7 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
/// @todo: instantiate LeaseMgr here once it is imlpemented.
} catch (const std::exception &e) {
cerr << "Error during DHCPv4 server startup: " << e.what() << endl;
LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
shutdown_ = true;
return;
}
@@ -68,13 +70,11 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
}
Dhcpv6Srv::~Dhcpv6Srv() {
cout << "DHCPv6 Srv shutdown." << endl;
IfaceMgr::instance().closeSockets();
}
void Dhcpv6Srv::shutdown() {
cout << "b10-dhcp6: DHCPv6 server shutdown." << endl;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
shutdown_ = true;
}
@@ -89,42 +89,58 @@ bool Dhcpv6Srv::run() {
if (query) {
if (!query->unpack()) {
cout << "Failed to parse incoming packet" << endl;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
DHCP6_PACKET_PARSE_FAIL);
continue;
}
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
.arg(serverReceivedPacketName(query->getType()))
.arg(query->getType());
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
.arg(query->getType())
.arg(query->getBuffer().getLength())
.arg(query->toText());
switch (query->getType()) {
case DHCPV6_SOLICIT:
rsp = processSolicit(query);
break;
case DHCPV6_REQUEST:
rsp = processRequest(query);
break;
case DHCPV6_RENEW:
rsp = processRenew(query);
break;
case DHCPV6_REBIND:
rsp = processRebind(query);
break;
case DHCPV6_CONFIRM:
rsp = processConfirm(query);
break;
case DHCPV6_RELEASE:
rsp = processRelease(query);
break;
case DHCPV6_DECLINE:
rsp = processDecline(query);
break;
case DHCPV6_INFORMATION_REQUEST:
rsp = processInfRequest(query);
break;
default:
cout << "Unknown pkt type received:"
<< query->getType() << endl;
// Only action is to output a message if debug is enabled,
// and that will be covered by the debug statement before
// the "switch" statement.
;
}
cout << "Received " << query->getBuffer().getLength() << " bytes packet type="
<< query->getType() << endl;
cout << query->toText();
if (rsp) {
rsp->setRemoteAddr(query->getRemoteAddr());
rsp->setLocalAddr(query->getLocalAddr());
@@ -132,14 +148,16 @@ bool Dhcpv6Srv::run() {
rsp->setLocalPort(DHCP6_SERVER_PORT);
rsp->setIndex(query->getIndex());
rsp->setIface(query->getIface());
cout << "Replying with:" << rsp->getType() << endl;
cout << rsp->toText();
cout << "----" << endl;
if (!rsp->pack()) {
cout << "Failed to assemble response packet." << endl;
continue;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
DHCP6_RESPONSE_DATA)
.arg(rsp->getType()).arg(rsp->toText());
if (rsp->pack()) {
IfaceMgr::instance().send(rsp);
} else {
LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL);
}
IfaceMgr::instance().send(rsp);
}
}
@@ -350,3 +368,46 @@ Pkt6Ptr Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, infRequest->getTransid()));
return reply;
}
const char*
Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
static const char* CONFIRM = "CONFIRM";
static const char* DECLINE = "DECLINE";
static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
static const char* REBIND = "REBIND";
static const char* RELEASE = "RELEASE";
static const char* RENEW = "RENEW";
static const char* REQUEST = "REQUEST";
static const char* SOLICIT = "SOLICIT";
static const char* UNKNOWN = "UNKNOWN";
switch (type) {
case DHCPV6_CONFIRM:
return (CONFIRM);
case DHCPV6_DECLINE:
return (DECLINE);
case DHCPV6_INFORMATION_REQUEST:
return (INFORMATION_REQUEST);
case DHCPV6_REBIND:
return (REBIND);
case DHCPV6_RELEASE:
return (RELEASE);
case DHCPV6_RENEW:
return (RENEW);
case DHCPV6_REQUEST:
return (REQUEST);
case DHCPV6_SOLICIT:
return (SOLICIT);
default:
;
}
return (UNKNOWN);
}

View File

@@ -69,6 +69,24 @@ public:
/// @brief Instructs the server to shut down.
void shutdown();
/// @brief Return textual type of packet received by server
///
/// Returns the name of valid packet received by the server (e.g. SOLICIT).
/// If the packet is unknown - or if it is a valid DHCP packet but not one
/// expected to be received by the server (such as an ADVERTISE), the string
/// "UNKNOWN" is returned. This method is used in debug messages.
///
/// As the operation of the method does not depend on any server state, it
/// is declared static.
///
/// @param type DHCPv4 packet type
///
/// @return Pointer to "const" string containing the packet name.
/// Note that this string is statically allocated and MUST NOT
/// be freed by the caller.
static const char* serverReceivedPacketName(uint8_t type);
protected:
/// @brief Processes incoming SOLICIT and returns response.
///

View File

@@ -14,13 +14,15 @@
#include <config.h>
#include <iostream>
#include <log/dummylog.h>
#include <log/logger_support.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <boost/lexical_cast.hpp>
using namespace std;
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <log/logger_support.h>
using namespace isc::dhcp;
using namespace std;
/// This file contains entry point (main() function) for standard DHCPv6 server
/// component for BIND10 framework. It parses command-line arguments and
@@ -37,11 +39,10 @@ const char* const DHCP6_NAME = "b10-dhcp6";
void
usage() {
cerr << "Usage: b10-dhcp6 [-v]"
<< endl;
cerr << "\t-v: verbose output" << endl;
cerr << "\t-s: stand-alone mode (don't connect to BIND10)" << endl;
cerr << "\t-p number: specify non-standard port number 1-65535 "
cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p number]" << endl;
cerr << " -v: verbose output" << endl;
cerr << " -s: stand-alone mode (don't connect to BIND10)" << endl;
cerr << " -p number: specify non-standard port number 1-65535 "
<< "(useful for testing only)" << endl;
exit(EXIT_FAILURE);
}
@@ -52,18 +53,19 @@ main(int argc, char* argv[]) {
int ch;
int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
// useful for testing only.
bool stand_alone = false; // Should be connect to BIND10 msgq?
bool verbose_mode = false; // Should server be verbose?
bool stand_alone = false; // should be connect to BIND10 msgq?
while ((ch = getopt(argc, argv, "vsp:")) != -1) {
switch (ch) {
case 'v':
verbose_mode = true;
isc::log::denabled = true;
break;
case 's':
stand_alone = true;
break;
case 'p':
try {
port_number = boost::lexical_cast<int>(optarg);
@@ -78,51 +80,45 @@ main(int argc, char* argv[]) {
usage();
}
break;
case ':':
default:
usage();
}
}
// Check for extraneous parameters.
if (argc > optind) {
usage();
}
// Initialize logging. If verbose, we'll use maximum verbosity.
isc::log::initLogger(DHCP6_NAME,
(verbose_mode ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL);
cout << "b10-dhcp6: My pid=" << getpid() << ", binding to port "
<< port_number << ", verbose " << (verbose_mode?"yes":"no")
<< ", stand-alone=" << (stand_alone?"yes":"no") << endl;
if (argc - optind > 0) {
usage();
}
LOG_INFO(dhcp6_logger, DHCP6_STARTING);
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
.arg(stand_alone ? "yes" : "no" );
int ret = EXIT_SUCCESS;
try {
cout << "b10-dhcp6: Initiating DHCPv6 server operation." << endl;
/// @todo: pass verbose to the actual server once logging is implemented
ControlledDhcpv6Srv server(port_number);
if (!stand_alone) {
try {
server.establishSession();
} catch (const std::exception& ex) {
cerr << "Failed to establish BIND10 session. "
"Running in stand-alone mode:" << ex.what() << endl;
LOG_ERROR(dhcp6_logger, DHCP6_SESSION_FAIL).arg(ex.what());
// Let's continue. It is useful to have the ability to run
// DHCP server in stand-alone mode, e.g. for testing
}
} else {
cout << "Skipping connection to the BIND10 msgq." << endl;
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
}
server.run();
LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
} catch (const std::exception& ex) {
cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;
LOG_FATAL(dhcp6_logger, DHCP6_SERVER_FAILED).arg(ex.what());
ret = EXIT_FAILURE;
}

1
src/bin/dhcp6/tests/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/dhcp6_unittests

View File

@@ -26,7 +26,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
CLEANFILES = $(builddir)/interfaces.txt
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -42,10 +42,13 @@ if HAVE_GTEST
TESTS += dhcp6_unittests
dhcp6_unittests_SOURCES = ../dhcp6_srv.h ../dhcp6_srv.cc ../ctrl_dhcp6_srv.cc
dhcp6_unittests_SOURCES += dhcp6_unittests.cc
dhcp6_unittests_SOURCES = dhcp6_unittests.cc
dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
if USE_CLANGPP
# Disable unused parameter warning caused by some of the

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2012 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
@@ -223,4 +223,49 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
// more checks to be implemented
}
TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
// Check all possible packet types
for (int itype = 0; itype < 256; ++itype) {
uint8_t type = itype;
switch (type) {
case DHCPV6_CONFIRM:
EXPECT_STREQ("CONFIRM", Dhcpv6Srv::serverReceivedPacketName(type));
break;
case DHCPV6_DECLINE:
EXPECT_STREQ("DECLINE", Dhcpv6Srv::serverReceivedPacketName(type));
break;
case DHCPV6_INFORMATION_REQUEST:
EXPECT_STREQ("INFORMATION_REQUEST",
Dhcpv6Srv::serverReceivedPacketName(type));
break;
case DHCPV6_REBIND:
EXPECT_STREQ("REBIND", Dhcpv6Srv::serverReceivedPacketName(type));
break;
case DHCPV6_RELEASE:
EXPECT_STREQ("RELEASE", Dhcpv6Srv::serverReceivedPacketName(type));
break;
case DHCPV6_RENEW:
EXPECT_STREQ("RENEW", Dhcpv6Srv::serverReceivedPacketName(type));
break;
case DHCPV6_REQUEST:
EXPECT_STREQ("REQUEST", Dhcpv6Srv::serverReceivedPacketName(type));
break;
case DHCPV6_SOLICIT:
EXPECT_STREQ("SOLICIT", Dhcpv6Srv::serverReceivedPacketName(type));
break;
default:
EXPECT_STREQ("UNKNOWN", Dhcpv6Srv::serverReceivedPacketName(type));
}
}
}
} // end of anonymous namespace

View File

@@ -27,16 +27,27 @@ import fcntl
class TestDhcpv6Daemon(unittest.TestCase):
def setUp(self):
# don't redirect stdout/stderr here as we want to print out things
# Don't redirect stdout/stderr here as we want to print out things
# during the test
pass
#
# However, we do want to set the logging lock directory to somewhere
# to which we can write - use the current working directory. We then
# set the appropriate environment variable. os.putenv() may be not
# supported on some platforms as suggested in
# http://docs.python.org/release/3.2/library/os.html?highlight=putenv#os.environ:
# "If the platform supports the putenv() function...". It was checked
# that it does not work on Ubuntu. To overcome this problem we access
# os.environ directly.
lockdir_envvar = "B10_LOCKFILE_DIR_FROM_BUILD"
if lockdir_envvar not in os.environ:
os.environ[lockdir_envvar] = os.getcwd()
def tearDown(self):
pass
def runCommand(self, params, wait=1):
"""
This method runs a command and returns a touple: (returncode, stdout, stderr)
This method runs a command and returns a tuple: (returncode, stdout, stderr)
"""
## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
@@ -79,9 +90,9 @@ class TestDhcpv6Daemon(unittest.TestCase):
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
# There's potential problem if b10-dhcp4 prints out more
# than 4k of text
# than 16k of text
try:
output = os.read(self.stdout_pipes[0], 4096)
output = os.read(self.stdout_pipes[0], 16384)
except OSError:
print("No data available from stdout")
output = ""
@@ -91,7 +102,7 @@ class TestDhcpv6Daemon(unittest.TestCase):
output = ""
try:
error = os.read(self.stderr_pipes[0], 4096)
error = os.read(self.stderr_pipes[0], 16384)
except OSError:
print("No data available on stderr")
error = ""
@@ -130,8 +141,8 @@ class TestDhcpv6Daemon(unittest.TestCase):
print("Note: Purpose of some of the tests is to check if DHCPv6 server can be started,")
print(" not that is can bind sockets correctly. Please ignore binding errors.")
(returncode, output, error) = self.runCommand(["../b10-dhcp6", "-v"])
self.assertEqual( str(output).count("b10-dhcp6: Initiating DHCPv6 server operation."), 1)
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP6_STARTING"), 1)
def test_portnumber_0(self):
print("Check that specifying port number 0 is not allowed.")
@@ -180,27 +191,19 @@ class TestDhcpv6Daemon(unittest.TestCase):
def test_portnumber_nonroot(self):
print("Check that specifying unprivileged port number will work.")
(returncode, output, error) = self.runCommand(['../b10-dhcp6', '-s', '-p', '10547'])
# When invalid port number is specified, return code must not be success
# TODO: Temporarily commented out as socket binding on systems that do not have
# interface detection implemented currently fails.
# self.assertTrue(returncode == 0)
self.assertEqual( str(output).count("opening sockets on port 10547"), 1)
# Check that there is a message about running with an unprivileged port
(returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP6_OPEN_SOCKET opening sockets on port 10547"), 1)
def test_skip_msgq(self):
print("Check that connection to BIND10 msgq can be disabled.")
(returncode, output, error) = self.runCommand(['../b10-dhcp6', '-s', '-p', '10547'])
# When invalid port number is specified, return code must not be success
# TODO: Temporarily commented out as socket binding on systems that do not have
# interface detection implemented currently fails.
# self.assertTrue(returncode == 0)
self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
# Check that the system outputs a message on one of its streams about running
# standalone.
(returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP6_STANDALONE"), 1)
if __name__ == '__main__':
unittest.main()

View File

@@ -153,6 +153,54 @@
</refsect1>
<refsect1>
<title>STATISTICS DATA</title>
<para>
The statistics data collected by the <command>b10-xfrout</command>
daemon for <quote>Xfrout</quote> include:
</para>
<variablelist>
<varlistentry>
<term>notifyoutv4</term>
<listitem><simpara>
Number of IPv4 notifies per zone name sent out from Xfrout
</simpara></listitem>
</varlistentry>
<varlistentry>
<term>notifyoutv6</term>
<listitem><simpara>
Number of IPv6 notifies per zone name sent out from Xfrout
</simpara></listitem>
</varlistentry>
<varlistentry>
<term>xfrrej</term>
<listitem><simpara>
Number of XFR requests per zone name rejected by Xfrout
</simpara></listitem>
</varlistentry>
<varlistentry>
<term>xfrreqdone</term>
<listitem><simpara>
Number of requested zone transfers per zone name completed
</simpara></listitem>
</varlistentry>
</variablelist>
<para>
In per-zone counters the special zone name '_SERVER_' exists. It doesn't
mean a specific zone. It represents an entire server and its value means
a total count of all zones.
</para>
</refsect1>
<!--
<refsect1>
<title>OPTIONS</title>

View File

@@ -277,13 +277,23 @@ class TestXfroutSessionBase(unittest.TestCase):
# When not testing ACLs, simply accept
isc.acl.dns.REQUEST_LOADER.load(
[{"action": "ACCEPT"}]),
{})
{},
counter_xfrrej=self._counter_xfrrej,
counter_xfrreqdone=self._counter_xfrreqdone)
self.set_request_type(RRType.AXFR()) # test AXFR by default
self.mdata = self.create_request_data()
self.soa_rrset = create_soa(SOA_CURRENT_VERSION)
# some test replaces a module-wide function. We should ensure the
# original is used elsewhere.
self.orig_get_rrset_len = xfrout.get_rrset_len
self._zone_name_xfrrej = None
self._zone_name_xfrreqdone = None
def _counter_xfrrej(self, zone_name):
self._zone_name_xfrrej = zone_name
def _counter_xfrreqdone(self, zone_name):
self._zone_name_xfrreqdone = zone_name
def tearDown(self):
xfrout.get_rrset_len = self.orig_get_rrset_len
@@ -458,7 +468,28 @@ class TestXfroutSession(TestXfroutSessionBase):
# ACL checks only with the default ACL
def acl_setter(acl):
self.xfrsess._acl = acl
self.assertIsNone(self._zone_name_xfrrej)
self.check_transfer_acl(acl_setter)
self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
def test_transfer_acl_with_nonetype_xfrrej(self):
# ACL checks only with the default ACL and NoneType xfrrej
# counter
def acl_setter(acl):
self.xfrsess._acl = acl
self.xfrsess._counter_xfrrej = None
self.assertIsNone(self._zone_name_xfrrej)
self.check_transfer_acl(acl_setter)
self.assertIsNone(self._zone_name_xfrrej)
def test_transfer_acl_with_notcallable_xfrrej(self):
# ACL checks only with the default ACL and not callable xfrrej
# counter
def acl_setter(acl):
self.xfrsess._acl = acl
self.xfrsess._counter_xfrrej = 'NOT CALLABLE'
self.assertRaises(TypeError,
self.check_transfer_acl, acl_setter)
def test_transfer_zoneacl(self):
# ACL check with a per zone ACL + default ACL. The per zone ACL
@@ -469,7 +500,9 @@ class TestXfroutSession(TestXfroutSessionBase):
self.xfrsess._zone_config[zone_key]['transfer_acl'] = acl
self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
{"from": "127.0.0.1", "action": "DROP"}])
self.assertIsNone(self._zone_name_xfrrej)
self.check_transfer_acl(acl_setter)
self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
def test_transfer_zoneacl_nomatch(self):
# similar to the previous one, but the per zone doesn't match the
@@ -481,7 +514,9 @@ class TestXfroutSession(TestXfroutSessionBase):
isc.acl.dns.REQUEST_LOADER.load([
{"from": "127.0.0.1", "action": "DROP"}])
self.xfrsess._acl = acl
self.assertIsNone(self._zone_name_xfrrej)
self.check_transfer_acl(acl_setter)
self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
def test_get_transfer_acl(self):
# set the default ACL. If there's no specific zone ACL, this one
@@ -831,9 +866,39 @@ class TestXfroutSession(TestXfroutSessionBase):
def myreply(msg, sock):
self.sock.send(b"success")
self.assertIsNone(self._zone_name_xfrreqdone)
self.xfrsess._reply_xfrout_query = myreply
self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
self.assertEqual(self.sock.readsent(), b"success")
self.assertEqual(self._zone_name_xfrreqdone, TEST_ZONE_NAME_STR)
def test_dns_xfrout_start_with_nonetype_xfrreqdone(self):
def noerror(msg, name, rrclass):
return Rcode.NOERROR()
self.xfrsess._xfrout_setup = noerror
def myreply(msg, sock):
self.sock.send(b"success")
self.assertIsNone(self._zone_name_xfrreqdone)
self.xfrsess._reply_xfrout_query = myreply
self.xfrsess._counter_xfrreqdone = None
self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
self.assertIsNone(self._zone_name_xfrreqdone)
def test_dns_xfrout_start_with_notcallable_xfrreqdone(self):
def noerror(msg, name, rrclass):
return Rcode.NOERROR()
self.xfrsess._xfrout_setup = noerror
def myreply(msg, sock):
self.sock.send(b"success")
self.xfrsess._reply_xfrout_query = myreply
self.xfrsess._counter_xfrreqdone = 'NOT CALLABLE'
self.assertRaises(TypeError,
self.xfrsess.dns_xfrout_start, self.sock,
self.mdata)
def test_reply_xfrout_query_axfr(self):
self.xfrsess._soa = self.soa_rrset
@@ -1153,6 +1218,7 @@ class MyUnixSockServer(UnixSockServer):
self._common_init()
self._cc = MyCCSession()
self.update_config_data(self._cc.get_full_config())
self._counters = {}
class TestUnixSockServer(unittest.TestCase):
def setUp(self):
@@ -1504,6 +1570,80 @@ class MyXfroutServer(XfroutServer):
self._unix_socket_server = None
# Disable the wait for threads
self._wait_for_threads = lambda : None
self._cc.get_module_spec = lambda:\
isc.config.module_spec_from_file(xfrout.SPECFILE_LOCATION)
# setup an XfroutCount object
self._counter = XfroutCounter(
self._cc.get_module_spec().get_statistics_spec())
class TestXfroutCounter(unittest.TestCase):
def setUp(self):
statistics_spec = \
isc.config.module_spec_from_file(\
xfrout.SPECFILE_LOCATION).get_statistics_spec()
self.xfrout_counter = XfroutCounter(statistics_spec)
self._counters = isc.config.spec_name_list(\
isc.config.find_spec_part(\
statistics_spec, XfroutCounter.perzone_prefix)\
['named_set_item_spec']['map_item_spec'])
self._started = threading.Event()
self._number = 3 # number of the threads
self._cycle = 10000 # number of counting per thread
def test_get_default_statistics_data(self):
self.assertEqual(self.xfrout_counter._get_default_statistics_data(),
{XfroutCounter.perzone_prefix: {
XfroutCounter.entire_server: \
dict([(cnt, 0) for cnt in self._counters])
}})
def setup_incrementer(self, incrementer):
self._started.wait()
for i in range(self._cycle): incrementer(TEST_ZONE_NAME_STR)
def start_incrementer(self, incrementer):
threads = []
for i in range(self._number):
threads.append(threading.Thread(\
target=self.setup_incrementer,\
args=(incrementer,)\
))
for th in threads: th.start()
self._started.set()
for th in threads: th.join()
def get_count(self, zone_name, counter_name):
return isc.cc.data.find(\
self.xfrout_counter.get_statistics(),\
'%s/%s/%s' % (XfroutCounter.perzone_prefix,\
zone_name, counter_name))
def test_incrementers(self):
result = { XfroutCounter.entire_server: {},
TEST_ZONE_NAME_STR: {} }
for counter_name in self._counters:
incrementer = getattr(self.xfrout_counter, 'inc_%s' % counter_name)
self.start_incrementer(incrementer)
self.assertEqual(self.get_count(\
TEST_ZONE_NAME_STR, counter_name), \
self._number * self._cycle)
self.assertEqual(self.get_count(\
XfroutCounter.entire_server, counter_name), \
self._number * self._cycle)
result[XfroutCounter.entire_server][counter_name] = \
result[TEST_ZONE_NAME_STR][counter_name] = \
self._number * self._cycle
self.assertEqual(
self.xfrout_counter.get_statistics(),
{XfroutCounter.perzone_prefix: result})
def test_add_perzone_counter(self):
for counter_name in self._counters:
self.assertRaises(isc.cc.data.DataNotFoundError,\
self.get_count, TEST_ZONE_NAME_STR, counter_name)
self.xfrout_counter._add_perzone_counter(TEST_ZONE_NAME_STR)
for counter_name in self._counters:
self.assertEqual(self.get_count(TEST_ZONE_NAME_STR, counter_name), 0)
class TestXfroutServer(unittest.TestCase):
def setUp(self):
@@ -1514,6 +1654,11 @@ class TestXfroutServer(unittest.TestCase):
self.assertTrue(self.xfrout_server._notifier.shutdown_called)
self.assertTrue(self.xfrout_server._cc.stopped)
def test_getstats(self):
self.assertEqual(
self.xfrout_server.command_handler('getstats', None), \
create_answer(0, {}))
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
unittest.main()

View File

@@ -153,7 +153,8 @@ def get_soa_serial(soa_rdata):
class XfroutSession():
def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
default_acl, zone_config, client_class=DataSourceClient):
default_acl, zone_config, client_class=DataSourceClient,
counter_xfrrej=None, counter_xfrreqdone=None):
self._sock_fd = sock_fd
self._request_data = request_data
self._server = server
@@ -168,6 +169,10 @@ class XfroutSession():
self.ClientClass = client_class # parameterize this for testing
self._soa = None # will be set in _xfrout_setup or in tests
self._jnl_reader = None # will be set to a reader for IXFR
# Set counter handlers for counting Xfr requests. An argument
# is required for zone name.
self._counter_xfrrej = counter_xfrrej
self._counter_xfrreqdone = counter_xfrreqdone
self._handle()
def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -270,6 +275,9 @@ class XfroutSession():
format_zone_str(zone_name, zone_class))
return None, None
elif acl_result == REJECT:
if self._counter_xfrrej is not None:
# count rejected Xfr request by each zone name
self._counter_xfrrej(zone_name.to_text())
logger.debug(DBG_XFROUT_TRACE, XFROUT_QUERY_REJECTED,
self._request_type, format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class))
@@ -525,6 +533,9 @@ class XfroutSession():
except Exception as err:
logger.error(XFROUT_XFR_TRANSFER_ERROR, self._request_typestr,
format_addrinfo(self._remote), zone_str, err)
if self._counter_xfrreqdone is not None:
# count done Xfr requests by each zone name
self._counter_xfrreqdone(zone_name.to_text())
logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
format_addrinfo(self._remote), zone_str)
@@ -634,7 +645,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
'''The unix domain socket server which accept xfr query sent from auth server.'''
def __init__(self, sock_file, handle_class, shutdown_event, config_data,
cc):
cc, **counters):
self._remove_unused_sock_file(sock_file)
self._sock_file = sock_file
socketserver_mixin.NoPollMixIn.__init__(self)
@@ -644,6 +655,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
self._common_init()
self._cc = cc
self.update_config_data(config_data)
# handlers for statistics use
self._counters = counters
def _common_init(self):
'''Initialization shared with the mock server class used for tests'''
@@ -798,7 +811,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
self._lock.release()
self.RequestHandlerClass(sock_fd, request_data, self,
isc.server_common.tsig_keyring.get_keyring(),
self._guess_remote(sock_fd), acl, zone_config)
self._guess_remote(sock_fd), acl, zone_config,
**self._counters)
def _remove_unused_sock_file(self, sock_file):
'''Try to remove the socket file. If the file is being used
@@ -926,6 +940,107 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
self._transfers_counter -= 1
self._lock.release()
class XfroutCounter:
"""A class for handling all statistics counters of Xfrout. In
this class, the structure of per-zone counters is assumed to be
like this:
zones/example.com./notifyoutv4
zones/example.com./notifyoutv6
zones/example.com./xfrrej
zones/example.com./xfrreqdone
"""
# '_SERVER_' is a special zone name representing an entire
# count. It doesn't mean a specific zone, but it means an
# entire count in the server.
entire_server = '_SERVER_'
# zone names are contained under this dirname in the spec file.
perzone_prefix = 'zones'
def __init__(self, statistics_spec):
self._statistics_spec = statistics_spec
# holding statistics data for Xfrout module
self._statistics_data = {}
self._lock = threading.RLock()
self._create_perzone_incrementers()
def get_statistics(self):
"""Calculates an entire server counts, and returns statistics
data format to send out the stats module including each
counter. If there is no counts, then it returns an empty
dictionary. Locks the thread because it is considered to be
invoked by a multi-threading caller."""
# If self._statistics_data contains nothing of zone name, it
# returns an empty dict.
if len(self._statistics_data) == 0: return {}
zones = {}
with self._lock:
zones = self._statistics_data[self.perzone_prefix].copy()
# Start calculation for '_SERVER_' counts
attrs = self._get_default_statistics_data()[self.perzone_prefix][self.entire_server]
statistics_data = {self.perzone_prefix: {}}
for attr in attrs:
sum_ = 0
for name in zones:
if name == self.entire_server: continue
if attr in zones[name]:
if name not in statistics_data[self.perzone_prefix]:
statistics_data[self.perzone_prefix][name] = {}
statistics_data[self.perzone_prefix][name].update(
{attr: zones[name][attr]}
)
sum_ += zones[name][attr]
if sum_ > 0:
if self.entire_server not in statistics_data[self.perzone_prefix]:
statistics_data[self.perzone_prefix][self.entire_server] = {}
statistics_data[self.perzone_prefix][self.entire_server].update({attr: sum_})
return statistics_data
def _get_default_statistics_data(self):
"""Returns default statistics data from the spec file"""
statistics_data = {}
for id_ in isc.config.spec_name_list(self._statistics_spec):
spec = isc.config.find_spec_part(self._statistics_spec, id_)
statistics_data.update({id_: spec['item_default']})
return statistics_data
def _create_perzone_incrementers(self):
"""Creates increment method of each per-zone counter based on
the spec file. Incrementer can be accessed by name
"inc_${item_name}".Incrementers are passed to the
XfroutSession and NotifyOut class as counter handlers."""
# add a new element under the named_set item for the zone
zones_spec = isc.config.find_spec_part(
self._statistics_spec, self.perzone_prefix)
item_list = isc.config.spec_name_list(\
zones_spec['named_set_item_spec']['map_item_spec'])
# can be accessed by the name 'inc_xxx'
for item in item_list:
def __perzone_incrementer(zone_name, counter_name=item, step=1):
"""A per-zone incrementer for counter_name. Locks the thread
because it is considered to be invoked by a multi-threading
caller."""
with self._lock:
self._add_perzone_counter(zone_name)
self._statistics_data[self.perzone_prefix][zone_name][counter_name] += step
setattr(self, 'inc_%s' % item, __perzone_incrementer)
def _add_perzone_counter(self, zone):
"""Adds named_set-type counter for each zone name"""
try:
self._statistics_data[self.perzone_prefix][zone]
except KeyError:
# add a new element under the named_set item for the zone
map_spec = isc.config.find_spec_part(
self._statistics_spec, '%s/%s' % \
(self.perzone_prefix, zone))['map_item_spec']
id_list = isc.config.spec_name_list(map_spec)
for id_ in id_list:
spec = isc.config.find_spec_part(map_spec, id_)
isc.cc.data.set(self._statistics_data,
'%s/%s/%s' % \
(self.perzone_prefix, zone, id_),
spec['item_default'])
class XfroutServer:
def __init__(self):
self._unix_socket_server = None
@@ -933,6 +1048,8 @@ class XfroutServer:
self._shutdown_event = threading.Event()
self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self._config_data = self._cc.get_full_config()
self._counter = XfroutCounter(
self._cc.get_module_spec().get_statistics_spec())
self._cc.start()
self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
isc.server_common.tsig_keyring.init_keyring(self._cc)
@@ -941,17 +1058,25 @@ class XfroutServer:
def _start_xfr_query_listener(self):
'''Start a new thread to accept xfr query. '''
self._unix_socket_server = UnixSockServer(self._listen_sock_file,
XfroutSession,
self._shutdown_event,
self._config_data,
self._cc)
self._unix_socket_server = UnixSockServer(
self._listen_sock_file,
XfroutSession,
self._shutdown_event,
self._config_data,
self._cc,
counter_xfrrej=self._counter.inc_xfrrej,
counter_xfrreqdone=self._counter.inc_xfrreqdone
)
listener = threading.Thread(target=self._unix_socket_server.serve_forever)
listener.start()
def _start_notifier(self):
datasrc = self._unix_socket_server.get_db_file()
self._notifier = notify_out.NotifyOut(datasrc)
self._notifier = notify_out.NotifyOut(
datasrc,
counter_notifyoutv4=self._counter.inc_notifyoutv4,
counter_notifyoutv6=self._counter.inc_notifyoutv6
)
if 'also_notify' in self._config_data:
for slave in self._config_data['also_notify']:
self._notifier.add_slave(slave['address'], slave['port'])
@@ -1027,6 +1152,15 @@ class XfroutServer:
else:
answer = create_answer(1, "Bad command parameter:" + str(args))
# return statistics data to the stats daemon
elif cmd == "getstats":
# The log level is here set to debug in order to avoid
# that a log becomes too verbose. Because the b10-stats
# daemon is periodically asking to the b10-xfrout daemon.
logger.debug(DBG_XFROUT_TRACE, \
XFROUT_RECEIVED_GETSTATS_COMMAND)
answer = create_answer(0, self._counter.get_statistics())
else:
answer = create_answer(1, "Unknown command:" + str(cmd))

View File

@@ -114,6 +114,65 @@
"item_default": "IN"
} ]
}
],
"statistics": [
{
"item_name": "zones",
"item_type": "named_set",
"item_optional": false,
"item_default": {
"_SERVER_" : {
"notifyoutv4" : 0,
"notifyoutv6" : 0,
"xfrrej" : 0,
"xfrreqdone" : 0
}
},
"item_title": "Zone names",
"item_description": "Zone names for Xfrout statistics",
"named_set_item_spec": {
"item_name": "zonename",
"item_type": "map",
"item_optional": false,
"item_default": {},
"item_title": "Zone name",
"item_description": "Zone name for Xfrout statistics",
"map_item_spec": [
{
"item_name": "notifyoutv4",
"item_type": "integer",
"item_optional": false,
"item_default": 0,
"item_title": "IPv4 notifies",
"item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
},
{
"item_name": "notifyoutv6",
"item_type": "integer",
"item_optional": false,
"item_default": 0,
"item_title": "IPv6 notifies",
"item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
},
{
"item_name": "xfrrej",
"item_type": "integer",
"item_optional": false,
"item_default": 0,
"item_title": "XFR rejected requests",
"item_description": "Number of XFR requests per zone name rejected by Xfrout"
},
{
"item_name": "xfrreqdone",
"item_type": "integer",
"item_optional": false,
"item_default": 0,
"item_title": "Requested zone transfers",
"item_description": "Number of requested zone transfers completed per zone name"
}
]
}
}
]
}
}

View File

@@ -107,6 +107,10 @@ received from the configuration manager.
The xfrout daemon received a command on the command channel that
NOTIFY packets should be sent for the given zone.
% XFROUT_RECEIVED_GETSTATS_COMMAND received command to get statistics data
The xfrout daemon received a command on the command channel that
statistics data should be sent to the stats daemon.
% XFROUT_PARSE_QUERY_ERROR error parsing query: %1
There was a parse error while reading an incoming query. The parse
error is shown in the log message. A remote client sent a packet we

View File

@@ -150,18 +150,15 @@ public:
/// Operations within one protocol family are obvious.
/// Comparisons between v4 and v6 will allways return v4
/// being smaller. This follows boost::asio::ip implementation
bool smallerThan(const IOAddress& other) const {
if (this->getFamily() < other.getFamily()) {
return (true);
}
if (this->getFamily() > other.getFamily()) {
return (false);
}
if (this->getFamily() == AF_INET6) {
return (this->asio_address_.to_v6() < other.asio_address_.to_v6());
} else {
return (this->asio_address_.to_v4() < other.asio_address_.to_v4());
bool lessThan(const IOAddress& other) const {
if (this->getFamily() == other.getFamily()) {
if (this->getFamily() == AF_INET6) {
return (this->asio_address_.to_v6() < other.asio_address_.to_v6());
} else {
return (this->asio_address_.to_v4() < other.asio_address_.to_v4());
}
}
return (this->getFamily() < other.getFamily());
}
/// \brief Checks if one address is smaller or equal than the other
@@ -173,7 +170,7 @@ public:
if (equals(other)) {
return (true);
}
return (smallerThan(other));
return (lessThan(other));
}
/// \brief Checks if one address is smaller than the other
@@ -182,7 +179,7 @@ public:
///
/// See \ref smaller_than method for details.
bool operator<(const IOAddress& other) const {
return (smallerThan(other));
return (lessThan(other));
}
/// \brief Checks if one address is smaller or equal than the other

View File

@@ -100,7 +100,7 @@ TEST(IOAddressTest, uint32) {
EXPECT_EQ(addr3.toText(), "192.0.2.5");
}
TEST(IOAddressTest, compare) {
TEST(IOAddressTest, lessThanEqual) {
IOAddress addr1("192.0.2.5");
IOAddress addr2("192.0.2.6");
IOAddress addr3("0.0.0.0");

View File

@@ -456,7 +456,7 @@ ModuleCCSession::ModuleCCSession(
ConstElementPtr answer, env;
session_.group_recvmsg(env, answer, false, seq);
int rcode;
int rcode = -1;
ConstElementPtr err = parseAnswer(rcode, answer);
if (rcode != 0) {
LOG_ERROR(config_logger, CONFIG_MOD_SPEC_REJECT).arg(answer->str());
@@ -535,7 +535,7 @@ ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
ConstElementPtr diff = removeIdentical(new_config, getLocalConfig());
// handle config update
answer = config_handler_(diff);
int rcode;
int rcode = -1;
parseAnswer(rcode, answer);
if (rcode == 0) {
ElementPtr local_config = getLocalConfig();
@@ -652,7 +652,7 @@ ModuleCCSession::fetchRemoteSpec(const std::string& module, bool is_filename) {
unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
ConstElementPtr env, answer;
session_.group_recvmsg(env, answer, false, seq);
int rcode;
int rcode = -1;
ConstElementPtr spec_data = parseAnswer(rcode, answer);
if (rcode == 0 && spec_data) {
// received OK, construct the spec out of it
@@ -689,7 +689,7 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_name,
ConstElementPtr env, answer;
session_.group_recvmsg(env, answer, false, seq);
int rcode;
int rcode = -1;
ConstElementPtr new_config = parseAnswer(rcode, answer);
ElementPtr local_config;
if (rcode == 0 && new_config) {

View File

@@ -1,4 +1,4 @@
SUBDIRS = memory . tests
SUBDIRS = . memory tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
@@ -64,7 +64,6 @@ libb10_datasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.
libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libb10-cc.la
libb10_datasrc_la_LIBADD += memory/libdatasrc_memory.la # convenience library
libb10_datasrc_la_LIBADD += $(SQLITE_LIBS)
BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc

2
src/lib/datasrc/memory/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/memory_messages.cc
/memory_messages.h

View File

@@ -6,7 +6,7 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
CLEANFILES = *.gcno *.gcda
noinst_LTLIBRARIES = libdatasrc_memory.la
@@ -16,5 +16,17 @@ libdatasrc_memory_la_SOURCES += treenode_rrset.h treenode_rrset.cc
libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
libdatasrc_memory_la_SOURCES += segment_object_holder.h
libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
libdatasrc_memory_la_SOURCES += logger.h logger.cc
libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
EXTRA_DIST = rdata_serialization_priv.cc
BUILT_SOURCES = memory_messages.h memory_messages.cc
memory_messages.h memory_messages.cc: Makefile memory_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/memory/memory_messages.mes
EXTRA_DIST += memory_messages.mes
CLEANFILES += memory_messages.h memory_messages.cc

View File

@@ -1 +1,2 @@
/rdata_reader_bench
/rrset_render_bench

View File

@@ -1153,9 +1153,11 @@ public:
/// Another special feature of this version is the ability to record
/// more detailed information regarding the search result.
///
/// This information will be returned via the \c node_path parameter,
/// which is an object of class \c DomainTreeNodeChain.
/// The passed parameter must be empty.
/// This information will be returned via the \c node_path
/// parameter, which is an object of class \c DomainTreeNodeChain.
/// The passed parameter must be empty if the label sequence is
/// absolute. If the label sequence is not absolute, then find()
/// will begin from the top of the node chain.
///
/// On success, the node sequence stored in \c node_path will contain all
/// the ancestor nodes from the found node towards the root.
@@ -1462,12 +1464,23 @@ DomainTree<T>::find(const isc::dns::LabelSequence& target_labels_orig,
bool (*callback)(const DomainTreeNode<T>&, CBARG),
CBARG callback_arg) const
{
if (!node_path.isEmpty()) {
if (node_path.isEmpty() ^ target_labels_orig.isAbsolute()) {
isc_throw(isc::BadValue,
"DomainTree::find is given a non empty chain");
"DomainTree::find() is given mismatched node chain"
" and label sequence");
}
DomainTreeNode<T>* node;
if (!node_path.isEmpty()) {
// Get the top node in the node chain
node = const_cast<DomainTreeNode<T>*>(node_path.top());
// Start searching from its down pointer
node = node->getDown();
} else {
node = root_.get();
}
DomainTreeNode<T>* node = root_.get();
Result ret = NOTFOUND;
dns::LabelSequence target_labels(target_labels_orig);

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2012 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 <datasrc/memory/logger.h>
namespace isc {
namespace datasrc {
namespace memory {
isc::log::Logger logger("datasrc_memory");
}
}
}

View File

@@ -0,0 +1,52 @@
// Copyright (C) 2012 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 DATASRC_MEMORY_LOGGER_H
#define DATASRC_MEMORY_LOGGER_H
#include <log/macros.h>
#include <datasrc/memory/memory_messages.h>
/// \file datasrc/memory/logger.h
/// \brief Data Source memory library global logger
///
/// This holds the logger for the data source memory library. It is a
/// private header and should not be included in any publicly used
/// header, only in local cc files.
namespace isc {
namespace datasrc {
namespace memory {
/// \brief The logger for this library
extern isc::log::Logger logger;
/// \brief Trace basic operations
const int DBG_TRACE_BASIC = DBGLVL_TRACE_BASIC;
/// \brief Trace data changes and lookups as well
const int DBG_TRACE_DATA = DBGLVL_TRACE_BASIC_DATA;
/// \brief Detailed even about how the lookups happen
const int DBG_TRACE_DETAILED = DBGLVL_TRACE_DETAIL;
} // namespace memory
} // namespace datasrc
} // namespace isc
#endif // DATASRC_MEMORY_LOGGER_H
// Local Variables:
// mode: c++
// End:

View File

@@ -0,0 +1,904 @@
// Copyright (C) 2012 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 <exceptions/exceptions.h>
#include <datasrc/memory/memory_client.h>
#include <datasrc/memory/logger.h>
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/rdata_serialization.h>
#include <datasrc/memory/rdataset.h>
#include <datasrc/memory/domaintree.h>
#include <datasrc/memory/segment_object_holder.h>
#include <datasrc/memory/treenode_rrset.h>
#include <util/memory_segment_local.h>
#include <datasrc/data_source.h>
#include <datasrc/factory.h>
#include <datasrc/result.h>
#include <dns/name.h>
#include <dns/nsec3hash.h>
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
#include <dns/rrsetlist.h>
#include <dns/masterload.h>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <algorithm>
#include <map>
#include <utility>
#include <cctype>
#include <cassert>
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc::memory;
using boost::scoped_ptr;
namespace isc {
namespace datasrc {
namespace memory {
using detail::SegmentObjectHolder;
namespace {
// Some type aliases
typedef DomainTree<std::string> FileNameTree;
typedef DomainTreeNode<std::string> FileNameNode;
// A functor type used for loading.
typedef boost::function<void(ConstRRsetPtr)> LoadCallback;
} // end of anonymous namespace
/// Implementation details for \c InMemoryClient hidden from the public
/// interface.
///
/// For now, \c InMemoryClient only contains a \c ZoneTable object, which
/// consists of (pointers to) \c InMemoryZoneFinder objects, we may add more
/// member variables later for new features.
class InMemoryClient::InMemoryClientImpl {
private:
// The deleter for the filenames stored in the tree.
struct FileNameDeleter {
FileNameDeleter() {}
void operator()(std::string* filename) const {
delete filename;
}
};
public:
InMemoryClientImpl(util::MemorySegment& mem_sgmt, RRClass rrclass) :
mem_sgmt_(mem_sgmt),
rrclass_(rrclass),
zone_count_(0),
zone_table_(ZoneTable::create(mem_sgmt_, rrclass)),
file_name_tree_(FileNameTree::create(mem_sgmt_, false))
{}
~InMemoryClientImpl() {
FileNameDeleter deleter;
FileNameTree::destroy(mem_sgmt_, file_name_tree_, deleter);
ZoneTable::destroy(mem_sgmt_, zone_table_, rrclass_);
// see above for the assert().
assert(mem_sgmt_.allMemoryDeallocated());
}
util::MemorySegment& mem_sgmt_;
const RRClass rrclass_;
unsigned int zone_count_;
ZoneTable* zone_table_;
FileNameTree* file_name_tree_;
ConstRRsetPtr last_rrset_;
// Common process for zone load.
// rrset_installer is a functor that takes another functor as an argument,
// and expected to call the latter for each RRset of the zone. How the
// sequence of the RRsets is generated depends on the internal
// details of the loader: either from a textual master file or from
// another data source.
// filename is the file name of the master file or empty if the zone is
// loaded from another data source.
result::Result load(const Name& zone_name, const string& filename,
boost::function<void(LoadCallback)> rrset_installer);
// Add the necessary magic for any wildcard contained in 'name'
// (including itself) to be found in the zone.
//
// In order for wildcard matching to work correctly in the zone finder,
// we must ensure that a node for the wildcarding level exists in the
// backend RBTree.
// E.g. if the wildcard name is "*.sub.example." then we must ensure
// that "sub.example." exists and is marked as a wildcard level.
// Note: the "wildcarding level" is for the parent name of the wildcard
// name (such as "sub.example.").
//
// We also perform the same trick for empty wild card names possibly
// contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
void addWildcards(const Name& zone_name, ZoneData& zone_data,
const Name& name)
{
Name wname(name);
const unsigned int labels(wname.getLabelCount());
const unsigned int origin_labels(zone_name.getLabelCount());
for (unsigned int l = labels;
l > origin_labels;
--l, wname = wname.split(1)) {
if (wname.isWildcard()) {
LOG_DEBUG(logger, DBG_TRACE_DATA,
DATASRC_MEMORY_MEM_ADD_WILDCARD).arg(name);
// Ensure a separate level exists for the "wildcarding" name,
// and mark the node as "wild".
ZoneNode* node;
zone_data.insertName(mem_sgmt_, wname.split(1), &node);
node->setFlag(ZoneData::WILDCARD_NODE);
// Ensure a separate level exists for the wildcard name.
// Note: for 'name' itself we do this later anyway, but the
// overhead should be marginal because wildcard names should
// be rare.
zone_data.insertName(mem_sgmt_, wname, &node);
}
}
}
/*
* Does some checks in context of the data that are already in the zone.
* Currently checks for forbidden combinations of RRsets in the same
* domain (CNAME+anything, DNAME+NS).
*
* If such condition is found, it throws AddError.
*/
void contextCheck(const Name& zone_name, const AbstractRRset& rrset,
const RdataSet* set) const {
// Ensure CNAME and other type of RR don't coexist for the same
// owner name except with NSEC, which is the only RR that can coexist
// with CNAME (and also RRSIG, which is handled separately)
if (rrset.getType() == RRType::CNAME()) {
for (const RdataSet* sp = set; sp != NULL; sp = sp->getNext()) {
if (sp->type != RRType::NSEC()) {
LOG_ERROR(logger, DATASRC_MEMORY_MEM_CNAME_TO_NONEMPTY).
arg(rrset.getName());
isc_throw(AddError, "CNAME can't be added with "
<< sp->type << " RRType for "
<< rrset.getName());
}
}
} else if ((rrset.getType() != RRType::NSEC()) &&
(RdataSet::find(set, RRType::CNAME()) != NULL)) {
LOG_ERROR(logger,
DATASRC_MEMORY_MEM_CNAME_COEXIST).arg(rrset.getName());
isc_throw(AddError, "CNAME and " << rrset.getType() <<
" can't coexist for " << rrset.getName());
}
/*
* Similar with DNAME, but it must not coexist only with NS and only in
* non-apex domains.
* RFC 2672 section 3 mentions that it is implied from it and RFC 2181
*/
if (rrset.getName() != zone_name &&
// Adding DNAME, NS already there
((rrset.getType() == RRType::DNAME() &&
RdataSet::find(set, RRType::NS()) != NULL) ||
// Adding NS, DNAME already there
(rrset.getType() == RRType::NS() &&
RdataSet::find(set, RRType::DNAME()) != NULL)))
{
LOG_ERROR(logger, DATASRC_MEMORY_MEM_DNAME_NS).arg(rrset.getName());
isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
"domain " << rrset.getName());
}
}
// Validate rrset before adding it to the zone. If something is wrong
// it throws an exception. It doesn't modify the zone, and provides
// the strong exception guarantee.
void addValidation(const Name& zone_name, const ConstRRsetPtr rrset) {
if (!rrset) {
isc_throw(NullRRset, "The rrset provided is NULL");
}
if (rrset->getRdataCount() == 0) {
isc_throw(AddError, "The rrset provided is empty: " <<
rrset->getName() << "/" << rrset->getType());
}
// Check for singleton RRs. It should probably handled at a different
// layer in future.
if ((rrset->getType() == RRType::CNAME() ||
rrset->getType() == RRType::DNAME()) &&
rrset->getRdataCount() > 1)
{
// XXX: this is not only for CNAME or DNAME. We should generalize
// this code for all other "singleton RR types" (such as SOA) in a
// separate task.
LOG_ERROR(logger,
DATASRC_MEMORY_MEM_SINGLETON).arg(rrset->getName()).
arg(rrset->getType());
isc_throw(AddError, "multiple RRs of singleton type for "
<< rrset->getName());
}
// NSEC3/NSEC3PARAM is not a "singleton" per protocol, but this
// implementation requests it be so at the moment.
if ((rrset->getType() == RRType::NSEC3() ||
rrset->getType() == RRType::NSEC3PARAM()) &&
rrset->getRdataCount() > 1) {
isc_throw(AddError, "Multiple NSEC3/NSEC3PARAM RDATA is given for "
<< rrset->getName() << " which isn't supported");
}
NameComparisonResult compare(zone_name.compare(rrset->getName()));
if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
compare.getRelation() != NameComparisonResult::EQUAL)
{
LOG_ERROR(logger,
DATASRC_MEMORY_MEM_OUT_OF_ZONE).arg(rrset->getName()).
arg(zone_name);
isc_throw(OutOfZone, "The name " << rrset->getName() <<
" is not contained in zone " << zone_name);
}
// Some RR types do not really work well with a wildcard.
// Even though the protocol specifically doesn't completely ban such
// usage, we refuse to load a zone containing such RR in order to
// keep the lookup logic simpler and more predictable.
// See RFC4592 and (for DNAME) draft-ietf-dnsext-rfc2672bis-dname
// for more technical background. Note also that BIND 9 refuses
// NS at a wildcard, so in that sense we simply provide compatible
// behavior.
if (rrset->getName().isWildcard()) {
if (rrset->getType() == RRType::NS()) {
LOG_ERROR(logger, DATASRC_MEMORY_MEM_WILDCARD_NS).
arg(rrset->getName());
isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
rrset->getName());
}
if (rrset->getType() == RRType::DNAME()) {
LOG_ERROR(logger, DATASRC_MEMORY_MEM_WILDCARD_DNAME).
arg(rrset->getName());
isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
rrset->getName());
}
}
// Owner names of NSEC3 have special format as defined in RFC5155,
// and cannot be a wildcard name or must be one label longer than
// the zone origin. While the RFC doesn't prohibit other forms of
// names, no sane zone would have such names for NSEC3.
// BIND 9 also refuses NSEC3 at wildcard.
if (rrset->getType() == RRType::NSEC3() &&
(rrset->getName().isWildcard() ||
rrset->getName().getLabelCount() !=
zone_name.getLabelCount() + 1)) {
LOG_ERROR(logger, DATASRC_MEMORY_BAD_NSEC3_NAME).
arg(rrset->getName());
isc_throw(AddError, "Invalid NSEC3 owner name: " <<
rrset->getName());
}
}
void addNSEC3(const ConstRRsetPtr rrset,
const ConstRRsetPtr rrsig,
ZoneData& zone_data) {
// We know rrset has exactly one RDATA
const generic::NSEC3& nsec3_rdata =
dynamic_cast<const generic::NSEC3&>(
rrset->getRdataIterator()->getCurrent());
NSEC3Data* nsec3_data = zone_data.getNSEC3Data();
if (nsec3_data == NULL) {
nsec3_data = NSEC3Data::create(mem_sgmt_, nsec3_rdata);
zone_data.setNSEC3Data(nsec3_data);
} else {
size_t salt_len = nsec3_data->getSaltLen();
const uint8_t* salt_data = nsec3_data->getSaltData();
const vector<uint8_t>& salt_data_2 = nsec3_rdata.getSalt();
if ((nsec3_rdata.getHashalg() != nsec3_data->hashalg) ||
(nsec3_rdata.getIterations() != nsec3_data->iterations) ||
(salt_data_2.size() != salt_len) ||
(std::memcmp(&salt_data_2[0], salt_data, salt_len) != 0)) {
isc_throw(AddError,
"NSEC3 with inconsistent parameters: " <<
rrset->toText());
}
}
string fst_label = rrset->getName().split(0, 1).toText(true);
transform(fst_label.begin(), fst_label.end(), fst_label.begin(),
::toupper);
ZoneNode* node;
nsec3_data->insertName(mem_sgmt_, Name(fst_label), &node);
RdataEncoder encoder;
// We assume that rrsig has already been checked to match rrset
// by the caller.
RdataSet* set = RdataSet::create(mem_sgmt_, encoder, rrset, rrsig);
RdataSet* old_set = node->setData(set);
if (old_set != NULL) {
RdataSet::destroy(mem_sgmt_, rrclass_, old_set);
}
}
void addRdataSet(const Name& zone_name, ZoneData& zone_data,
const ConstRRsetPtr rrset, const ConstRRsetPtr rrsig) {
// Only one of these can be passed at a time.
assert(!(rrset && rrsig));
// If rrsig is passed, validate it against the last-saved rrset.
if (rrsig) {
// The covered RRset should have been saved by now.
if (!last_rrset_) {
isc_throw(AddError,
"RRSIG is being added, "
"but doesn't follow its covered RR: "
<< rrsig->getName());
}
if (rrsig->getName() != last_rrset_->getName()) {
isc_throw(AddError,
"RRSIG is being added, "
"but doesn't match the last RR's name: "
<< last_rrset_->getName() << " vs. "
<< rrsig->getName());
}
// Consistency of other types in rrsig are checked in addRRsig().
RdataIteratorPtr rit = rrsig->getRdataIterator();
const RRType covered = dynamic_cast<const generic::RRSIG&>(
rit->getCurrent()).typeCovered();
if (covered != last_rrset_->getType()) {
isc_throw(AddError,
"RRSIG is being added, "
"but doesn't match the last RR's type: "
<< last_rrset_->getType() << " vs. "
<< covered);
}
}
if (!last_rrset_) {
last_rrset_ = rrset;
return;
}
if (last_rrset_->getType() == RRType::NSEC3()) {
addNSEC3(last_rrset_, rrsig, zone_data);
} else {
ZoneNode* node;
zone_data.insertName(mem_sgmt_, last_rrset_->getName(), &node);
RdataSet* set = node->getData();
// Checks related to the surrounding data.
// Note: when the check fails and the exception is thrown,
// it may break strong exception guarantee. At the moment
// we prefer code simplicity and don't bother to introduce
// complicated recovery code.
contextCheck(zone_name, *last_rrset_, set);
if (RdataSet::find(set, last_rrset_->getType()) != NULL) {
isc_throw(AddError,
"RRset of the type already exists: "
<< last_rrset_->getName() << " (type: "
<< last_rrset_->getType() << ")");
}
RdataEncoder encoder;
RdataSet *new_set = RdataSet::create(mem_sgmt_, encoder,
last_rrset_, rrsig);
new_set->next = set;
node->setData(new_set);
// Ok, we just put it in
// If this RRset creates a zone cut at this node, mark the
// node indicating the need for callback in find().
if (last_rrset_->getType() == RRType::NS() &&
last_rrset_->getName() != zone_name) {
node->setFlag(ZoneNode::FLAG_CALLBACK);
// If it is DNAME, we have a callback as well here
} else if (last_rrset_->getType() == RRType::DNAME()) {
node->setFlag(ZoneNode::FLAG_CALLBACK);
}
// If we've added NSEC3PARAM at zone origin, set up NSEC3
// specific data or check consistency with already set up
// parameters.
if (last_rrset_->getType() == RRType::NSEC3PARAM() &&
last_rrset_->getName() == zone_name) {
// We know rrset has exactly one RDATA
const generic::NSEC3PARAM& param =
dynamic_cast<const generic::NSEC3PARAM&>
(last_rrset_->getRdataIterator()->getCurrent());
NSEC3Data* nsec3_data = zone_data.getNSEC3Data();
if (nsec3_data == NULL) {
nsec3_data = NSEC3Data::create(mem_sgmt_, param);
zone_data.setNSEC3Data(nsec3_data);
} else {
size_t salt_len = nsec3_data->getSaltLen();
const uint8_t* salt_data = nsec3_data->getSaltData();
const vector<uint8_t>& salt_data_2 = param.getSalt();
if ((param.getHashalg() != nsec3_data->hashalg) ||
(param.getIterations() != nsec3_data->iterations) ||
(salt_data_2.size() != salt_len) ||
(std::memcmp(&salt_data_2[0],
salt_data, salt_len) != 0)) {
isc_throw(AddError,
"NSEC3PARAM with inconsistent parameters: "
<< last_rrset_->toText());
}
}
} else if (last_rrset_->getType() == RRType::NSEC()) {
// If it is NSEC signed zone, so we put a flag there
// (flag is enough)
zone_data.setSigned(true);
}
}
last_rrset_ = rrset;
}
result::Result addRRsig(const ConstRRsetPtr sig_rrset,
const Name& zone_name, ZoneData& zone_data)
{
// Check consistency of the type covered.
// We know the RRset isn't empty, so the following check is safe.
RdataIteratorPtr rit = sig_rrset->getRdataIterator();
const RRType covered = dynamic_cast<const generic::RRSIG&>(
rit->getCurrent()).typeCovered();
for (rit->next(); !rit->isLast(); rit->next()) {
if (dynamic_cast<const generic::RRSIG&>(
rit->getCurrent()).typeCovered() != covered) {
isc_throw(AddError, "RRSIG contains mixed covered types: "
<< sig_rrset->toText());
}
}
addRdataSet(zone_name, zone_data, ConstRRsetPtr(), sig_rrset);
return (result::SUCCESS);
}
/*
* Implementation of longer methods. We put them here, because the
* access is without the impl_-> and it will get inlined anyway.
*/
// Implementation of InMemoryClient::add()
result::Result add(const ConstRRsetPtr& rrset,
const Name& zone_name, ZoneData& zone_data)
{
// Sanitize input. This will cause an exception to be thrown
// if the input RRset is empty.
addValidation(zone_name, rrset);
// OK, can add the RRset.
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).
arg(rrset->getName()).arg(rrset->getType()).arg(zone_name);
if (rrset->getType() == RRType::NSEC3()) {
addRdataSet(zone_name, zone_data, rrset, ConstRRsetPtr());
return (result::SUCCESS);
}
// RRSIGs are special in various points, so we handle it in a
// separate dedicated method.
if (rrset->getType() == RRType::RRSIG()) {
return (addRRsig(rrset, zone_name, zone_data));
}
// Add wildcards possibly contained in the owner name to the domain
// tree.
// Note: this can throw an exception, breaking strong exception
// guarantee. (see also the note for contextCheck() below).
addWildcards(zone_name, zone_data, rrset->getName());
addRdataSet(zone_name, zone_data, rrset, ConstRRsetPtr());
return (result::SUCCESS);
}
/*
* Wrapper around above.
*/
void addFromLoad(const ConstRRsetPtr& set,
const Name& zone_name, ZoneData* zone_data)
{
switch (add(set, zone_name, *zone_data)) {
case result::SUCCESS:
return;
default:
assert(0);
}
}
};
result::Result
InMemoryClient::InMemoryClientImpl::load(
const Name& zone_name,
const string& filename,
boost::function<void(LoadCallback)> rrset_installer)
{
SegmentObjectHolder<ZoneData, RRClass> holder(
mem_sgmt_, ZoneData::create(mem_sgmt_, zone_name),
rrclass_);
assert(!last_rrset_);
try {
rrset_installer(boost::bind(&InMemoryClientImpl::addFromLoad, this,
_1, zone_name, holder.get()));
// Add any last RRset that was left
addRdataSet(zone_name, *holder.get(),
ConstRRsetPtr(), ConstRRsetPtr());
} catch (...) {
last_rrset_ = ConstRRsetPtr();
throw;
}
assert(!last_rrset_);
const ZoneNode* origin_node = holder.get()->getOriginNode();
const RdataSet* set = origin_node->getData();
// If the zone is NSEC3-signed, check if it has NSEC3PARAM
if (holder.get()->isNSEC3Signed()) {
// Note: origin_data_ is set on creation of ZoneData, and the load
// process only adds new nodes (and their data), so this assertion
// should hold.
if (RdataSet::find(set, RRType::NSEC3PARAM()) == NULL) {
LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM).
arg(zone_name).arg(rrclass_);
}
}
// When an empty zone file is loaded, the origin doesn't even have
// an SOA RR. This condition should be avoided, and hence load()
// should throw when an empty zone is loaded.
if (RdataSet::find(set, RRType::SOA()) == NULL) {
isc_throw(EmptyZone,
"Won't create an empty zone for: " << zone_name);
}
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
arg(zone_name).arg(rrclass_.toText());
// Set the filename in file_name_tree_ now, so that getFileName()
// can use it (during zone reloading).
FileNameNode* node(NULL);
switch (file_name_tree_->insert(mem_sgmt_, zone_name, &node)) {
case FileNameTree::SUCCESS:
case FileNameTree::ALREADYEXISTS:
// These are OK
break;
default:
// Can Not Happen
assert(false);
}
// node must point to a valid node now
assert(node != NULL);
std::string* tstr = node->setData(new std::string(filename));
delete tstr;
ZoneTable::AddResult result(zone_table_->addZone(mem_sgmt_, rrclass_,
zone_name));
if (result.code == result::SUCCESS) {
// Only increment the zone count if the zone doesn't already
// exist.
++zone_count_;
}
ZoneTable::FindResult fr(zone_table_->setZoneData(zone_name,
holder.release()));
assert(fr.code == result::SUCCESS);
if (fr.zone_data != NULL) {
ZoneData::destroy(mem_sgmt_, fr.zone_data, rrclass_);
}
return (result.code);
}
namespace {
// A wrapper for dns::masterLoad used by load() below. Essentially it
// converts the two callback types. Note the mostly redundant wrapper of
// boost::bind. It converts function<void(ConstRRsetPtr)> to
// function<void(RRsetPtr)> (masterLoad() expects the latter). SunStudio
// doesn't seem to do this conversion if we just pass 'callback'.
void
masterLoadWrapper(const char* const filename, const Name& origin,
const RRClass& zone_class, LoadCallback callback)
{
masterLoad(filename, origin, zone_class, boost::bind(callback, _1));
}
// The installer called from Impl::load() for the iterator version of load().
void
generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) {
ConstRRsetPtr rrset;
vector<ConstRRsetPtr> rrsigs; // placeholder for RRSIGs until "commitable".
// The current internal implementation assumes an RRSIG is always added
// after the RRset they cover. So we store any RRSIGs in 'rrsigs' until
// it's safe to add them; based on our assumption if the owner name
// changes, all covered RRsets of the previous name should have been
// installed and any pending RRSIGs can be added at that point. RRSIGs
// of the last name from the iterator must be added separately.
while ((rrset = iterator->getNextRRset()) != NULL) {
if (!rrsigs.empty() && rrset->getName() != rrsigs[0]->getName()) {
BOOST_FOREACH(ConstRRsetPtr sig_rrset, rrsigs) {
callback(sig_rrset);
}
rrsigs.clear();
}
if (rrset->getType() == RRType::RRSIG()) {
rrsigs.push_back(rrset);
} else {
callback(rrset);
}
}
BOOST_FOREACH(ConstRRsetPtr sig_rrset, rrsigs) {
callback(sig_rrset);
}
}
}
InMemoryClient::InMemoryClient(util::MemorySegment& mem_sgmt,
RRClass rrclass) :
impl_(new InMemoryClientImpl(mem_sgmt, rrclass))
{}
InMemoryClient::~InMemoryClient() {
delete impl_;
}
RRClass
InMemoryClient::getClass() const {
return (impl_->rrclass_);
}
unsigned int
InMemoryClient::getZoneCount() const {
return (impl_->zone_count_);
}
isc::datasrc::memory::ZoneTable::FindResult
InMemoryClient::findZone2(const isc::dns::Name& zone_name) const {
LOG_DEBUG(logger, DBG_TRACE_DATA,
DATASRC_MEMORY_MEM_FIND_ZONE).arg(zone_name);
ZoneTable::FindResult result(impl_->zone_table_->findZone(zone_name));
return (result);
}
isc::datasrc::DataSourceClient::FindResult
InMemoryClient::findZone(const isc::dns::Name&) const {
// This variant of findZone() is not implemented and should be
// removed eventually. It currently throws an exception. It is
// required right now to derive from DataSourceClient.
isc_throw(isc::NotImplemented,
"This variant of findZone() is not implemented.");
}
result::Result
InMemoryClient::load(const isc::dns::Name& zone_name,
const std::string& filename) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD).arg(zone_name).
arg(filename);
return (impl_->load(zone_name, filename,
boost::bind(masterLoadWrapper, filename.c_str(),
zone_name, getClass(), _1)));
}
result::Result
InMemoryClient::load(const isc::dns::Name& zone_name,
ZoneIterator& iterator) {
return (impl_->load(zone_name, string(),
boost::bind(generateRRsetFromIterator,
&iterator, _1)));
}
const std::string
InMemoryClient::getFileName(const isc::dns::Name& zone_name) const {
FileNameNode* node(NULL);
FileNameTree::Result result = impl_->file_name_tree_->find(zone_name,
&node);
if (result == FileNameTree::EXACTMATCH) {
return (*node->getData());
} else {
return (std::string());
}
}
result::Result
InMemoryClient::add(const isc::dns::Name& zone_name,
const ConstRRsetPtr& rrset) {
assert(!impl_->last_rrset_);
ZoneTable::FindResult result(impl_->zone_table_->findZone(zone_name));
if (result.code != result::SUCCESS) {
isc_throw(DataSourceError, "No such zone: " + zone_name.toText());
}
result::Result ret(impl_->add(rrset, zone_name, *result.zone_data));
// Add any associated RRSIG too. This has to be done here, as both
// the RRset and its RRSIG have to be passed when constructing an
// RdataSet.
if ((ret == result::SUCCESS) && rrset->getRRsig()) {
impl_->add(rrset->getRRsig(), zone_name, *result.zone_data);
}
// Add any last RRset that was left
impl_->addRdataSet(zone_name, *result.zone_data,
ConstRRsetPtr(), ConstRRsetPtr());
assert(!impl_->last_rrset_);
return (ret);
}
namespace {
class MemoryIterator : public ZoneIterator {
private:
ZoneChain chain_;
const RdataSet* set_node_;
const RRClass rrclass_;
const ZoneTree& tree_;
const ZoneNode* node_;
// Only used when separate_rrs_ is true
ConstRRsetPtr rrset_;
RdataIteratorPtr rdata_iterator_;
bool separate_rrs_;
bool ready_;
public:
MemoryIterator(const RRClass rrclass,
const ZoneTree& tree, const Name& origin,
bool separate_rrs) :
rrclass_(rrclass),
tree_(tree),
separate_rrs_(separate_rrs),
ready_(true)
{
// Find the first node (origin) and preserve the node chain for future
// searches
ZoneTree::Result result(tree_.find(origin, &node_, chain_));
// It can't happen that the origin is not in there
if (result != ZoneTree::EXACTMATCH) {
isc_throw(Unexpected,
"In-memory zone corrupted, missing origin node");
}
// Initialize the iterator if there's somewhere to point to
if (node_ != NULL && node_->getData() != NULL) {
set_node_ = node_->getData();
if (separate_rrs_ && set_node_ != NULL) {
rrset_.reset(new TreeNodeRRset(rrclass_,
node_, set_node_, true));
rdata_iterator_ = rrset_->getRdataIterator();
}
}
}
virtual ConstRRsetPtr getNextRRset() {
if (!ready_) {
isc_throw(Unexpected, "Iterating past the zone end");
}
/*
* This cycle finds the first nonempty node with yet unused
* RdataSset. If it is NULL, we run out of nodes. If it is
* empty, it doesn't contain any RdataSets. If we are at the
* end, just get to next one.
*/
while (node_ != NULL &&
(node_->getData() == NULL || set_node_ == NULL)) {
node_ = tree_.nextNode(chain_);
// If there's a node, initialize the iterator and check next time
// if the map is empty or not
if (node_ != NULL && node_->getData() != NULL) {
set_node_ = node_->getData();
// New RRset, so get a new rdata iterator
if (separate_rrs_ && set_node_ != NULL) {
rrset_.reset(new TreeNodeRRset(rrclass_,
node_, set_node_, true));
rdata_iterator_ = rrset_->getRdataIterator();
}
}
}
if (node_ == NULL) {
// That's all, folks
ready_ = false;
return (ConstRRsetPtr());
}
if (separate_rrs_) {
// For separate rrs, reconstruct a new RRset with just the
// 'current' rdata
RRsetPtr result(new RRset(rrset_->getName(),
rrset_->getClass(),
rrset_->getType(),
rrset_->getTTL()));
result->addRdata(rdata_iterator_->getCurrent());
rdata_iterator_->next();
if (rdata_iterator_->isLast()) {
// all used up, next.
set_node_ = set_node_->getNext();
// New RRset, so get a new rdata iterator, but only if this
// was not the final RRset in the chain
if (set_node_ != NULL) {
rrset_.reset(new TreeNodeRRset(rrclass_,
node_, set_node_, true));
rdata_iterator_ = rrset_->getRdataIterator();
}
}
return (result);
} else {
ConstRRsetPtr result(new TreeNodeRRset(rrclass_,
node_, set_node_, true));
// This one is used, move it to the next time for next call
set_node_ = set_node_->getNext();
return (result);
}
}
virtual ConstRRsetPtr getSOA() const {
isc_throw(NotImplemented, "Not implemented");
}
};
} // End of anonymous namespace
ZoneIteratorPtr
InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
ZoneTable::FindResult result(impl_->zone_table_->findZone(name));
if (result.code != result::SUCCESS) {
isc_throw(DataSourceError, "No such zone: " + name.toText());
}
return (ZoneIteratorPtr(new MemoryIterator(
getClass(),
result.zone_data->getZoneTree(), name,
separate_rrs)));
}
ZoneUpdaterPtr
InMemoryClient::getUpdater(const isc::dns::Name&, bool, bool) const {
isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
}
pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
InMemoryClient::getJournalReader(const isc::dns::Name&, uint32_t,
uint32_t) const
{
isc_throw(isc::NotImplemented, "Journaling isn't supported for "
"in memory data source");
}
} // end of namespace memory
} // end of namespace datasrc
} // end of namespace isc

View File

@@ -0,0 +1,257 @@
// Copyright (C) 2012 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 DATASRC_MEMORY_CLIENT_H
#define DATASRC_MEMORY_CLIENT_H 1
#include <util/memory_segment.h>
#include <datasrc/iterator.h>
#include <datasrc/client.h>
#include <datasrc/memory/zone_table.h>
// for isc::datasrc::ZoneTable::FindResult returned by findZone(). This
// variant of findZone() is not implemented and should be removed
// eventually.
#include <datasrc/zonetable.h>
#include <string>
namespace isc {
namespace dns {
class Name;
class RRsetList;
};
namespace datasrc {
namespace memory {
/// \brief A data source client that holds all necessary data in memory.
///
/// The \c InMemoryClient class provides an access to a conceptual data
/// source that maintains all necessary data in a memory image, thereby
/// allowing much faster lookups. The in memory data is a copy of some
/// real physical source - in the current implementation a list of zones
/// are populated as a result of \c load() calls; zone data is given in
/// a standard master file, or as an iterator of some other datasource
/// including database backed ones.
///
/// The InMemoryClient enforces through its interface that all data
/// loaded to the data source is of the same RR class. For example, the
/// \c load() method assumes that the zone being loaded belongs to the
/// same RR class as the memory::Client instance.
class InMemoryClient : public DataSourceClient {
public:
///
/// \name Constructors and Destructor.
///
//@{
/// Default constructor.
///
/// This constructor internally involves resource allocation, and if
/// it fails, a corresponding standard exception will be thrown.
/// It never throws an exception otherwise.
InMemoryClient(util::MemorySegment& mem_sgmt,
isc::dns::RRClass rrclass);
/// The destructor.
~InMemoryClient();
//@}
/// \brief Returns the class of the data source client.
virtual isc::dns::RRClass getClass() const;
/// Return the number of zones stored in the client.
///
/// This method never throws an exception.
///
/// \return The number of zones stored in the client.
virtual unsigned int getZoneCount() const;
/// \brief Load zone from masterfile.
///
/// This loads data from masterfile specified by filename. It replaces
/// current content. The masterfile parsing ability is kind of limited,
/// see isc::dns::masterLoad.
///
/// This throws isc::dns::MasterLoadError if there is problem with loading
/// (missing file, malformed, it contains different zone, etc - see
/// isc::dns::masterLoad for details).
///
/// In case of internal problems, OutOfZone, NullRRset or AssertError could
/// be thrown, but they should not be expected. Exceptions caused by
/// allocation may be thrown as well.
///
/// If anything is thrown, the previous content is preserved (so it can
/// be used to update the data, but if user makes a typo, the old one
/// is kept).
///
/// \param filename The master file to load.
///
/// \todo We may need to split it to some kind of build and commit/abort.
/// This will probably be needed when a better implementation of
/// configuration reloading is written.
result::Result load(const isc::dns::Name& zone_name,
const std::string& filename);
/// \brief Load zone from another data source.
///
/// This is similar to the other version, but zone's RRsets are provided
/// by an iterator of another data source. On successful load, the
/// internal filename will be cleared.
///
/// This implementation assumes the iterator produces combined RRsets,
/// that is, there should exactly one RRset for the same owner name and
/// RR type. This means the caller is expected to create the iterator
/// with \c separate_rrs being \c false. This implementation also assumes
/// RRsets of different names are not mixed; so if the iterator produces
/// an RRset of a different name than that of the previous RRset, that
/// previous name must never appear in the subsequent sequence of RRsets.
/// Note that the iterator API does not ensure this. If the underlying
/// implementation does not follow it, load() will fail. Note, however,
/// that this whole interface is tentative. in-memory zone loading will
/// have to be revisited fundamentally, and at that point this restriction
/// probably won't matter.
result::Result load(const isc::dns::Name& zone_name,
ZoneIterator& iterator);
/// Return the master file name of the zone
///
/// This method returns the name of the zone's master file to be loaded.
/// The returned string will be an empty unless the data source client has
/// successfully loaded the \c zone_name zone from a file before.
///
/// This method should normally not throw an exception. But the creation
/// of the return string may involve a resource allocation, and if it
/// fails, the corresponding standard exception will be thrown.
///
/// \return The name of the zone file corresponding to the zone, or
/// an empty string if the client hasn't loaded the \c zone_name
/// zone from a file before.
const std::string getFileName(const isc::dns::Name& zone_name) const;
/// \brief Inserts an rrset into the zone.
///
/// It puts another RRset into the zone.
///
/// In the current implementation, this method doesn't allow an existing
/// RRset to be updated or overridden. So the caller must make sure that
/// all RRs of the same type and name must be given in the form of a
/// single RRset. The current implementation will also require that
/// when an RRSIG is added, the RRset to be covered has already been
/// added. These restrictions are probably too strict when this data
/// source accepts various forms of input, so they should be revisited
/// later.
///
/// Except for NullRRset and OutOfZone, this method does not guarantee
/// strong exception safety (it is currently not needed, if it is needed
/// in future, it should be implemented).
///
/// \throw NullRRset \c rrset is a NULL pointer.
/// \throw OutOfZone The owner name of \c rrset is outside of the
/// origin of the zone.
/// \throw AddError Other general errors.
/// \throw Others This method might throw standard allocation exceptions.
///
/// \param rrset The set to add.
/// \return SUCCESS or EXIST (if an rrset for given name and type already
/// exists).
result::Result add(const isc::dns::Name& zone_name,
const isc::dns::ConstRRsetPtr& rrset);
/// \brief RRset is NULL exception.
///
/// This is thrown if the provided RRset parameter is NULL.
struct NullRRset : public InvalidParameter {
NullRRset(const char* file, size_t line, const char* what) :
InvalidParameter(file, line, what)
{ }
};
/// \brief Zone is empty exception.
///
/// This is thrown if we have an empty zone created as a result of
/// load().
struct EmptyZone : public InvalidParameter {
EmptyZone(const char* file, size_t line, const char* what) :
InvalidParameter(file, line, what)
{ }
};
/// \brief General failure exception for \c add().
///
/// This is thrown against general error cases in adding an RRset
/// to the zone.
///
/// Note: this exception would cover cases for \c OutOfZone or
/// \c NullRRset. We'll need to clarify and unify the granularity
/// of exceptions eventually. For now, exceptions are added as
/// developers see the need for it.
struct AddError : public InvalidParameter {
AddError(const char* file, size_t line, const char* what) :
InvalidParameter(file, line, what)
{ }
};
/// Returns a \c ZoneTable result that best matches the given name.
///
/// This derived version of the method never throws an exception.
/// For other details see \c DataSourceClient::findZone().
virtual isc::datasrc::memory::ZoneTable::FindResult
findZone2(const isc::dns::Name& name) const;
// This variant of findZone() is not implemented and should be
// removed eventually. It currently throws an exception. It is
// required right now to derive from DataSourceClient.
virtual isc::datasrc::DataSourceClient::FindResult
findZone(const isc::dns::Name& name) const;
/// \brief Implementation of the getIterator method
virtual isc::datasrc::ZoneIteratorPtr
getIterator(const isc::dns::Name& name, bool separate_rrs = false) const;
/// In-memory data source doesn't write back persistently, so this
/// derived method will result in a NotImplemented exception.
///
/// \note We plan to use a database-based data source as a backend
/// persistent storage for an in-memory data source. When it's
/// implemented we may also want to allow the user of the in-memory client
/// to update via its updater (this may or may not be a good idea and
/// is subject to further discussions).
virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
bool replace, bool journaling = false)
const;
virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
getJournalReader(const isc::dns::Name& zone, uint32_t begin_serial,
uint32_t end_serial) const;
private:
// TODO: Do we still need the PImpl if nobody should manipulate this class
// directly any more (it should be handled through DataSourceClient)?
class InMemoryClientImpl;
InMemoryClientImpl* impl_;
};
} // namespace memory
} // namespace datasrc
} // namespace isc
#endif // DATASRC_MEMORY_CLIENT_H
// Local Variables:
// mode: c++
// End:

View File

@@ -0,0 +1,90 @@
# 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.
$NAMESPACE isc::datasrc::memory
# \brief Messages for the data source memory library
% DATASRC_MEMORY_BAD_NSEC3_NAME NSEC3 record has a bad owner name '%1'
The software refuses to load NSEC3 records into a wildcard domain or
the owner name has two or more labels below the zone origin.
It isn't explicitly forbidden, but no sane zone wouldn have such names
for NSEC3. BIND 9 also refuses NSEC3 at wildcard, so this behavior is
compatible with BIND 9.
% DATASRC_MEMORY_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
Debug information. An RRset is being added to the in-memory data source.
% DATASRC_MEMORY_MEM_ADD_WILDCARD adding wildcards for '%1'
This is a debug message issued during the processing of a wildcard
name. The internal domain name tree is scanned and some nodes are
specially marked to allow the wildcard lookup to succeed.
% DATASRC_MEMORY_MEM_ADD_ZONE adding zone '%1/%2'
Debug information. A zone is being added into the in-memory data source.
% DATASRC_MEMORY_MEM_CNAME_COEXIST can't add data to CNAME in domain '%1'
This is the same problem as in MEM_CNAME_TO_NONEMPTY, but it happened the
other way around -- adding some other data to CNAME.
% DATASRC_MEMORY_MEM_CNAME_TO_NONEMPTY can't add CNAME to domain with other data in '%1'
Someone or something tried to add a CNAME into a domain that already contains
some other data. But the protocol forbids coexistence of CNAME with anything
(RFC 1034, section 3.6.2). This indicates a problem with provided data.
% DATASRC_MEMORY_MEM_DNAME_NS DNAME and NS can't coexist in non-apex domain '%1'
A request was made for DNAME and NS records to be put into the same
domain which is not the apex (the top of the zone). This is forbidden
by RFC 2672 (section 3) and indicates a problem with provided data.
% DATASRC_MEMORY_MEM_DUP_RRSET duplicate RRset '%1/%2'
An RRset is being inserted into in-memory data source for a second time. The
original version must be removed first. Note that loading master files where an
RRset is split into multiple locations is not supported yet.
% DATASRC_MEMORY_MEM_FIND_ZONE looking for zone '%1'
Debug information. A zone object for this zone is being searched for in the
in-memory data source.
% DATASRC_MEMORY_MEM_LOAD loading zone '%1' from file '%2'
Debug information. The content of master file is being loaded into the memory.
% DATASRC_MEMORY_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2
The in-memory data source has loaded a zone signed with NSEC3 RRs,
but it doesn't have a NSEC3PARAM RR at the zone origin. It's likely that
the zone is somehow broken, but this RR is not necessarily needed for
handling lookups with NSEC3 in this data source, so it accepts the given
content of the zone. Nevertheless the administrator should look into
the integrity of the zone data.
% DATASRC_MEMORY_MEM_OUT_OF_ZONE domain '%1' doesn't belong to zone '%2'
It was attempted to add the domain into a zone that shouldn't have it
(eg. the domain is not subdomain of the zone origin). This indicates a
problem with provided data.
% DATASRC_MEMORY_MEM_SINGLETON trying to add multiple RRs for domain '%1' and type '%2'
Some resource types are singletons -- only one is allowed in a domain
(for example CNAME or SOA). This indicates a problem with provided data.
% DATASRC_MEMORY_MEM_WILDCARD_DNAME DNAME record in wildcard domain '%1'
The software refuses to load DNAME records into a wildcard domain. It isn't
explicitly forbidden, but the protocol is ambiguous about how this should
behave and BIND 9 refuses that as well. Please describe your intention using
different tools.
% DATASRC_MEMORY_MEM_WILDCARD_NS NS record in wildcard domain '%1'
The software refuses to load NS records into a wildcard domain. It isn't
explicitly forbidden, but the protocol is ambiguous about how this should
behave and BIND 9 refuses that as well. Please describe your intention using
different tools.

View File

@@ -1,6 +1,9 @@
SUBDIRS = testdata .
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -24,18 +27,23 @@ run_unittests_SOURCES += domaintree_unittest.cc
run_unittests_SOURCES += treenode_rrset_unittest.cc
run_unittests_SOURCES += zone_table_unittest.cc
run_unittests_SOURCES += zone_data_unittest.cc
run_unittests_SOURCES += zone_finder_unittest.cc
run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
run_unittests_SOURCES += memory_segment_test.h
run_unittests_SOURCES += segment_object_holder_unittest.cc
run_unittests_SOURCES += memory_client_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(builddir)/../libdatasrc_memory.la
run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libb10-datasrc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libb10-testutils.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
run_unittests_LDADD += $(GTEST_LDADD)
endif

View File

@@ -454,6 +454,142 @@ TEST_F(DomainTreeTest, callbackLabelSequence) {
performCallbackTest(dtree, mem_sgmt_, ls1, ls2);
}
TEST_F(DomainTreeTest, findInSubTree) {
// For the version that takes a node chain, the chain must be empty.
DomainTreeNodeChain<int> chain;
bool flag;
// Searching for a non-absolute (right-stripped) label sequence when
// chain is empty should throw.
const Name n0("w.y.d.e.f");
LabelSequence ls0(n0);
ls0.stripRight(1);
EXPECT_THROW(dtree_expose_empty_node.find(ls0, &cdtnode, chain,
testCallback, &flag),
isc::BadValue);
// First, find a sub-tree node
chain.clear();
const LabelSequence ls1(n0);
DomainTree<int>::Result result =
dtree_expose_empty_node.find(ls1, &cdtnode, chain,
testCallback, &flag);
EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
EXPECT_EQ(n0, chain.getAbsoluteName());
// Searching for an absolute label sequence when chain is already
// populated should throw.
const Name n2a("o");
const LabelSequence ls2a(n2a);
EXPECT_THROW(dtree_expose_empty_node.find(ls2a, &cdtnode, chain,
testCallback, &flag),
isc::BadValue);
// Now, find "o.w.y.d.e.f." by right-stripping the "w.y.d.e.f."
// suffix to "o" (non-absolute).
const Name n2("o.w.y.d.e.f");
LabelSequence ls2(n2);
ls2.stripRight(6);
EXPECT_EQ("o", ls2.toText());
result = dtree_expose_empty_node.find(ls2, &cdtnode, chain,
testCallback, &flag);
EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
EXPECT_EQ(n2, chain.getAbsoluteName());
// Another test. Start with "d.e.f." node.
chain.clear();
const Name n3("d.e.f");
const LabelSequence ls3(n3);
result =
dtree_expose_empty_node.find(ls3, &cdtnode, chain,
testCallback, &flag);
EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
EXPECT_EQ(n3, chain.getAbsoluteName());
// Now, find "o.w.y.d.e.f." by right-stripping the "w.y.d.e.f."
// suffix to "o.w.y" (non-absolute).
const Name n4("o.w.y.d.e.f");
LabelSequence ls4(n2);
ls4.stripRight(4);
EXPECT_EQ("o.w.y", ls4.toText());
result = dtree_expose_empty_node.find(ls4, &cdtnode, chain,
testCallback, &flag);
EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
EXPECT_EQ(n4, chain.getAbsoluteName());
}
TEST_F(DomainTreeTest, findInSubTreeSameLabelSequence) {
// For the version that takes a node chain, the chain must be empty.
DomainTreeNodeChain<int> chain;
bool flag;
const Name n1("c.g.h");
// First insert a "c.g.h." node.
dtree_expose_empty_node.insert(mem_sgmt_, n1, &dtnode);
/* Now, the tree looks like:
*
* .
* |
* b
* / \
* a d.e.f
* / | \____
* c | \
* | g.h
* | |
* w.y i
* / | \ / \
* x | z c k
* | |
* p j
* / \
* o q
*/
// Make a non-absolute label sequence. We will search for this same
// sequence in two places in the tree.
LabelSequence ls1(n1);
ls1.stripRight(3);
EXPECT_EQ("c", ls1.toText());
// First, find "g.h."
const Name n2("g.h");
const LabelSequence ls2(n2);
DomainTree<int>::Result result =
dtree_expose_empty_node.find(ls2, &cdtnode, chain,
testCallback, &flag);
EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
EXPECT_EQ(n2, chain.getAbsoluteName());
// Now, find "c.g.h." by searching just the non-absolute ls1 label
// sequence.
result = dtree_expose_empty_node.find(ls1, &cdtnode, chain,
testCallback, &flag);
EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
EXPECT_EQ(n1, chain.getAbsoluteName());
// Now, find "." (the root node)
chain.clear();
const Name n3(".");
const LabelSequence ls3(n3);
result =
dtree_expose_empty_node.find(ls3, &cdtnode, chain,
testCallback, &flag);
EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
EXPECT_EQ(n3, chain.getAbsoluteName());
// Now, find "c." by searching just the non-absolute ls1 label
// sequence.
result = dtree_expose_empty_node.find(ls1, &cdtnode, chain,
testCallback, &flag);
EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
EXPECT_EQ(Name("c."), chain.getAbsoluteName());
}
TEST_F(DomainTreeTest, chainLevel) {
TestDomainTreeNodeChain chain;

View File

@@ -0,0 +1,740 @@
// Copyright (C) 2012 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 <exceptions/exceptions.h>
#include <util/memory_segment_local.h>
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/masterload.h>
#include <dns/nsec3hash.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rrsetlist.h>
#include <dns/rrttl.h>
#include <dns/masterload.h>
#include <datasrc/result.h>
#include <datasrc/data_source.h>
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/zone_table.h>
#include <datasrc/memory/memory_client.h>
#include <testutils/dnsmessage_test.h>
#include <gtest/gtest.h>
#include <new> // for bad_alloc
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc;
using namespace isc::datasrc::memory;
using namespace isc::testutils;
namespace {
// Memory segment specified for tests. It normally behaves like a "local"
// memory segment. If "throw count" is set to non 0 via setThrowCount(),
// it continues the normal behavior up to the specified number of calls to
// allocate(), and throws an exception at the next call.
class TestMemorySegment : public isc::util::MemorySegmentLocal {
public:
TestMemorySegment() : throw_count_(0) {}
virtual void* allocate(size_t size) {
if (throw_count_ > 0) {
if (--throw_count_ == 0) {
throw std::bad_alloc();
}
}
return (isc::util::MemorySegmentLocal::allocate(size));
}
void setThrowCount(size_t count) { throw_count_ = count; }
private:
size_t throw_count_;
};
const char* rrset_data[] = {
"example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 68 3600 300 3600000 3600",
"a.example.org. 3600 IN A 192.168.0.1",
"a.example.org. 3600 IN MX 10 mail.example.org.",
NULL
};
class MockIterator : public ZoneIterator {
private:
MockIterator() :
rrset_data_ptr_(rrset_data)
{
}
const char** rrset_data_ptr_;
public:
virtual ConstRRsetPtr getNextRRset() {
if (*rrset_data_ptr_ == NULL) {
return (ConstRRsetPtr());
}
RRsetPtr result(textToRRset(*rrset_data_ptr_,
RRClass::IN(), Name("example.org")));
rrset_data_ptr_++;
return (result);
}
virtual ConstRRsetPtr getSOA() const {
isc_throw(isc::NotImplemented, "Not implemented");
}
static ZoneIteratorPtr makeIterator(void) {
return (ZoneIteratorPtr(new MockIterator()));
}
};
class MemoryClientTest : public ::testing::Test {
protected:
MemoryClientTest() : zclass_(RRClass::IN()),
client_(new InMemoryClient(mem_sgmt_, zclass_))
{}
~MemoryClientTest() {
if (client_ != NULL) {
delete client_;
}
}
void TearDown() {
delete client_;
client_ = NULL;
EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
}
const RRClass zclass_;
TestMemorySegment mem_sgmt_;
InMemoryClient* client_;
};
TEST_F(MemoryClientTest, loadRRsetDoesntMatchOrigin) {
// Attempting to load example.org to example.com zone should result
// in an exception.
EXPECT_THROW(client_->load(Name("example.com"),
TEST_DATA_DIR "/example.org-empty.zone"),
MasterLoadError);
}
TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak1) {
// Attempting to load broken example.org zone should result in an
// exception. This should not leak ZoneData and other such
// allocations.
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-broken1.zone"),
MasterLoadError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak2) {
// Attempting to load broken example.org zone should result in an
// exception. This should not leak ZoneData and other such
// allocations.
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-broken2.zone"),
MasterLoadError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNonExistentZoneFile) {
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR "/somerandomfilename"),
MasterLoadError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadEmptyZoneFileThrows) {
// When an empty zone file is loaded, the origin doesn't even have
// an SOA RR. This condition should be avoided, and hence load()
// should throw when an empty zone is loaded.
EXPECT_EQ(0, client_->getZoneCount());
EXPECT_THROW(client_->load(Name("."),
TEST_DATA_DIR "/empty.zone"),
InMemoryClient::EmptyZone);
EXPECT_EQ(0, client_->getZoneCount());
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, load) {
// This is a simple load check for a "full" and correct zone that
// should not result in any exceptions.
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org.zone");
}
TEST_F(MemoryClientTest, loadFromIterator) {
client_->load(Name("example.org"),
*MockIterator::makeIterator());
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// First we have the SOA
ConstRRsetPtr rrset(iterator->getNextRRset());
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::SOA(), rrset->getType());
// RRType::MX() RRset
rrset = iterator->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::MX(), rrset->getType());
// RRType::A() RRset
rrset = iterator->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::A(), rrset->getType());
// There's nothing else in this iterator
EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
// Iterating past the end should result in an exception
EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
}
TEST_F(MemoryClientTest, loadMemoryAllocationFailures) {
// Just to check that things get cleaned up
for (int i = 1; i < 16; i++) {
mem_sgmt_.setThrowCount(i);
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org.zone"),
std::bad_alloc);
}
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3Signed) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-nsec3-signed.zone");
}
TEST_F(MemoryClientTest, loadNSEC3SignedNoParam) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-nsec3-signed-no-param.zone");
}
TEST_F(MemoryClientTest, loadReloadZone) {
// Because we reload the same zone, also check that the zone count
// doesn't increase.
EXPECT_EQ(0, client_->getZoneCount());
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-empty.zone");
EXPECT_EQ(1, client_->getZoneCount());
// Reload zone with same data
client_->load(Name("example.org"),
client_->getFileName(Name("example.org")));
EXPECT_EQ(1, client_->getZoneCount());
isc::datasrc::memory::ZoneTable::FindResult
result(client_->findZone2(Name("example.org")));
EXPECT_EQ(result::SUCCESS, result.code);
EXPECT_NE(static_cast<ZoneData*>(NULL),
result.zone_data);
/* Check SOA */
const ZoneNode* node = result.zone_data->getOriginNode();
EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
const RdataSet* set = node->getData();
EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
EXPECT_EQ(RRType::SOA(), set->type);
set = set->getNext();
EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
/* Check ns1.example.org */
const ZoneTree& tree = result.zone_data->getZoneTree();
ZoneTree::Result zresult(tree.find(Name("ns1.example.org"), &node));
EXPECT_NE(ZoneTree::EXACTMATCH, zresult);
// Reload zone with different data
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-rrsigs.zone");
EXPECT_EQ(1, client_->getZoneCount());
isc::datasrc::memory::ZoneTable::FindResult
result2(client_->findZone2(Name("example.org")));
EXPECT_EQ(result::SUCCESS, result2.code);
EXPECT_NE(static_cast<ZoneData*>(NULL),
result2.zone_data);
/* Check SOA */
node = result2.zone_data->getOriginNode();
EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
set = node->getData();
EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
EXPECT_EQ(RRType::SOA(), set->type);
set = set->getNext();
EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
/* Check ns1.example.org */
const ZoneTree& tree2 = result2.zone_data->getZoneTree();
ZoneTree::Result zresult2(tree2.find(Name("ns1.example.org"), &node));
EXPECT_EQ(ZoneTree::EXACTMATCH, zresult2);
EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
set = node->getData();
EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
EXPECT_EQ(RRType::AAAA(), set->type);
set = set->getNext();
EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
EXPECT_EQ(RRType::A(), set->type);
set = set->getNext();
EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDuplicateType) {
// This should not result in any exceptions:
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-duplicate-type.zone");
// This should throw:
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-duplicate-type-bad.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleCNAMEThrows) {
// Multiple CNAME RRs should throw.
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-multiple-cname.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleDNAMEThrows) {
// Multiple DNAME RRs should throw.
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-multiple-dname.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleNSEC3Throws) {
// Multiple NSEC3 RRs should throw.
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-multiple-nsec3.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleNSEC3PARAMThrows) {
// Multiple NSEC3PARAM RRs should throw.
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-multiple-nsec3param.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadOutOfZoneThrows) {
// Out of zone names should throw.
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-out-of-zone.zone"),
MasterLoadError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardNSThrows) {
// Wildcard NS names should throw
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-wildcard-ns.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardDNAMEThrows) {
// Wildcard DNAME names should throw
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-wildcard-dname.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardNSEC3Throws) {
// Wildcard NSEC3 names should throw
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-wildcard-nsec3.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3WithFewerLabelsThrows) {
// NSEC3 names with labels != (origin_labels + 1) should throw
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-nsec3-fewer-labels.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3WithMoreLabelsThrows) {
// NSEC3 names with labels != (origin_labels + 1) should throw
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-nsec3-more-labels.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadCNAMEAndNotNSECThrows) {
// CNAME and not NSEC should throw
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-cname-and-not-nsec-1.zone"),
InMemoryClient::AddError);
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-cname-and-not-nsec-2.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSApex1) {
// DNAME + NS (apex) is OK
client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-dname-ns-apex-1.zone");
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSApex2) {
// DNAME + NS (apex) is OK (reverse order)
client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-dname-ns-apex-2.zone");
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex1) {
// DNAME + NS (non-apex) must throw
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-dname-ns-nonapex-1.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
// DNAME + NS (non-apex) must throw (reverse order)
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-dname-ns-nonapex-2.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadRRSIGFollowsNothing) {
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-rrsig-follows-nothing.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadRRSIGNameUnmatched) {
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-rrsig-name-unmatched.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadRRSIGTypeUnmatched) {
EXPECT_THROW(client_->load(Name("example.org"),
TEST_DATA_DIR
"/example.org-rrsig-type-unmatched.zone"),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadRRSIGs) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-rrsigs.zone");
EXPECT_EQ(1, client_->getZoneCount());
}
TEST_F(MemoryClientTest, loadRRSIGsRdataMixedCoveredTypes) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-rrsigs.zone");
RRsetPtr rrset(new RRset(Name("example.org"),
RRClass::IN(), RRType::A(), RRTTL(3600)));
rrset->addRdata(in::A("192.0.2.1"));
rrset->addRdata(in::A("192.0.2.2"));
RRsetPtr rrsig(new RRset(Name("example.org"), zclass_,
RRType::RRSIG(), RRTTL(300)));
rrsig->addRdata(generic::RRSIG("A 5 3 3600 20000101000000 20000201000000 "
"12345 example.org. FAKEFAKEFAKE"));
rrsig->addRdata(generic::RRSIG("NS 5 3 3600 20000101000000 20000201000000 "
"54321 example.org. FAKEFAKEFAKEFAKE"));
rrset->addRRsig(rrsig);
EXPECT_THROW(client_->add(Name("example.org"), rrset),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, getZoneCount) {
EXPECT_EQ(0, client_->getZoneCount());
client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
EXPECT_EQ(1, client_->getZoneCount());
}
TEST_F(MemoryClientTest, getFileNameForNonExistentZone) {
// Zone "example.org." doesn't exist
EXPECT_TRUE(client_->getFileName(Name("example.org.")).empty());
}
TEST_F(MemoryClientTest, getFileName) {
client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
EXPECT_EQ(TEST_DATA_DIR "/example.org-empty.zone",
client_->getFileName(Name("example.org")));
}
TEST_F(MemoryClientTest, getIteratorForNonExistentZone) {
// Zone "." doesn't exist
EXPECT_THROW(client_->getIterator(Name(".")), DataSourceError);
}
TEST_F(MemoryClientTest, getIterator) {
client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// First we have the SOA
ConstRRsetPtr rrset_soa(iterator->getNextRRset());
EXPECT_TRUE(rrset_soa);
EXPECT_EQ(RRType::SOA(), rrset_soa->getType());
// There's nothing else in this iterator
EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
// Iterating past the end should result in an exception
EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
}
TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-multiple.zone");
// separate_rrs = false
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// First we have the SOA
ConstRRsetPtr rrset(iterator->getNextRRset());
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::SOA(), rrset->getType());
// Only one RRType::A() RRset
rrset = iterator->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::A(), rrset->getType());
// There's nothing else in this zone
EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
// separate_rrs = true
ZoneIteratorPtr iterator2(client_->getIterator(Name("example.org"), true));
// First we have the SOA
rrset = iterator2->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::SOA(), rrset->getType());
// First RRType::A() RRset
rrset = iterator2->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::A(), rrset->getType());
// Second RRType::A() RRset
rrset = iterator2->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::A(), rrset->getType());
// There's nothing else in this iterator
EXPECT_EQ(ConstRRsetPtr(), iterator2->getNextRRset());
}
TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// This method is not implemented.
EXPECT_THROW(iterator->getSOA(), isc::NotImplemented);
}
TEST_F(MemoryClientTest, addRRsetToNonExistentZoneThrows) {
// The zone "example.org" doesn't exist, so we can't add an RRset to
// it.
RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
RRTTL(300)));
rrset_a->addRdata(rdata::in::A("192.0.2.1"));
EXPECT_THROW(client_->add(Name("example.org"), rrset_a), DataSourceError);
}
TEST_F(MemoryClientTest, addOutOfZoneThrows) {
// Out of zone names should throw.
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-empty.zone");
RRsetPtr rrset_a(new RRset(Name("a.example.com"),
RRClass::IN(), RRType::A(), RRTTL(300)));
rrset_a->addRdata(rdata::in::A("192.0.2.1"));
EXPECT_THROW(client_->add(Name("example.org"), rrset_a),
OutOfZone);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, addNullRRsetThrows) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-rrsigs.zone");
EXPECT_THROW(client_->add(Name("example.org"), ConstRRsetPtr()),
InMemoryClient::NullRRset);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, addEmptyRRsetThrows) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-rrsigs.zone");
RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
RRTTL(300)));
EXPECT_THROW(client_->add(Name("example.org"), rrset_a),
InMemoryClient::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, add) {
client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
// Add another RRset
RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
RRTTL(300)));
rrset_a->addRdata(rdata::in::A("192.0.2.1"));
client_->add(Name("example.org"), rrset_a);
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// First we have the SOA
ConstRRsetPtr rrset(iterator->getNextRRset());
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::A(), rrset->getType());
rrset = iterator->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::SOA(), rrset->getType());
// There's nothing else in this zone
EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
}
TEST_F(MemoryClientTest, findZoneThrowsNotImplemented) {
// This method is not implemented.
EXPECT_THROW(client_->findZone(Name(".")),
isc::NotImplemented);
}
TEST_F(MemoryClientTest, findZone2) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-rrsigs.zone");
isc::datasrc::memory::ZoneTable::FindResult
result(client_->findZone2(Name("example.com")));
EXPECT_EQ(result::NOTFOUND, result.code);
EXPECT_EQ(static_cast<ZoneData*>(NULL),
result.zone_data);
isc::datasrc::memory::ZoneTable::FindResult
result2(client_->findZone2(Name("example.org")));
EXPECT_EQ(result::SUCCESS, result2.code);
EXPECT_NE(static_cast<ZoneData*>(NULL),
result2.zone_data);
/* Check SOA */
const ZoneNode* node = result2.zone_data->getOriginNode();
EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
const RdataSet* set = node->getData();
EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
EXPECT_EQ(RRType::SOA(), set->type);
set = set->getNext();
EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
/* Check ns1.example.org */
const ZoneTree& tree = result2.zone_data->getZoneTree();
ZoneTree::Result result3(tree.find(Name("ns1.example.org"), &node));
EXPECT_EQ(ZoneTree::EXACTMATCH, result3);
EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
set = node->getData();
EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
EXPECT_EQ(RRType::AAAA(), set->type);
set = set->getNext();
EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
EXPECT_EQ(RRType::A(), set->type);
set = set->getNext();
EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
}
TEST_F(MemoryClientTest, getUpdaterThrowsNotImplemented) {
// This method is not implemented.
EXPECT_THROW(client_->getUpdater(Name("."), false, false),
isc::NotImplemented);
}
TEST_F(MemoryClientTest, getJournalReaderNotImplemented) {
// This method is not implemented.
EXPECT_THROW(client_->getJournalReader(Name("."), 0, 0),
isc::NotImplemented);
}
}

View File

@@ -14,10 +14,13 @@
#include <gtest/gtest.h>
#include <util/unittests/run_all.h>
#include <log/logger_support.h>
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
isc::log::initLogger();
return (isc::util::unittests::run_all());
}

View File

@@ -0,0 +1,32 @@
CLEANFILES = *.copied
EXTRA_DIST = empty.zone
EXTRA_DIST += example.org.zone
EXTRA_DIST += example.org-empty.zone
EXTRA_DIST += example.org-broken1.zone
EXTRA_DIST += example.org-broken2.zone
EXTRA_DIST += example.org-cname-and-not-nsec-1.zone
EXTRA_DIST += example.org-cname-and-not-nsec-2.zone
EXTRA_DIST += example.org-dname-ns-apex-1.zone
EXTRA_DIST += example.org-dname-ns-apex-2.zone
EXTRA_DIST += example.org-dname-ns-nonapex-1.zone
EXTRA_DIST += example.org-dname-ns-nonapex-2.zone
EXTRA_DIST += example.org-duplicate-type-bad.zone
EXTRA_DIST += example.org-duplicate-type.zone
EXTRA_DIST += example.org-multiple-cname.zone
EXTRA_DIST += example.org-multiple-dname.zone
EXTRA_DIST += example.org-multiple-nsec3.zone
EXTRA_DIST += example.org-multiple-nsec3param.zone
EXTRA_DIST += example.org-multiple.zone
EXTRA_DIST += example.org-nsec3-fewer-labels.zone example.org-nsec3-more-labels.zone
EXTRA_DIST += example.org-nsec3-signed-no-param.zone
EXTRA_DIST += example.org-nsec3-signed.zone
EXTRA_DIST += example.org-out-of-zone.zone
EXTRA_DIST += example.org-rrsig-follows-nothing.zone
EXTRA_DIST += example.org-rrsig-name-unmatched.zone
EXTRA_DIST += example.org-rrsig-type-unmatched.zone
EXTRA_DIST += example.org-rrsigs.zone
EXTRA_DIST += example.org-wildcard-dname.zone
EXTRA_DIST += example.org-wildcard-ns.zone
EXTRA_DIST += example.org-wildcard-nsec3.zone

View File

View File

@@ -0,0 +1 @@
This is a broken zone that should not parse.

View File

@@ -0,0 +1,5 @@
;; broken example.org zone, where some RRs are OK, but others aren't
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 73 3600 300 3600000 3600
ns1.example.org. 3600 IN A 192.0.2.1
ns2.example.org. 3600 IN A 192.0.2.2
ns2.a.example.com. 3600 IN AAAA

View File

@@ -0,0 +1,4 @@
;; CNAME + other is an error
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012091009 7200 3600 2592000 1200
a.example.org. 7200 IN A 192.168.0.1
a.example.org. 3600 IN CNAME foo.example.com.

View File

@@ -0,0 +1,4 @@
;; CNAME + other is an error
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012091007 7200 3600 2592000 1200
a.example.org. 3600 IN CNAME foo.example.com.
a.example.org. 7200 IN A 192.168.0.1

View File

@@ -0,0 +1,4 @@
;; DNAME + NS (apex)
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012091015 7200 3600 2592000 1200
example.org. 3600 IN DNAME foo.example.com.
example.org. 3600 IN NS bar.example.com.

View File

@@ -0,0 +1,4 @@
;; DNAME + NS (apex)
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012091016 7200 3600 2592000 1200
example.org. 3600 IN NS bar.example.com.
example.org. 3600 IN DNAME foo.example.com.

View File

@@ -0,0 +1,4 @@
;; DNAME + NS (non-apex)
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012091014 7200 3600 2592000 1200
ns1.example.org. 3600 IN DNAME foo.example.com.
ns1.example.org. 3600 IN NS bar.example.com.

View File

@@ -0,0 +1,4 @@
;; DNAME + NS (non-apex)
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012091015 7200 3600 2592000 1200
ns1.example.org. 3600 IN NS bar.example.com.
ns1.example.org. 3600 IN DNAME foo.example.com.

View File

@@ -0,0 +1,4 @@
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 77 3600 300 3600000 3600
ns1.example.org. 3600 IN A 192.168.0.1
ns1.example.org. 3600 IN AAAA ::1
ns1.example.org. 3600 IN A 192.168.0.2

View File

@@ -0,0 +1,4 @@
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 76 3600 300 3600000 3600
ns1.example.org. 3600 IN A 192.168.0.1
ns1.example.org. 3600 IN A 192.168.0.2
ns1.example.org. 3600 IN AAAA ::1

View File

@@ -0,0 +1,2 @@
;; empty example.org zone
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 68 3600 300 3600000 3600

View File

@@ -0,0 +1,3 @@
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
ns1.example.org. 3600 IN CNAME foo.example.com.
ns1.example.org. 3600 IN CNAME bar.example.com.

View File

@@ -0,0 +1,3 @@
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 79 3600 300 3600000 3600
ns1.example.org. 3600 IN DNAME foo.example.com.
ns1.example.org. 3600 IN DNAME bar.example.com.

View File

@@ -0,0 +1,3 @@
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012090702 7200 3600 2592000 1200
09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM

View File

@@ -0,0 +1,3 @@
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012090700 7200 3600 2592000 1200
example.org. 0 IN NSEC3PARAM 1 0 10 AABBCCDD
example.org. 0 IN NSEC3PARAM 1 0 10 AABBCCDD

View File

@@ -0,0 +1,4 @@
;; Multiple RDATA for testing separate RRs iterator
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
a.example.org. 3600 IN A 192.168.0.1
a.example.org. 3600 IN A 192.168.0.2

View File

@@ -0,0 +1,3 @@
;; NSEC3 names with labels != (origin_labels + 1)
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012091001 7200 3600 2592000 1200
example.org. 1200 IN NSEC3 1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG

View File

@@ -0,0 +1,3 @@
;; NSEC3 names with labels != (origin_labels + 1)
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012091002 7200 3600 2592000 1200
a.b.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG

View File

@@ -0,0 +1,15 @@
;; This file intentionally removes NSEC3PARAM from example.org.nsec3-signed
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012013000 7200 3600 2592000 1200
example.org. 86400 IN RRSIG SOA 7 2 86400 20120301040838 20120131040838 19562 example.org. Jt9wCRLS5TQxZH0IBqrM9uMGD453rIoxYopfM9AjjRZfEx+HGlBpOZeR pGN7yLcN+URnicOD0ydLHiakaBODiZyNoYCKYG5d2ZOhL+026REnDKNM 0m5T3X3sczP+l55An/GITheTdrKt3Y1Ouc2yKI8ro8JjOxV/a4nGDWjK x9A=
example.org. 86400 IN NS ns.example.org.
example.org. 86400 IN RRSIG NS 7 2 86400 20120301040838 20120131040838 19562 example.org. gYXL3xK4IFdJU6TtiVuzqDBb2MeA8xB3AKtHlJGFTfTRNHyuej0ZGovx TeUYsLYmoiGYaJG66iD1tYYFq0qdj0xWq+LEa53ACtKvYf9IIwK4ijJs k0g6xCNavc6/qPppymDhN7MvoFVkW59uJa0HPWlsIIuRlEAr7xyt85vq yoA=
example.org. 86400 IN DNSKEY 256 3 7 AwEAAbrBkKf2gmGtG4aogZY4svIZCrOLLZlQzVHwz7WxJdTR8iEnvz/x Q/jReDroS5CBZWvzwLlhPIpsJAojx0oj0RvfJNsz3+6LN8q7x9u6+86B 85CYjTk3dcFOebgkF4fXr7/kkOX+ZY94Zk0Z1+pUC3eY4gkKcyME/Uxm O18PBTeB
example.org. 86400 IN RRSIG DNSKEY 7 2 86400 20120301040838 20120131040838 19562 example.org. d0eLF8JqNHaGuBSX0ashU5c1O/wyWU43UUsKGrMQIoBDiJ588MWQOnas rwvW6vdkLNqRqCsP/B4epV/EtLL0tBsk5SHkTYbNo80gGrBufQ6YrWRr Ile8Z+h+MR4y9DybbjmuNKqaO4uQMg/X6+4HqRAKx1lmZMTcrcVeOwDM ZA4=
;; example.org. 0 IN NSEC3PARAM 1 0 10 AABBCCDD
;; example.org. 0 IN RRSIG NSEC3PARAM 7 2 0 20120301040838 20120131040838 19562 example.org. Ggs5MiQDlXXt22Fz9DNg3Ujc0T6MBfumlRkd8/enBbJwLmqw2QXAzDEk pjUeGstCEHKzxJDJstavGoCpTDJgoV4Fd9szooMx69rzPrq9tdoM46jG xZHqw+Pv2fkRGC6aP7ZX1r3Qnpwpk47AQgATftbO4G6KcMcO8JoKE47x XLM=
ns.example.org. 86400 IN A 192.0.2.1
ns.example.org. 86400 IN RRSIG A 7 3 86400 20120301040838 20120131040838 19562 example.org. dOH+Dxib8VcGnjLrKILsqDhS1wki6BWk1dZwpOGUGHyLWcLNW8ygWY2o r29jPhHtaFCNWpn46JebgnXDPRiQjaY3dQqL8rcf2QX1e3+Cicw1OSrs S0sUDE5DmMNEac+ZCUQ0quCStZTCldl05hlspV2RS92TpApnoOK0nXMp Uak=
09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKO yfZc8wKRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVv cD3dFksPyiKHf/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3 CTM=
RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM
RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN RRSIG NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. j7d8GL4YqX035FBcPPsEcSWHjWcKdlQMHLL4TB67xVNFnl4SEFQCp4OO AtPap5tkKakwgWxoQVN9XjnqrBz+oQhofDkB3aTatAjIIkcwcnrm3AYQ rTI3E03ySiRwuCPKVmHOLUV2cG6O4xzcmP+MYZcvPTS8V3F5LlaU22i7 A3E=

View File

@@ -0,0 +1,14 @@
example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012013000 7200 3600 2592000 1200
example.org. 86400 IN RRSIG SOA 7 2 86400 20120301040838 20120131040838 19562 example.org. Jt9wCRLS5TQxZH0IBqrM9uMGD453rIoxYopfM9AjjRZfEx+HGlBpOZeR pGN7yLcN+URnicOD0ydLHiakaBODiZyNoYCKYG5d2ZOhL+026REnDKNM 0m5T3X3sczP+l55An/GITheTdrKt3Y1Ouc2yKI8ro8JjOxV/a4nGDWjK x9A=
example.org. 86400 IN NS ns.example.org.
example.org. 86400 IN RRSIG NS 7 2 86400 20120301040838 20120131040838 19562 example.org. gYXL3xK4IFdJU6TtiVuzqDBb2MeA8xB3AKtHlJGFTfTRNHyuej0ZGovx TeUYsLYmoiGYaJG66iD1tYYFq0qdj0xWq+LEa53ACtKvYf9IIwK4ijJs k0g6xCNavc6/qPppymDhN7MvoFVkW59uJa0HPWlsIIuRlEAr7xyt85vq yoA=
example.org. 86400 IN DNSKEY 256 3 7 AwEAAbrBkKf2gmGtG4aogZY4svIZCrOLLZlQzVHwz7WxJdTR8iEnvz/x Q/jReDroS5CBZWvzwLlhPIpsJAojx0oj0RvfJNsz3+6LN8q7x9u6+86B 85CYjTk3dcFOebgkF4fXr7/kkOX+ZY94Zk0Z1+pUC3eY4gkKcyME/Uxm O18PBTeB
example.org. 86400 IN RRSIG DNSKEY 7 2 86400 20120301040838 20120131040838 19562 example.org. d0eLF8JqNHaGuBSX0ashU5c1O/wyWU43UUsKGrMQIoBDiJ588MWQOnas rwvW6vdkLNqRqCsP/B4epV/EtLL0tBsk5SHkTYbNo80gGrBufQ6YrWRr Ile8Z+h+MR4y9DybbjmuNKqaO4uQMg/X6+4HqRAKx1lmZMTcrcVeOwDM ZA4=
example.org. 0 IN NSEC3PARAM 1 0 10 AABBCCDD
example.org. 0 IN RRSIG NSEC3PARAM 7 2 0 20120301040838 20120131040838 19562 example.org. Ggs5MiQDlXXt22Fz9DNg3Ujc0T6MBfumlRkd8/enBbJwLmqw2QXAzDEk pjUeGstCEHKzxJDJstavGoCpTDJgoV4Fd9szooMx69rzPrq9tdoM46jG xZHqw+Pv2fkRGC6aP7ZX1r3Qnpwpk47AQgATftbO4G6KcMcO8JoKE47x XLM=
ns.example.org. 86400 IN A 192.0.2.1
ns.example.org. 86400 IN RRSIG A 7 3 86400 20120301040838 20120131040838 19562 example.org. dOH+Dxib8VcGnjLrKILsqDhS1wki6BWk1dZwpOGUGHyLWcLNW8ygWY2o r29jPhHtaFCNWpn46JebgnXDPRiQjaY3dQqL8rcf2QX1e3+Cicw1OSrs S0sUDE5DmMNEac+ZCUQ0quCStZTCldl05hlspV2RS92TpApnoOK0nXMp Uak=
09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKO yfZc8wKRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVv cD3dFksPyiKHf/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3 CTM=
RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM
RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN RRSIG NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. j7d8GL4YqX035FBcPPsEcSWHjWcKdlQMHLL4TB67xVNFnl4SEFQCp4OO AtPap5tkKakwgWxoQVN9XjnqrBz+oQhofDkB3aTatAjIIkcwcnrm3AYQ rTI3E03ySiRwuCPKVmHOLUV2cG6O4xzcmP+MYZcvPTS8V3F5LlaU22i7 A3E=

View File

@@ -0,0 +1,5 @@
;; test zone file used for ZoneFinderContext tests.
;; RRSIGs are (obviouslly) faked ones for testing.
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 75 3600 300 3600000 3600
a.example.com. 3600 IN A 192.168.0.1

View File

@@ -0,0 +1,5 @@
;; test zone file used for ZoneFinderContext tests.
;; RRSIGs are (obviouslly) faked ones for testing.
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 68 3600 300 3600000 3600
ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE

View File

@@ -0,0 +1,6 @@
;; test zone file used for ZoneFinderContext tests.
;; RRSIGs are (obviouslly) faked ones for testing.
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 70 3600 300 3600000 3600
ns1.example.org. 3600 IN A 192.0.2.1
ns2.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE

View File

@@ -0,0 +1,6 @@
;; test zone file used for ZoneFinderContext tests.
;; RRSIGs are (obviouslly) faked ones for testing.
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 72 3600 300 3600000 3600
ns1.example.org. 3600 IN AAAA 2001:db8::1
ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE

View File

@@ -0,0 +1,8 @@
;; test zone file used for ZoneFinderContext tests.
;; RRSIGs are (obviouslly) faked ones for testing.
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 74 3600 300 3600000 3600
ns1.example.org. 3600 IN A 192.168.0.1
ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
ns1.example.org. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
ns1.example.org. 3600 IN AAAA ::1

View File

@@ -0,0 +1,4 @@
;; test zone file with wildcard DNAME names
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 79 3600 300 3600000 3600
*.example.org. 3600 IN DNAME dname.example.com.

View File

@@ -0,0 +1,4 @@
;; test zone file with wildcard NS names
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
*.example.org. 3600 IN NS ns1.example.org.

View File

@@ -0,0 +1,4 @@
;; test zone file with wildcard NS names
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 79 3600 300 3600000 3600
*.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG

View File

@@ -0,0 +1,81 @@
;; test zone file used for ZoneFinderContext tests.
;; RRSIGs are (obviouslly) faked ones for testing.
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 67 3600 300 3600000 3600
example.org. 3600 IN NS ns1.example.org.
example.org. 3600 IN NS ns2.example.org.
example.org. 3600 IN MX 1 mx1.example.org.
example.org. 3600 IN MX 2 mx2.example.org.
example.org. 3600 IN MX 3 mx.a.example.org.
ns1.example.org. 3600 IN A 192.0.2.1
ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
ns1.example.org. 3600 IN AAAA 2001:db8::1
ns1.example.org. 3600 IN RRSIG AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
ns2.example.org. 3600 IN A 192.0.2.2
ns2.example.org. 3600 IN TXT "text data"
mx1.example.org. 3600 IN A 192.0.2.10
mx2.example.org. 3600 IN AAAA 2001:db8::10
;; delegation
a.example.org. 3600 IN NS ns1.a.example.org.
a.example.org. 3600 IN NS ns2.a.example.org.
a.example.org. 3600 IN NS ns.example.com.
ns1.a.example.org. 3600 IN A 192.0.2.5
ns2.a.example.org. 3600 IN A 192.0.2.6
ns2.a.example.org. 3600 IN AAAA 2001:db8::6
mx.a.example.org. 3600 IN A 192.0.2.7
;; delegation, one of its NS names is at zone cut.
b.example.org. 3600 IN NS ns.b.example.org.
b.example.org. 3600 IN NS b.example.org.
b.example.org. 3600 IN AAAA 2001:db8::8
ns.b.example.org. 3600 IN A 192.0.2.9
;; The MX name is at a zone cut. shouldn't be included in the
;; additional section.
mxatcut.example.org. 3600 IN MX 1 b.example.org.
;; delegation, one of its NS names is under a DNAME delegation point;
;; another is at that point; and yet another is under DNAME below a
;; zone cut.
c.example.org. 3600 IN NS ns.dname.example.org.
c.example.org. 3600 IN NS dname.example.org.
c.example.org. 3600 IN NS ns.deepdname.example.org.
ns.dname.example.org. 3600 IN A 192.0.2.11
dname.example.org. 3600 IN A 192.0.2.12
ns.deepdname.example.org. 3600 IN AAAA 2001:db8::9
;; delegation, one of its NS name is at an empty non terminal.
d.example.org. 3600 IN NS ns.empty.example.org.
d.example.org. 3600 IN NS ns1.example.org.
;; by adding these two we can create an empty RB node for
;; ns.empty.example.org in the in-memory zone
foo.ns.empty.example.org. 3600 IN A 192.0.2.13
bar.ns.empty.example.org. 3600 IN A 192.0.2.14
;; delegation; the NS name matches a wildcard (and there's no exact
;; match). One of the NS names matches an empty wildcard node, for
;; which no additional record should be provided (or any other
;; disruption should happen).
e.example.org. 3600 IN NS ns.wild.example.org.
e.example.org. 3600 IN NS ns.emptywild.example.org.
e.example.org. 3600 IN NS ns2.example.org.
*.wild.example.org. 3600 IN A 192.0.2.15
a.*.emptywild.example.org. 3600 IN AAAA 2001:db8::2
;; additional for an answer RRset (MX) as a result of wildcard
;; expansion
*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
;; CNAME
alias.example.org. 3600 IN CNAME cname.example.org.
;; DNAME
dname.example.org. 3600 IN DNAME dname.example.com.
;; DNAME under a NS (strange one)
deepdname.c.example.org. 3600 IN DNAME deepdname.example.com.

View File

@@ -143,11 +143,13 @@ protected:
void
checkBasicFields(const AbstractRRset& actual_rrset, const Name& expected_name,
const RRClass& expected_class, const RRType& expected_type,
const uint32_t expected_ttl,
size_t expected_rdatacount, size_t expected_sigcount)
{
EXPECT_EQ(expected_name, actual_rrset.getName());
EXPECT_EQ(expected_class, actual_rrset.getClass());
EXPECT_EQ(expected_type, actual_rrset.getType());
EXPECT_EQ(RRTTL(expected_ttl), actual_rrset.getTTL());
EXPECT_EQ(expected_rdatacount, actual_rrset.getRdataCount());
EXPECT_EQ(expected_sigcount, actual_rrset.getRRsigDataCount());
}
@@ -176,30 +178,30 @@ createRRset(const Name& realname, const RRClass& rrclass, const ZoneNode* node,
TEST_F(TreeNodeRRsetTest, create) {
// Constructed with RRSIG, and it should be visible.
checkBasicFields(*createRRset(rrclass_, www_node_, a_rdataset_, true),
www_name_, rrclass_, RRType::A(), 2, 1);
www_name_, rrclass_, RRType::A(), 3600, 2, 1);
// Constructed with RRSIG, and it should be invisible.
checkBasicFields(*createRRset(rrclass_, www_node_, a_rdataset_, false),
www_name_, rrclass_, RRType::A(), 2, 0);
www_name_, rrclass_, RRType::A(), 3600, 2, 0);
// Constructed without RRSIG, and it would be visible (but of course won't)
checkBasicFields(*createRRset(rrclass_, origin_node_, ns_rdataset_, true),
origin_name_, rrclass_, RRType::NS(), 1, 0);
origin_name_, rrclass_, RRType::NS(), 3600, 1, 0);
// Constructed without RRSIG, and it should be visible
checkBasicFields(*createRRset(rrclass_, origin_node_, ns_rdataset_, false),
origin_name_, rrclass_, RRType::NS(), 1, 0);
origin_name_, rrclass_, RRType::NS(), 3600, 1, 0);
// RRSIG-only case (note the RRset's type is covered type)
checkBasicFields(*createRRset(rrclass_, www_node_, rrsig_only_rdataset_,
true),
www_name_, rrclass_, RRType::TXT(), 0, 1);
www_name_, rrclass_, RRType::TXT(), 3600, 0, 1);
// RRSIG-only case (note the RRset's type is covered type), but it's
// invisible
checkBasicFields(*createRRset(rrclass_, www_node_, rrsig_only_rdataset_,
false),
www_name_, rrclass_, RRType::TXT(), 0, 0);
www_name_, rrclass_, RRType::TXT(), 3600, 0, 0);
// Wildcard substitution
checkBasicFields(*createRRset(match_name_, rrclass_,
wildcard_node_, wildcard_rdataset_,
true),
match_name_, rrclass_, RRType::A(), 2, 1);
match_name_, rrclass_, RRType::A(), 3600, 2, 1);
}
// The following two templated functions are helper to encapsulate the
@@ -572,7 +574,6 @@ TEST_F(TreeNodeRRsetTest, unexpectedMethods) {
TreeNodeRRset rrset(rrclass_, www_node_, a_rdataset_, true);
EXPECT_THROW(rrset.getTTL(), isc::Unexpected);
EXPECT_THROW(rrset.setTTL(RRTTL(0)), isc::Unexpected);
EXPECT_THROW(rrset.setName(Name("example")), isc::Unexpected);
EXPECT_THROW(rrset.addRdata(createRdata(RRType::A(), rrclass_, "0.0.0.0")),

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,13 @@ TreeNodeRRset::getName() const {
const RRTTL&
TreeNodeRRset::getTTL() const {
isc_throw(Unexpected, "unexpected method called on TreeNodeRRset");
if (ttl_ == NULL) {
util::InputBuffer ttl_buffer(rdataset_->getTTLData(),
sizeof(uint32_t));
ttl_ = new RRTTL(ttl_buffer);
}
return (*ttl_);
}
void

View File

@@ -112,7 +112,7 @@ public:
const RdataSet* rdataset, bool dnssec_ok) :
node_(node), rdataset_(rdataset),
rrsig_count_(rdataset_->getSigRdataCount()), rrclass_(rrclass),
dnssec_ok_(dnssec_ok), name_(NULL), realname_(NULL)
dnssec_ok_(dnssec_ok), name_(NULL), realname_(NULL), ttl_(NULL)
{}
/// \brief Constructor for wildcard-expanded owner name.
@@ -132,11 +132,13 @@ public:
bool dnssec_ok) :
node_(node), rdataset_(rdataset),
rrsig_count_(rdataset_->getSigRdataCount()), rrclass_(rrclass),
dnssec_ok_(dnssec_ok), name_(NULL), realname_(new dns::Name(realname))
dnssec_ok_(dnssec_ok), name_(NULL), realname_(new dns::Name(realname)),
ttl_(NULL)
{}
virtual ~TreeNodeRRset() {
delete realname_;
delete ttl_;
delete name_;
}
@@ -154,8 +156,6 @@ public:
}
/// \brief Specialized version of \c getTTL() for \c TreeNodeRRset.
///
/// It throws \c isc::Unexpected unconditionally.
virtual const dns::RRTTL& getTTL() const;
/// \brief Specialized version of \c setName() for \c TreeNodeRRset.
@@ -257,8 +257,11 @@ private:
const bool dnssec_ok_;
mutable dns::Name* name_;
const dns::Name* const realname_;
mutable dns::RRTTL* ttl_;
};
typedef boost::shared_ptr<TreeNodeRRset> TreeNodeRRsetPtr;
} // namespace memory
} // namespace datasrc
} // namespace isc

View File

@@ -43,6 +43,7 @@ namespace memory {
typedef DomainTree<RdataSet> ZoneTree;
typedef DomainTreeNode<RdataSet> ZoneNode;
typedef DomainTreeNodeChain<RdataSet> ZoneChain;
/// \brief NSEC3 data for a DNS zone.
///

View File

@@ -0,0 +1,600 @@
// Copyright (C) 2012 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 <datasrc/memory/zone_finder.h>
#include <datasrc/memory/domaintree.h>
#include <datasrc/memory/treenode_rrset.h>
#include <datasrc/zone.h>
#include <datasrc/data_source.h>
#include <dns/labelsequence.h>
#include <dns/name.h>
#include <dns/rrset.h>
#include <dns/rrtype.h>
#include <datasrc/logger.h>
using namespace isc::dns;
using namespace isc::datasrc::memory;
using namespace isc::datasrc;
namespace isc {
namespace datasrc {
namespace memory {
namespace {
/// Creates a TreeNodeRRsetPtr for the given RdataSet at the given Node, for
/// the given RRClass
///
/// We should probably have some pool so these do not need to be allocated
/// dynamically.
///
/// \param node The ZoneNode found by the find() calls
/// \param rdataset The RdataSet to create the RRsetPtr for
/// \param rrclass The RRClass as passed by the client
/// \param realname If given, the TreeNodeRRset is created with this name
/// (e.g. for wildcard substitution)
///
/// Returns an empty TreeNodeRRsetPtr if node is NULL or if rdataset is NULL.
TreeNodeRRsetPtr
createTreeNodeRRset(const ZoneNode* node,
const RdataSet* rdataset,
const RRClass& rrclass,
const Name* realname = NULL)
{
if (node != NULL && rdataset != NULL) {
if (realname != NULL) {
return TreeNodeRRsetPtr(new TreeNodeRRset(*realname, rrclass, node,
rdataset, true));
} else {
return TreeNodeRRsetPtr(new TreeNodeRRset(rrclass, node,
rdataset, true));
}
} else {
return TreeNodeRRsetPtr();
}
}
/// Maintain intermediate data specific to the search context used in
/// \c find().
///
/// It will be passed to \c cutCallback() (see below) and record a possible
/// zone cut node and related RRset (normally NS or DNAME).
struct FindState {
FindState(bool glue_ok) :
zonecut_node_(NULL),
dname_node_(NULL),
rrset_(NULL),
glue_ok_(glue_ok)
{}
// These will be set to a domain node of the highest delegation point,
// if any. In fact, we could use a single variable instead of both.
// But then we would need to distinquish these two cases by something
// else and it seemed little more confusing when this was written.
const ZoneNode* zonecut_node_;
const ZoneNode* dname_node_;
// Delegation RRset (NS or DNAME), if found.
const RdataSet* rrset_;
// Whether to continue search below a delegation point.
// Set at construction time.
const bool glue_ok_;
};
// A callback called from possible zone cut nodes and nodes with DNAME.
// This will be passed from findNode() to \c RBTree::find().
bool cutCallback(const ZoneNode& node, FindState* state) {
// We need to look for DNAME first, there's allowed case where
// DNAME and NS coexist in the apex. DNAME is the one to notice,
// the NS is authoritative, not delegation (corner case explicitly
// allowed by section 3 of 2672)
const RdataSet* found_dname = RdataSet::find(node.getData(),
RRType::DNAME());
if (found_dname != NULL) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_DNAME_ENCOUNTERED);
state->dname_node_ = &node;
state->rrset_ = found_dname;
return (true);
}
// Look for NS
const RdataSet* found_ns = RdataSet::find(node.getData(), RRType::NS());
if (found_ns != NULL) {
// We perform callback check only for the highest zone cut in the
// rare case of nested zone cuts.
if (state->zonecut_node_ != NULL) {
return (false);
}
LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
// BIND 9 checks if this node is not the origin. That's probably
// because it can support multiple versions for dynamic updates
// and IXFR, and it's possible that the callback is called at
// the apex and the DNAME doesn't exist for a particular version.
// It cannot happen for us (at least for now), so we don't do
// that check.
state->zonecut_node_ = &node;
state->rrset_ = found_ns;
// Unless glue is allowed the search stops here, so we return
// false; otherwise return true to continue the search.
return (!state->glue_ok_);
}
// This case should not happen because we enable callback only
// when we add an RR searched for above.
assert(0);
// This is here to avoid warning (therefore compilation error)
// in case assert is turned off. Otherwise we could get "Control
// reached end of non-void function".
return (false);
}
// convenience function to fill in the final details
//
// Set up ZoneFinderResultContext object as a return value of find(),
// taking into account wildcard matches and DNSSEC information. We set
// the NSEC/NSEC3 flag when applicable regardless of the find option; the
// caller would simply ignore these when they didn't request DNSSEC
// related results.
//
// Also performs the conversion of node + RdataSet into a TreeNodeRRsetPtr
//
// if wild is true, the RESULT_WILDCARD flag will be set.
// If qname is not NULL, this is the query name, to be used in wildcard
// substitution instead of the Node's name).
isc::datasrc::memory::ZoneFinderResultContext
createFindResult(const RRClass& rrclass,
const ZoneData& zone_data,
ZoneFinder::Result code,
const RdataSet* rrset,
const ZoneNode* node,
bool wild = false,
const Name* qname = NULL) {
ZoneFinder::FindResultFlags flags = ZoneFinder::RESULT_DEFAULT;
const Name* rename = NULL;
if (wild) {
flags = flags | ZoneFinder::RESULT_WILDCARD;
// only use the rename qname if wild is true
rename = qname;
}
if (code == ZoneFinder::NXRRSET || code == ZoneFinder::NXDOMAIN || wild) {
if (zone_data.isNSEC3Signed()) {
flags = flags | ZoneFinder::RESULT_NSEC3_SIGNED;
} else if (zone_data.isSigned()) {
flags = flags | ZoneFinder::RESULT_NSEC_SIGNED;
}
}
return (ZoneFinderResultContext(code, createTreeNodeRRset(node, rrset,
rrclass, rename),
flags, node));
}
// A helper function for NSEC-signed zones. It searches the zone for
// the "closest" NSEC corresponding to the search context stored in
// node_path (it should contain sufficient information to identify the
// previous name of the query name in the zone). In some cases the
// immediate closest name may not have NSEC (when it's under a zone cut
// for glue records, or even when the zone is partly broken), so this
// method continues the search until it finds a name that has NSEC,
// and returns the one found first. Due to the prerequisite (see below),
// it should always succeed.
//
// node_path must store valid search context (in practice, it's expected
// to be set by findNode()); otherwise the underlying RBTree implementation
// throws.
//
// If the zone is not considered NSEC-signed or DNSSEC records were not
// required in the original search context (specified in options), this
// method doesn't bother to find NSEC, and simply returns NULL. So, by
// definition of "NSEC-signed", when it really tries to find an NSEC it
// should succeed; there should be one at least at the zone origin.
const RdataSet*
getClosestNSEC(const ZoneData& zone_data,
ZoneChain& node_path,
const ZoneNode** nsec_node,
ZoneFinder::FindOptions options)
{
if (!zone_data.isSigned() ||
(options & ZoneFinder::FIND_DNSSEC) == 0 ||
zone_data.isNSEC3Signed()) {
return (NULL);
}
const ZoneNode* prev_node;
while ((prev_node = zone_data.getZoneTree().previousNode(node_path))
!= NULL) {
if (!prev_node->isEmpty()) {
const RdataSet* found =
RdataSet::find(prev_node->getData(), RRType::NSEC());
if (found != NULL) {
*nsec_node = prev_node;
return (found);
}
}
}
// This must be impossible and should be an internal bug.
// See the description at the method declaration.
assert(false);
// Even though there is an assert here, strict compilers
// will still need some return value.
return (NULL);
}
// A helper function for the NXRRSET case in find(). If the zone is
// NSEC-signed and DNSSEC records are requested, try to find NSEC
// on the given node, and return it if found; return NULL for all other
// cases.
const RdataSet*
getNSECForNXRRSET(const ZoneData& zone_data,
ZoneFinder::FindOptions options,
const ZoneNode* node)
{
if (zone_data.isSigned() &&
!zone_data.isNSEC3Signed() &&
(options & ZoneFinder::FIND_DNSSEC) != 0) {
const RdataSet* found = RdataSet::find(node->getData(),
RRType::NSEC());
if (found != NULL) {
return (found);
}
}
return (NULL);
}
// Structure to hold result data of the findNode() call
class FindNodeResult {
public:
// Bitwise flags to represent supplemental information of the
// search result:
// Search resulted in a wildcard match.
static const unsigned int FIND_WILDCARD = 1;
// Search encountered a zone cut due to NS but continued to look for
// a glue.
static const unsigned int FIND_ZONECUT = 2;
FindNodeResult(ZoneFinder::Result code_param,
const ZoneNode* node_param,
const RdataSet* rrset_param,
unsigned int flags_param = 0) :
code(code_param),
node(node_param),
rrset(rrset_param),
flags(flags_param)
{}
const ZoneFinder::Result code;
const ZoneNode* node;
const RdataSet* rrset;
const unsigned int flags;
};
// Implementation notes: this method identifies an ZoneNode that best matches
// the give name in terms of DNS query handling. In many cases,
// DomainTree::find() will result in EXACTMATCH or PARTIALMATCH (note that
// the given name is generally expected to be contained in the zone, so
// even if it doesn't exist, it should at least match the zone origin).
// If it finds an exact match, that's obviously the best one. The partial
// match case is more complicated.
//
// We first need to consider the case where search hits a delegation point,
// either due to NS or DNAME. They are indicated as either dname_node_ or
// zonecut_node_ being non NULL. Usually at most one of them will be
// something else than NULL (it might happen both are NULL, in which case we
// consider it NOT FOUND). There's one corner case when both might be
// something else than NULL and it is in case there's a DNAME under a zone
// cut and we search in glue OK mode in that case we don't stop on the
// domain with NS and ignore it for the answer, but it gets set anyway. Then
// we find the DNAME and we need to act by it, therefore we first check for
// DNAME and then for NS. In all other cases it doesn't matter, as at least
// one of them is NULL.
//
// Next, we need to check if the ZoneTree search stopped at a node for a
// subdomain of the search name (so the comparison result that stopped the
// search is "SUPERDOMAIN"), it means the stopping node is an empty
// non-terminal node. In this case the search name is considered to exist
// but no data should be found there.
//
// If none of above is the case, we then consider whether there's a matching
// wildcard. DomainTree::find() records the node if it encounters a
// "wildcarding" node, i.e., the immediate ancestor of a wildcard name
// (e.g., wild.example.com for *.wild.example.com), and returns it if it
// doesn't find any node that better matches the query name. In this case
// we'll check if there's indeed a wildcard below the wildcarding node.
//
// Note, first, that the wildcard is checked after the empty
// non-terminal domain case above, because if that one triggers, it
// means we should not match according to 4.3.3 of RFC 1034 (the query
// name is known to exist).
//
// Before we try to find a wildcard, we should check whether there's
// an existing node that would cancel the wildcard match. If
// DomainTree::find() stopped at a node which has a common ancestor
// with the query name, it might mean we are comparing with a
// non-wildcard node. In that case, we check which part is common. If
// we have something in common that lives below the node we got (the
// one above *), then we should cancel the match according to section
// 4.3.3 of RFC 1034 (as the name between the wildcard domain and the
// query name is known to exist).
//
// If there's no node below the wildcarding node that shares a common ancestor
// of the query name, we can conclude the wildcard is the best match.
// We'll then identify the wildcard node via an incremental search. Note that
// there's no possibility that the query name is at an empty non terminal
// node below the wildcarding node at this stage; that case should have been
// caught above.
//
// If none of the above succeeds, we conclude the name doesn't exist in
// the zone, and throw an OutOfZone exception.
FindNodeResult findNode(const ZoneData& zone_data,
const Name& name,
ZoneChain& node_path,
ZoneFinder::FindOptions options)
{
ZoneNode* node = NULL;
FindState state((options & ZoneFinder::FIND_GLUE_OK) != 0);
const ZoneTree& tree(zone_data.getZoneTree());
ZoneTree::Result result = tree.find(isc::dns::LabelSequence(name),
&node, node_path, cutCallback,
&state);
const unsigned int zonecut_flag =
(state.zonecut_node_ != NULL) ? FindNodeResult::FIND_ZONECUT : 0;
if (result == ZoneTree::EXACTMATCH) {
return (FindNodeResult(ZoneFinder::SUCCESS, node, state.rrset_,
zonecut_flag));
} else if (result == ZoneTree::PARTIALMATCH) {
assert(node != NULL);
if (state.dname_node_ != NULL) { // DNAME
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
arg(state.dname_node_->getName());
return (FindNodeResult(ZoneFinder::DNAME, state.dname_node_,
state.rrset_));
}
if (state.zonecut_node_ != NULL) { // DELEGATION due to NS
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
arg(state.zonecut_node_->getName());
return (FindNodeResult(ZoneFinder::DELEGATION,
state.zonecut_node_,
state.rrset_));
}
if (node_path.getLastComparisonResult().getRelation() ==
NameComparisonResult::SUPERDOMAIN) { // empty node, so NXRRSET
LOG_DEBUG(logger, DBG_TRACE_DATA,
DATASRC_MEM_SUPER_STOP).arg(name);
const ZoneNode* nsec_node;
const RdataSet* nsec_rds = getClosestNSEC(zone_data,
node_path,
&nsec_node,
options);
return (FindNodeResult(ZoneFinder::NXRRSET, nsec_node,
nsec_rds));
}
// Nothing really matched.
// May be a wildcard, but check only if not disabled
if (node->getFlag(ZoneData::WILDCARD_NODE) &&
(options & ZoneFinder::NO_WILDCARD) == 0) {
if (node_path.getLastComparisonResult().getRelation() ==
NameComparisonResult::COMMONANCESTOR) {
// This means, e.g., we have *.wild.example and
// bar.foo.wild.example and are looking for
// baz.foo.wild.example. The common ancestor, foo.wild.example,
// should cancel wildcard. Treat it as NXDOMAIN.
LOG_DEBUG(logger, DBG_TRACE_DATA,
DATASRC_MEM_WILDCARD_CANCEL).arg(name);
const ZoneNode* nsec_node;
const RdataSet* nsec_rds = getClosestNSEC(zone_data,
node_path,
&nsec_node,
options);
return (FindNodeResult(ZoneFinder::NXDOMAIN, nsec_node,
nsec_rds));
}
uint8_t ls_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
// Create the wildcard name (i.e. take "*" and extend it
// with all node labels down to the wildcard node
LabelSequence wildcard_ls(LabelSequence::WILDCARD(), ls_buf);
const ZoneNode* extend_with = node;
while (extend_with != NULL) {
wildcard_ls.extend(extend_with->getLabels(), ls_buf);
extend_with = extend_with->getUpperNode();
}
// Clear the node_path so that we don't keep incorrect (NSEC)
// context
node_path.clear();
ZoneTree::Result result = tree.find(LabelSequence(wildcard_ls),
&node, node_path, cutCallback,
&state);
// Otherwise, why would the domain_flag::WILD be there if
// there was no wildcard under it?
assert(result == ZoneTree::EXACTMATCH);
return (FindNodeResult(ZoneFinder::SUCCESS, node, state.rrset_,
FindNodeResult::FIND_WILDCARD | zonecut_flag));
}
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).arg(name);
const ZoneNode* nsec_node;
const RdataSet* nsec_rds = getClosestNSEC(zone_data, node_path,
&nsec_node, options);
return (FindNodeResult(ZoneFinder::NXDOMAIN, nsec_node, nsec_rds));
} else {
// If the name is neither an exact or partial match, it is
// out of bailiwick, which is considered an error.
isc_throw(OutOfZone, name.toText() << " not in " <<
zone_data.getOriginNode()->getName());
}
}
} // end anonymous namespace
// Specialization of the ZoneFinder::Context for the in-memory finder.
class InMemoryZoneFinder::Context : public ZoneFinder::Context {
public:
/// \brief Constructor.
///
/// Note that we don't have a specific constructor for the findAll() case.
/// For (successful) type ANY query, found_node points to the
/// corresponding RB node, which is recorded within this specialized
/// context.
Context(ZoneFinder& finder, ZoneFinder::FindOptions options,
ZoneFinderResultContext result) :
ZoneFinder::Context(finder, options,
ResultContext(result.code, result.rrset,
result.flags)),
rrset_(result.rrset), found_node_(result.found_node)
{}
private:
const TreeNodeRRsetPtr rrset_;
const ZoneNode* const found_node_;
};
boost::shared_ptr<ZoneFinder::Context>
InMemoryZoneFinder::find(const isc::dns::Name& name,
const isc::dns::RRType& type,
const FindOptions options)
{
return ZoneFinderContextPtr(new Context(*this, options,
find_internal(name,
type,
NULL,
options)));
}
boost::shared_ptr<ZoneFinder::Context>
InMemoryZoneFinder::findAll(const isc::dns::Name& name,
std::vector<isc::dns::ConstRRsetPtr>& target,
const FindOptions options)
{
return ZoneFinderContextPtr(new Context(*this, options,
find_internal(name,
RRType::ANY(),
&target,
options)));
}
ZoneFinderResultContext
InMemoryZoneFinder::find_internal(const isc::dns::Name& name,
const isc::dns::RRType& type,
std::vector<ConstRRsetPtr>* target,
const FindOptions options)
{
// Get the node. All other cases than an exact match are handled
// in findNode(). We simply construct a result structure and return.
ZoneChain node_path;
const FindNodeResult node_result =
findNode(zone_data_, name, node_path, options);
if (node_result.code != SUCCESS) {
return (createFindResult(rrclass_, zone_data_, node_result.code,
node_result.rrset, node_result.node));
}
const ZoneNode* node = node_result.node;
assert(node != NULL);
// We've found an exact match, may or may not be a result of wildcard.
const bool wild = ((node_result.flags &
FindNodeResult::FIND_WILDCARD) != 0);
// If there is an exact match but the node is empty, it's equivalent
// to NXRRSET.
if (node->isEmpty()) {
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
arg(name);
const ZoneNode* nsec_node;
const RdataSet* nsec_rds = getClosestNSEC(zone_data_, node_path,
&nsec_node, options);
return (createFindResult(rrclass_, zone_data_, NXRRSET,
nsec_rds,
nsec_node,
wild));
}
const RdataSet* found;
// If the node callback is enabled, this may be a zone cut. If it
// has a NS RR, we should return a delegation, but not in the apex.
// There is one exception: the case for DS query, which should always
// be considered in-zone lookup.
if (node->getFlag(ZoneNode::FLAG_CALLBACK) &&
node != zone_data_.getOriginNode() && type != RRType::DS()) {
found = RdataSet::find(node->getData(), RRType::NS());
if (found != NULL) {
LOG_DEBUG(logger, DBG_TRACE_DATA,
DATASRC_MEM_EXACT_DELEGATION).arg(name);
return (createFindResult(rrclass_, zone_data_, DELEGATION,
found, node, wild, &name));
}
}
// Handle type any query
if (target != NULL && node->getData() != NULL) {
// Empty domain will be handled as NXRRSET by normal processing
const RdataSet* cur_rds = node->getData();
while (cur_rds != NULL) {
target->push_back(createTreeNodeRRset(node, cur_rds, rrclass_,
&name));
cur_rds = cur_rds->getNext();
}
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ANY_SUCCESS).
arg(name);
return (createFindResult(rrclass_, zone_data_, SUCCESS, NULL, node,
wild, &name));
}
const RdataSet* currds = node->getData();
while (currds != NULL) {
currds = currds->getNext();
}
found = RdataSet::find(node->getData(), type);
if (found != NULL) {
// Good, it is here
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUCCESS).arg(name).
arg(type);
return (createFindResult(rrclass_, zone_data_, SUCCESS, found, node,
wild, &name));
} else {
// Next, try CNAME.
found = RdataSet::find(node->getData(), RRType::CNAME());
if (found != NULL) {
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_CNAME).arg(name);
return (createFindResult(rrclass_, zone_data_, CNAME, found, node,
wild, &name));
}
}
// No exact match or CNAME. Get NSEC if necessary and return NXRRSET.
return (createFindResult(rrclass_, zone_data_, NXRRSET,
getNSECForNXRRSET(zone_data_, options, node),
node, wild, &name));
}
isc::datasrc::ZoneFinder::FindNSEC3Result
InMemoryZoneFinder::findNSEC3(const isc::dns::Name& name, bool recursive) {
(void)name;
(void)recursive;
isc_throw(isc::NotImplemented, "not completed yet! please implement me");
}
} // namespace memory
} // namespace datasrc
} // namespace isc

View File

@@ -0,0 +1,128 @@
// Copyright (C) 2012 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 DATASRC_MEMORY_ZONE_FINDER_H
#define DATASRC_MEMORY_ZONE_FINDER_H 1
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/treenode_rrset.h>
#include <datasrc/zone.h>
#include <dns/name.h>
#include <dns/rrset.h>
#include <dns/rrtype.h>
namespace isc {
namespace datasrc {
namespace memory {
class ZoneFinderResultContext {
public:
/// \brief Constructor
///
/// The first three parameters correspond to those of
/// ZoneFinder::ResultContext. If node is non NULL, it specifies the
/// found RBNode in the search.
ZoneFinderResultContext(ZoneFinder::Result code_param,
TreeNodeRRsetPtr rrset_param,
ZoneFinder::FindResultFlags flags_param,
const ZoneNode* node) :
code(code_param), rrset(rrset_param), flags(flags_param),
found_node(node)
{}
const ZoneFinder::Result code;
const TreeNodeRRsetPtr rrset;
const ZoneFinder::FindResultFlags flags;
const ZoneNode* const found_node;
};
/// A derived zone finder class intended to be used with the memory data
/// source, using ZoneData for its contents.
class InMemoryZoneFinder : boost::noncopyable, public ZoneFinder {
public:
/// \brief Constructor.
///
/// Since ZoneData does not keep RRClass information, but this
/// information is needed in order to construct actual RRsets,
/// this needs to be passed here (the datasource client should
/// have this information). In the future, this may be replaced
/// by some construction to pull TreeNodeRRsets from a pool, but
/// currently, these are created dynamically with the given RRclass
///
/// \param zone_data The ZoneData containing the zone.
/// \param rrclass The RR class of the zone
InMemoryZoneFinder(const ZoneData& zone_data,
const isc::dns::RRClass& rrclass) :
zone_data_(zone_data),
rrclass_(rrclass)
{}
/// \brief Find an RRset in the datasource
virtual boost::shared_ptr<ZoneFinder::Context> find(
const isc::dns::Name& name,
const isc::dns::RRType& type,
const FindOptions options = FIND_DEFAULT);
/// \brief Version of find that returns all types at once
///
/// It acts the same as find, just that when the correct node is found,
/// all the RRsets are filled into the target parameter instead of being
/// returned by the result.
virtual boost::shared_ptr<ZoneFinder::Context> findAll(
const isc::dns::Name& name,
std::vector<isc::dns::ConstRRsetPtr>& target,
const FindOptions options = FIND_DEFAULT);
/// Look for NSEC3 for proving (non)existence of given name.
///
/// See documentation in \c Zone.
virtual FindNSEC3Result
findNSEC3(const isc::dns::Name& name, bool recursive);
/// \brief Returns the origin of the zone.
virtual isc::dns::Name getOrigin() const {
return zone_data_.getOriginNode()->getName();
}
/// \brief Returns the RR class of the zone.
virtual isc::dns::RRClass getClass() const {
return rrclass_;
}
private:
/// \brief In-memory version of finder context.
///
/// The implementation (and any specialized interface) is completely local
/// to the InMemoryZoneFinder class, so it's defined as private
class Context;
/// Actual implementation for both find() and findAll()
ZoneFinderResultContext find_internal(
const isc::dns::Name& name,
const isc::dns::RRType& type,
std::vector<isc::dns::ConstRRsetPtr>* target,
const FindOptions options =
FIND_DEFAULT);
const ZoneData& zone_data_;
const isc::dns::RRClass& rrclass_;
};
} // namespace memory
} // namespace datasrc
} // namespace isc
#endif // DATASRC_MEMORY_ZONE_FINDER_H

View File

@@ -132,6 +132,20 @@ ZoneTable::findZone(const Name& name) const {
return (FindResult(my_result, node->getData()));
}
ZoneTable::FindResult
ZoneTable::setZoneData(const Name& name, ZoneData* data)
{
ZoneTableNode* node(NULL);
ZoneTableTree::Result result(zones_->find(name, &node));
if (result != ZoneTableTree::EXACTMATCH) {
return (FindResult(result::NOTFOUND, NULL));
} else {
return (FindResult(result::SUCCESS, node->setData(data)));
}
}
} // end of namespace memory
} // end of namespace datasrc
} // end of namespace isc

View File

@@ -86,11 +86,11 @@ public:
/// \brief Result data of findZone() method.
struct FindResult {
FindResult(result::Result param_code,
const ZoneData* param_zone_data) :
ZoneData* param_zone_data) :
code(param_code), zone_data(param_zone_data)
{}
const result::Result code;
const ZoneData* const zone_data;
ZoneData* const zone_data;
};
private:
@@ -185,6 +185,16 @@ public:
/// \return A \c FindResult object enclosing the search result (see above).
FindResult findZone(const isc::dns::Name& name) const;
/// Override the ZoneData for a node (zone) in the zone tree.
///
/// \throw none
///
/// \param name A domain name for which the zone data is set.
/// \param data The new zone data to set.
/// \return A \c FindResult object containing the old data if the
/// zone was found.
FindResult setZoneData(const isc::dns::Name& name, ZoneData* data);
private:
boost::interprocess::offset_ptr<ZoneTableTree> zones_;
};

View File

@@ -30,6 +30,9 @@ libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
libb10_dhcpsrv_la_SOURCES = cfgmgr.cc cfgmgr.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.h
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)

View File

@@ -14,46 +14,80 @@
#include <dhcp/addr_utilities.h>
using namespace isc::asiolink;
namespace isc {
namespace dhcp {
isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t len) {
uint8_t len) {
static char bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
uint8_t packed[16];
uint8_t packed[V6ADDRESS_LEN];
// First we copy the whole address as 16 bytes.
memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
// If the length is divisible by 8, it is simple. We just zero out the host
// part. Otherwise we need to handle the byte that has to be partially
// zeroed.
if (len % 8 != 0) {
// Get the appropriate mask. It has relevant bits (those that should
// stay) set and irrelevant (those that should be wiped) cleared.
uint8_t mask = bitMask[len % 8];
// Let's leave only whatever the mask says should not be cleared.
packed[len / 8] = packed[len / 8] & mask;
len = (len/8 + 1) * 8;
// Since we have just dealt with this byte, let's move the prefix length
// to the beginning of the next byte (len is expressed in bits).
len = (len / 8 + 1) * 8;
}
for (int i = len / 8; i < 16; ++i) {
// Clear out the remaining bits.
for (int i = len / 8; i < sizeof(packed); ++i) {
packed[i] = 0x0;
}
// Finally, let's wrap this into nice and easy IOAddress object.
return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
}
isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t len) {
uint8_t len) {
static char bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
uint8_t packed[16];
uint8_t packed[V6ADDRESS_LEN];
// First we copy the whole address as 16 bytes.
memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
// if the length is divisible by 8, it is simple. We just fill the host part
// with ones. Otherwise we need to handle the byte that has to be partially
// zeroed.
if (len % 8 != 0) {
// Get the appropriate mask. It has relevant bits (those that should
// stay) set and irrelevant (those that should be set to 1) cleared.
uint8_t mask = bitMask[len % 8];
// Let's set those irrelevant bits with 1. It would be perhaps
// easier to not use negation here and invert bitMask content. However,
// with this approach, we can use the same mask in first and last
// address calculations.
packed[len / 8] = packed[len / 8] | ~mask;
len = (len/8 + 1) * 8;
// Since we have just dealt with this byte, let's move the prefix length
// to the beginning of the next byte (len is expressed in bits).
len = (len / 8 + 1) * 8;
}
for (int i = len / 8; i < 16; ++i) {
// Finally set remaining bits to 1.
for (int i = len / 8; i < sizeof(packed); ++i) {
packed[i] = 0xff;
}
// Finally, let's wrap this into nice and easy IOAddress object.
return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
}

View File

@@ -18,32 +18,36 @@ namespace isc {
namespace dhcp {
/// This code is based on similar code from the Dibbler project. I, Tomasz Mrugalski,
/// as a sole creater of that code hereby release it under BSD license for the benefit
/// as a sole creator of that code hereby release it under BSD license for the benefit
/// of the BIND10 project.
/// @brief returns a first address in a given prefix
///
/// Example: For 2001:db8:1::deaf:beef and length /120 the function will return
/// 2001:db8:1::dead:bee0. See also @ref lastAddrInPrefix.
/// 2001:db8:1::dead:be00. See also @ref lastAddrInPrefix.
///
/// @todo It currently works for v6 only and will throw if v4 address is passed.
///
/// @param prefix and address that belongs to a prefix
/// @param len prefix length
///
/// @return first address from a prefix
isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t len);
uint8_t len);
/// @brief returns a last address in a given prefix
///
/// Example: For 2001:db8:1::deaf:beef and length /112 the function will return
/// 2001:db8:1::dead:ffff. See also @ref firstAddrInPrefix.
///
/// @todo It currently works for v6 only and will throw if v4 address is passed.
///
/// @param prefix and address that belongs to a prefix
/// @param len prefix length
///
/// @return first address from a prefix
isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t len);
uint8_t len);
};
};

View File

@@ -12,7 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/addr_utilities.h>
#include <asiolink/io_address.h>
#include <dhcp/cfgmgr.h>
@@ -22,125 +21,7 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
Pool::Pool(const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
:id_(getNextID()), first_(first), last_(last) {
}
bool Pool::inRange(const isc::asiolink::IOAddress& addr) {
return ( first_.smallerEqual(addr) && addr.smallerEqual(last_) );
}
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
:Pool(first, last), type_(type), prefix_len_(0) {
// check if specified address boundaries are sane
if (first.getFamily() != AF_INET6 || last.getFamily() != AF_INET6) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
if (last < first) {
isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
// This check is a bit strict. If we decide that it is too strict,
// we need to comment it and uncomment lines below.
// On one hand, letting the user specify 2001::f - 2001::1 is nice, but
// on the other hand, 2001::1 may be a typo and the user really meant
// 2001::1:0 (or 1something), so a at least a warning would be useful.
// first_ = last;
// last_ = first;
}
// TYPE_PD is not supported by this constructor. first-last style
// parameters are for IA and TA only. There is another dedicated
// constructor for that (it uses prefix/length)
if ((type != TYPE_IA) && (type != TYPE_TA)) {
isc_throw(BadValue, "Invalid Pool6 type specified");
}
}
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len)
:Pool(prefix, IOAddress("::")),
type_(type), prefix_len_(prefix_len) {
// check if the prefix is sane
if (prefix.getFamily() != AF_INET6) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
// check if the prefix length is sane
if (prefix_len == 0 || prefix_len > 128) {
isc_throw(BadValue, "Invalid prefix length");
}
// Let's now calculate the last address in defined pool
last_ = lastAddrInPrefix(prefix, prefix_len);
}
Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime)
:id_(getNextID()), prefix_(prefix), prefix_len_(len), t1_(t1),
t2_(t2), valid_(valid_lifetime) {
if ( (prefix.getFamily() == AF_INET6 && len > 128) ||
(prefix.getFamily() == AF_INET && len > 32) ) {
isc_throw(BadValue, "Invalid prefix length specified for subnet: " << len);
}
}
bool Subnet::inRange(const isc::asiolink::IOAddress& addr) {
IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
IOAddress last = lastAddrInPrefix(prefix_, prefix_len_);
return ( (first <= addr) && (addr <= last) );
}
Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& preferred_lifetime,
const Triplet<uint32_t>& valid_lifetime)
:Subnet(prefix, length, t1, t2, valid_lifetime),
preferred_(preferred_lifetime){
if (prefix.getFamily() != AF_INET6) {
isc_throw(BadValue, "Invalid prefix " << prefix.toText()
<< " specified in subnet6");
}
}
void Subnet6::addPool6(const Pool6Ptr& pool) {
IOAddress first_addr = pool->getFirstAddress();
IOAddress last_addr = pool->getLastAddress();
if (!inRange(first_addr) || !inRange(last_addr)) {
isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
<< " does not belong in this (" << prefix_ << "/" << prefix_len_
<< ") subnet6");
}
pools_.push_back(pool);
}
Pool6Ptr Subnet6::getPool6(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
Pool6Ptr candidate;
for (Pool6Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
// if we won't find anything better, then let's just use the first pool
if (!candidate) {
candidate = *pool;
}
// if the client provided a pool and there's a pool that hint is valid in,
// then let's use that pool
if ((*pool)->inRange(hint)) {
return (*pool);
}
}
return (candidate);
}
CfgMgr&
@@ -153,6 +34,13 @@ Subnet6Ptr
CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// If there's only one subnet configured, let's just use it
// The idea is to keep small deployments easy. In a small network - one
// router that also runs DHCPv6 server. Users specifies a single pool and
// expects it to just work. Without this, the server would complain that it
// doesn't have IP address on its interfaces that matches that
// configuration. Such requirement makes sense in IPv4, but not in IPv6.
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if (subnets6_.size() == 1) {
return (subnets6_[0]);
}

View File

@@ -23,331 +23,12 @@
#include <asiolink/io_address.h>
#include <util/buffer.h>
#include <dhcp/option.h>
#include <dhcp/pool.h>
#include <dhcp/subnet.h>
namespace isc {
namespace dhcp {
class Pool6;
class Subnet6;
/// @brief this template specifes a parameter value
///
/// This template class is used to store configuration parameters, like lifetime or T1.
/// It defines 3 parameters: min, default, and max value. There are 2 constructors:
/// - simple (just one value that sets all parameters)
/// - extended (that sets default value and two thresholds)
/// It will be used with integer types. It provides necessary operators, so
/// it can be assigned to a plain integer or integer assigned to a Triplet.
/// See TripletTest.operator test for details on an easy Triplet usage.
template <class T>
class Triplet {
public:
/// @brief base type to Triple conversion
///
/// Typically: uint32_t to Triplet assignment. It is very convenient
/// to be able to simply write Triplet<uint32_t> x = 7;
Triplet<T>& operator = (T base_type) {
return Triplet<T>(base_type);
}
/// @brief triplet to base type conversion
///
/// Typically: Triplet to uint32_t assignment. It is very convenient
/// to be able to simply write uint32_t z = x; (where x is a Triplet)
operator T () const {
return (default_);
}
/// @brief sets a fixed value
///
/// This constructor assigns a fixed (i.e. no range, just a single value)
/// value.
Triplet(T value)
:min_(value), default_(value), max_(value) {
}
/// @brief sets the default value and thresholds
///
/// @throw BadValue if min <= def <= max rule is violated
Triplet(T min, T def, T max)
:min_(min), default_(def), max_(max) {
if ( (min_>def) || (def > max_) ) {
isc_throw(BadValue, "Invalid triplet values.");
}
}
/// @brief returns a minimum allowed value
T getMin() const { return min_;}
/// @brief returns the default value
T get() const { return default_;}
/// @brief returns value with a hint
///
/// DHCP protocol treats any values sent by a client as hints.
/// This is a method that implements that. We can assign any value
/// from configured range that client asks.
T get(T hint) const {
if (hint <= min_) {
return (min_);
}
if (hint >= max_) {
return (max_);
}
return (hint);
}
/// @brief returns a maximum allowed value
T getMax() const { return max_; }
protected:
/// @brief the minimum value
T min_;
/// @brief the default value
T default_;
/// @brief the maximum value
T max_;
};
/// @brief base class for Pool4 and Pool6
///
/// Stores information about pool of IPv4 or IPv6 addresses.
/// That is a basic component of a configuration.
class Pool {
public:
/// @brief returns Pool-id
///
/// Pool-id is an unique value that can be used to identify a pool.
uint32_t getId() const {
return (id_);
}
/// @brief Returns the first address in a pool.
///
/// @return first address in a pool
const isc::asiolink::IOAddress& getFirstAddress() const {
return (first_);
}
/// @brief Returns the last address in a pool.
/// @return last address in a pool
const isc::asiolink::IOAddress& getLastAddress() const {
return (last_);
}
/// @brief Checks if a given address is in the range.
///
/// @return true, if the address is in pool
bool inRange(const isc::asiolink::IOAddress& addr);
protected:
/// @brief protected constructor
///
/// This constructor is protected to prevent anyone from instantiating
/// Pool class directly. Instances of Pool4 and Pool6 should be created
/// instead.
Pool(const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last);
/// @brief returns the next unique Pool-ID
///
/// @return the next unique Pool-ID
static uint32_t getNextID() {
static uint32_t id = 0;
return (id++);
}
/// @brief pool-id
///
/// This ID is used to indentify this specific pool.
uint32_t id_;
/// @brief The first address in a pool
isc::asiolink::IOAddress first_;
/// @brief The last address in a pool
isc::asiolink::IOAddress last_;
/// @brief Comments field
///
/// @todo: This field is currently not used.
std::string comments_;
};
/// @brief Pool information for IPv6 addresses and prefixes
///
/// It holds information about pool6, i.e. a range of IPv6 address space that
/// is configured for DHCP allocation.
class Pool6 : public Pool {
public:
/// @brief specifies Pool type
///
/// Currently there are 3 pool types defined in DHCPv6:
/// - Non-temporary addresses (conveyed in IA_NA)
/// - Temporary addresses (conveyed in IA_TA)
/// - Delegated Prefixes (conveyed in IA_PD)
/// There is a new one being worked on (IA_PA, see draft-ietf-dhc-host-gen-id), but
/// support for it is not planned for now.
typedef enum {
TYPE_IA,
TYPE_TA,
TYPE_PD
} Pool6Type;
/// @brief the constructor for Pool6 "min-max" style definition
///
/// @param first the first address in a pool
/// @param last the last address in a pool
Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last);
/// @brief the constructor for Pool6 "prefix/len" style definition
///
/// @param prefix specifies prefix of the pool
/// @param prefix_len specifies length of the prefix of the pool
Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len);
/// @brief returns pool type
///
/// @return pool type
Pool6Type getType() const {
return (type_);
}
protected:
/// @brief defines a pool type
Pool6Type type_;
/// @brief prefix length
/// used by TYPE_PD only (zeroed for other types)
uint8_t prefix_len_;
};
/// @brief a pointer an IPv6 Pool
typedef boost::shared_ptr<Pool6> Pool6Ptr;
/// @brief a container for IPv6 Pools
typedef std::vector<Pool6Ptr> Pool6Collection;
/// @brief a base class for Subnet4 and Subnet6
///
/// This class presents a common base for IPv4 and IPv6 subnets.
/// In a physical sense, a subnet defines a single network link with all devices
/// attached to it. In most cases all devices attached to a single link can
/// share the same parameters. Therefore Subnet holds several values that are
/// typically shared by all hosts: renew timer (T1), rebind timer (T2) and
/// leased addresses lifetime (valid-lifetime).
///
/// @todo: Implement support for options here
class Subnet {
public:
/// @brief checks if specified address is in range
bool inRange(const isc::asiolink::IOAddress& addr);
/// @brief return valid-lifetime for addresses in that prefix
Triplet<uint32_t> getValid() const {
return (valid_);
}
/// @brief returns T1 (renew timer), expressed in seconds
Triplet<uint32_t> getT1() const {
return (t1_);
}
/// @brief returns T2 (rebind timer), expressed in seconds
Triplet<uint32_t> getT2() const {
return (t2_);
}
protected:
/// @brief protected constructor
//
/// By making the constructor protected, we make sure that noone will
/// ever instantiate that class. Pool4 and Pool6 should be used instead.
Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime);
/// @brief returns the next unique Subnet-ID
///
/// @return the next unique Subnet-ID
static uint32_t getNextID() {
static uint32_t id = 0;
return (id++);
}
/// @brief subnet-id
///
/// Subnet-id is a unique value that can be used to find or identify
/// a Subnet4 or Subnet6.
uint32_t id_;
/// @brief a prefix of the subnet
isc::asiolink::IOAddress prefix_;
/// @brief a prefix length of the subnet
uint8_t prefix_len_;
/// @brief a tripet (min/default/max) holding allowed renew timer values
Triplet<uint32_t> t1_;
/// @brief a tripet (min/default/max) holding allowed rebind timer values
Triplet<uint32_t> t2_;
/// @brief a tripet (min/default/max) holding allowed valid lifetime values
Triplet<uint32_t> valid_;
};
/// @brief A configuration holder for IPv6 subnet.
///
/// This class represents an IPv6 subnet.
class Subnet6 : public Subnet {
public:
Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& preferred_lifetime,
const Triplet<uint32_t>& valid_lifetime);
Triplet<uint32_t> getPreferred() const {
return (preferred_);
}
Pool6Ptr getPool6(const isc::asiolink::IOAddress& hint =
isc::asiolink::IOAddress("::"));
void addPool6(const Pool6Ptr& pool);
const Pool6Collection& getPools() const {
return pools_;
}
protected:
/// collection of pools in that list
Pool6Collection pools_;
Triplet<uint32_t> preferred_;
};
/// @brief A pointer to a Subnet6 object
typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
/// @brief A collection of Subnet6 objects
typedef std::vector<Subnet6Ptr> Subnet6Collection;
/// @brief Configuration Manager
///

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2012 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
@@ -727,6 +727,13 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
m.msg_control = &control_buf_[0];
m.msg_controllen = control_buf_len_;
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&m);
// FIXME: Code below assumes that cmsg is not NULL, but
// CMSG_FIRSTHDR() is coded to return NULL as a possibility. The
// following assertion should never fail, but if it did and you came
// here, fix the code. :)
assert(cmsg != NULL);
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
@@ -813,8 +820,12 @@ IfaceMgr::send(const Pkt4Ptr& pkt)
boost::shared_ptr<Pkt4>
IfaceMgr::receive4(uint32_t timeout) {
IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
// Sanity check for microsecond timeout.
if (timeout_usec >= 1000000) {
isc_throw(BadValue, "fractional timeout must be shorter than"
" one million microseconds");
}
const SocketInfo* candidate = 0;
IfaceCollection::const_iterator iface;
fd_set sockets;
@@ -854,13 +865,13 @@ IfaceMgr::receive4(uint32_t timeout) {
names << session_socket_ << "(session)";
}
/// @todo: implement sub-second precision one day
struct timeval select_timeout;
select_timeout.tv_sec = timeout;
select_timeout.tv_usec = 0;
select_timeout.tv_sec = timeout_sec;
select_timeout.tv_usec = timeout_usec;
cout << "Trying to receive data on sockets: " << names.str()
<< ". Timeout is " << timeout << " seconds." << endl;
<< ". Timeout is " << timeout_sec << "." << setw(6) << setfill('0')
<< timeout_usec << " seconds." << endl;
int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
cout << "select returned " << result << endl;
@@ -983,7 +994,12 @@ IfaceMgr::receive4(uint32_t timeout) {
return (pkt);
}
Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
// Sanity check for microsecond timeout.
if (timeout_usec >= 1000000) {
isc_throw(BadValue, "fractional timeout must be shorter than"
" one million microseconds");
}
const SocketInfo* candidate = 0;
fd_set sockets;
@@ -1023,13 +1039,13 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
names << session_socket_ << "(session)";
}
cout << "Trying to receive data on sockets:" << names.str()
<< ".Timeout is " << timeout << " seconds." << endl;
cout << "Trying to receive data on sockets: " << names.str()
<< ". Timeout is " << timeout_sec << "." << setw(6) << setfill('0')
<< timeout_usec << " seconds." << endl;
/// @todo: implement sub-second precision one day
struct timeval select_timeout;
select_timeout.tv_sec = timeout;
select_timeout.tv_usec = 0;
select_timeout.tv_sec = timeout_sec;
select_timeout.tv_usec = timeout_usec;
int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2012 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
@@ -364,10 +364,13 @@ public:
/// to not wait infinitely, but rather do something useful
/// (e.g. remove expired leases)
///
/// @param timeout specifies timeout (in seconds)
/// @param timeout_sec specifies integral part of the timeout (in seconds)
/// @param timeout_usec specifies fractional part of the timeout
/// (in microseconds)
///
/// @throw isc::BadValue if timeout_usec is greater than one million
/// @return Pkt6 object representing received packet (or NULL)
Pkt6Ptr receive6(uint32_t timeout);
Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec = 0);
/// @brief Tries to receive IPv4 packet over open IPv4 sockets.
///
@@ -375,10 +378,13 @@ public:
/// If reception is successful and all information about its sender
/// are obtained, Pkt4 object is created and returned.
///
/// @param timeout specifies timeout (in seconds)
/// @param timeout_sec specifies integral part of the timeout (in seconds)
/// @param timeout_usec specifies fractional part of the timeout
/// (in microseconds)
///
/// @throw isc::BadValue if timeout_usec is greater than one million
/// @return Pkt4 object representing received packet (or NULL)
Pkt4Ptr receive4(uint32_t timeout);
Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec = 0);
/// Opens UDP/IP socket and binds it to address, interface and port.
///

87
src/lib/dhcp/pool.cc Normal file
View File

@@ -0,0 +1,87 @@
// Copyright (C) 2012 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 <asiolink/io_address.h>
#include <dhcp/addr_utilities.h>
#include <dhcp/pool.h>
using namespace isc::asiolink;
namespace isc {
namespace dhcp {
Pool::Pool(const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
:id_(getNextID()), first_(first), last_(last) {
}
bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
return (first_.smallerEqual(addr) && addr.smallerEqual(last_));
}
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
:Pool(first, last), type_(type), prefix_len_(0) {
// check if specified address boundaries are sane
if (first.getFamily() != AF_INET6 || last.getFamily() != AF_INET6) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
if (last < first) {
isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
// This check is a bit strict. If we decide that it is too strict,
// we need to comment it and uncomment lines below.
// On one hand, letting the user specify 2001::f - 2001::1 is nice, but
// on the other hand, 2001::1 may be a typo and the user really meant
// 2001::1:0 (or 1 followed by some hex digit), so a at least a warning
// would be useful.
// first_ = last;
// last_ = first;
}
// TYPE_PD is not supported by this constructor. first-last style
// parameters are for IA and TA only. There is another dedicated
// constructor for that (it uses prefix/length)
if ((type != TYPE_IA) && (type != TYPE_TA)) {
isc_throw(BadValue, "Invalid Pool6 type specified");
}
}
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len)
:Pool(prefix, IOAddress("::")),
type_(type), prefix_len_(prefix_len) {
// check if the prefix is sane
if (prefix.getFamily() != AF_INET6) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
// check if the prefix length is sane
if (prefix_len == 0 || prefix_len > 128) {
isc_throw(BadValue, "Invalid prefix length");
}
/// @todo: We should probably implement checks against weird addresses
/// here, like ::, starting with fe80, starting with ff etc. .
// Let's now calculate the last address in defined pool
last_ = lastAddrInPrefix(prefix, prefix_len);
}
}; // end of isc::dhcp namespace
}; // end of isc namespace

155
src/lib/dhcp/pool.h Normal file
View File

@@ -0,0 +1,155 @@
// Copyright (C) 2012 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 POOL_H
#define POOL_H
#include <vector>
#include <asiolink/io_address.h>
#include <boost/shared_ptr.hpp>
namespace isc {
namespace dhcp {
/// @brief base class for Pool4 and Pool6
///
/// Stores information about pool of IPv4 or IPv6 addresses.
/// That is a basic component of a configuration.
class Pool {
public:
/// @brief returns Pool-id
///
/// @return pool-id value
/// Pool-id is an unique value that can be used to identify a pool.
uint32_t getId() const {
return (id_);
}
/// @brief Returns the first address in a pool.
///
/// @return first address in a pool
const isc::asiolink::IOAddress& getFirstAddress() const {
return (first_);
}
/// @brief Returns the last address in a pool.
/// @return last address in a pool
const isc::asiolink::IOAddress& getLastAddress() const {
return (last_);
}
/// @brief Checks if a given address is in the range.
///
/// @return true, if the address is in pool
bool inRange(const isc::asiolink::IOAddress& addr) const;
protected:
/// @brief protected constructor
///
/// This constructor is protected to prevent anyone from instantiating
/// Pool class directly. Instances of Pool4 and Pool6 should be created
/// instead.
Pool(const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last);
/// @brief returns the next unique Pool-ID
///
/// @return the next unique Pool-ID
static uint32_t getNextID() {
static uint32_t id = 0;
return (id++);
}
/// @brief pool-id
///
/// This ID is used to identify this specific pool.
uint32_t id_;
/// @brief The first address in a pool
isc::asiolink::IOAddress first_;
/// @brief The last address in a pool
isc::asiolink::IOAddress last_;
/// @brief Comments field
///
/// @todo: This field is currently not used.
std::string comments_;
};
/// @brief Pool information for IPv6 addresses and prefixes
///
/// It holds information about pool6, i.e. a range of IPv6 address space that
/// is configured for DHCP allocation.
class Pool6 : public Pool {
public:
/// @brief specifies Pool type
///
/// Currently there are 3 pool types defined in DHCPv6:
/// - Non-temporary addresses (conveyed in IA_NA)
/// - Temporary addresses (conveyed in IA_TA)
/// - Delegated Prefixes (conveyed in IA_PD)
/// There is a new one being worked on (IA_PA, see draft-ietf-dhc-host-gen-id), but
/// support for it is not planned for now.
typedef enum {
TYPE_IA,
TYPE_TA,
TYPE_PD
} Pool6Type;
/// @brief the constructor for Pool6 "min-max" style definition
///
/// @param first the first address in a pool
/// @param last the last address in a pool
Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last);
/// @brief the constructor for Pool6 "prefix/len" style definition
///
/// @param prefix specifies prefix of the pool
/// @param prefix_len specifies length of the prefix of the pool
Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len);
/// @brief returns pool type
///
/// @return pool type
Pool6Type getType() const {
return (type_);
}
private:
/// @brief defines a pool type
Pool6Type type_;
/// @brief prefix length
/// used by TYPE_PD only (zeroed for other types)
uint8_t prefix_len_;
};
/// @brief a pointer an IPv6 Pool
typedef boost::shared_ptr<Pool6> Pool6Ptr;
/// @brief a container for IPv6 Pools
typedef std::vector<Pool6Ptr> Pool6Collection;
} // end of isc::dhcp namespace
} // end of isc namespace
#endif // POOL_H

Some files were not shown because too many files have changed in this diff Show More