Commit 016c9ba5 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[1651] Control interface in DHCPv4 refactored into separate class

- See ControlledDhcpv4Srv class for msgq support.
parent d1accb3e
......@@ -20,11 +20,20 @@
* @section dhcpv4Session BIND10 message queue integration
*
* DHCPv4 server component is now integrated with BIND10 message queue.
* The integration is performed by establish_session() and disconnect_session()
* functions in src/bin/dhcp4/main.cc file. isc::cc::Session cc_session
* The integration is performed by establishSession() and disconnectSession()
* functions in isc::dhcp::ControlledDhcpv4Srv class. main() method deifined
* in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv
* class that establishes connection with msgq and install necessary handlers
* for receiving commands and configuration updates. It is derived from
* a base isc::dhcp::Dhcpv4Srv class that implements DHCPv4 server functionality,
* without any controlling mechanisms.
*
* ControlledDhcpv4Srv instantiates several components to make management
* session possible. In particular, isc::cc::Session cc_session
* object uses ASIO for establishing connection. It registers its socket
* in isc::asiolink::IOService io_service object. Typically, other components that
* use ASIO for their communication, register their other sockets in the
* in isc::asiolink::IOService io_service object. Typically, other components
* (e.g. auth or resolver) that use ASIO for their communication, register their
* other sockets in the
* same io_service and then just call io_service.run() method that does
* not return, until one of the callback decides that it is time to shut down
* the whole component cal calls io_service.stop(). DHCPv4 works in a
......@@ -43,7 +52,10 @@
* be used with and without message queue. Second benefit is related to the
* first one: \ref libdhcp is supposed to be simple and robust and not require
* many dependencies. One notable example of a use case that benefits from
* this approach is a perfdhcp tool.
* this approach is a perfdhcp tool. Finally, the idea is that it should be
* possible to instantiate Dhcpv4Srv object directly, thus getting a server
* that does not support msgq. That is useful for embedded environments.
* It may also be useful in validation.
*
* @page dhcpv6 DHCPv6 Server Component
*
......
......@@ -31,6 +31,7 @@ BUILT_SOURCES = spec_config.h
pkglibexec_PROGRAMS = b10-dhcp4
b10_dhcp4_SOURCES = main.cc dhcp4_srv.cc dhcp4_srv.h
b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libdhcp++.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
......
// 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 <config.h>
//#include <sys/types.h>
//#include <sys/socket.h>
//#include <sys/select.h>
//#include <netdb.h>
//#include <netinet/in.h>
//#include <stdlib.h>
//#include <errno.h>
#include <cassert>
#include <iostream>
#include <cc/session.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
#include <cc/session.h>
#include <config/ccsession.h>
#include <util/buffer.h>
#include <log/dummylog.h>
#include <dhcp4/spec_config.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp/iface_mgr.h>
#include <asiolink/asiolink.h>
#include <log/logger_support.h>
const char* const DHCP4_NAME = "b10-dhcp4";
using namespace std;
using namespace isc::util;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::data;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::asiolink;
namespace isc {
namespace dhcp {
ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
ConstElementPtr
dhcp4_config_handler(ConstElementPtr new_config) {
cout << "b10-dhcp4: Received new config:" << new_config->str() << endl;
ConstElementPtr answer = isc::config::createAnswer(0,
"Thank you for sending config.");
return (answer);
}
ConstElementPtr
dhcp4_command_handler(const string& command, ConstElementPtr args) {
cout << "b10-dhcp4: Received new command: [" << command << "], args="
<< args->str() << endl;
if (command == "shutdown") {
if (ControlledDhcpv4Srv::server_) {
ControlledDhcpv4Srv::server_->shutdown();
}
ConstElementPtr answer = isc::config::createAnswer(0,
"Shutting down.");
return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(1,
"Unrecognized command.");
return (answer);
}
void ControlledDhcpv4Srv::sessionReader(void) {
// Process one asio event. If there are more events, iface_mgr will call
// this callback more than once.
if (server_) {
server_->io_service_.run_one();
}
}
void ControlledDhcpv4Srv::establishSession() {
string specfile;
if (getenv("B10_FROM_BUILD")) {
specfile = string(getenv("B10_FROM_BUILD")) +
"/src/bin/auth/dhcp4.spec";
} else {
specfile = string(DHCP4_SPECFILE_LOCATION);
}
/// @todo: Check if session is not established already. Throw, if it is.
cout << "b10-dhcp4: my specfile is " << specfile << endl;
cc_session_ = new Session(io_service_.get_io_service());
config_session_ = new ModuleCCSession(specfile, *cc_session_,
dhcp4_config_handler,
dhcp4_command_handler, false);
config_session_->start();
int ctrl_socket = cc_session_->getSocketDesc();
cout << "b10-dhcp4: Control session started, socket="
<< ctrl_socket << endl;
IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
}
void ControlledDhcpv4Srv::disconnectSession() {
if (config_session_) {
delete config_session_;
config_session_ = NULL;
}
if (cc_session_) {
cc_session_->disconnect();
delete cc_session_;
cc_session_ = NULL;
}
}
ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/,
bool verbose /* false */)
:Dhcpv4Srv(port), cc_session_(NULL), config_session_(NULL) {
// Initialize logging. If verbose, we'll use maximum verbosity.
isc::log::initLogger(DHCP4_NAME,
(verbose ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL);
server_ = this; // remember this instance for use in callback
establishSession();
}
void ControlledDhcpv4Srv::shutdown() {
io_service_.stop(); // Stop ASIO transmissions
Dhcpv4Srv::shutdown(); // Initiate DHCPv4 shutdown procedure.
}
ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
disconnectSession();
server_ = NULL; // forget this instance. There should be no callback anymore
// at this stage anyway.
}
};
};
// 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 CTRL_DHCPV4_SRV_H
#define CTRL_DHCPV4_SRV_H
#include <dhcp4/dhcp4_srv.h>
#include <asiolink/asiolink.h>
#include <cc/session.h>
#include <config/ccsession.h>
namespace isc {
namespace dhcp {
/// @brief Controlled version of the DHCPv4 server
///
/// This is a class that is responsible for establishing connection
/// with msqg (receving commands and configuration). This is an extended
/// version of Dhcpv4Srv class that is purely a DHCPv4 server, without
/// external control. ControlledDhcpv4Srv should be used in typical BIND10
/// (i.e. featuring msgq) environment, while Dhcpv4Srv should be used in
/// embedded environments.
///
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
class ControlledDhcpv4Srv : public isc::dhcp::Dhcpv4Srv {
public:
ControlledDhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
bool verbose = false);
/// @brief Establishes msgq session.
///
/// Creates session that will be used to receive commands and updated
/// configuration from boss (or indirectly from user via bindctl).
void establishSession();
/// @brief Terminates existing session.
///
/// This method terminates existing session with msgq. After calling
/// it, not further messages over msgq (commands or configuration updates)
/// may be received.
///
/// It is ok to call this method when session is disconnected already.
void disconnectSession();
/// @brief Initiates shutdown procedure for the whole DHCPv4 server.
void shutdown();
~ControlledDhcpv4Srv();
static ControlledDhcpv4Srv* server_;
protected:
/// @brief callback that will be called from iface_mgr when command/config arrives
///
/// This static callback method is called from IfaceMgr::receive4() method,
/// when there is a new command or configuration sent over msgq.
static void sessionReader(void);
/// @brief IOService object, used for all ASIO operations
isc::asiolink::IOService io_service_;
/// @brief Helper session object that represents raw connection to msgq
isc::cc::Session* cc_session_;
/// @brief Session that receives configuation and commands
isc::config::ModuleCCSession* config_session_;
};
}; // namespace isc::dhcp
}; // namespace isc
#endif
......@@ -32,6 +32,13 @@ namespace dhcp {
/// that is going to be used as server-identifier, receives incoming
/// packets, processes them, manages leases assignment and generates
/// appropriate responses.
///
/// This class does not support any controlling mechanisms directly.
/// See derived \ref ControlledDhcv4Srv class for support for
/// command and configuration updates over msgq.
///
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
class Dhcpv4Srv : public boost::noncopyable {
public:
......@@ -60,7 +67,7 @@ class Dhcpv4Srv : public boost::noncopyable {
/// critical error.
bool run();
/// @brief instructs server to shut down.
/// @brief Instructs the server to shut down.
void shutdown();
protected:
......
// Copyright (C) 2009-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
......@@ -13,49 +13,25 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
#include <cassert>
#include <iostream>
#include <cc/session.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
#include <cc/session.h>
#include <config/ccsession.h>
#include <util/buffer.h>
#include <log/dummylog.h>
#include <dhcp4/spec_config.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp/iface_mgr.h>
#include <asiolink/asiolink.h>
#include <log/logger_support.h>
const char* const DHCP4_NAME = "b10-dhcp4";
using namespace std;
using namespace isc::util;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::data;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::asiolink;
namespace {
/// This file contains entry point (main() function) for standard DHCPv4 server
/// component for BIND10 framework. It parses command-line arguments and
/// instantiates ControlledDhcpv4Srv class that is responsible for establishing
/// connection with msgq (receiving commands and configuration) and also
/// creating Dhcpv4 server object as well.
///
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
bool verbose_mode = false;
namespace {
void
usage() {
......@@ -66,102 +42,10 @@ usage() {
}
} // end of anonymous namespace
/// @brief DHCPv4 context (provides access to essential objects)
///
/// This is a structure that provides access to essential objects
/// used during DHCPv4 operation: Dhcpv4Srv object itself and
/// also objects required for msgq session management.
struct DHCPv4Context {
IOService io_service;
Session* cc_session;
ModuleCCSession* config_session;
Dhcpv4Srv* server;
DHCPv4Context(): cc_session(NULL), config_session(NULL), server(NULL) { };
};
DHCPv4Context dhcp4;
// Global objects are ugly, but that is the most convenient way of
// having it accessible from handlers.
// The same applies to global pointers. Ugly, but useful.
ConstElementPtr
dhcp4_config_handler(ConstElementPtr new_config) {
cout << "b10-dhcp4: Received new config:" << new_config->str() << endl;
ConstElementPtr answer = isc::config::createAnswer(0,
"Thank you for sending config.");
return (answer);
}
ConstElementPtr
dhcp4_command_handler(const string& command, ConstElementPtr args) {
cout << "b10-dhcp4: Received new command: [" << command << "], args="
<< args->str() << endl;
if (command == "shutdown") {
if (dhcp4.server) {
dhcp4.server->shutdown();
}
dhcp4.io_service.stop();
ConstElementPtr answer = isc::config::createAnswer(0,
"Shutting down.");
return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(1,
"Unrecognized command.");
return (answer);
}
/// @brief callback that will be called from iface_mgr when command/config arrives
void session_reader(void) {
// Process one asio event. If there are more events, iface_mgr will call
// this callback more than once.
dhcp4.io_service.run_one();
}
/// @brief Establishes msgq session.
///
/// Creates session that will be used to receive commands and updated
/// configuration from boss (or indirectly from user via bindctl).
void establish_session() {
string specfile;
if (getenv("B10_FROM_BUILD")) {
specfile = string(getenv("B10_FROM_BUILD")) +
"/src/bin/auth/dhcp4.spec";
} else {
specfile = string(DHCP4_SPECFILE_LOCATION);
}
cout << "b10-dhcp4: my specfile is " << specfile << endl;
dhcp4.cc_session = new Session(dhcp4.io_service.get_io_service());
dhcp4.config_session = new ModuleCCSession(specfile, *dhcp4.cc_session,
dhcp4_config_handler,
dhcp4_command_handler, false);
dhcp4.config_session->start();
int ctrl_socket = dhcp4.cc_session->getSocketDesc();
cout << "b10-dhcp4: Control session started, socket="
<< ctrl_socket << endl;
IfaceMgr::instance().set_session_socket(ctrl_socket, session_reader);
}
void disconnect_session() {
dhcp4.cc_session->disconnect();
delete dhcp4.config_session;
dhcp4.config_session = NULL;
delete dhcp4.cc_session;
dhcp4.cc_session = NULL;
}
int
main(int argc, char* argv[]) {
int ch;
bool verbose_mode = false; // should server be verbose?
while ((ch = getopt(argc, argv, ":v")) != -1) {
switch (ch) {
......@@ -175,11 +59,6 @@ main(int argc, char* argv[]) {
}
}
// 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);
cout << "b10-dhcp4: My pid is " << getpid() << endl;
if (argc - optind > 0) {
......@@ -187,19 +66,17 @@ main(int argc, char* argv[]) {
}
int ret = 0;
ControlledDhcpv4Srv* server = NULL;
try {
establish_session();
cout << "[b10-dhcp4] Initiating DHCPv4 server operation." << endl;
dhcp4.server = new Dhcpv4Srv();
dhcp4.server->run();
disconnect_session();
server = new ControlledDhcpv4Srv(DHCP4_SERVER_PORT, verbose_mode);
server->run();
delete server;
delete dhcp4.server;
dhcp4.server = NULL;
server = NULL;
} catch (const std::exception& ex) {
cerr << "[b10-dhcp4] Server failed: " << ex.what() << endl;
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <dhcp/dhcp4.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp/option.h>
#include <asiolink/io_address.h>
using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
namespace {
const char* const INTERFACE_FILE = "interfaces.txt";
class NakedDhcpv4Srv: public Dhcpv4Srv {
// "naked" DHCPv4 server, exposes internal fields
public:
NakedDhcpv4Srv():Dhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
boost::shared_ptr<Pkt4> processDiscover(boost::shared_ptr<Pkt4>& discover) {
return Dhcpv4Srv::processDiscover(discover);
}
boost::shared_ptr<Pkt4> processRequest(boost::shared_ptr<Pkt4>& request) {
return Dhcpv4Srv::processRequest(request);
}
void processRelease(boost::shared_ptr<Pkt4>& release) {
return Dhcpv4Srv::processRelease(release);
}
void processDecline(boost::shared_ptr<Pkt4>& decline) {
Dhcpv4Srv::processDecline(decline);
}
boost::shared_ptr<Pkt4> processInform(boost::shared_ptr<Pkt4>& inform) {
return Dhcpv4Srv::processInform(inform);
}
};
class Dhcpv4SrvTest : public ::testing::Test {
public:
Dhcpv4SrvTest() {
unlink(INTERFACE_FILE);
fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
if (if_nametoindex("lo") > 0) {
fakeifaces << "lo 127.0.0.1";
} else if (if_nametoindex("lo0") > 0) {
fakeifaces << "lo0 127.0.0.1";
}
fakeifaces.close();
}
void MessageCheck(const boost::shared_ptr<Pkt4>& q,
const boost::shared_ptr<Pkt4>& a) {
ASSERT_TRUE(q);
ASSERT_TRUE(a);
EXPECT_EQ(q->getHops(), a->getHops());
EXPECT_EQ(q->getIface(), a->getIface());
EXPECT_EQ(q->getIndex(), a->getIndex());
EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
// check that bare minimum of required options are there
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
EXPECT_TRUE(a->getOption(DHO_ROUTERS));
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
EXPECT_TRUE(a->getOption(DHO_DHCP_LEASE_TIME));
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
EXPECT_TRUE(a->getOption(DHO_ROUTERS));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
// check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
}
~Dhcpv4SrvTest() {
unlink(INTERFACE_FILE);
};
};
TEST_F(Dhcpv4SrvTest, basic) {
// nothing to test. DHCPv4_srv instance is created
// in test fixture. It is destroyed in destructor
Dhcp