Browse Source

[1651] Control interface in DHCPv4 refactored into separate class

- See ControlledDhcpv4Srv class for msgq support.
Tomek Mrugalski 13 years ago
parent
commit
016c9ba563

+ 17 - 5
doc/devel/02-dhcp.dox

@@ -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
  *

+ 1 - 0
src/bin/dhcp4/Makefile.am

@@ -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

+ 162 - 0
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -0,0 +1,162 @@
+// 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.
+}
+
+};
+};

+ 85 - 0
src/bin/dhcp4/ctrl_dhcp4_srv.h

@@ -0,0 +1,85 @@
+// 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

+ 8 - 1
src/bin/dhcp4/dhcp4_srv.h

@@ -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:

+ 17 - 140
src/bin/dhcp4/main.cc

@@ -1,4 +1,4 @@
-// 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;

+ 274 - 0
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -0,0 +1,274 @@
+// 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
+
+    Dhcpv4Srv* srv = NULL;
+    ASSERT_NO_THROW({
+        srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
+    });
+
+    delete srv;
+}
+
+TEST_F(Dhcpv4SrvTest, processDiscover) {
+    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+    vector<uint8_t> mac(6);
+    for (int i = 0; i < 6; i++) {
+        mac[i] = 255 - i;
+    }
+
+    boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, 1234));
+    boost::shared_ptr<Pkt4> offer;
+
+    pkt->setIface("eth0");
+    pkt->setIndex(17);
+    pkt->setHWAddr(1, 6, mac);
+    pkt->setRemoteAddr(IOAddress("192.0.2.56"));
+    pkt->setGiaddr(IOAddress("192.0.2.67"));
+
+    // let's make it a relayed message
+    pkt->setHops(3);
+    pkt->setRemotePort(DHCP4_SERVER_PORT);
+
+    // should not throw
+    EXPECT_NO_THROW(
+        offer = srv->processDiscover(pkt);
+    );
+
+    // should return something
+    ASSERT_TRUE(offer);
+
+    EXPECT_EQ(DHCPOFFER, offer->getType());
+
+    // this is relayed message. It should be sent back to relay address.
+    EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
+
+    MessageCheck(pkt, offer);
+
+    // now repeat the test for directly sent message
+    pkt->setHops(0);
+    pkt->setGiaddr(IOAddress("0.0.0.0"));
+    pkt->setRemotePort(DHCP4_CLIENT_PORT);
+
+    EXPECT_NO_THROW(
+        offer = srv->processDiscover(pkt);
+    );
+
+    // should return something
+    ASSERT_TRUE(offer);
+
+    EXPECT_EQ(DHCPOFFER, offer->getType());
+
+    // this is direct message. It should be sent back to origin, not
+    // to relay.
+    EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
+
+    MessageCheck(pkt, offer);
+
+    delete srv;
+}
+
+TEST_F(Dhcpv4SrvTest, processRequest) {
+    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+    vector<uint8_t> mac(6);
+    for (int i = 0; i < 6; i++) {
+        mac[i] = i*10;
+    }
+
+    boost::shared_ptr<Pkt4> req(new Pkt4(DHCPREQUEST, 1234));
+    boost::shared_ptr<Pkt4> ack;
+
+    req->setIface("eth0");
+    req->setIndex(17);
+    req->setHWAddr(1, 6, mac);
+    req->setRemoteAddr(IOAddress("192.0.2.56"));
+    req->setGiaddr(IOAddress("192.0.2.67"));
+
+    // should not throw
+    ASSERT_NO_THROW(
+        ack = srv->processRequest(req);
+    );
+
+    // should return something
+    ASSERT_TRUE(ack);
+
+    EXPECT_EQ(DHCPACK, ack->getType());
+
+    // this is relayed message. It should be sent back to relay address.
+    EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
+
+    MessageCheck(req, ack);
+
+    // now repeat the test for directly sent message
+    req->setHops(0);
+    req->setGiaddr(IOAddress("0.0.0.0"));
+    req->setRemotePort(DHCP4_CLIENT_PORT);
+
+    EXPECT_NO_THROW(
+        ack = srv->processDiscover(req);
+    );
+
+    // should return something
+    ASSERT_TRUE(ack);
+
+    EXPECT_EQ(DHCPOFFER, ack->getType());
+
+    // this is direct message. It should be sent back to origin, not
+    // to relay.
+    EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
+
+    MessageCheck(req, ack);
+
+    delete srv;
+}
+
+TEST_F(Dhcpv4SrvTest, processRelease) {
+    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+
+    boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPRELEASE, 1234));
+
+    // should not throw
+    EXPECT_NO_THROW(
+        srv->processRelease(pkt);
+    );
+
+    // TODO: Implement more reasonable tests before starting
+    // work on processSomething() method.
+
+    delete srv;
+}
+
+TEST_F(Dhcpv4SrvTest, processDecline) {
+    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+
+    boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDECLINE, 1234));
+
+    // should not throw
+    EXPECT_NO_THROW(
+        srv->processDecline(pkt);
+    );
+
+    // TODO: Implement more reasonable tests before starting
+    // work on processSomething() method.
+    delete srv;
+}
+
+TEST_F(Dhcpv4SrvTest, processInform) {
+    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+
+    boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPINFORM, 1234));
+
+    // should not throw
+    EXPECT_NO_THROW(
+        srv->processInform(pkt);
+    );
+
+    // should return something
+    EXPECT_TRUE(srv->processInform(pkt));
+
+    // TODO: Implement more reasonable tests before starting
+    // work on processSomething() method.
+
+    delete srv;
+}
+
+} // end of anonymous namespace