Parcourir la source

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

Conflicts:
	ChangeLog
Tomek Mrugalski il y a 10 ans
Parent
commit
d330a5830d
46 fichiers modifiés avec 1051 ajouts et 624 suppressions
  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
 	Compilation fix for PostgreSQL on i686. Thanks to Jiri Popelka
 	from RedHat for providing a patch!
 	from RedHat for providing a patch!
 	(Trac #3532, git 96a06654f2177444dcea3a0e9f6fa06947855497)
 	(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
 831.	[bug]		marcin
 	Kea deamons report configuration summary when the configuration is
 	Kea deamons report configuration summary when the configuration is
 	completed successfully.
 	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
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
         // reopen sockets according to new configuration.
-        openActiveSockets(getPort(), useBroadcast());
+        CfgMgr::instance().getConfiguration()->cfg_iface_
+            .openSockets(getPort(), useBroadcast());
 
 
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
         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 <dhcp4/dhcp4_log.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
 #include <dhcp4/json_config_parser.h>
 #include <dhcp4/json_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
 
 
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::hooks;
 using namespace isc::hooks;
@@ -117,7 +118,7 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
         err << "Server object not initialized, can't process config.";
         err << "Server object not initialized, can't process config.";
         return (isc::config::createAnswer(1, err.str()));
         return (isc::config::createAnswer(1, err.str()));
     }
     }
-    
+
     ConstElementPtr answer = configureDhcp4Server(*srv, config);
     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
     // safe and we really don't want to emit exceptions to whoever called this
     // method. Instead, catch an exception and create appropriate answer.
     // method. Instead, catch an exception and create appropriate answer.
     try {
     try {
-        srv->openActiveSockets(srv->getPort(), getInstance()->useBroadcast());
+        CfgMgr::instance().getConfiguration()->cfg_iface_
+            .openSockets(srv->getPort(), getInstance()->useBroadcast());
+
     } catch (std::exception& ex) {
     } catch (std::exception& ex) {
         err << "failed to open sockets after server reconfiguration: "
         err << "failed to open sockets after server reconfiguration: "
             << ex.what();
             << 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
 configuration is committed by the administrator.  Additional information
 may be provided.
 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
 % DHCP4_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
 This is an informational message reporting that the configuration has
 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
 size_t
 Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
 Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
                          const std::string& option_space,
                          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.
     /// @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.
     /// @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.
     /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
     ///
     ///
     /// If updates are enabled, it Instructs the D2ClientMgr singleton to
     /// 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) {
     } catch (const isc::Exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
         LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
                   .arg(config_pair.first).arg(ex.what());
                   .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.
         // An error occured, so make sure that we restore original data.
         rollback = true;
         rollback = true;
@@ -603,8 +602,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     } catch (...) {
     } catch (...) {
         // For things like bad_cast in boost::lexical_cast
         // For things like bad_cast in boost::lexical_cast
         LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(config_pair.first);
         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.
         // An error occured, so make sure that we restore original data.
         rollback = true;
         rollback = true;
@@ -636,14 +635,13 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
         }
         }
         catch (const isc::Exception& ex) {
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
             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;
             rollback = true;
         } catch (...) {
         } catch (...) {
             // For things like bad_cast in boost::lexical_cast
             // For things like bad_cast in boost::lexical_cast
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
             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;
             rollback = true;
         }
         }
     }
     }
@@ -659,7 +657,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
              getConfigSummary(Configuration::CFGSEL_ALL4));
              getConfigSummary(Configuration::CFGSEL_ALL4));
 
 
     // Everything was fine. Configuration is successful.
     // Everything was fine. Configuration is successful.
-    answer = isc::config::createAnswer(0, "Configuration committed.");
+    answer = isc::config::createAnswer(0, "Configuration successful.");
     return (answer);
     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
         // Read contents of the file and parse it as JSON
         json = isc::data::Element::fromJSONFile(file_name, true);
         json = isc::data::Element::fromJSONFile(file_name, true);
-
         if (!json) {
         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,
         // 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
         // Get Dhcp4 component from the config
         dhcp4 = json->get("Dhcp4");
         dhcp4 = json->get("Dhcp4");
-
         if (!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
         // Use parsed JSON structures to configure the server
         result = ControlledDhcpv4Srv::processCommand("config-reload", dhcp4);
         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)
         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/option_int.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/classify.h>
 #include <dhcp/classify.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/testutils/config_result_check.h>
 #include <dhcpsrv/testutils/config_result_check.h>
@@ -77,7 +78,6 @@ public:
         // deal with sockets here, just check if configuration handling
         // deal with sockets here, just check if configuration handling
         // is sane.
         // is sane.
         srv_.reset(new Dhcpv4Srv(0));
         srv_.reset(new Dhcpv4Srv(0));
-        CfgMgr::instance().deleteActiveIfaces();
         // Create fresh context.
         // Create fresh context.
         globalContext()->copyContext(ParserContext(Option::V4));
         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
 // This test verifies that it is possible to select subset of interfaces
 // on which server should listen.
 // on which server should listen.
 TEST_F(Dhcp4ParserTest, selectedInterfaces) {
 TEST_F(Dhcp4ParserTest, selectedInterfaces) {
+    IfaceMgrTestConfig test_config(true);
+
     ConstElementPtr x;
     ConstElementPtr x;
     string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
     string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
@@ -2907,24 +2909,26 @@ TEST_F(Dhcp4ParserTest, selectedInterfaces) {
 
 
     // Make sure the config manager is clean and there is no hanging
     // Make sure the config manager is clean and there is no hanging
     // interface configuration.
     // 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.
     // Apply configuration.
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
     ASSERT_TRUE(status);
     checkResult(status, 0);
     checkResult(status, 0);
 
 
+    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+
     // eth0 and eth1 were explicitly selected. eth2 was not.
     // 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
 // This test verifies that it is possible to configure the server in such a way
 // that it listens on all interfaces.
 // that it listens on all interfaces.
 TEST_F(Dhcp4ParserTest, allInterfaces) {
 TEST_F(Dhcp4ParserTest, allInterfaces) {
+    IfaceMgrTestConfig test_config(true);
+
     ConstElementPtr x;
     ConstElementPtr x;
     // This configuration specifies two interfaces on which server should listen
     // This configuration specifies two interfaces on which server should listen
     // but it also includes asterisk. The asterisk switches server into the
     // but it also includes asterisk. The asterisk switches server into the
@@ -2940,19 +2944,19 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Make sure there is no old configuration.
     // 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.
     // Apply configuration.
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
     ASSERT_TRUE(status);
     checkResult(status, 0);
     checkResult(status, 0);
 
 
+    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+
     // All interfaces should be now active.
     // 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
 // 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)));
     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,
 // Verifies that DISCOVER message can be processed correctly,
 // that the OFFER message generated in response is valid and
 // that the OFFER message generated in response is valid and
 // contains necessary options.
 // contains necessary options.
@@ -2970,7 +2950,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
     NakedDhcpv4Srv srv(0);
     NakedDhcpv4Srv srv(0);
 
 
     ConstElementPtr x;
     ConstElementPtr x;
-    string config = "{ \"interfaces\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
         "    \"option-data\": [ {"
         "    \"option-data\": [ {"
@@ -3055,7 +3035,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
 // src/lib/dhcp/docsis3_option_defs.h.
 // src/lib/dhcp/docsis3_option_defs.h.
 TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
 TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
     ConstElementPtr x;
     ConstElementPtr x;
-    string config_prefix = "{ \"interfaces\": [ \"all\" ],"
+    string config_prefix = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
         "    \"option-data\": [ {"
         "    \"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")));
     pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
     subnet_->addPool(pool_);
     subnet_->addPool(pool_);
 
 
-    CfgMgr::instance().deleteActiveIfaces();
+    CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
     CfgMgr::instance().deleteSubnets4();
     CfgMgr::instance().deleteSubnets4();
     CfgMgr::instance().addSubnet4(subnet_);
     CfgMgr::instance().addSubnet4(subnet_);
 
 
@@ -58,6 +58,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
 
 
     // Make sure that we revert to default value
     // Make sure that we revert to default value
+    CfgMgr::instance().getConfiguration()->cfg_iface_.reset();
     CfgMgr::instance().echoClientId(true);
     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.
 ///   - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
 const char* DORA_CONFIGS[] = {
 const char* DORA_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"valid-lifetime\": 600,"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "\"subnet4\": [ { "
         "    \"subnet\": \"10.0.0.0/24\", "
         "    \"subnet\": \"10.0.0.0/24\", "
@@ -86,7 +86,7 @@ const char* DORA_CONFIGS[] = {
     "}",
     "}",
 
 
 // Configuration 1
 // Configuration 1
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"valid-lifetime\": 600,"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "\"subnet4\": [ { "
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"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.
 ///   - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
 const char* INFORM_CONFIGS[] = {
 const char* INFORM_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"valid-lifetime\": 600,"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "\"subnet4\": [ { "
         "    \"subnet\": \"10.0.0.0/24\", "
         "    \"subnet\": \"10.0.0.0/24\", "
@@ -85,7 +85,7 @@ const char* INFORM_CONFIGS[] = {
     "}",
     "}",
 
 
 // Configuration 1
 // Configuration 1
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"valid-lifetime\": 600,"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "\"subnet4\": [ { "
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"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
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
         // 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) {
     } catch (const std::exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
         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 <config.h>
 #include <cc/data.h>
 #include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
 #include <hooks/hooks_manager.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.
     // safe and we really don't want to emit exceptions to the callback caller.
     // Instead, catch an exception and create appropriate answer.
     // Instead, catch an exception and create appropriate answer.
     try {
     try {
-        srv->openActiveSockets(srv->getPort());
+        CfgMgr::instance().getConfiguration()->cfg_iface_
+            .openSockets(srv->getPort());
+
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
         std::ostringstream err;
         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());
         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
 configuration is committed by the administrator.  Additional information
 may be provided.
 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
 % DHCP6_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
 This is an informational message reporting that the configuration has
 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
         // Instantiate allocation engine
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
         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
         /// @todo call loadLibraries() when handling configuration changes
 
 
     } catch (const std::exception &e) {
     } catch (const std::exception &e) {
@@ -2429,64 +2435,6 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
     return reply;
     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
 size_t
 Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
 Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
                          const std::string& option_space,
                          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
     /// Typically, server listens on UDP port 547. Other ports are only
     /// used for testing purposes.
     /// 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.
     /// @return UDP port on which server should listen.
     uint16_t getPort() const {
     uint16_t getPort() const {
         return (port_);
         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.
     /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
     ///
     ///
     /// If updates are enabled, it Instructs the D2ClientMgr singleton to
     /// 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) {
     } catch (const isc::Exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_PARSER_FAIL)
         LOG_ERROR(dhcp6_logger, DHCP6_PARSER_FAIL)
                   .arg(config_pair.first).arg(ex.what());
                   .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.
         // An error occured, so make sure that we restore original data.
         rollback = true;
         rollback = true;
 
 
     } catch (...) {
     } catch (...) {
         // for things like bad_cast in boost::lexical_cast
         // for things like bad_cast in boost::lexical_cast
         LOG_ERROR(dhcp6_logger, DHCP6_PARSER_EXCEPTION).arg(config_pair.first);
         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.
         // An error occured, so make sure that we restore original data.
         rollback = true;
         rollback = true;
     }
     }
@@ -835,15 +834,14 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
         }
         }
         catch (const isc::Exception& ex) {
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
             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.
             // An error occured, so make sure to restore the original data.
             rollback = true;
             rollback = true;
         } catch (...) {
         } catch (...) {
             // for things like bad_cast in boost::lexical_cast
             // for things like bad_cast in boost::lexical_cast
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_EXCEPTION);
             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.
             // An error occured, so make sure to restore the original data.
             rollback = true;
             rollback = true;
         }
         }
@@ -860,7 +858,7 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
              getConfigSummary(Configuration::CFGSEL_ALL6));
              getConfigSummary(Configuration::CFGSEL_ALL6));
 
 
     // Everything was fine. Configuration is successful.
     // Everything was fine. Configuration is successful.
-    answer = isc::config::createAnswer(0, "Configuration committed.");
+    answer = isc::config::createAnswer(0, "Configuration successful.");
     return (answer);
     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
         // Read contents of the file and parse it as JSON
         json = isc::data::Element::fromJSONFile(file_name, true);
         json = isc::data::Element::fromJSONFile(file_name, true);
-
         if (!json) {
         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,
         // Let's configure logging before applying the configuration,
@@ -80,43 +76,38 @@ void configure(const std::string& file_name) {
         dhcp6 = json->get("Dhcp6");
         dhcp6 = json->get("Dhcp6");
 
 
         if (!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
         // Use parsed JSON structures to configure the server
         result = ControlledDhcpv6Srv::processCommand("config-reload", dhcp6);
         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)
         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.
 /// @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/iface_mgr.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/addr_utilities.h>
@@ -74,7 +75,8 @@ public:
         // deal with sockets here, just check if configuration handling
         // deal with sockets here, just check if configuration handling
         // is sane.
         // is sane.
 
 
-        const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+        const IfaceMgr::IfaceCollection& ifaces =
+            IfaceMgr::instance().getIfaces();
 
 
         // There must be some interface detected
         // There must be some interface detected
         if (ifaces.empty()) {
         if (ifaces.empty()) {
@@ -341,7 +343,7 @@ public:
     /// test to make sure that contents of the database do not affect the
     /// test to make sure that contents of the database do not affect the
     /// results of subsequent tests.
     /// results of subsequent tests.
     void resetConfiguration() {
     void resetConfiguration() {
-        string config = "{ \"interfaces\": [ \"all\" ],"
+        string config = "{ \"interfaces\": [ \"*\" ],"
             "\"hooks-libraries\": [ ],"
             "\"hooks-libraries\": [ ],"
             "\"preferred-lifetime\": 3000,"
             "\"preferred-lifetime\": 3000,"
             "\"rebind-timer\": 2000, "
             "\"rebind-timer\": 2000, "
@@ -357,7 +359,9 @@ public:
         // properly test interface configuration we disable listening on
         // properly test interface configuration we disable listening on
         // all interfaces before each test and later check that this setting
         // all interfaces before each test and later check that this setting
         // has been overriden by the configuration used in the test.
         // 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.
         // Create fresh context.
         globalContext()->copyContext(ParserContext(Option::V6));
         globalContext()->copyContext(ParserContext(Option::V6));
     }
     }
@@ -2892,7 +2896,7 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
 
 
     // Create the first part of the configuration string.
     // Create the first part of the configuration string.
     string config =
     string config =
-        "{ \"interfaces\": [ \"all\" ],"
+        "{ \"interfaces\": [ \"*\" ],"
             "\"hooks-libraries\": [";
             "\"hooks-libraries\": [";
 
 
     // Append the libraries (separated by commas if needed)
     // 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
 // This test verifies that it is possible to select subset of interfaces on
 // which server should listen.
 // which server should listen.
 TEST_F(Dhcp6ParserTest, selectedInterfaces) {
 TEST_F(Dhcp6ParserTest, selectedInterfaces) {
+    IfaceMgrTestConfig test_config(true);
 
 
     // Make sure there is no garbage interface configuration in the CfgMgr.
     // 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;
     ConstElementPtr status;
 
 
-    string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
+    string config = "{ \"interfaces\": [ \"eth0\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -3053,20 +3057,20 @@ TEST_F(Dhcp6ParserTest, selectedInterfaces) {
     // as the pool does not belong to that subnet
     // as the pool does not belong to that subnet
     checkResult(status, 0);
     checkResult(status, 0);
 
 
+    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+
     // eth0 and eth1 were explicitly selected. eth2 was not.
     // 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
 // This test verifies that it is possible to configure the server to listen on
 // all interfaces.
 // all interfaces.
 TEST_F(Dhcp6ParserTest, allInterfaces) {
 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;
     ConstElementPtr status;
 
 
@@ -3084,15 +3088,13 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
     ElementPtr json = Element::fromJSON(config);
     ElementPtr json = Element::fromJSON(config);
 
 
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
     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);
     checkResult(status, 0);
 
 
+    CfgMgr::instance().getConfiguration()->cfg_iface_.openSockets(10000);
+
     // All interfaces should be now active.
     // 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[] = {
 const char* CONFIRM_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -61,7 +61,7 @@ const char* CONFIRM_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
         "\"valid-lifetime\": 4000 }",
 
 
 // Configuration 1
 // Configuration 1
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "

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

@@ -75,7 +75,7 @@ Dhcp6SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
 
 
 void
 void
 Dhcp6SrvD2Test::reset() {
 Dhcp6SrvD2Test::reset() {
-    std::string config = "{ \"interfaces\": [ \"all\" ],"
+    std::string config = "{ \"interfaces\": [ \"*\" ],"
             "\"hooks-libraries\": [ ],"
             "\"hooks-libraries\": [ ],"
             "\"preferred-lifetime\": 3000,"
             "\"preferred-lifetime\": 3000,"
             "\"rebind-timer\": 2000, "
             "\"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);
     IfaceMgrTestConfig test_config(true);
 
 
     ConstElementPtr x;
     ConstElementPtr x;
-    string config = "{ \"interfaces\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -1557,7 +1557,7 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
 
 
     IfaceMgrTestConfig test_config(true);
     IfaceMgrTestConfig test_config(true);
 
 
-    string config = "{ \"interfaces\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -1642,7 +1642,7 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
 // src/lib/dhcp/docsis3_option_defs.h.
 // src/lib/dhcp/docsis3_option_defs.h.
 TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
 TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
     ConstElementPtr x;
     ConstElementPtr x;
-    string config_prefix = "{ \"interfaces\": [ \"all\" ],"
+    string config_prefix = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"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
 ///   - this specific configuration is used by tests which don't use relays
 const char* REBIND_CONFIGS[] = {
 const char* REBIND_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -83,7 +83,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
         "\"valid-lifetime\": 4000 }",
 
 
 // Configuration 1
 // Configuration 1
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -102,7 +102,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
         "\"valid-lifetime\": 4000 }",
 
 
 // Configuration 2
 // Configuration 2
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -121,7 +121,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
         "\"valid-lifetime\": 4000 }",
 
 
 // Configuration 3
 // Configuration 3
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -140,7 +140,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
         "\"valid-lifetime\": 4000 }",
 
 
 // Configuration 4
 // Configuration 4
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
@@ -167,7 +167,7 @@ const char* REBIND_CONFIGS[] = {
         "\"valid-lifetime\": 4000 }",
         "\"valid-lifetime\": 4000 }",
 
 
 // Configuration 5
 // Configuration 5
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"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
 ///   - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
 const char* CONFIGS[] = {
 const char* CONFIGS[] = {
     // Configuration 0
     // Configuration 0
-    "{ \"interfaces\": [ \"all\" ],"
+    "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"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())
     if (!infile.is_open())
     {
     {
         const char* error = strerror(errno);
         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));
     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);
     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() {
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
          iface != ifaces_.end(); ++iface) {
@@ -457,7 +469,11 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
                                " interface " << iface->getName());
                                " interface " << iface->getName());
                 continue;
                 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,
                 IFACEMGR_ERROR(SocketConfigError, error_handler,
                                "the interface " << iface->getName()
                                "the interface " << iface->getName()
                                << " is down or has no usable IPv4"
                                << " is down or has no usable IPv4"
@@ -682,6 +698,14 @@ IfaceMgr::clearIfaces() {
     ifaces_.clear();
     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,
 int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
                          const uint16_t port, const bool receive_bcast,
                          const uint16_t port, const bool receive_bcast,
                          const bool send_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).
     /// for the interface (if true), or not (false).
     bool getAddress4(isc::asiolink::IOAddress& address) const;
     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.
     /// @brief Adds an address to an interface.
     ///
     ///
     /// This only adds an address to collection, it does not physically
     /// This only adds an address to collection, it does not physically
@@ -549,6 +555,9 @@ public:
     /// IPv6 address is read from interfaces.txt file.
     /// IPv6 address is read from interfaces.txt file.
     void detectIfaces();
     void detectIfaces();
 
 
+    /// @brief Clears unicast addresses on all interfaces.
+    void clearUnicasts();
+
     /// @brief Return most suitable socket for transmitting specified IPv6 packet.
     /// @brief Return most suitable socket for transmitting specified IPv6 packet.
     ///
     ///
     /// This method takes Pkt6 (see overloaded implementation that takes
     /// 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.");
                   " 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) {
     if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
         // Get the error message immediately after the bind because the
         // Get the error message immediately after the bind because the
         // invocation to close() below would override the errno.
         // 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_;
     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 FlagInactive4& inactive4,
                        const FlagInactive6& inactive6);
                        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:
 private:
     /// @brief Currently used packet filter for DHCPv4.
     /// @brief Currently used packet filter for DHCPv4.
     PktFilterPtr packet_filter4_;
     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
 // TODO: Implement getPlainMac() test as soon as interface detection
 // is implemented.
 // is implemented.
 TEST_F(IfaceMgrTest, getIface) {
 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 += cfgmgr.cc cfgmgr.h
 libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.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 += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.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_);
     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
 bool
 CfgMgr::isDuplicate(const Subnet4& subnet) const {
 CfgMgr::isDuplicate(const Subnet4& subnet) const {
     for (Subnet4Collection::const_iterator subnet_it = subnets4_.begin();
     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
 void
 CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
 CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
     d2_client_mgr_.setD2ClientConfig(new_config);
     d2_client_mgr_.setD2ClientConfig(new_config);
@@ -443,8 +363,7 @@ CfgMgr::getConfiguration() {
 }
 }
 
 
 CfgMgr::CfgMgr()
 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()) {
       d2_client_mgr_(), configuration_(new Configuration()) {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // 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
     // 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
     /// @return data directory
     std::string getDataDir();
     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
     /// @brief Sets whether server should send back client-id in DHCPv4
     ///
     ///
     /// This is a compatibility flag. The default (true) is compliant with
     /// This is a compatibility flag. The default (true) is compliant with
@@ -458,20 +410,6 @@ protected:
 
 
 private:
 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.
     /// @brief Checks that the IPv4 subnet with the given id already exists.
     ///
     ///
     /// @param subnet Subnet for which this function will check if the other
     /// @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
     /// @brief directory where data files (e.g. server-id) are stored
     std::string datadir_;
     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
     /// Indicates whether v4 server should send back client-id
     bool echo_v4_client_id_;
     bool echo_v4_client_id_;
 
 

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

@@ -15,6 +15,7 @@
 #ifndef DHCPSRV_CONFIGURATION_H
 #ifndef DHCPSRV_CONFIGURATION_H
 #define DHCPSRV_CONFIGURATION_H
 #define DHCPSRV_CONFIGURATION_H
 
 
+#include <dhcpsrv/cfg_iface.h>
 #include <log/logger_level.h>
 #include <log/logger_level.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <vector>
 #include <vector>
@@ -112,6 +113,12 @@ struct Configuration {
     /// @brief logging specific information
     /// @brief logging specific information
     LoggingInfoStorage logging_info_;
     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.
     /// @brief Returns summary of the configuration in the textual format.
     ///
     ///
     /// This method returns the brief text describing the current configuration.
     /// 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 isc {
 namespace dhcp {
 namespace dhcp {
 
 
-namespace {
-const char* ALL_IFACES_KEYWORD = "*";
-}
-
 // *********************** ParserContext  *************************
 // *********************** ParserContext  *************************
 
 
 ParserContext::ParserContext(Option::Universe universe):
 ParserContext::ParserContext(Option::Universe universe):
@@ -184,8 +180,7 @@ template <> void ValueParser<std::string>::build(ConstElementPtr value) {
 
 
 InterfaceListConfigParser::
 InterfaceListConfigParser::
 InterfaceListConfigParser(const std::string& param_name)
 InterfaceListConfigParser(const std::string& param_name)
-    : activate_all_(false),
-      param_name_(param_name) {
+    : param_name_(param_name) {
     if (param_name_ != "interfaces") {
     if (param_name_ != "interfaces") {
         isc_throw(BadValue, "Internal error. Interface configuration "
         isc_throw(BadValue, "Internal error. Interface configuration "
             "parser called for the wrong parameter: " << param_name);
             "parser called for the wrong parameter: " << param_name);
@@ -194,53 +189,26 @@ InterfaceListConfigParser(const std::string& param_name)
 
 
 void
 void
 InterfaceListConfigParser::build(ConstElementPtr value) {
 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()) {
     BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
         std::string iface_name = iface->stringValue();
         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
 void
 InterfaceListConfigParser::commit() {
 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
 bool

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

@@ -20,6 +20,7 @@
 #include <dhcp/option_definition.h>
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/d2_client_cfg.h>
 #include <dhcpsrv/d2_client_cfg.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
@@ -406,7 +407,9 @@ public:
     /// @param value pointer to the content of parsed values
     /// @param value pointer to the content of parsed values
     virtual void build(isc::data::ConstElementPtr value);
     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();
     virtual void commit();
 
 
 private:
 private:
@@ -422,11 +425,11 @@ private:
     typedef std::list<std::string> IfaceListStorage;
     typedef std::list<std::string> IfaceListStorage;
     IfaceListStorage interfaces_;
     IfaceListStorage interfaces_;
 
 
-    // Should server listen on all interfaces.
-    bool activate_all_;
-
     // Parsed parameter name
     // Parsed parameter name
     std::string param_name_;
     std::string param_name_;
+
+    /// Holds the configuration created during
+    CfgIface cfg_iface_;
 };
 };
 
 
 /// @brief Parser for hooks library list
 /// @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
 to clients that are no longer active on the network will become available
 available sooner.
 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.
 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
 % DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
 A debug message reported when the DHCP configuration manager is adding the
 A debug message reported when the DHCP configuration manager is adding the
 specified IPv4 subnet to its database.
 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
 returned the specified IPv6 subnet, because detected relay agent address
 matches value specified for this subnet.
 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
 % DHCPSRV_CLOSE_DB closing currently open %1 database
 This is a debug message, issued when the DHCP server closes the currently
 This is a debug message, issued when the DHCP server closes the currently
 open lease database.  It is issued at program shutdown and whenever
 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
 A debug message issued when the server is attempting to update IPv6
 lease from the MySQL database for the specified address.
 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
 % 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
 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
 a database backend, but where no 'type' keyword has been included in
 the access string.  The access string (less any passwords) is included
 the access string.  The access string (less any passwords) is included
 in the message.
 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
 % DHCPSRV_PGSQL_ADD_ADDR4 adding IPv4 lease with address %1
 A debug message issued when the server is about to add an IPv4 lease
 A debug message issued when the server is about to add an IPv4 lease
 with the specified address to the PostgreSQL backend database.
 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 += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_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_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_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().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
         CfgMgr::instance().deleteOptionDefs();
-        CfgMgr::instance().deleteActiveIfaces();
     }
     }
 
 
     /// @brief generates interface-id option based on provided text
     /// @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.
     /// @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
 // This test verifies that RFC6842 (echo client-id) compatibility may be
 // configured.
 // configured.
 TEST_F(CfgMgrTest, echoClientId) {
 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.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/dhcp_parsers.h>
@@ -47,9 +48,19 @@ namespace {
 class DhcpParserTest : public ::testing::Test {
 class DhcpParserTest : public ::testing::Test {
 public:
 public:
     /// @brief Constructor
     /// @brief Constructor
-    ///
     DhcpParserTest() {
     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
 /// 4. Parses wildcard interface name and sets a CfgMgr flag which indicates
 /// that server will listen on all interfaces.
 /// that server will listen on all interfaces.
 TEST_F(DhcpParserTest, interfaceListParserTest) {
 TEST_F(DhcpParserTest, interfaceListParserTest) {
+    IfaceMgrTestConfig test_config(true);
 
 
     const std::string name = "interfaces";
     const std::string name = "interfaces";
 
 
@@ -220,11 +232,6 @@ TEST_F(DhcpParserTest, interfaceListParserTest) {
         parser(new InterfaceListConfigParser(name));
         parser(new InterfaceListConfigParser(name));
     ElementPtr list_element = Element::createList();
     ElementPtr list_element = Element::createList();
     list_element->add(Element::create("eth0"));
     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
     // This should parse the configuration and add eth0 and eth1 to the list
     // of interfaces that server should listen on.
     // 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
     // Use CfgMgr instance to check if eth0 and eth1 was added, and that
     // eth2 was not added.
     // 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
     // Add keyword all to the configuration. This should activate all
     // interfaces, including eth2, even though it has not been explicitly
     // interfaces, including eth2, even though it has not been explicitly
     // added.
     // added.
     list_element->add(Element::create("*"));
     list_element->add(Element::create("*"));
 
 
-    // Reset parser's state.
+    // Reset parser and configuration.
     parser.reset(new InterfaceListConfigParser(name));
     parser.reset(new InterfaceListConfigParser(name));
+    cfg->cfg_iface_.closeSockets();
+    cfg->cfg_iface_.reset();
+
     parser->build(list_element);
     parser->build(list_element);
     parser->commit();
     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
 // Checks whether option space can be detected as vendor-id