Browse Source

[master] Merge branch 'master' of ssh://git.kea.isc.org/git/kea

Conflicts:
	ChangeLog
Tomek Mrugalski 10 years ago
parent
commit
d330a5830d
46 changed files with 1051 additions and 624 deletions
  1. 9 1
      ChangeLog
  2. 2 1
      src/bin/dhcp4/bundy_controller.cc
  3. 5 2
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  4. 5 4
      src/bin/dhcp4/dhcp4_messages.mes
  5. 0 49
      src/bin/dhcp4/dhcp4_srv.cc
  6. 0 16
      src/bin/dhcp4/dhcp4_srv.h
  7. 7 9
      src/bin/dhcp4/json_config_parser.cc
  8. 24 37
      src/bin/dhcp4/kea_controller.cc
  9. 17 13
      src/bin/dhcp4/tests/config_parser_unittest.cc
  10. 2 22
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  11. 2 1
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  12. 2 2
      src/bin/dhcp4/tests/dora_unittest.cc
  13. 2 2
      src/bin/dhcp4/tests/inform_unittest.cc
  14. 4 1
      src/bin/dhcp6/bundy_controller.cc
  15. 6 2
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  16. 5 4
      src/bin/dhcp6/dhcp6_messages.mes
  17. 6 58
      src/bin/dhcp6/dhcp6_srv.cc
  18. 0 16
      src/bin/dhcp6/dhcp6_srv.h
  19. 7 9
      src/bin/dhcp6/json_config_parser.cc
  20. 24 33
      src/bin/dhcp6/kea_controller.cc
  21. 23 21
      src/bin/dhcp6/tests/config_parser_unittest.cc
  22. 2 2
      src/bin/dhcp6/tests/confirm_unittest.cc
  23. 1 1
      src/bin/dhcp6/tests/d2_unittest.cc
  24. 3 3
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  25. 6 6
      src/bin/dhcp6/tests/rebind_unittest.cc
  26. 1 1
      src/bin/dhcp6/tests/sarr_unittest.cc
  27. 2 2
      src/lib/cc/data.cc
  28. 25 1
      src/lib/dhcp/iface_mgr.cc
  29. 9 0
      src/lib/dhcp/iface_mgr.h
  30. 13 0
      src/lib/dhcp/pkt_filter_inet6.cc
  31. 36 0
      src/lib/dhcp/tests/iface_mgr_test_config.cc
  32. 12 0
      src/lib/dhcp/tests/iface_mgr_test_config.h
  33. 14 0
      src/lib/dhcp/tests/iface_mgr_unittest.cc
  34. 1 0
      src/lib/dhcpsrv/Makefile.am
  35. 270 0
      src/lib/dhcpsrv/cfg_iface.cc
  36. 203 0
      src/lib/dhcpsrv/cfg_iface.h
  37. 1 82
      src/lib/dhcpsrv/cfgmgr.cc
  38. 0 78
      src/lib/dhcpsrv/cfgmgr.h
  39. 7 0
      src/lib/dhcpsrv/configuration.h
  40. 12 44
      src/lib/dhcpsrv/dhcp_parsers.cc
  41. 7 4
      src/lib/dhcpsrv/dhcp_parsers.h
  42. 20 2
      src/lib/dhcpsrv/dhcpsrv_messages.mes
  43. 1 0
      src/lib/dhcpsrv/tests/Makefile.am
  44. 226 0
      src/lib/dhcpsrv/tests/cfg_iface_unittest.cc
  45. 0 80
      src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
  46. 27 15
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

+ 9 - 1
ChangeLog

@@ -1,8 +1,16 @@
-832.	[bug]		jiri
+833.	[bug]		jiri
 	Compilation fix for PostgreSQL on i686. Thanks to Jiri Popelka
 	from RedHat for providing a patch!
 	(Trac #3532, git 96a06654f2177444dcea3a0e9f6fa06947855497)
 
+832.	[func]		marcin
+	DHCP servers check if the interfaces specified in the configuration,
+	to be used to receive DHCP messsages, are present in the system.
+	If the interface doesn't exist, an error is reported. In addition,
+	the SO_REUSEPORT flag is set for IPv6 sockets as multiple multicast
+	sockets can be bound to the DHCPv6 server port.
+	(Trac #3512, git 5cbbab2d01c6e1bf6d563ba64d80bc6bc857f73d)
+
 831.	[bug]		marcin
 	Kea deamons report configuration summary when the configuration is
 	completed successfully.

+ 2 - 1
src/bin/dhcp4/bundy_controller.cc

@@ -177,7 +177,8 @@ void ControlledDhcpv4Srv::init(const std::string& config_file) {
 
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
-        openActiveSockets(getPort(), useBroadcast());
+        CfgMgr::instance().getConfiguration()->cfg_iface_
+            .openSockets(getPort(), useBroadcast());
 
     } catch (const std::exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());

+ 5 - 2
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -18,6 +18,7 @@
 #include <dhcp4/dhcp4_log.h>
 #include <hooks/hooks_manager.h>
 #include <dhcp4/json_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
 
 using namespace isc::data;
 using namespace isc::hooks;
@@ -117,7 +118,7 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
         err << "Server object not initialized, can't process config.";
         return (isc::config::createAnswer(1, err.str()));
     }
-    
+
     ConstElementPtr answer = configureDhcp4Server(*srv, config);
 
 
@@ -148,7 +149,9 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
     // safe and we really don't want to emit exceptions to whoever called this
     // method. Instead, catch an exception and create appropriate answer.
     try {
-        srv->openActiveSockets(srv->getPort(), getInstance()->useBroadcast());
+        CfgMgr::instance().getConfiguration()->cfg_iface_
+            .openSockets(srv->getPort(), getInstance()->useBroadcast());
+
     } catch (std::exception& ex) {
         err << "failed to open sockets after server reconfiguration: "
             << ex.what();

+ 5 - 4
src/bin/dhcp4/dhcp4_messages.mes

@@ -50,10 +50,11 @@ new configuration. It is output during server startup, and when an updated
 configuration is committed by the administrator.  Additional information
 may be provided.
 
-% DHCP4_CONFIG_LOAD_FAIL failed to load configuration: %1
-This critical error message indicates that the initial DHCPv4
-configuration has failed. The server will start, but nothing will be
-served until the configuration has been corrected.
+% DHCP4_CONFIG_LOAD_FAIL configuration error using file: %1, reason: %2
+This error message indicates that the DHCPv4 configuration has failed.
+If this is an initial configuration (during server's startup) the server
+will fail to start. If this is a dynamic reconfiguration attempt the
+server will continue to use an old configuration.
 
 % DHCP4_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
 This is an informational message reporting that the configuration has

+ 0 - 49
src/bin/dhcp4/dhcp4_srv.cc

@@ -1795,55 +1795,6 @@ Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
     }
 }
 
-void
-Dhcpv4Srv::openActiveSockets(const uint16_t port,
-                             const bool use_bcast) {
-    IfaceMgr::instance().closeSockets();
-
-    // Get the reference to the collection of interfaces. This reference should
-    // be valid as long as the program is run because IfaceMgr is a singleton.
-    // Therefore we can safely iterate over instances of all interfaces and
-    // modify their flags. Here we modify flags which indicate whether socket
-    // should be open for a particular interface or not.
-    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
-    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
-         iface != ifaces.end(); ++iface) {
-        Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
-        if (iface_ptr == NULL) {
-            isc_throw(isc::Unexpected, "Interface Manager returned NULL"
-                      << " instance of the interface when DHCPv4 server was"
-                      << " trying to reopen sockets after reconfiguration");
-        }
-        // Ignore loopback interfaces.
-        if (iface_ptr->flag_loopback_) {
-            iface_ptr->inactive4_ = true;
-
-        } else if (CfgMgr::instance().isActiveIface(iface->getName())) {
-            iface_ptr->inactive4_ = false;
-            LOG_INFO(dhcp4_logger, DHCP4_ACTIVATE_INTERFACE)
-                .arg(iface->getFullName());
-
-        } else {
-            // For deactivating interface, it should be sufficient to log it
-            // on the debug level because it is more useful to know what
-            // interface is activated which is logged on the info level.
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC,
-                      DHCP4_DEACTIVATE_INTERFACE).arg(iface->getName());
-            iface_ptr->inactive4_ = true;
-
-        }
-    }
-    // Let's reopen active sockets. openSockets4 will check internally whether
-    // sockets are marked active or inactive.
-    /// @todo Optimization: we should not reopen all sockets but rather select
-    /// those that have been affected by the new configuration.
-    isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
-        boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
-    if (!IfaceMgr::instance().openSockets4(port, use_bcast, error_handler)) {
-        LOG_WARN(dhcp4_logger, DHCP4_NO_SOCKETS_OPEN);
-    }
-}
-
 size_t
 Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
                          const std::string& option_space,

+ 0 - 16
src/bin/dhcp4/dhcp4_srv.h

@@ -128,11 +128,6 @@ public:
     ///
     /// @name Public accessors returning values required to (re)open sockets.
     ///
-    /// These accessors must be public because sockets are reopened from the
-    /// static configuration callback handler. This callback handler invokes
-    /// @c ControlledDhcpv4Srv::openActiveSockets which requires parameters
-    /// which has to be retrieved from the @c ControlledDhcpv4Srv object.
-    /// They are retrieved using these public functions
     //@{
     ///
     /// @brief Get UDP port on which server should listen.
@@ -154,17 +149,6 @@ public:
     }
     //@}
 
-    /// @brief Open sockets which are marked as active in @c CfgMgr.
-    ///
-    /// This function reopens sockets according to the current settings in the
-    /// Configuration Manager. It holds the list of the interfaces which server
-    /// should listen on. This function will open sockets on these interfaces
-    /// only. This function is not exception safe.
-    ///
-    /// @param port UDP port on which server should listen.
-    /// @param use_bcast should broadcast flags be set on the sockets.
-    static void openActiveSockets(const uint16_t port, const bool use_bcast);
-
     /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
     ///
     /// If updates are enabled, it Instructs the D2ClientMgr singleton to

+ 7 - 9
src/bin/dhcp4/json_config_parser.cc

@@ -594,8 +594,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     } catch (const isc::Exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
                   .arg(config_pair.first).arg(ex.what());
-        answer = isc::config::createAnswer(1,
-                     string("Configuration parsing failed: ") + ex.what());
+        answer = isc::config::createAnswer(1, ex.what());
 
         // An error occured, so make sure that we restore original data.
         rollback = true;
@@ -603,8 +602,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     } catch (...) {
         // For things like bad_cast in boost::lexical_cast
         LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(config_pair.first);
-        answer = isc::config::createAnswer(1,
-                     string("Configuration parsing failed"));
+        answer = isc::config::createAnswer(1, "undefined configuration"
+                                           " processing error");
 
         // An error occured, so make sure that we restore original data.
         rollback = true;
@@ -636,14 +635,13 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
         }
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
-            answer = isc::config::createAnswer(2,
-                         string("Configuration commit failed: ") + ex.what());
+            answer = isc::config::createAnswer(2, ex.what());
             rollback = true;
         } catch (...) {
             // For things like bad_cast in boost::lexical_cast
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
-            answer = isc::config::createAnswer(2,
-                         string("Configuration commit failed"));
+            answer = isc::config::createAnswer(2, "undefined configuration"
+                                               " parsing error");
             rollback = true;
         }
     }
@@ -659,7 +657,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
              getConfigSummary(Configuration::CFGSEL_ALL4));
 
     // Everything was fine. Configuration is successful.
-    answer = isc::config::createAnswer(0, "Configuration committed.");
+    answer = isc::config::createAnswer(0, "Configuration successful.");
     return (answer);
 }
 

+ 24 - 37
src/bin/dhcp4/kea_controller.cc

@@ -56,12 +56,8 @@ void configure(const std::string& file_name) {
 
         // Read contents of the file and parse it as JSON
         json = isc::data::Element::fromJSONFile(file_name, true);
-
         if (!json) {
-            LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
-                .arg("Config file " + file_name + " missing or empty.");
-            isc_throw(isc::BadValue, "Unable to process JSON configuration"
-                      " file: " << file_name);
+            isc_throw(isc::BadValue, "no configuration found");
         }
 
         // Let's configure logging before applying the configuration,
@@ -75,46 +71,37 @@ void configure(const std::string& file_name) {
 
         // Get Dhcp4 component from the config
         dhcp4 = json->get("Dhcp4");
-
         if (!dhcp4) {
-            LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
-                .arg("Config file " + file_name + " does not include 'Dhcp4'"
-                     " entry.");
-            isc_throw(isc::BadValue, "Unable to process JSON configuration"
-                      " file: " << file_name);
+            isc_throw(isc::BadValue, "no mandatory 'Dhcp4' entry in"
+                      " the configuration");
         }
 
         // Use parsed JSON structures to configure the server
         result = ControlledDhcpv4Srv::processCommand("config-reload", dhcp4);
+        if (!result) {
+            // Undetermined status of the configuration. This should never
+            // happen, but as the configureDhcp4Server returns a pointer, it is
+            // theoretically possible that it will return NULL.
+            isc_throw(isc::BadValue, "undefined result of "
+                      "processCommand(\"config-reload\", dhcp4)");
+        }
 
-    }  catch (const std::exception& ex) {
-        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
-        isc_throw(isc::BadValue, "Unable to process JSON configuration file: "
-                  << file_name);
-    }
+        // Now check is the returned result is successful (rcode=0) or not
+        // (see @ref isc::config::parseAnswer).
+        int rcode;
+        isc::data::ConstElementPtr comment =
+            isc::config::parseAnswer(rcode, result);
+        if (rcode != 0) {
+            string reason = comment ? comment->stringValue() :
+                "no details available";
+            isc_throw(isc::BadValue, reason);
+        }
 
-    if (!result) {
-        // Undetermined status of the configuration. This should never happen,
-        // but as the configureDhcp4Server returns a pointer, it is
-        // theoretically possible that it will return NULL.
+    }  catch (const std::exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
-            .arg("Configuration failed: Undefined result of processCommand("
-                 "config-reload, " + file_name + ")");
-        isc_throw(isc::BadValue, "Configuration failed: Undefined result of "
-                  "processCommand('config-reload', " << file_name << ")");
-    }
-
-    // Now check is the returned result is successful (rcode=0) or not
-    isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer
-    int rcode;
-    comment = isc::config::parseAnswer(rcode, result);
-    if (rcode != 0) {
-        string reason = "";
-        if (comment) {
-            reason = comment->stringValue();
-        }
-        LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(reason);
-        isc_throw(isc::BadValue, "Failed to apply configuration: " << reason);
+            .arg(file_name).arg(ex.what());
+        isc_throw(isc::BadValue, "configuration error using file '"
+                  << file_name << "': " << ex.what());
     }
 }
 

+ 17 - 13
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -25,6 +25,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/classify.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/testutils/config_result_check.h>
@@ -77,7 +78,6 @@ public:
         // deal with sockets here, just check if configuration handling
         // is sane.
         srv_.reset(new Dhcpv4Srv(0));
-        CfgMgr::instance().deleteActiveIfaces();
         // Create fresh context.
         globalContext()->copyContext(ParserContext(Option::V4));
     }
@@ -2895,6 +2895,8 @@ TEST_F(Dhcp4ParserTest, LibrariesSpecified) {
 // This test verifies that it is possible to select subset of interfaces
 // on which server should listen.
 TEST_F(Dhcp4ParserTest, selectedInterfaces) {
+    IfaceMgrTestConfig test_config(true);
+
     ConstElementPtr x;
     string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
         "\"rebind-timer\": 2000, "
@@ -2907,24 +2909,26 @@ TEST_F(Dhcp4ParserTest, selectedInterfaces) {
 
     // Make sure the config manager is clean and there is no hanging
     // interface configuration.
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+    EXPECT_FALSE(test_config.socketOpen("eth0", AF_INET));
+    EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET));
 
     // Apply configuration.
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
     checkResult(status, 0);
 
+    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+
     // eth0 and eth1 were explicitly selected. eth2 was not.
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
-    EXPECT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+    EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
+    EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));
 }
 
 // This test verifies that it is possible to configure the server in such a way
 // that it listens on all interfaces.
 TEST_F(Dhcp4ParserTest, allInterfaces) {
+    IfaceMgrTestConfig test_config(true);
+
     ConstElementPtr x;
     // This configuration specifies two interfaces on which server should listen
     // but it also includes asterisk. The asterisk switches server into the
@@ -2940,19 +2944,19 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
     ConstElementPtr status;
 
     // Make sure there is no old configuration.
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+    ASSERT_FALSE(test_config.socketOpen("eth0", AF_INET));
+    ASSERT_FALSE(test_config.socketOpen("eth1", AF_INET));
 
     // Apply configuration.
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
     checkResult(status, 0);
 
+    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+
     // All interfaces should be now active.
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
+    ASSERT_TRUE(test_config.socketOpen("eth0", AF_INET));
+    ASSERT_TRUE(test_config.socketOpen("eth1", AF_INET));
 }
 
 // This test checks the ability of the server to parse a configuration

+ 2 - 22
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -420,26 +420,6 @@ TEST_F(Dhcpv4SrvTest, basic) {
     ASSERT_NO_THROW(naked_srv.reset(new NakedDhcpv4Srv(0)));
 }
 
-// This test verifies that exception is not thrown when an error occurs during
-// opening sockets. This test forces an error by adding a fictious interface
-// to the IfaceMgr. An attempt to open socket on this interface must always
-// fail. The DHCPv4 installs the error handler function to prevent exceptions
-// being thrown from the openSockets4 function.
-// @todo The server tests for socket should be extended but currently the
-// ability to unit test the sockets code is somewhat limited.
-TEST_F(Dhcpv4SrvTest, openActiveSockets) {
-    ASSERT_NO_THROW(CfgMgr::instance().activateAllIfaces());
-
-    Iface iface("bogusiface", 255);
-    iface.flag_loopback_ = false;
-    iface.flag_up_ = true;
-    iface.flag_running_ = true;
-    iface.inactive4_ = false;
-    iface.addAddress(IOAddress("192.0.0.0"));
-    IfaceMgr::instance().addInterface(iface);
-    ASSERT_NO_THROW(Dhcpv4Srv::openActiveSockets(DHCP4_SERVER_PORT, false));
-}
-
 // Verifies that DISCOVER message can be processed correctly,
 // that the OFFER message generated in response is valid and
 // contains necessary options.
@@ -2970,7 +2950,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
     NakedDhcpv4Srv srv(0);
 
     ConstElementPtr x;
-    string config = "{ \"interfaces\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "    \"option-data\": [ {"
@@ -3055,7 +3035,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
 // src/lib/dhcp/docsis3_option_defs.h.
 TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
     ConstElementPtr x;
-    string config_prefix = "{ \"interfaces\": [ \"all\" ],"
+    string config_prefix = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "    \"option-data\": [ {"

+ 2 - 1
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -45,7 +45,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
     subnet_->addPool(pool_);
 
-    CfgMgr::instance().deleteActiveIfaces();
+    CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
     CfgMgr::instance().deleteSubnets4();
     CfgMgr::instance().addSubnet4(subnet_);
 
@@ -58,6 +58,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
 
     // Make sure that we revert to default value
+    CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
     CfgMgr::instance().echoClientId(true);
 }
 

+ 2 - 2
src/bin/dhcp4/tests/dora_unittest.cc

@@ -49,7 +49,7 @@ namespace {
 ///   - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
 const char* DORA_CONFIGS[] = {
 // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "    \"subnet\": \"10.0.0.0/24\", "
@@ -86,7 +86,7 @@ const char* DORA_CONFIGS[] = {
     "}",
 
 // Configuration 1
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "    \"subnet\": \"192.0.2.0/24\", "

+ 2 - 2
src/bin/dhcp4/tests/inform_unittest.cc

@@ -48,7 +48,7 @@ namespace {
 ///   - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
 const char* INFORM_CONFIGS[] = {
 // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "    \"subnet\": \"10.0.0.0/24\", "
@@ -85,7 +85,7 @@ const char* INFORM_CONFIGS[] = {
     "}",
 
 // Configuration 1
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "    \"subnet\": \"192.0.2.0/24\", "

+ 4 - 1
src/bin/dhcp6/bundy_controller.cc

@@ -183,7 +183,10 @@ ControlledDhcpv6Srv::init(const std::string& config_file) {
 
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
-        openActiveSockets(getPort());
+        // Configuration may disable or enable interfaces so we have to
+        // reopen sockets according to new configuration.
+        CfgMgr::instance().getConfiguration()->cfg_iface_
+            .openSockets(getPort(), useBroadcast());
 
     } catch (const std::exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());

+ 6 - 2
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -14,6 +14,7 @@
 
 #include <config.h>
 #include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <hooks/hooks_manager.h>
@@ -143,10 +144,13 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
     // safe and we really don't want to emit exceptions to the callback caller.
     // Instead, catch an exception and create appropriate answer.
     try {
-        srv->openActiveSockets(srv->getPort());
+        CfgMgr::instance().getConfiguration()->cfg_iface_
+            .openSockets(srv->getPort());
+
     } catch (const std::exception& ex) {
         std::ostringstream err;
-        err << "failed to open sockets after server reconfiguration: " << ex.what();
+        err << "failed to open sockets after server reconfiguration: "
+            << ex.what();
         answer = isc::config::createAnswer(1, err.str());
     }
 

+ 5 - 4
src/bin/dhcp6/dhcp6_messages.mes

@@ -46,10 +46,11 @@ new configuration. it is output during server startup, and when an updated
 configuration is committed by the administrator.  Additional information
 may be provided.
 
-% DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
-This critical error message indicates that the initial DHCPv6
-configuration has failed. The server will start, but nothing will be
-served until the configuration has been corrected.
+% DHCP6_CONFIG_LOAD_FAIL configuration error using file: %1, reason: %2
+This error message indicates that the DHCPv6 configuration has failed.
+If this is an initial configuration (during server's startup) the server
+will fail to start. If this is a dynamic reconfiguration attempt the
+server will continue to use an old configuration.
 
 % DHCP6_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
 This is an informational message reporting that the configuration has

+ 6 - 58
src/bin/dhcp6/dhcp6_srv.cc

@@ -150,6 +150,12 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
         // Instantiate allocation engine
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
 
+        // We have to point out to the CfgMgr that the we are in the IPv6
+        // domain, so as the IPv6 sockets are opened rather than IPv4 sockets
+        // which are the default.
+        CfgMgr::instance().getConfiguration()
+            ->cfg_iface_.setFamily(CfgIface::V6);
+
         /// @todo call loadLibraries() when handling configuration changes
 
     } catch (const std::exception &e) {
@@ -2429,64 +2435,6 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
     return reply;
 }
 
-void
-Dhcpv6Srv::openActiveSockets(const uint16_t port) {
-    IfaceMgr::instance().closeSockets();
-
-    // Get the reference to the collection of interfaces. This reference should be
-    // valid as long as the program is run because IfaceMgr is a singleton.
-    // Therefore we can safely iterate over instances of all interfaces and modify
-    // their flags. Here we modify flags which indicate wheter socket should be
-    // open for a particular interface or not.
-    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
-    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
-         iface != ifaces.end(); ++iface) {
-        Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
-        if (iface_ptr == NULL) {
-            isc_throw(isc::Unexpected, "Interface Manager returned NULL"
-                      << " instance of the interface when DHCPv6 server was"
-                      << " trying to reopen sockets after reconfiguration");
-        }
-
-        // Ignore loopback interfaces.
-        if (iface_ptr->flag_loopback_) {
-            iface_ptr->inactive6_ = true;
-
-        } else  if (CfgMgr::instance().isActiveIface(iface->getName())) {
-            iface_ptr->inactive6_ = false;
-            LOG_INFO(dhcp6_logger, DHCP6_ACTIVATE_INTERFACE)
-                .arg(iface->getFullName());
-
-        } else {
-            // For deactivating interface, it should be sufficient to log it
-            // on the debug level because it is more useful to know what
-            // interface is activated which is logged on the info level.
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC,
-                      DHCP6_DEACTIVATE_INTERFACE).arg(iface->getName());
-            iface_ptr->inactive6_ = true;
-
-        }
-
-        iface_ptr->clearUnicasts();
-
-        const IOAddress* unicast = CfgMgr::instance().getUnicast(iface->getName());
-        if (unicast) {
-            LOG_INFO(dhcp6_logger, DHCP6_SOCKET_UNICAST).arg(unicast->toText())
-                .arg(iface->getName());
-            iface_ptr->addUnicast(*unicast);
-        }
-    }
-    // Let's reopen active sockets. openSockets6 will check internally whether
-    // sockets are marked active or inactive.
-    // @todo Optimization: we should not reopen all sockets but rather select
-    // those that have been affected by the new configuration.
-    isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
-        boost::bind(&Dhcpv6Srv::ifaceMgrSocket6ErrorHandler, _1);
-    if (!IfaceMgr::instance().openSockets6(port, error_handler)) {
-        LOG_WARN(dhcp6_logger, DHCP6_NO_SOCKETS_OPEN);
-    }
-}
-
 size_t
 Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
                          const std::string& option_space,

+ 0 - 16
src/bin/dhcp6/dhcp6_srv.h

@@ -100,27 +100,11 @@ public:
     /// Typically, server listens on UDP port 547. Other ports are only
     /// used for testing purposes.
     ///
-    /// This accessor must be public because sockets are reopened from the
-    /// static configuration callback handler. This callback handler invokes
-    /// @c ControlledDhcpv4Srv::openActiveSockets which requires port parameter
-    /// which has to be retrieved from the @c ControlledDhcpv4Srv object.
-    /// They are retrieved using this public function.
-    ///
     /// @return UDP port on which server should listen.
     uint16_t getPort() const {
         return (port_);
     }
 
-    /// @brief Open sockets which are marked as active in @c CfgMgr.
-    ///
-    /// This function reopens sockets according to the current settings in the
-    /// Configuration Manager. It holds the list of the interfaces which server
-    /// should listen on. This function will open sockets on these interfaces
-    /// only. This function is not exception safe.
-    ///
-    /// @param port UDP port on which server should listen.
-    static void openActiveSockets(const uint16_t port);
-
     /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
     ///
     /// If updates are enabled, it Instructs the D2ClientMgr singleton to

+ 7 - 9
src/bin/dhcp6/json_config_parser.cc

@@ -798,16 +798,15 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
     } catch (const isc::Exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_PARSER_FAIL)
                   .arg(config_pair.first).arg(ex.what());
-        answer = isc::config::createAnswer(1,
-                     string("Configuration parsing failed: ") + ex.what());
+        answer = isc::config::createAnswer(1, ex.what());
         // An error occured, so make sure that we restore original data.
         rollback = true;
 
     } catch (...) {
         // for things like bad_cast in boost::lexical_cast
         LOG_ERROR(dhcp6_logger, DHCP6_PARSER_EXCEPTION).arg(config_pair.first);
-        answer = isc::config::createAnswer(1,
-                     string("Configuration parsing failed"));
+        answer = isc::config::createAnswer(1, "undefined configuration"
+                                           " processing error");
         // An error occured, so make sure that we restore original data.
         rollback = true;
     }
@@ -835,15 +834,14 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
         }
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
-            answer = isc::config::createAnswer(2,
-                         string("Configuration commit failed:") + ex.what());
+            answer = isc::config::createAnswer(2, ex.what());
             // An error occured, so make sure to restore the original data.
             rollback = true;
         } catch (...) {
             // for things like bad_cast in boost::lexical_cast
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_EXCEPTION);
-            answer = isc::config::createAnswer(2,
-                         string("Configuration commit failed"));
+            answer = isc::config::createAnswer(2, "undefined configuration"
+                                               " parsing error");
             // An error occured, so make sure to restore the original data.
             rollback = true;
         }
@@ -860,7 +858,7 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
              getConfigSummary(Configuration::CFGSEL_ALL6));
 
     // Everything was fine. Configuration is successful.
-    answer = isc::config::createAnswer(0, "Configuration committed.");
+    answer = isc::config::createAnswer(0, "Configuration successful.");
     return (answer);
 }
 

+ 24 - 33
src/bin/dhcp6/kea_controller.cc

@@ -60,12 +60,8 @@ void configure(const std::string& file_name) {
 
         // Read contents of the file and parse it as JSON
         json = isc::data::Element::fromJSONFile(file_name, true);
-
         if (!json) {
-            LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
-                .arg("Config file " + file_name + " missing or empty.");
-            isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
-                      + file_name);
+            isc_throw(isc::BadValue, "no configuration found");
         }
 
         // Let's configure logging before applying the configuration,
@@ -80,43 +76,38 @@ void configure(const std::string& file_name) {
         dhcp6 = json->get("Dhcp6");
 
         if (!dhcp6) {
-            LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
-                .arg("Config file " + file_name + " does not include 'Dhcp6' entry.");
-            isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
-                      + file_name);
+            isc_throw(isc::BadValue, "no mandatory 'Dhcp6' entry in"
+                      " the configuration");
         }
 
         // Use parsed JSON structures to configure the server
         result = ControlledDhcpv6Srv::processCommand("config-reload", dhcp6);
+        if (!result) {
+            // Undetermined status of the configuration. This should never
+            // happen, but as the configureDhcp6Server returns a pointer, it is
+            // theoretically possible that it will return NULL.
+            isc_throw(isc::BadValue, "undefined result of "
+                      "processCommand(\"config-reload\", dhcp6)");
+        }
 
-    }  catch (const std::exception& ex) {
-        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
-        isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
-                  + file_name);
-    }
+        // Now check is the returned result is successful (rcode=0) or not
+        // (see @ref isc::config::parseAnswer).
+        int rcode;
+        isc::data::ConstElementPtr comment =
+            isc::config::parseAnswer(rcode, result);
+        if (rcode != 0) {
+            string reason = comment ? comment->stringValue() :
+                "no details available";
+            isc_throw(isc::BadValue, reason);
+        }
 
-    if (!result) {
-        // Undetermined status of the configuration. This should never happen,
-        // but as the configureDhcp6Server returns a pointer, it is theoretically
-        // possible that it will return NULL.
+    }  catch (const std::exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
-            .arg("Configuration failed: Undefined result of configureDhcp6Server"
-                 "() function after attempting to read " + file_name);
-        return;
+            .arg(file_name).arg(ex.what());
+        isc_throw(isc::BadValue, "configuration error using file '"
+                  << file_name << "': " << ex.what());
     }
 
-    // Now check is the returned result is successful (rcode=0) or not
-    isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer
-    int rcode;
-    comment = isc::config::parseAnswer(rcode, result);
-    if (rcode != 0) {
-        string reason = "";
-        if (comment) {
-            reason = comment->stringValue();
-        }
-        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(reason);
-        isc_throw(isc::BadValue, "Failed to apply configuration:" << reason);
-    }
 }
 
 /// @brief Signals handler for DHCPv6 server.

+ 23 - 21
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -20,6 +20,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/addr_utilities.h>
@@ -74,7 +75,8 @@ public:
         // deal with sockets here, just check if configuration handling
         // is sane.
 
-        const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+        const IfaceMgr::IfaceCollection& ifaces =
+            IfaceMgr::instance().getIfaces();
 
         // There must be some interface detected
         if (ifaces.empty()) {
@@ -341,7 +343,7 @@ public:
     /// test to make sure that contents of the database do not affect the
     /// results of subsequent tests.
     void resetConfiguration() {
-        string config = "{ \"interfaces\": [ \"all\" ],"
+        string config = "{ \"interfaces\": [ \"*\" ],"
             "\"hooks-libraries\": [ ],"
             "\"preferred-lifetime\": 3000,"
             "\"rebind-timer\": 2000, "
@@ -357,7 +359,9 @@ public:
         // properly test interface configuration we disable listening on
         // all interfaces before each test and later check that this setting
         // has been overriden by the configuration used in the test.
-        CfgMgr::instance().deleteActiveIfaces();
+        CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
+        CfgMgr::instance().getConfiguration()->
+            cfg_iface_.setFamily(CfgIface::V6);
         // Create fresh context.
         globalContext()->copyContext(ParserContext(Option::V6));
     }
@@ -2892,7 +2896,7 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
 
     // Create the first part of the configuration string.
     string config =
-        "{ \"interfaces\": [ \"all\" ],"
+        "{ \"interfaces\": [ \"*\" ],"
             "\"hooks-libraries\": [";
 
     // Append the libraries (separated by commas if needed)
@@ -3030,15 +3034,15 @@ TEST_F(Dhcp6ParserTest, LibrariesSpecified) {
 // This test verifies that it is possible to select subset of interfaces on
 // which server should listen.
 TEST_F(Dhcp6ParserTest, selectedInterfaces) {
+    IfaceMgrTestConfig test_config(true);
 
     // Make sure there is no garbage interface configuration in the CfgMgr.
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+    ASSERT_FALSE(test_config.socketOpen("eth0", AF_INET6));
+    ASSERT_FALSE(test_config.socketOpen("eth1", AF_INET6));
 
     ConstElementPtr status;
 
-    string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
+    string config = "{ \"interfaces\": [ \"eth0\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -3053,20 +3057,20 @@ TEST_F(Dhcp6ParserTest, selectedInterfaces) {
     // as the pool does not belong to that subnet
     checkResult(status, 0);
 
+    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+
     // eth0 and eth1 were explicitly selected. eth2 was not.
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
-    EXPECT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+    EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET6));
+    EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET6));
 }
 
 // This test verifies that it is possible to configure the server to listen on
 // all interfaces.
 TEST_F(Dhcp6ParserTest, allInterfaces) {
+    IfaceMgrTestConfig test_config(true);
 
-    // Make sure there is no garbage interface configuration in the CfgMgr.
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+    ASSERT_FALSE(test_config.socketOpen("eth0", AF_INET6));
+    ASSERT_FALSE(test_config.socketOpen("eth1", AF_INET6));
 
     ConstElementPtr status;
 
@@ -3084,15 +3088,13 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
-
-    // returned value must be 1 (values error)
-    // as the pool does not belong to that subnet
     checkResult(status, 0);
 
+    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+
     // All interfaces should be now active.
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
-    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
+    EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET6));
+    EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET6));
 }
 
 

+ 2 - 2
src/bin/dhcp6/tests/confirm_unittest.cc

@@ -42,7 +42,7 @@ namespace {
 ///
 const char* CONFIRM_CONFIGS[] = {
 // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -61,7 +61,7 @@ const char* CONFIRM_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
 
 // Configuration 1
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "

+ 1 - 1
src/bin/dhcp6/tests/d2_unittest.cc

@@ -75,7 +75,7 @@ Dhcp6SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
 
 void
 Dhcp6SrvD2Test::reset() {
-    std::string config = "{ \"interfaces\": [ \"all\" ],"
+    std::string config = "{ \"interfaces\": [ \"*\" ],"
             "\"hooks-libraries\": [ ],"
             "\"preferred-lifetime\": 3000,"
             "\"rebind-timer\": 2000, "

+ 3 - 3
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -289,7 +289,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
     IfaceMgrTestConfig test_config(true);
 
     ConstElementPtr x;
-    string config = "{ \"interfaces\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -1557,7 +1557,7 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
 
     IfaceMgrTestConfig test_config(true);
 
-    string config = "{ \"interfaces\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -1642,7 +1642,7 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
 // src/lib/dhcp/docsis3_option_defs.h.
 TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
     ConstElementPtr x;
-    string config_prefix = "{ \"interfaces\": [ \"all\" ],"
+    string config_prefix = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "

+ 6 - 6
src/bin/dhcp6/tests/rebind_unittest.cc

@@ -64,7 +64,7 @@ namespace {
 ///   - this specific configuration is used by tests which don't use relays
 const char* REBIND_CONFIGS[] = {
 // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -83,7 +83,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
 
 // Configuration 1
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -102,7 +102,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
 
 // Configuration 2
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -121,7 +121,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
 
 // Configuration 3
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -140,7 +140,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
 
 // Configuration 4
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -167,7 +167,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
 
 // Configuration 5
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "

+ 1 - 1
src/bin/dhcp6/tests/sarr_unittest.cc

@@ -31,7 +31,7 @@ namespace {
 ///   - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
 const char* CONFIGS[] = {
     // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "

+ 2 - 2
src/lib/cc/data.cc

@@ -736,8 +736,8 @@ Element::fromJSONFile(const std::string& file_name,
     if (!infile.is_open())
     {
         const char* error = strerror(errno);
-        isc_throw(InvalidOperation, "Failed to read file '" << file_name
-                  << "', error:" << error);
+        isc_throw(InvalidOperation, "failed to read file '" << file_name
+                  << "': " << error);
     }
 
     return (fromJSON(infile, file_name, preproc));

+ 25 - 1
src/lib/dhcp/iface_mgr.cc

@@ -242,6 +242,18 @@ Iface::getAddress4(isc::asiolink::IOAddress& address) const {
     return (false);
 }
 
+bool
+Iface::hasAddress(const isc::asiolink::IOAddress& address) const {
+    const AddressCollection& addrs = getAddresses();
+    for (AddressCollection::const_iterator addr = addrs.begin();
+         addr != addrs.end(); ++addr) {
+        if (address == *addr) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
@@ -457,7 +469,11 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
                                " interface " << iface->getName());
                 continue;
 
-            } else if (!iface->flag_up_ || !iface->flag_running_) {
+            }
+
+            IOAddress out_address("0.0.0.0");
+            if (!iface->flag_up_ || !iface->flag_running_ ||
+                !iface->getAddress4(out_address)) {
                 IFACEMGR_ERROR(SocketConfigError, error_handler,
                                "the interface " << iface->getName()
                                << " is down or has no usable IPv4"
@@ -682,6 +698,14 @@ IfaceMgr::clearIfaces() {
     ifaces_.clear();
 }
 
+void
+IfaceMgr::clearUnicasts() {
+    for (IfaceCollection::iterator iface=ifaces_.begin();
+         iface!=ifaces_.end(); ++iface) {
+        iface->clearUnicasts();
+    }
+}
+
 int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
                          const uint16_t port, const bool receive_bcast,
                          const bool send_bcast) {

+ 9 - 0
src/lib/dhcp/iface_mgr.h

@@ -279,6 +279,12 @@ public:
     /// for the interface (if true), or not (false).
     bool getAddress4(isc::asiolink::IOAddress& address) const;
 
+    /// @brief Check if the interface has the specified address assigned.
+    ///
+    /// @param address Address to be checked.
+    /// @return true if address is assigned to the intefrace, false otherwise.
+    bool hasAddress(const isc::asiolink::IOAddress& address) const;
+
     /// @brief Adds an address to an interface.
     ///
     /// This only adds an address to collection, it does not physically
@@ -549,6 +555,9 @@ public:
     /// IPv6 address is read from interfaces.txt file.
     void detectIfaces();
 
+    /// @brief Clears unicast addresses on all interfaces.
+    void clearUnicasts();
+
     /// @brief Return most suitable socket for transmitting specified IPv6 packet.
     ///
     /// This method takes Pkt6 (see overloaded implementation that takes

+ 13 - 0
src/lib/dhcp/pkt_filter_inet6.cc

@@ -73,6 +73,19 @@ PktFilterInet6::openSocket(const Iface& iface,
                   " socket.");
     }
 
+#ifdef SO_REUSEPORT
+    // Set SO_REUSEPORT has to be set to open multiple sockets and bind to
+    // in6addr_any (binding to port). Binding to port is required on some
+    // operating systems, e.g. NetBSD and OpenBSD so as the socket can
+    // join the socket to multicast group.
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+                   (char *)&flag, sizeof(flag)) < 0) {
+        close(sock);
+        isc_throw(SocketConfigError, "Can't set SO_REUSEPORT option on IPv6"
+                  " socket.");
+    }
+#endif
+
     if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
         // Get the error message immediately after the bind because the
         // invocation to close() below would override the errno.

+ 36 - 0
src/lib/dhcp/tests/iface_mgr_test_config.cc

@@ -141,6 +141,42 @@ IfaceMgrTestConfig::setIfaceFlags(const std::string& name,
     iface->inactive6_ = inactive6.flag_;
 }
 
+bool
+IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
+                         const int family) const {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    if (iface == NULL) {
+        isc_throw(Unexpected, "No such interface '" << iface_name << "'");
+    }
+
+    const Iface::SocketCollection& sockets = iface->getSockets();
+    for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+         sock != sockets.end(); ++sock) {
+        if (sock->family_ == family) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+bool
+IfaceMgrTestConfig::unicastOpen(const std::string& iface_name) const {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    if (iface == NULL) {
+        isc_throw(Unexpected, "No such interface '" << iface_name << "'");
+    }
+
+    const Iface::SocketCollection& sockets = iface->getSockets();
+    for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+         sock != sockets.end(); ++sock) {
+        if ((!sock->addr_.isV6LinkLocal()) &&
+            (!sock->addr_.isV6Multicast())) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
 }
 }
 }

+ 12 - 0
src/lib/dhcp/tests/iface_mgr_test_config.h

@@ -229,6 +229,18 @@ public:
                        const FlagInactive4& inactive4,
                        const FlagInactive6& inactive6);
 
+    /// @brief Checks if socket of the specified family is opened on interface.
+    ///
+    /// @param iface_name Interface name.
+    /// @param family One of: AF_INET or AF_INET6
+    bool socketOpen(const std::string& iface_name, const int family) const;
+
+    /// @brief Checks if unicast socket is opened on interface.
+    ///
+    /// @param iface_name Interface name.
+    bool unicastOpen(const std::string& iface_name) const;
+
+
 private:
     /// @brief Currently used packet filter for DHCPv4.
     PktFilterPtr packet_filter4_;

+ 14 - 0
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -606,6 +606,20 @@ TEST_F(IfaceMgrTest, ifaceGetAddress) {
 
 }
 
+// This test checks if it is possible to check that the specific address is
+// assigned to the interface.
+TEST_F(IfaceMgrTest, ifaceHasAddress) {
+    IfaceMgrTestConfig config(true);
+
+    Iface* iface = IfaceMgr::instance().getIface("eth0");
+    ASSERT_FALSE(iface == NULL);
+    EXPECT_TRUE(iface->hasAddress(IOAddress("10.0.0.1")));
+    EXPECT_FALSE(iface->hasAddress(IOAddress("10.0.0.2")));
+    EXPECT_TRUE(iface->hasAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")));
+    EXPECT_TRUE(iface->hasAddress(IOAddress("2001:db8:1::1")));
+    EXPECT_FALSE(iface->hasAddress(IOAddress("2001:db8:1::2")));
+}
+
 // TODO: Implement getPlainMac() test as soon as interface detection
 // is implemented.
 TEST_F(IfaceMgrTest, getIface) {

+ 1 - 0
src/lib/dhcpsrv/Makefile.am

@@ -55,6 +55,7 @@ libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
 libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
+libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h

+ 270 - 0
src/lib/dhcpsrv/cfg_iface.cc

@@ -0,0 +1,270 @@
+// Copyright (C) 2014 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 <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <util/strutil.h>
+#include <boost/bind.hpp>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+const char* CfgIface::ALL_IFACES_KEYWORD = "*";
+
+CfgIface::CfgIface(Family family)
+    : family_(family),
+      wildcard_used_(false) {
+}
+
+void
+CfgIface::closeSockets() {
+    IfaceMgr::instance().closeSockets();
+}
+
+void
+CfgIface::openSockets(const uint16_t port, const bool use_bcast) {
+    // If wildcard interface '*' was not specified, set all interfaces to
+    // inactive state. We will later enable them selectively using the
+    // interface names specified by the user. If wildcard interface was
+    // specified, mark all interfaces active. In all cases, mark loopback
+    // inactive.
+    setState(!wildcard_used_, true);
+    // Remove selection of unicast addresses from all interfaces.
+    IfaceMgr::instance().clearUnicasts();
+    // If there is no wildcard interface specified, we will have to iterate
+    // over the names specified by the caller and enable them.
+    if (!wildcard_used_) {
+        for (IfaceSet::const_iterator iface_name = iface_set_.begin();
+             iface_name != iface_set_.end(); ++iface_name) {
+            Iface* iface = IfaceMgr::instance().getIface(*iface_name);
+            // This shouldn't really happen because we are checking the
+            // names of interfaces when they are being added (use()
+            // function). But, if someone has triggered detection of
+            // interfaces since then, some interfaces may have disappeared.
+            if (iface == NULL) {
+                isc_throw(Unexpected,
+                          "fail to open socket on interface '"
+                          << *iface_name << "' as this interface doesn't"
+                          " exist");
+
+            } else if (getFamily() == V4) {
+                iface->inactive4_ = false;
+
+            } else {
+                iface->inactive6_ = false;
+            }
+        }
+    }
+
+    // Select unicast sockets. It works only for V6. Ignore for V4.
+    if (getFamily() == V6) {
+        for (UnicastMap::const_iterator unicast = unicast_map_.begin();
+             unicast != unicast_map_.end(); ++unicast) {
+            Iface* iface = IfaceMgr::instance().getIface(unicast->first);
+            if (iface == NULL) {
+                isc_throw(Unexpected,
+                          "fail to open unicast socket on interface '"
+                          << unicast->first << "' as this interface doesn't"
+                          " exist");
+            }
+            iface->addUnicast(unicast->second);
+            iface->inactive6_ = false;
+        }
+    }
+
+    // Before opening any sockets, close existing ones.
+    closeSockets();
+
+    // Set the callback which is called when the socket fails to open
+    // for some specific interface. This callback will simply log a
+    // warning message.
+    IfaceMgrErrorMsgCallback error_callback =
+        boost::bind(&CfgIface::socketOpenErrorHandler, _1);
+    bool sopen;
+    if (getFamily() == V4) {
+        sopen = IfaceMgr::instance().openSockets4(port, use_bcast,
+                                                  error_callback);
+    } else {
+        // use_bcast is ignored for V6.
+        sopen = IfaceMgr::instance().openSockets6(port, error_callback);
+    }
+
+    // If no socket were opened, log a warning because the server will
+    // not respond to any queries.
+    if (!sopen) {
+        LOG_WARN(dhcpsrv_logger, DHCPSRV_NO_SOCKETS_OPEN);
+    }
+}
+
+void
+CfgIface::reset() {
+    wildcard_used_ = false;
+    iface_set_.clear();
+}
+
+void
+CfgIface::setState(const bool inactive, const bool loopback_inactive) {
+    IfaceMgr::IfaceCollection ifaces = IfaceMgr::instance().getIfaces();
+    for (IfaceMgr::IfaceCollection::iterator iface = ifaces.begin();
+         iface != ifaces.end(); ++iface) {
+        Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
+        if (getFamily() == V4) {
+            iface_ptr->inactive4_ = iface_ptr->flag_loopback_ ?
+                loopback_inactive : inactive;
+        } else {
+            iface_ptr->inactive6_ = iface_ptr->flag_loopback_ ?
+                loopback_inactive : inactive;
+        }
+    }
+}
+
+void
+CfgIface::socketOpenErrorHandler(const std::string& errmsg) {
+    LOG_WARN(dhcpsrv_logger, DHCPSRV_OPEN_SOCKET_FAIL).arg(errmsg);
+}
+
+void
+CfgIface::use(const std::string& iface_name) {
+    // The interface name specified may have two formats, e.g.:
+    // - eth0
+    // - eth0/2001:db8:1::1.
+    // The latter format is used to open unicast socket on the specified
+    // interface. Here we are detecting which format was used and we strip
+    // all extraneous spaces.
+    size_t pos = iface_name.find("/");
+    std::string name;
+    std::string addr_str;
+    // There is no unicast address so the whole string is an interface name.
+    if (pos == std::string::npos) {
+        name = util::str::trim(iface_name);
+        if (name.empty()) {
+            isc_throw(InvalidIfaceName,
+                      "empty interface name used in configuration");
+
+        } if (name != ALL_IFACES_KEYWORD) {
+            if (IfaceMgr::instance().getIface(name) == NULL) {
+                isc_throw(NoSuchIface, "interface '" << name
+                          << "' doesn't exist in the system");
+            }
+
+            // If interface has already been specified.
+            if (iface_set_.find(name) != iface_set_.end()) {
+                isc_throw(DuplicateIfaceName, "interface '" << name
+                          << "' has already been specified");
+
+            }
+
+            // All ok, add interface.
+            LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_IFACE)
+                .arg(name);
+            iface_set_.insert(name);
+
+        } else if (wildcard_used_) {
+            isc_throw(DuplicateIfaceName, "the wildcard interface '"
+                      << ALL_IFACES_KEYWORD << "' can only be specified once");
+
+        } else {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
+            wildcard_used_ = true;
+
+        }
+
+    } else if (getFamily() == V4) {
+        isc_throw(InvalidIfaceName, "unicast addresses in the format of: "
+                  "iface-name/unicast-addr_stress can only be specified for"
+                  " IPv6 addr_stress family");
+
+    } else {
+        // The interface name includes the unicast addr_stress, so we split
+        // interface name and the unicast addr_stress to two variables.
+        name = util::str::trim(iface_name.substr(0, pos));
+        addr_str = util::str::trim(iface_name.substr(pos + 1));
+
+        // Interface name must not be empty.
+        if (name.empty()) {
+            isc_throw(InvalidIfaceName,
+                      "empty interface name specified in the"
+                      " interface configuration");
+
+        }
+        // Unicast addr_stress following the interface name must not be empty.
+        if (addr_str.empty()) {
+            isc_throw(InvalidIfaceName,
+                      "empty unicast addr_stress specified in the interface"
+                      << " configuration");
+
+        }
+
+        // Interface name must not be the wildcard name.
+        if (name == ALL_IFACES_KEYWORD) {
+            isc_throw(InvalidIfaceName,
+                      "wildcard interface name '" << ALL_IFACES_KEYWORD
+                      << "' must not be used in conjunction with a"
+                      " unicast addr_stress");
+
+        }
+
+        // Interface must exist.
+        Iface* iface = IfaceMgr::instance().getIface(name);
+        if (iface == NULL) {
+            isc_throw(NoSuchIface, "interface '" << name
+                      << "' doesn't exist in the system");
+
+        }
+
+        // Convert address string. This may throw an exception if the address
+        // is invalid.
+        IOAddress addr(addr_str);
+
+        // Check that the address is a valid unicast address.
+        if (!addr.isV6() || addr.isV6LinkLocal() || addr.isV6Multicast()) {
+            isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
+                      " a valid IPv6 unicast address");
+        }
+
+        // There are valid cases where link local address can be specified to
+        // receive unicast traffic, e.g. sent by relay agent.
+        if (addr.isV6LinkLocal()) {
+            LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL)
+                .arg(addr.toText()).arg(name);
+        }
+
+        // Interface must have this address assigned.
+        if (!iface->hasAddress(addr)) {
+            isc_throw(NoSuchAddress,
+                      "interface '" << name << "' doesn't have address '"
+                      << addr << "' assigned");
+        }
+
+        // Insert address and the interface to the collection of unicast
+        // addresses.
+        if (unicast_map_.find(name) != unicast_map_.end()) {
+            isc_throw(DuplicateIfaceName, "must not specify unicast address '"
+                      << addr << "' for interface '" << name << "' "
+                      "because other unicast address has already been"
+                      " specified for this interface");
+        }
+        LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_UNICAST)
+            .arg(addr.toText()).arg(name);
+        unicast_map_.insert(std::pair<std::string, IOAddress>(name, addr));
+    }
+
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 203 - 0
src/lib/dhcpsrv/cfg_iface.h

@@ -0,0 +1,203 @@
+// Copyright (C) 2014 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 IFACE_CFG_H
+#define IFACE_CFG_H
+
+#include <asiolink/io_address.h>
+#include <map>
+#include <set>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when duplicated interface names specified.
+class DuplicateIfaceName : public Exception {
+public:
+    DuplicateIfaceName(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when specified interface name is invalid.
+class InvalidIfaceName : public Exception {
+public:
+    InvalidIfaceName(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when specified interface doesn't exist in a system.
+class NoSuchIface : public Exception {
+public:
+    NoSuchIface(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when specified unicast address is not assigned
+/// to the interface specified.
+class NoSuchAddress : public Exception {
+public:
+    NoSuchAddress(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents selection of interfaces for DHCP server.
+///
+/// This class manages selection of interfaces on which the DHCP server is
+/// listening to queries. The interfaces are selected in the server
+/// configuration by their names or by the pairs of interface names and unicast
+/// addresses (e.g. eth0/2001:db8:1::1). The latter format is only accepted when
+/// IPv6 configuration is in use.
+///
+/// This class also accepts "wildcard" interface name which, if specified,
+/// instructs the server to listen on all available interfaces.
+///
+/// Once interfaces have been specified the sockets (either IPv4 or IPv6)
+/// can be opened by calling @c CfgIface::openSockets function.
+class CfgIface {
+public:
+    /// @brief Keyword used to enable all interfaces.
+    ///
+    /// This keyword can be used instead of the interface name to specify
+    /// that DHCP server should listen on all interfaces.
+    static const char* ALL_IFACES_KEYWORD;
+
+    /// @brief Protocol family: IPv4 or IPv6.
+    ///
+    /// Depending on the family specified, the IPv4 or IPv6 sockets are
+    /// opened.
+    enum Family {
+        V4, V6
+    };
+
+    /// @brief Constructor.
+    ///
+    /// @param family Protocol family (default is V4).
+    CfgIface(Family family = V4);
+
+    /// @brief Convenience function which closes all open sockets.
+    void closeSockets();
+
+    /// @brief Returns protocol family used by the @c CfgIface.
+    Family getFamily() const {
+        return (family_);
+    }
+
+    /// @brief Tries to open sockets on selected interfaces.
+    ///
+    /// This function opens sockets bound to link-local address as well as
+    /// sockets bound to unicast address. See @c CfgIface::use function
+    /// documentation for details how to specify interfaces and unicast
+    /// addresses to bind the sockets to.
+    ///
+    /// @param port Port number to be used to bind sockets to.
+    /// @param use_bcast A boolean flag which indicates if the broadcast
+    /// traffic should be received through the socket. This parameter is
+    /// ignored for IPv6.
+    void openSockets(const uint16_t port, const bool use_bcast = true);
+
+    /// @brief Puts the interface configuration into default state.
+    ///
+    /// This function removes interface names from the set.
+    void reset();
+
+    /// @brief Sets protocol family.
+    ///
+    /// @param family New family value (V4 or V6).
+    void setFamily(Family family) {
+        family_ = family;
+    }
+
+    /// @brief Select interface to be used to receive DHCP traffic.
+    ///
+    /// This function controls the selection of the interface on which the
+    /// DHCP queries should be received by the server. The interface name
+    /// passed as the argument of this function may appear in one of the following
+    /// formats:
+    /// - interface-name, e.g. eth0
+    /// - interface-name/unicast-address, e.g. eth0/2001:db8:1::1 (V6 only)
+    ///
+    /// Extraneous spaces surrounding the interface name and/or unicast address
+    /// are accepted. For example: eth0 / 2001:db8:1::1 will be accepted.
+    ///
+    /// When only interface name is specified (without an address) it is allowed
+    /// to use the "wildcard" interface name (*) which indicates that the server
+    /// should open sockets on all interfaces. When IPv6 is in use, the sockets
+    /// will be bound to the link local addresses. Wildcard interface names are
+    /// not allowed when specifying a unicast address. For example:
+    /// */2001:db8:1::1 is not allowed.
+    ///
+    /// @param iface_name Explicit interface name, a wildcard name (*) of
+    /// the interface(s) or the pair of iterface/unicast-address to be used
+    /// to receive DHCP traffic.
+    ///
+    /// @throw InvalidIfaceName If the interface name is incorrect, e.g. empty.
+    /// @throw NoSuchIface If the specified interface is not present.
+    /// @throw NoSuchAddress If the specified unicast address is not assigned
+    /// to the interface.
+    /// @throw DuplicateIfaceName If the interface is already selected, i.e.
+    /// @throw IOError when specified unicast address is invalid.
+    /// @c CfgIface::use has been already called for this interface.
+    void use(const std::string& iface_name);
+
+private:
+
+    /// @brief Selects or deselects interfaces.
+    ///
+    /// This function selects all interfaces to receive DHCP traffic or
+    /// deselects all interfaces so as none of them receives a DHCP traffic.
+    ///
+    /// @param inactive A boolean value which indicates if all interfaces
+    /// (except loopback) should be selected or deselected.
+    /// @param loopback_inactive A boolean value which indicates if loopback
+    /// interface should be selected or deselected.
+    /// should be deselected/inactive (true) or selected/active (false).
+    void setState(const bool inactive, const bool loopback_inactive);
+
+    /// @brief Error handler for executed when opening a socket fail.
+    ///
+    /// A pointer to this function is passed to the @c IfaceMgr::openSockets4
+    /// or @c IfaceMgr::openSockets6. These functions call this handler when
+    /// they fail to open a socket. The handler logs an error passed in the
+    /// parameter.
+    ///
+    /// @param errmsg Error message being logged by the function.
+    static void socketOpenErrorHandler(const std::string& errmsg);
+
+    /// @brief Protocol family.
+    Family family_;
+
+    /// @brief Represents a set of interface names.
+    typedef std::set<std::string> IfaceSet;
+
+    /// @brief A set of interface names specified by the user.
+    IfaceSet iface_set_;
+
+    /// @brief A map of interfaces and unicast addresses.
+    typedef std::map<std::string, asiolink::IOAddress> UnicastMap;
+
+    /// @brief A map which holds the pairs of interface names and unicast
+    /// addresses for which the unicast sockets should be opened.
+    ///
+    /// This is only used for V6 family.
+    UnicastMap unicast_map_;
+
+    /// @brief A booolean value which indicates that the wildcard interface name
+    /// has been specified (*).
+    bool wildcard_used_;
+};
+
+}
+}
+
+#endif // IFACE_CFG_H

+ 1 - 82
src/lib/dhcpsrv/cfgmgr.cc

@@ -314,77 +314,6 @@ std::string CfgMgr::getDataDir() {
     return (datadir_);
 }
 
-void
-CfgMgr::addActiveIface(const std::string& iface) {
-
-    size_t pos = iface.find("/");
-    std::string iface_copy = iface;
-
-    if (pos != std::string::npos) {
-        std::string addr_string = iface.substr(pos + 1);
-        try {
-            IOAddress addr(addr_string);
-            iface_copy = iface.substr(0,pos);
-            unicast_addrs_.insert(make_pair(iface_copy, addr));
-        } catch (...) {
-            isc_throw(BadValue, "Can't convert '" << addr_string
-                      << "' into address in interface defition ('"
-                      << iface << "')");
-        }
-    }
-
-    if (isIfaceListedActive(iface_copy)) {
-        isc_throw(DuplicateListeningIface,
-                  "attempt to add duplicate interface '" << iface_copy << "'"
-                  " to the set of interfaces on which server listens");
-    }
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_IFACE)
-        .arg(iface_copy);
-    active_ifaces_.push_back(iface_copy);
-}
-
-void
-CfgMgr::activateAllIfaces() {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-              DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
-    all_ifaces_active_ = true;
-}
-
-void
-CfgMgr::deleteActiveIfaces() {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-              DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES);
-    active_ifaces_.clear();
-    all_ifaces_active_ = false;
-
-    unicast_addrs_.clear();
-}
-
-bool
-CfgMgr::isActiveIface(const std::string& iface) const {
-
-    // @todo Verify that the interface with the specified name is
-    // present in the system.
-
-    // If all interfaces are marked active, there is no need to check that
-    // the name of this interface has been explicitly listed.
-    if (all_ifaces_active_) {
-        return (true);
-    }
-    return (isIfaceListedActive(iface));
-}
-
-bool
-CfgMgr::isIfaceListedActive(const std::string& iface) const {
-    for (ActiveIfacesCollection::const_iterator it = active_ifaces_.begin();
-         it != active_ifaces_.end(); ++it) {
-        if (iface == *it) {
-            return (true);
-        }
-    }
-    return (false);
-}
-
 bool
 CfgMgr::isDuplicate(const Subnet4& subnet) const {
     for (Subnet4Collection::const_iterator subnet_it = subnets4_.begin();
@@ -408,15 +337,6 @@ CfgMgr::isDuplicate(const Subnet6& subnet) const {
 }
 
 
-const isc::asiolink::IOAddress*
-CfgMgr::getUnicast(const std::string& iface) const {
-    UnicastIfacesCollection::const_iterator addr = unicast_addrs_.find(iface);
-    if (addr == unicast_addrs_.end()) {
-        return (NULL);
-    }
-    return (&(*addr).second);
-}
-
 void
 CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
     d2_client_mgr_.setD2ClientConfig(new_config);
@@ -443,8 +363,7 @@ CfgMgr::getConfiguration() {
 }
 
 CfgMgr::CfgMgr()
-    : datadir_(DHCP_DATA_DIR),
-      all_ifaces_active_(false), echo_v4_client_id_(true),
+    : datadir_(DHCP_DATA_DIR), echo_v4_client_id_(true),
       d2_client_mgr_(), configuration_(new Configuration()) {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks

+ 0 - 78
src/lib/dhcpsrv/cfgmgr.h

@@ -334,54 +334,6 @@ public:
     /// @return data directory
     std::string getDataDir();
 
-    /// @brief Adds the name of the interface to the set of interfaces on which
-    /// server should listen.
-    ///
-    /// @param iface A name of the interface being added to the listening set.
-    void addActiveIface(const std::string& iface);
-
-    /// @brief Sets the flag which indicates that server is supposed to listen
-    /// on all available interfaces.
-    ///
-    /// This function does not close or open sockets. It simply marks that
-    /// server should start to listen on all interfaces the next time sockets
-    /// are reopened. Server should examine this flag when it gets reconfigured
-    /// and configuration changes the interfaces that server should listen on.
-    void activateAllIfaces();
-
-    /// @brief Clear the collection of the interfaces that server should listen
-    /// on.
-    ///
-    /// Apart from clearing the list of interfaces specified with
-    /// @c CfgMgr::addListeningInterface, it also disables listening on all
-    /// interfaces if it has been enabled using
-    /// @c CfgMgr::activateAllInterfaces.
-    /// Likewise @c CfgMgr::activateAllIfaces, this function does not close or
-    /// open sockets. It marks all interfaces inactive for DHCP traffic.
-    /// Server should examine this new setting when it attempts to
-    /// reopen sockets (as a result of reconfiguration).
-    void deleteActiveIfaces();
-
-    /// @brief Check if specified interface should be used to listen to DHCP
-    /// traffic.
-    ///
-    /// @param iface A name of the interface to be checked.
-    ///
-    /// @return true if the specified interface belongs to the set of the
-    /// interfaces on which server is configured to listen.
-    bool isActiveIface(const std::string& iface) const;
-
-    /// @brief returns unicast a given interface should listen on (or NULL)
-    ///
-    /// This method will return an address for a specified interface, if the
-    /// server is supposed to listen on unicast address. This address is
-    /// intended to be used immediately. This pointer is valid only until
-    /// the next configuration change.
-    ///
-    /// @return IOAddress pointer (or NULL if none)
-    const isc::asiolink::IOAddress*
-    getUnicast(const std::string& iface) const;
-
     /// @brief Sets whether server should send back client-id in DHCPv4
     ///
     /// This is a compatibility flag. The default (true) is compliant with
@@ -458,20 +410,6 @@ protected:
 
 private:
 
-    /// @brief Checks if the specified interface is listed as active.
-    ///
-    /// This function searches for the specified interface name on the list of
-    /// active interfaces: @c CfgMgr::active_ifaces_. It does not take into
-    /// account @c CfgMgr::all_ifaces_active_ flag. If this flag is set to true
-    /// but the specified interface does not belong to
-    /// @c CfgMgr::active_ifaces_, it will return false.
-    ///
-    /// @param iface interface name.
-    ///
-    /// @return true if specified interface belongs to
-    /// @c CfgMgr::active_ifaces_.
-    bool isIfaceListedActive(const std::string& iface) const;
-
     /// @brief Checks that the IPv4 subnet with the given id already exists.
     ///
     /// @param subnet Subnet for which this function will check if the other
@@ -502,22 +440,6 @@ private:
     /// @brief directory where data files (e.g. server-id) are stored
     std::string datadir_;
 
-    /// @name A collection of interface names on which server listens.
-    //@{
-    typedef std::list<std::string> ActiveIfacesCollection;
-    std::list<std::string> active_ifaces_;
-    //@}
-
-    /// @name a collection of unicast addresses and the interfaces names the
-    //        server is supposed to listen on
-    //@{
-    typedef std::map<std::string, isc::asiolink::IOAddress> UnicastIfacesCollection;
-    UnicastIfacesCollection unicast_addrs_;
-
-    /// A flag which indicates that server should listen on all available
-    /// interfaces.
-    bool all_ifaces_active_;
-
     /// Indicates whether v4 server should send back client-id
     bool echo_v4_client_id_;
 

+ 7 - 0
src/lib/dhcpsrv/configuration.h

@@ -15,6 +15,7 @@
 #ifndef DHCPSRV_CONFIGURATION_H
 #define DHCPSRV_CONFIGURATION_H
 
+#include <dhcpsrv/cfg_iface.h>
 #include <log/logger_level.h>
 #include <boost/shared_ptr.hpp>
 #include <vector>
@@ -112,6 +113,12 @@ struct Configuration {
     /// @brief logging specific information
     LoggingInfoStorage logging_info_;
 
+    /// @brief Interface configuration.
+    ///
+    /// Used to select interfaces on which the DHCP server will listen to
+    /// queries.
+    CfgIface cfg_iface_;
+
     /// @brief Returns summary of the configuration in the textual format.
     ///
     /// This method returns the brief text describing the current configuration.

+ 12 - 44
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -36,10 +36,6 @@ using namespace isc::hooks;
 namespace isc {
 namespace dhcp {
 
-namespace {
-const char* ALL_IFACES_KEYWORD = "*";
-}
-
 // *********************** ParserContext  *************************
 
 ParserContext::ParserContext(Option::Universe universe):
@@ -184,8 +180,7 @@ template <> void ValueParser<std::string>::build(ConstElementPtr value) {
 
 InterfaceListConfigParser::
 InterfaceListConfigParser(const std::string& param_name)
-    : activate_all_(false),
-      param_name_(param_name) {
+    : param_name_(param_name) {
     if (param_name_ != "interfaces") {
         isc_throw(BadValue, "Internal error. Interface configuration "
             "parser called for the wrong parameter: " << param_name);
@@ -194,53 +189,26 @@ InterfaceListConfigParser(const std::string& param_name)
 
 void
 InterfaceListConfigParser::build(ConstElementPtr value) {
-    // First, we iterate over all specified entries and add it to the
-    // local container so as we can do some basic validation, e.g. eliminate
-    // duplicates.
+    // Copy the current interface configuration.
+    ConfigurationPtr config = CfgMgr::instance().getConfiguration();
+    cfg_iface_ = config->cfg_iface_;
+    cfg_iface_.reset();
     BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
         std::string iface_name = iface->stringValue();
-        if (iface_name != ALL_IFACES_KEYWORD) {
-            // Let's eliminate duplicates. We could possibly allow duplicates,
-            // but if someone specified duplicated interface name it is likely
-            // that he mistyped the configuration. Failing here should draw his
-            // attention.
-            if (isIfaceAdded(iface_name)) {
-                isc_throw(isc::dhcp::DhcpConfigError, "duplicate interface"
-                          << " name '" << iface_name << "' specified in '"
-                          << param_name_ << "' configuration parameter "
-                          "(" << value->getPosition() << ")");
-            }
-            // @todo check that this interface exists in the system!
-            // The IfaceMgr exposes mechanisms to check this.
-
-            // Add the interface name if ok.
-            interfaces_.push_back(iface_name);
-
-        } else {
-            activate_all_ = true;
+        try {
+            cfg_iface_.use(iface_name);
 
+        } catch (const std::exception& ex) {
+            isc_throw(DhcpConfigError, "Failed to select interface: "
+                      << ex.what() << " (" << value->getPosition() << ")");
         }
     }
 }
 
 void
 InterfaceListConfigParser::commit() {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-    // Remove active interfaces and clear a flag which marks all interfaces
-    // active
-    cfg_mgr.deleteActiveIfaces();
-
-    if (activate_all_) {
-        // Activate all interfaces. There is not need to add their names
-        // explicitly.
-        cfg_mgr.activateAllIfaces();
-
-    } else {
-        // Explicitly add names of the interfaces which server should listen on.
-        BOOST_FOREACH(std::string iface, interfaces_) {
-            cfg_mgr.addActiveIface(iface);
-        }
-    }
+    // Use the new configuration created in a build time.
+    CfgMgr::instance().getConfiguration()->cfg_iface_ = cfg_iface_;
 }
 
 bool

+ 7 - 4
src/lib/dhcpsrv/dhcp_parsers.h

@@ -20,6 +20,7 @@
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/d2_client_cfg.h>
 #include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
@@ -406,7 +407,9 @@ public:
     /// @param value pointer to the content of parsed values
     virtual void build(isc::data::ConstElementPtr value);
 
-    /// @brief commits interfaces list configuration
+    /// @brief Assignes a parsed list of interfaces to the configuration.
+    ///
+    /// This is exception safe operation.
     virtual void commit();
 
 private:
@@ -422,11 +425,11 @@ private:
     typedef std::list<std::string> IfaceListStorage;
     IfaceListStorage interfaces_;
 
-    // Should server listen on all interfaces.
-    bool activate_all_;
-
     // Parsed parameter name
     std::string param_name_;
+
+    /// Holds the configuration created during
+    CfgIface cfg_iface_;
 };
 
 /// @brief Parser for hooks library list

+ 20 - 2
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -54,10 +54,14 @@ consider reducing the lease lifetime.  In this way, addresses allocated
 to clients that are no longer active on the network will become available
 available sooner.
 
-% DHCPSRV_CFGMGR_ADD_IFACE adding listening interface %1
-A debug message issued when new interface is being added to the collection of
+% DHCPSRV_CFGMGR_ADD_IFACE listening on interface %1
+An info message issued when new interface is being added to the collection of
 interfaces on which server listens to DHCP messages.
 
+% DHCPSRV_CFGMGR_ADD_UNICAST listening on unicast address %1, on interface %2
+A debug message issued when new configuring DHCP server to listen on unicast
+address on the specific interface.
+
 % DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
 A debug message reported when the DHCP configuration manager is adding the
 specified IPv4 subnet to its database.
@@ -141,6 +145,11 @@ This is a debug message reporting that the DHCP configuration manager has
 returned the specified IPv6 subnet, because detected relay agent address
 matches value specified for this subnet.
 
+% DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL specified link local address %1 for unicast traffic on interface %2
+This warning message is logged when user specified a link-local address to
+receive unicast traffic. The warning message is issued because it is an
+uncommon use.
+
 % DHCPSRV_CLOSE_DB closing currently open %1 database
 This is a debug message, issued when the DHCP server closes the currently
 open lease database.  It is issued at program shutdown and whenever
@@ -393,12 +402,21 @@ lease from the MySQL database for the specified address.
 A debug message issued when the server is attempting to update IPv6
 lease from the MySQL database for the specified address.
 
+% DHCPSRV_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
+This warning message is issued when current server configuration specifies
+no interfaces that server should listen on, or specified interfaces are not
+configured to receive the traffic.
+
 % DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
 This is an error message, logged when an attempt has been made to access
 a database backend, but where no 'type' keyword has been included in
 the access string.  The access string (less any passwords) is included
 in the message.
 
+% DHCPSRV_OPEN_SOCKET_FAIL failed to open socket: %1
+A warning message issued when IfaceMgr fails to open and bind a socket.
+The reason for the failure is appended as an argument of the log message.
+
 % DHCPSRV_PGSQL_ADD_ADDR4 adding IPv4 lease with address %1
 A debug message issued when the server is about to add an IPv4 lease
 with the specified address to the PostgreSQL backend database.

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -63,6 +63,7 @@ libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc

+ 226 - 0
src/lib/dhcpsrv/tests/cfg_iface_unittest.cc

@@ -0,0 +1,226 @@
+// Copyright (C) 2014 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 <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture class for testing the @c CfgIface class.
+class CfgIfaceTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// By initializing the @c IfaceMgrTestConfig object it creates a set of
+    /// fake interfaces: lo, eth0, eth1.
+    CfgIfaceTest() :
+        iface_mgr_test_config_(true) {
+    }
+
+    /// @brief Checks if socket of the specified family is opened on interface.
+    ///
+    /// @param iface_name Interface name.
+    /// @param family One of: AF_INET or AF_INET6
+    bool socketOpen(const std::string& iface_name, const int family) const;
+
+    /// @brief Checks if unicast socket is opened on interface.
+    ///
+    /// @param iface_name Interface name.
+    bool unicastOpen(const std::string& iface_name) const;
+
+    /// @brief Holds a fake configuration of the interfaces.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+bool
+CfgIfaceTest::socketOpen(const std::string& iface_name,
+                         const int family) const {
+    return (iface_mgr_test_config_.socketOpen(iface_name, family));
+}
+bool
+CfgIfaceTest::unicastOpen(const std::string& iface_name) const {
+    return (iface_mgr_test_config_.unicastOpen(iface_name));
+}
+
+// This test checks that the interface names can be explicitly selected
+// by their names and IPv4 sockets are opened on these interfaces.
+TEST_F(CfgIfaceTest, explicitNamesV4) {
+    CfgIface cfg(CfgIface::V4);
+    // Specify valid interface names. There should be no error.
+    ASSERT_NO_THROW(cfg.use("eth0"));
+    ASSERT_NO_THROW(cfg.use("eth1"));
+
+    // Open sockets on specified interfaces.
+    cfg.openSockets(DHCP4_SERVER_PORT);
+
+    // Sockets should be now open on eth0 and eth1, but not on loopback.
+    EXPECT_TRUE(socketOpen("eth0", AF_INET));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET));
+    EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+    // No IPv6 sockets should be present because we wanted IPv4 sockets.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET6));
+    EXPECT_FALSE(socketOpen("eth1", AF_INET6));
+    EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+    // Close all sockets and make sure they are really closed.
+    cfg.closeSockets();
+    ASSERT_FALSE(socketOpen("eth0", AF_INET));
+    ASSERT_FALSE(socketOpen("eth1", AF_INET));
+    ASSERT_FALSE(socketOpen("lo", AF_INET));
+
+    // Reset configuration and select only one interface this time.
+    cfg.reset();
+    ASSERT_NO_THROW(cfg.use("eth1"));
+
+    cfg.openSockets(DHCP4_SERVER_PORT);
+
+    // Socket should be open on eth1 only.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET));
+    EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+}
+
+// This test checks that the interface names can be explicitly selected
+// by their names and IPv6 sockets are opened on these interfaces.
+TEST_F(CfgIfaceTest, explicitNamesV6) {
+    CfgIface cfg(CfgIface::V6);
+    // Specify valid interface names. There should be no error.
+    ASSERT_NO_THROW(cfg.use("eth0"));
+    ASSERT_NO_THROW(cfg.use("eth1"));
+
+    // Open sockets on specified interfaces.
+    cfg.openSockets(DHCP6_SERVER_PORT);
+
+    // Sockets should be now open on eth0 and eth1, but not on loopback.
+    EXPECT_TRUE(socketOpen("eth0", AF_INET6));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET6));
+    EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+    // No IPv4 sockets should be present because we wanted IPv4 sockets.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET));
+    EXPECT_FALSE(socketOpen("eth1", AF_INET));
+    EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+    // Close all sockets and make sure they are really closed.
+    cfg.closeSockets();
+    ASSERT_FALSE(socketOpen("eth0", AF_INET6));
+    ASSERT_FALSE(socketOpen("eth1", AF_INET6));
+    ASSERT_FALSE(socketOpen("lo", AF_INET6));
+
+    // Reset configuration and select only one interface this time.
+    cfg.reset();
+    ASSERT_NO_THROW(cfg.use("eth1"));
+
+    cfg.openSockets(DHCP6_SERVER_PORT);
+
+    // Socket should be open on eth1 only.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET6));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET6));
+    EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+}
+
+// This test checks that the wildcard interface name can be specified to
+// select all interfaces to open IPv4 sockets.
+TEST_F(CfgIfaceTest, wildcardV4) {
+    CfgIface cfg(CfgIface::V4);
+    ASSERT_NO_THROW(cfg.use("*"));
+
+    cfg.openSockets(DHCP4_SERVER_PORT);
+
+    // Sockets should be now open on eth0 and eth1, but not on loopback.
+    EXPECT_TRUE(socketOpen("eth0", AF_INET));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET));
+    EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+    // No IPv6 sockets should be present because we wanted IPv4 sockets.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET6));
+    EXPECT_FALSE(socketOpen("eth1", AF_INET6));
+    EXPECT_FALSE(socketOpen("lo", AF_INET6));
+}
+
+// This test checks that the wildcard interface name can be specified to
+// select all interfaces to open IPv6 sockets.
+TEST_F(CfgIfaceTest, wildcardV6) {
+    CfgIface cfg(CfgIface::V6);
+    ASSERT_NO_THROW(cfg.use("*"));
+
+    cfg.openSockets(DHCP4_SERVER_PORT);
+
+    // Sockets should be now open on eth0 and eth1, but not on loopback.
+    EXPECT_TRUE(socketOpen("eth0", AF_INET6));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET6));
+    EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+    // No IPv6 sockets should be present because we wanted IPv6 sockets.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET));
+    EXPECT_FALSE(socketOpen("eth1", AF_INET));
+    EXPECT_FALSE(socketOpen("lo", AF_INET));
+}
+
+// Test that unicast address can be specified for the socket to be opened on
+// the interface on which the socket bound to link local address is also
+// opened.
+TEST_F(CfgIfaceTest, validUnicast) {
+    CfgIface cfg(CfgIface::V6);
+
+    // One socket will be opened on link-local address, one on unicast but
+    // on the same interface.
+    ASSERT_NO_THROW(cfg.use("eth0"));
+    ASSERT_NO_THROW(cfg.use("eth0/2001:db8:1::1"));
+
+    cfg.openSockets(DHCP6_SERVER_PORT);
+
+    EXPECT_TRUE(socketOpen("eth0", AF_INET6));
+    EXPECT_TRUE(unicastOpen("eth0"));
+}
+
+// Test that when invalid interface names are specified an exception is thrown.
+TEST_F(CfgIfaceTest, invalidValues) {
+    CfgIface cfg(CfgIface::V4);
+    ASSERT_THROW(cfg.use(""), InvalidIfaceName);
+    ASSERT_THROW(cfg.use(" "), InvalidIfaceName);
+    ASSERT_THROW(cfg.use("bogus"), NoSuchIface);
+
+    ASSERT_NO_THROW(cfg.use("eth0"));
+    ASSERT_THROW(cfg.use("eth0"), DuplicateIfaceName);
+
+    ASSERT_THROW(cfg.use("eth0/2001:db8:1::1"), InvalidIfaceName);
+
+    cfg.setFamily(CfgIface::V6);
+
+    ASSERT_THROW(cfg.use("eth0/"), InvalidIfaceName);
+    ASSERT_THROW(cfg.use("/2001:db8:1::1"), InvalidIfaceName);
+    ASSERT_THROW(cfg.use("*/2001:db8:1::1"), InvalidIfaceName);
+    ASSERT_THROW(cfg.use("bogus/2001:db8:1::1"), NoSuchIface);
+    ASSERT_THROW(cfg.use("eth0/fe80::3a60:77ff:fed5:cdef"), InvalidIfaceName);
+    ASSERT_THROW(cfg.use("eth0/fe80::3a60:77ff:fed5:cdef"), InvalidIfaceName);
+    ASSERT_THROW(cfg.use("eth0/2001:db8:1::2"), NoSuchAddress);
+
+    ASSERT_NO_THROW(cfg.use("*"));
+    ASSERT_THROW(cfg.use("*"), DuplicateIfaceName);
+}
+
+} // end of anonymous namespace

+ 0 - 80
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -265,7 +265,6 @@ public:
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
-        CfgMgr::instance().deleteActiveIfaces();
     }
 
     /// @brief generates interface-id option based on provided text
@@ -983,85 +982,6 @@ TEST_F(CfgMgrTest, optionSpace6) {
     /// @todo decide if a duplicate vendor space is allowed.
 }
 
-// This test verifies that it is possible to specify interfaces that server
-// should listen on.
-TEST_F(CfgMgrTest, addActiveIface) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth0"));
-    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth1"));
-
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
-
-    EXPECT_NO_THROW(cfg_mgr.deleteActiveIfaces());
-
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
-}
-
-
-// This test verifies that it is possible to specify interfaces that server
-// should listen on.
-TEST_F(CfgMgrTest, addUnicastAddresses) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth1/2001:db8::1"));
-    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth2/2001:db8::2"));
-    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth3"));
-
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth3"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth4"));
-
-    ASSERT_TRUE(cfg_mgr.getUnicast("eth1"));
-    EXPECT_EQ("2001:db8::1", cfg_mgr.getUnicast("eth1")->toText());
-    EXPECT_EQ("2001:db8::2", cfg_mgr.getUnicast("eth2")->toText());
-    EXPECT_FALSE(cfg_mgr.getUnicast("eth3"));
-    EXPECT_FALSE(cfg_mgr.getUnicast("eth4"));
-
-    EXPECT_NO_THROW(cfg_mgr.deleteActiveIfaces());
-
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth3"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth4"));
-
-    ASSERT_FALSE(cfg_mgr.getUnicast("eth1"));
-    ASSERT_FALSE(cfg_mgr.getUnicast("eth2"));
-    EXPECT_FALSE(cfg_mgr.getUnicast("eth3"));
-    EXPECT_FALSE(cfg_mgr.getUnicast("eth4"));
-}
-
-
-// This test verifies that it is possible to set the flag which configures the
-// server to listen on all interfaces.
-TEST_F(CfgMgrTest, activateAllIfaces) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    cfg_mgr.addActiveIface("eth0");
-    cfg_mgr.addActiveIface("eth1");
-
-    ASSERT_TRUE(cfg_mgr.isActiveIface("eth0"));
-    ASSERT_TRUE(cfg_mgr.isActiveIface("eth1"));
-    ASSERT_FALSE(cfg_mgr.isActiveIface("eth2"));
-
-    cfg_mgr.activateAllIfaces();
-
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
-
-    cfg_mgr.deleteActiveIfaces();
-
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
-}
-
 // This test verifies that RFC6842 (echo client-id) compatibility may be
 // configured.
 TEST_F(CfgMgrTest, echoClientId) {

+ 27 - 15
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -18,6 +18,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/dhcp_parsers.h>
@@ -47,9 +48,19 @@ namespace {
 class DhcpParserTest : public ::testing::Test {
 public:
     /// @brief Constructor
-    ///
     DhcpParserTest() {
-        CfgMgr::instance().deleteActiveIfaces();
+        resetIfaceCfg();
+    }
+
+    /// @brief Destructor.
+    virtual ~DhcpParserTest() {
+        resetIfaceCfg();
+    }
+
+    /// @brief Resets selection of the interfaces from previous tests.
+    void resetIfaceCfg() {
+        CfgMgr::instance().getConfiguration()->cfg_iface_.closeSockets();
+        CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
     }
 };
 
@@ -210,6 +221,7 @@ TEST_F(DhcpParserTest, uint32ParserTest) {
 /// 4. Parses wildcard interface name and sets a CfgMgr flag which indicates
 /// that server will listen on all interfaces.
 TEST_F(DhcpParserTest, interfaceListParserTest) {
+    IfaceMgrTestConfig test_config(true);
 
     const std::string name = "interfaces";
 
@@ -220,11 +232,6 @@ TEST_F(DhcpParserTest, interfaceListParserTest) {
         parser(new InterfaceListConfigParser(name));
     ElementPtr list_element = Element::createList();
     list_element->add(Element::create("eth0"));
-    list_element->add(Element::create("eth1"));
-
-    // Make sure there are no interfaces added yet.
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
-    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
 
     // This should parse the configuration and add eth0 and eth1 to the list
     // of interfaces that server should listen on.
@@ -233,24 +240,29 @@ TEST_F(DhcpParserTest, interfaceListParserTest) {
 
     // Use CfgMgr instance to check if eth0 and eth1 was added, and that
     // eth2 was not added.
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
-    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+    ConfigurationPtr cfg = CfgMgr::instance().getConfiguration();
+    ASSERT_TRUE(cfg);
+    ASSERT_NO_THROW(cfg->cfg_iface_.openSockets(10000));
+
+    EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
+    EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET));
 
     // Add keyword all to the configuration. This should activate all
     // interfaces, including eth2, even though it has not been explicitly
     // added.
     list_element->add(Element::create("*"));
 
-    // Reset parser's state.
+    // Reset parser and configuration.
     parser.reset(new InterfaceListConfigParser(name));
+    cfg->cfg_iface_.closeSockets();
+    cfg->cfg_iface_.reset();
+
     parser->build(list_element);
     parser->commit();
+    ASSERT_NO_THROW(cfg->cfg_iface_.openSockets(10000));
 
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
-    EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
+    EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
+    EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));
 }
 
 // Checks whether option space can be detected as vendor-id