Commit 3c285e7e authored by Stephen Morris's avatar Stephen Morris
Browse files

[2981] Merge branch 'master' into trac2981

Conflicts:
	src/bin/dhcp6/config_parser.cc
	src/bin/dhcp6/dhcp6.spec
	src/bin/dhcp6/tests/Makefile.am
	src/bin/dhcp6/tests/config_parser_unittest.cc
	src/lib/dhcpsrv/dhcp_parsers.h
	src/lib/dhcpsrv/tests/Makefile.am
	src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
parents c706b28a c845a960
646. [func] stephen
Extended the hooks framework to add a "validate libraries" function.
This will be used to check libraries specified during BIND 10
configuration.
(Trac #3054, git 0f845ed94f462dee85b67f056656b2a197878b04)
645. [func] tomek
Added initial set of hooks (pk4_receive, subnet4_select,
lease4_select, pkt4_send) to the DHCPv6 server.
(Trac #2994, git be65cfba939a6a7abd3c93931ce35c33d3e8247b)
644. [func] marcin
b10-dhcp4, b10-dhcp6: Implemented selection of the interfaces
that server listens on, using Configuration Manager. It is
possible to specify interface names explicitly or use asterisk
to specify that server should listen on all available interfaces.
Sockets are reopened according to the new configuration as
soon as it is committed.
(Trac #1555, git f48a3bff3fbbd15584d788a264d5966154394f04)
643. [bug] muks
When running some unittests as root that depended on insufficient
file permissions, the tests used to fail because the root user
could still access such files. Such tests are now skipped when
they are run as the root user.
(Trac #3056, git 92ebabdbcf6168666b03d7f7fbb31f899be39322)
642. [func] tomek
Added initial set of hooks (pk6_receive, subnet6_select,
lease6_select, pkt6_send) to the DHCPv6 server.
(Trac #2995, git d6de376f97313ba40fef989e4a437d184fdf70cc)
641. [func] stephen
Added the hooks framework. This allows shared libraries of
user-written functions to be loaded at run-time and the
......
......@@ -37,6 +37,8 @@
* <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
* @section hooksFramework Hooks Framework
* - @subpage hooksComponentDeveloperGuide
* - @subpage dhcpv4Hooks
* - @subpage dhcpv6Hooks
*
* @section dnsMaintenanceGuide DNS Maintenance Guide
* - Authoritative DNS (todo)
......@@ -48,10 +50,12 @@
* - @subpage dhcpv4Session
* - @subpage dhcpv4ConfigParser
* - @subpage dhcpv4ConfigInherit
* - @subpage dhcpv4Other
* - @subpage dhcp6
* - @subpage dhcpv6Session
* - @subpage dhcpv6ConfigParser
* - @subpage dhcpv6ConfigInherit
* - @subpage dhcpv6Other
* - @subpage libdhcp
* - @subpage libdhcpIntro
* - @subpage libdhcpRelay
......
......@@ -3599,7 +3599,7 @@ $</screen>
will be available. It will look similar to this:
<screen>
&gt; <userinput>config show Dhcp4</userinput>
Dhcp4/interface/ list (default)
Dhcp4/interfaces/ list (default)
Dhcp4/renew-timer 1000 integer (default)
Dhcp4/rebind-timer 2000 integer (default)
Dhcp4/valid-lifetime 4000 integer (default)
......@@ -3686,6 +3686,60 @@ Dhcp4/subnet4 [] list (default)
</note>
</section>
<section id="dhcp4-interface-selection">
<title>Interface selection</title>
<para>
When DHCPv4 server starts up, by default it will listen to the DHCP
traffic and respond to it on all interfaces detected during startup.
However, in many cases it is desired to configure the server to listen and
respond on selected interfaces only. The sample commands in this section
show how to make interface selection using bindctl.
</para>
<para>
The default configuration can be presented with the following command:
<screen>
&gt; <userinput>config show Dhcp4/interfaces</userinput>
<userinput>Dhcp4/interfaces[0] "*" string</userinput></screen>
An asterisk sign plays a role of the wildcard and means "listen on all interfaces".
</para>
<para>
In order to override the default configuration, the existing entry can be replaced
with the actual interface name:
<screen>
&gt; <userinput>config set Dhcp4/interfaces[0] eth1</userinput>
&gt; <userinput>config commit</userinput></screen>
Other interface names can be added on one-by-one basis:
<screen>
&gt; <userinput>config add Dhcp4/interfaces eth2</userinput>
&gt; <userinput>config commit</userinput></screen>
Configuration will now contain two interfaces which can be presented as follows:
<screen>
&gt; <userinput>config show Dhcp4/interfaces</userinput>
<userinput>Dhcp4/interfaces[0] "eth1" string</userinput>
<userinput>Dhcp4/interfaces[1] "eth2" string</userinput></screen>
When configuration gets committed, the server will start to listen on
eth1 and eth2 interfaces only.
</para>
<para>
It is possible to use wildcard interface name (asterisk) concurrently with explicit
interface names:
<screen>
&gt; <userinput>config add Dhcp4/interfaces *</userinput>
&gt; <userinput>config commit</userinput></screen>
This will result in the following configuration:
<screen>
&gt; <userinput>config show Dhcp4/interfaces</userinput>
<userinput>Dhcp4/interfaces[0] "eth1" string</userinput>
<userinput>Dhcp4/interfaces[1] "eth2" string</userinput>
<userinput>Dhcp4/interfaces[2] "*" string</userinput></screen>
The presence of the wildcard name implies that server will listen on all interfaces.
In order to fall back to the previous configuration when server listens on eth1 and eth2:
<screen>
&gt; <userinput>config remove Dhcp4/interfaces[2]</userinput>
&gt; <userinput>config commit</userinput></screen>
</para>
</section>
<section id="dhcp4-address-config">
<title>Configuration of Address Pools</title>
<para>
......@@ -4366,7 +4420,7 @@ Dhcp4/renew-timer 1000 integer (default)
will be available. It will look similar to this:
<screen>
&gt; <userinput>config show Dhcp6</userinput>
Dhcp6/interface/ list (default)
Dhcp6/interfaces/ list (default)
Dhcp6/renew-timer 1000 integer (default)
Dhcp6/rebind-timer 2000 integer (default)
Dhcp6/preferred-lifetime 3000 integer (default)
......@@ -4459,6 +4513,59 @@ Dhcp6/subnet6/ list
</note>
</section>
<section id="dhcp6-interface-selection">
<title>Interface selection</title>
<para>
When DHCPv6 server starts up, by default it will listen to the DHCP
traffic and respond to it on all interfaces detected during startup.
However, in many cases it is desired to configure the server to listen and
respond on selected interfaces only. The sample commands in this section
show how to make interface selection using bindctl.
</para>
<para>
The default configuration can be presented with the following command:
<screen>
&gt; <userinput>config show Dhcp6/interfaces</userinput>
<userinput>Dhcp6/interfaces[0] "*" string</userinput></screen>
An asterisk sign plays a role of the wildcard and means "listen on all interfaces".
</para>
<para>
In order to override the default configuration, the existing entry can be replaced
with the actual interface name:
<screen>
&gt; <userinput>config set Dhcp6/interfaces[0] eth1</userinput>
&gt; <userinput>config commit</userinput></screen>
Other interface names can be added on one-by-one basis:
<screen>
&gt; <userinput>config add Dhcp6/interfaces eth2</userinput>
&gt; <userinput>config commit</userinput></screen>
Configuration will now contain two interfaces which can be presented as follows:
<screen>
&gt; <userinput>config show Dhcp6/interfaces</userinput>
<userinput>Dhcp6/interfaces[0] "eth1" string</userinput>
<userinput>Dhcp6/interfaces[1] "eth2" string</userinput></screen>
When configuration gets committed, the server will start to listen on
eth1 and eth2 interfaces only.
</para>
<para>
It is possible to use wildcard interface name (asterisk) concurrently with explicit
interface names:
<screen>
&gt; <userinput>config add Dhcp6/interfaces *</userinput>
&gt; <userinput>config commit</userinput></screen>
This will result in the following configuration:
<screen>
&gt; <userinput>config show Dhcp6/interfaces</userinput>
<userinput>Dhcp6/interfaces[0] "eth1" string</userinput>
<userinput>Dhcp6/interfaces[1] "eth2" string</userinput>
<userinput>Dhcp6/interfaces[2] "*" string</userinput></screen>
The presence of the wildcard name implies that server will listen on all interfaces.
In order to fall back to the previous configuration when server listens on eth1 and eth2:
<screen>
&gt; <userinput>config remove Dhcp6/interfaces[2]</userinput>
&gt; <userinput>config commit</userinput></screen>
</para>
</section>
<section>
<title>Subnet and Address Pool</title>
......
......@@ -151,6 +151,10 @@ A separate thread for maintaining data source clients has been started.
% AUTH_DATASRC_CLIENTS_BUILDER_STOPPED data source builder thread stopped
The separate thread for maintaining data source clients has been stopped.
% AUTH_DATASRC_CLIENTS_BUILDER_WAKE_ERR failed to wake up main thread: %1
A low-level error happened when trying to send data to the main thread to wake
it up. Terminating to prevent inconsistent state and possiblu hang ups.
% AUTH_DATASRC_CLIENTS_SHUTDOWN_ERROR error on waiting for data source builder thread: %1
This indicates that the separate thread for maintaining data source
clients had been terminated due to an uncaught exception, and the
......
......@@ -319,6 +319,7 @@ AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client,
xfrin_session_(NULL),
counters_(),
keyring_(NULL),
datasrc_clients_mgr_(io_service_),
ddns_base_forwarder_(ddns_forwarder),
ddns_forwarder_(NULL),
xfrout_connected_(false),
......
......@@ -29,6 +29,9 @@
#include <datasrc/client_list.h>
#include <datasrc/memory/zone_writer.h>
#include <asiolink/io_service.h>
#include <asiolink/local_socket.h>
#include <auth/auth_log.h>
#include <auth/datasrc_config.h>
......@@ -36,11 +39,16 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
#include <boost/function.hpp>
#include <boost/foreach.hpp>
#include <exception>
#include <cassert>
#include <cerrno>
#include <list>
#include <utility>
#include <sys/types.h>
#include <sys/socket.h>
namespace isc {
namespace auth {
......@@ -77,13 +85,40 @@ enum CommandID {
NUM_COMMANDS
};
/// \brief Callback to be called when the command is completed.
typedef boost::function<void ()> FinishedCallback;
/// \brief The data type passed from DataSrcClientsMgr to
/// DataSrcClientsBuilder.
/// DataSrcClientsBuilder.
///
/// The first element of the pair is the command ID, and the second element
/// is its argument. If the command doesn't take an argument it should be
/// a null pointer.
typedef std::pair<CommandID, data::ConstElementPtr> Command;
/// This just holds the data items together, no logic or protection
/// is present here.
struct Command {
/// \brief Constructor
///
/// It just initializes the member variables of the same names
/// as the parameters.
Command(CommandID id, const data::ConstElementPtr& params,
const FinishedCallback& callback) :
id(id),
params(params),
callback(callback)
{}
/// \brief The command to execute
CommandID id;
/// \brief Argument of the command.
///
/// If the command takes no argument, it should be null pointer.
///
/// This may be a null pointer if the command takes no parameters.
data::ConstElementPtr params;
/// \brief A callback to be called once the command finishes.
///
/// This may be an empty boost::function. In such case, no callback
/// will be called after completion.
FinishedCallback callback;
};
} // namespace datasrc_clientmgr_internal
/// \brief Frontend to the manager object for data source clients.
......@@ -113,6 +148,24 @@ private:
boost::shared_ptr<datasrc::ConfigurableClientList> >
ClientListsMap;
class FDGuard : boost::noncopyable {
public:
FDGuard(DataSrcClientsMgrBase *mgr) :
mgr_(mgr)
{}
~FDGuard() {
if (mgr_->read_fd_ != -1) {
close(mgr_->read_fd_);
}
if (mgr_->write_fd_ != -1) {
close(mgr_->write_fd_);
}
}
private:
DataSrcClientsMgrBase* mgr_;
};
friend class FDGuard;
public:
/// \brief Thread-safe accessor to the data source client lists.
///
......@@ -176,12 +229,20 @@ public:
///
/// \throw std::bad_alloc internal memory allocation failure.
/// \throw isc::Unexpected general unexpected system errors.
DataSrcClientsMgrBase() :
DataSrcClientsMgrBase(asiolink::IOService& service) :
clients_map_(new ClientListsMap),
builder_(&command_queue_, &cond_, &queue_mutex_, &clients_map_,
&map_mutex_),
builder_thread_(boost::bind(&BuilderType::run, &builder_))
{}
fd_guard_(new FDGuard(this)),
read_fd_(-1), write_fd_(-1),
builder_(&command_queue_, &callback_queue_, &cond_, &queue_mutex_,
&clients_map_, &map_mutex_, createFds()),
builder_thread_(boost::bind(&BuilderType::run, &builder_)),
wakeup_socket_(service, read_fd_)
{
// Schedule wakeups when callbacks are pushed.
wakeup_socket_.asyncRead(
boost::bind(&DataSrcClientsMgrBase::processCallbacks, this, _1),
buffer, 1);
}
/// \brief The destructor.
///
......@@ -220,6 +281,7 @@ public:
AUTH_DATASRC_CLIENTS_SHUTDOWN_UNEXPECTED_ERROR);
}
processCallbacks(); // Any leftover callbacks
cleanup(); // see below
}
......@@ -234,11 +296,18 @@ public:
/// \brief std::bad_alloc
///
/// \param config_arg The new data source configuration. Must not be NULL.
void reconfigure(data::ConstElementPtr config_arg) {
/// \param callback Called once the reconfigure command completes. It is
/// called in the main thread (not in the work one). It should be
/// exceptionless.
void reconfigure(const data::ConstElementPtr& config_arg,
const datasrc_clientmgr_internal::FinishedCallback&
callback = datasrc_clientmgr_internal::FinishedCallback())
{
if (!config_arg) {
isc_throw(InvalidParameter, "Invalid null config argument");
}
sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg);
sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg,
callback);
reconfigureHook(); // for test's customization
}
......@@ -257,12 +326,18 @@ public:
/// \param args Element argument that should be a map of the form
/// { "class": "IN", "origin": "example.com" }
/// (but class is optional and will default to IN)
/// \param callback Called once the loadZone command completes. It
/// is called in the main thread, not in the work thread. It should
/// be exceptionless.
///
/// \exception CommandError if the args value is null, or not in
/// the expected format, or contains
/// a bad origin or class string
void
loadZone(data::ConstElementPtr args) {
loadZone(const data::ConstElementPtr& args,
const datasrc_clientmgr_internal::FinishedCallback& callback =
datasrc_clientmgr_internal::FinishedCallback())
{
if (!args) {
isc_throw(CommandError, "loadZone argument empty");
}
......@@ -303,7 +378,7 @@ public:
// implement it would be to factor out the code from
// the start of doLoadZone(), and call it here too
sendCommand(datasrc_clientmgr_internal::LOADZONE, args);
sendCommand(datasrc_clientmgr_internal::LOADZONE, args, callback);
}
private:
......@@ -317,30 +392,79 @@ private:
void reconfigureHook() {}
void sendCommand(datasrc_clientmgr_internal::CommandID command,
data::ConstElementPtr arg)
const data::ConstElementPtr& arg,
const datasrc_clientmgr_internal::FinishedCallback&
callback = datasrc_clientmgr_internal::FinishedCallback())
{
// The lock will be held until the end of this method. Only
// push_back has to be protected, but we can avoid having an extra
// block this way.
typename MutexType::Locker locker(queue_mutex_);
command_queue_.push_back(
datasrc_clientmgr_internal::Command(command, arg));
datasrc_clientmgr_internal::Command(command, arg, callback));
cond_.signal();
}
int createFds() {
int fds[2];
int result = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
if (result != 0) {
isc_throw(Unexpected, "Can't create socket pair: " <<
strerror(errno));
}
read_fd_ = fds[0];
write_fd_ = fds[1];
return write_fd_;
}
void processCallbacks(const std::string& error = std::string()) {
// Schedule the next read.
wakeup_socket_.asyncRead(
boost::bind(&DataSrcClientsMgrBase::processCallbacks, this, _1),
buffer, 1);
if (!error.empty()) {
// Generally, there should be no errors (as we are the other end
// as well), but check just in case.
isc_throw(Unexpected, error);
}
// Steal the callbacks into local copy.
std::list<datasrc_clientmgr_internal::FinishedCallback> queue;
{
typename MutexType::Locker locker(queue_mutex_);
queue.swap(callback_queue_);
}
// Execute the callbacks
BOOST_FOREACH(const datasrc_clientmgr_internal::FinishedCallback&
callback, queue) {
callback();
}
}
//
// The following are shared with the builder.
//
// The list is used as a one-way queue: back-in, front-out
std::list<datasrc_clientmgr_internal::Command> command_queue_;
// Similar to above, for the callbacks that are ready to be called.
// While the command queue is for sending commands from the main thread
// to the work thread, this one is for the other direction. Protected
// by the same mutex (queue_mutex_).
std::list<datasrc_clientmgr_internal::FinishedCallback> callback_queue_;
CondVarType cond_; // condition variable for queue operations
MutexType queue_mutex_; // mutex to protect the queue
datasrc::ClientListMapPtr clients_map_;
// map of actual data source client objects
boost::scoped_ptr<FDGuard> fd_guard_; // A guard to close the fds.
int read_fd_, write_fd_; // Descriptors for wakeup
MutexType map_mutex_; // mutex to protect the clients map
BuilderType builder_;
ThreadType builder_thread_; // for safety this should be placed last
isc::asiolink::LocalSocket wakeup_socket_; // For integration of read_fd_
// to the asio loop
char buffer[1]; // Buffer for the wakeup socket.
};
namespace datasrc_clientmgr_internal {
......@@ -385,12 +509,15 @@ public:
///
/// \throw None
DataSrcClientsBuilderBase(std::list<Command>* command_queue,
std::list<FinishedCallback>* callback_queue,
CondVarType* cond, MutexType* queue_mutex,
datasrc::ClientListMapPtr* clients_map,
MutexType* map_mutex
MutexType* map_mutex,
int wake_fd
) :
command_queue_(command_queue), cond_(cond), queue_mutex_(queue_mutex),
clients_map_(clients_map), map_mutex_(map_mutex)
command_queue_(command_queue), callback_queue_(callback_queue),
cond_(cond), queue_mutex_(queue_mutex),
clients_map_(clients_map), map_mutex_(map_mutex), wake_fd_(wake_fd)
{}
/// \brief The main loop.
......@@ -457,10 +584,12 @@ private:
// The following are shared with the manager
std::list<Command>* command_queue_;
std::list<FinishedCallback> *callback_queue_;
CondVarType* cond_;
MutexType* queue_mutex_;
datasrc::ClientListMapPtr* clients_map_;
MutexType* map_mutex_;
int wake_fd_;
};
// Shortcut typedef for normal use
......@@ -494,6 +623,31 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::run() {
AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR).
arg(e.what());
}
if (current_commands.front().callback) {
// Lock the queue
typename MutexType::Locker locker(*queue_mutex_);
callback_queue_->
push_back(current_commands.front().callback);
// Wake up the other end. If it would block, there are data
// and it'll wake anyway.
int result = send(wake_fd_, "w", 1, MSG_DONTWAIT);
if (result == -1 &&
(errno != EWOULDBLOCK && errno != EAGAIN)) {
// Note: the strerror might not be thread safe, as
// subsequent call to it might change the returned
// string. But that is unlikely and strerror_r is
// not portable and we are going to terminate anyway,
// so that's better than nothing.
//
// Also, this error handler is not tested. It should
// be generally impossible to happen, so it is hard
// to trigger in controlled way.
LOG_FATAL(auth_logger,
AUTH_DATASRC_CLIENTS_BUILDER_WAKE_ERR).
arg(strerror(errno));
std::terminate();
}
}
current_commands.pop_front();
}
}
......@@ -515,7 +669,7 @@ bool
DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
const Command& command)
{
const CommandID cid = command.first;
const CommandID cid = command.id;
if (cid >= NUM_COMMANDS) {
// This shouldn't happen except for a bug within this file.
isc_throw(Unexpected, "internal bug: invalid command, ID: " << cid);
......@@ -526,12 +680,12 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
};
LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
switch (command.first) {
switch (command.id) {
case RECONFIGURE:
doReconfigure(command.second);
doReconfigure(command.params);
break;
case LOADZONE:
doLoadZone(command.second);
doLoadZone(command.params);
break;
case SHUTDOWN:
return (false);
......
......@@ -36,9 +36,13 @@
#include <boost/function.hpp>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <string>
#include <sstream>
#include <cerrno>
using isc::data::ConstElementPtr;
using namespace isc::dns;
......@@ -54,17 +58,24 @@ protected:
DataSrcClientsBuilderTest() :
clients_map(new std::map<RRClass,
boost::shared_ptr<ConfigurableClientList> >),
builder(&command_queue, &cond, &queue_mutex, &clients_map, &map_mutex),
write_end(-1), read_end(-1),
builder(&command_queue, &callback_queue, &cond, &queue_mutex,
&clients_map, &map_mutex, generateSockets()),
cond(command_queue, delayed_command_queue), rrclass(RRClass::IN()),
shutdown_cmd(SHUTDOWN, ConstElementPtr()),
noop_cmd(NOOP, ConstElementPtr())
shutdown_cmd(SHUTDOWN, ConstElementPtr(), FinishedCallback()),
noop_cmd(NOOP, ConstElementPtr(), FinishedCallback())
{}
~ DataSrcClientsBuilderTest() {
}
void configureZones(); // used for loadzone related tests
ClientListMapPtr clients_map; // configured clients
std::list<Command> command_queue; // test command queue
std::list<Command> delayed_command_queue; // commands available after wait
std::list<FinishedCallback> callback_queue; // Callbacks from commands
int write_end, read_end;
TestDataSrcClientsBuilder builder;
TestCondVar cond;
TestMutex queue_mutex;
......@@ -72,6 +83,15 @@ protected:
const RRClass rrclass;
const Command shutdown_cmd;
const Command noop_cmd;
private:
int generateSockets() {
int pair[2];
int result = socketpair(AF_LOCAL, SOCK_STREAM, 0, pair);
assert(result == 0);
write_end = pair[0];
read_end = pair[1];
return write_end;
}
};
TEST_F(DataSrcClientsBuilderTest, runSingleCommand) {
......@@ -82,6 +102,45 @@ TEST_F(DataSrcClientsBuilderTest, runSingleCommand) {
EXPECT_EQ(0, cond.wait_count); // no wait because command queue is not empty