]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3145] backport #3017 to v2_4
authorRazvan Becheriu <razvan@isc.org>
Mon, 13 Nov 2023 12:05:50 +0000 (14:05 +0200)
committerRazvan Becheriu <razvan@isc.org>
Tue, 14 Nov 2023 15:10:25 +0000 (15:10 +0000)
16 files changed:
ChangeLog
src/bin/dhcp4/json_config_parser.cc
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
src/bin/dhcp6/json_config_parser.cc
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
src/lib/dhcp/duid_factory.cc
src/lib/dhcp/iface_mgr.cc
src/lib/dhcp/iface_mgr.h
src/lib/dhcp/iface_mgr_bsd.cc
src/lib/dhcp/iface_mgr_linux.cc
src/lib/dhcp/iface_mgr_sun.cc
src/lib/dhcp/tests/iface_mgr_test_config.cc
src/lib/dhcp/tests/iface_mgr_test_config.h
src/lib/dhcp/tests/pkt_captures4.cc
src/lib/dhcp/tests/pkt_captures6.cc
src/lib/dhcpsrv/parsers/ifaces_config_parser.cc

index 7364c98e7f02fe9883445859ca7569da776d4471..69ec83340d94e33f5ee4d33d119d177ee93e7b23 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2168.  [bug]           andrei
+       Fixed interface redetection which had stopped working since
+       Kea 2.3.6.
+       (Gitlab #3017, #3145)
+
 2167.  [bug]           razvan
        Fixed a race condition in free lease queue allocator.
        (Gitlab #3111, #3143)
index b62bbe116f6e2e9e41973d94056090ea5a758ba2..d958c415ea4864f31404211af78ad3174dbff7ed 100644 (file)
@@ -322,6 +322,10 @@ void configureCommandChannel() {
     }
 }
 
+/// @brief Process a DHCPv4 confguration and return an answer stating if the
+/// configuration is valid, or specifying details about the error otherwise.
+///
+/// @param config_set the configuration being processed
 isc::data::ConstElementPtr
 processDhcp4Config(isc::data::ConstElementPtr config_set) {
     // Before starting any subnet operations, let's reset the subnet-id counter,
@@ -444,14 +448,6 @@ processDhcp4Config(isc::data::ConstElementPtr config_set) {
             parser.parse(hr_identifiers);
         }
 
-        ConstElementPtr ifaces_config = mutable_cfg->get("interfaces-config");
-        if (ifaces_config) {
-            parameter_name = "interfaces-config";
-            IfacesConfigParser parser(AF_INET, true);
-            CfgIfacePtr cfg_iface = srv_config->getCfgIface();
-            parser.parse(cfg_iface, ifaces_config);
-        }
-
         ConstElementPtr sanity_checks = mutable_cfg->get("sanity-checks");
         if (sanity_checks) {
             parameter_name = "sanity-checks";
@@ -809,9 +805,6 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
                 }
             }
         } else {
-            string parameter_name;
-            ElementPtr mutable_cfg;
-
             // disable multi-threading (it will be applied by new configuration)
             // this must be done in order to properly handle MT to ST transition
             // when 'multi-threading' structure is missing from new config and
@@ -825,9 +818,12 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
             TimerMgr::instance()->unregisterTimers();
             server.discardPackets();
             server.getCBControl()->reset();
+        }
 
+        if (status_code == CONTROL_RESULT_SUCCESS) {
+            string parameter_name;
+            ElementPtr mutable_cfg;
             try {
-
                 // Get the staging configuration.
                 srv_config = CfgMgr::instance().getStagingCfg();
 
@@ -839,7 +835,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
                 ConstElementPtr ifaces_config = mutable_cfg->get("interfaces-config");
                 if (ifaces_config) {
                     parameter_name = "interfaces-config";
-                    IfacesConfigParser parser(AF_INET, false);
+                    IfacesConfigParser parser(AF_INET, check_only);
                     CfgIfacePtr cfg_iface = srv_config->getCfgIface();
                     cfg_iface->reset();
                     parser.parse(cfg_iface, ifaces_config);
index 88d685f3e67bc5be4702cd441a6b0d2db0c06520..ee0e28a31babacee56e63b1e2c389460fcdd3ac5 100644 (file)
@@ -86,7 +86,7 @@ private:
 class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
     // "Naked" DHCPv4 server, exposes internal fields
 public:
-    NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) {
+    NakedControlledDhcpv4Srv() : ControlledDhcpv4Srv(0) {
         CfgMgr::instance().setFamily(AF_INET);
     }
 
@@ -103,13 +103,16 @@ public:
     /// @brief Path to the UNIX socket being used to communicate with the server
     std::string socket_path_;
 
+    /// @brief List of interfaces (defaults to "*").
+    std::string interfaces_;
+
     /// @brief Pointer to the tested server object
     boost::shared_ptr<NakedControlledDhcpv4Srv> server_;
 
     /// @brief Default constructor
     ///
     /// Sets socket path to its default value.
-    CtrlChannelDhcpv4SrvTest() {
+    CtrlChannelDhcpv4SrvTest() : interfaces_("\"*\"") {
         const char* env = getenv("KEA_SOCKET_TEST_DIR");
         if (env) {
             socket_path_ = string(env) + "/kea4.sock";
@@ -117,6 +120,9 @@ public:
             socket_path_ = sandbox.join("kea4.sock");
         }
         reset();
+        IfaceMgr::instance().setTestMode(false);
+        IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
+                                               IfaceMgr::instancePtr().get(), ph::_1));
     }
 
     /// @brief Destructor
@@ -129,6 +135,12 @@ public:
         CommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
 
         server_.reset();
+        IfaceMgr::instance().setTestMode(false);
+        IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
+                                               IfaceMgr::instancePtr().get(), ph::_1));
+        IfaceMgr::instance().clearIfaces();
+        IfaceMgr::instance().closeSockets();
+        IfaceMgr::instance().detectIfaces();
     };
 
     /// @brief Returns pointer to the server's IO service.
@@ -147,7 +159,9 @@ public:
         std::string header =
             "{"
             "    \"interfaces-config\": {"
-            "        \"interfaces\": [ \"*\" ]"
+            "        \"interfaces\": [";
+
+        std::string body = "]"
             "    },"
             "    \"expired-leases-processing\": {"
             "         \"reclaim-timer-wait-time\": 60,"
@@ -175,8 +189,7 @@ public:
 
         // Fill in the socket-name value with socket_path_  to
         // make the actual configuration text.
-        std::string config_txt = header + socket_path_  + footer;
-
+        std::string config_txt = header + interfaces_ + body + socket_path_  + footer;
         ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv4Srv()));
 
         ConstElementPtr config;
@@ -210,7 +223,6 @@ public:
         ASSERT_GT(isc::config::CommandMgr::instance().getControlSocketFD(), -1);
     }
 
-
     /// @brief Reset hooks data
     ///
     /// Resets the data for the hooks-related portion of the test by ensuring
@@ -817,7 +829,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
     sendUnixCommand(os.str(), response);
 
     // Verify the configuration was successful. The config contains random
-    // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+    // socket name (/tmp/kea-<value-changing-each-time>/kea4.sock), so the
     // hash will be different each time. As such, we can do simplified checks:
     // - verify the "result": 0 is there
     // - verify the "text": "Configuration successful." is there
@@ -885,7 +897,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
     EXPECT_FALSE(fileExists(socket_path_));
 
     // With no command channel, should still receive the response. The config contains random
-    // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+    // socket name (/tmp/kea-<value-changing-each-time>/kea4.sock), so the
     // hash will be different each time. As such, we can do simplified checks:
     // - verify the "result": 0 is there
     // - verify the "text": "Configuration successful." is there
@@ -1030,7 +1042,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configTest) {
     sendUnixCommand(os.str(), response);
 
     // Verify the configuration was successful. The config contains random
-    // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+    // socket name (/tmp/kea-<value-changing-each-time>/kea4.sock), so the
     // hash will be different each time. As such, we can do simplified checks:
     // - verify the "result": 0 is there
     // - verify the "text": "Configuration successful." is there
@@ -1508,6 +1520,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configWriteFilename) {
 
     sendUnixCommand("{ \"command\": \"config-write\", "
                     "\"arguments\": { \"filename\": \"test2.json\" } }", response);
+
     checkConfigWrite(response, CONTROL_RESULT_SUCCESS, "test2.json");
     ::remove("test2.json");
 }
@@ -1591,8 +1604,85 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configReloadValid) {
 
     // This command should reload test8.json config.
     sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the configuration was successful. The config contains random
+    // socket name (/tmp/kea-<value-changing-each-time>/kea4.sock), so the
+    // hash will be different each time. As such, we can do simplified checks:
+    // - verify the "result": 0 is there
+    // - verify the "text": "Configuration successful." is there
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+    // Check that the config was indeed applied.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(2, subnets->size());
+
+    ::remove("test8.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is loaded correctly.
+TEST_F(CtrlChannelDhcpv4SrvTest, configReloadDetectInterfaces) {
+    interfaces_ = "\"eth0\"";
+    IfacePtr eth0 = IfaceMgrTestConfig::createIface("eth0", ETH0_INDEX,
+                                                    "11:22:33:44:55:66");
+    auto detectIfaces = [&](bool update_only) {
+        if (!update_only) {
+            eth0->addAddress(IOAddress("10.0.0.1"));
+            eth0->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+            eth0->addAddress(IOAddress("2001:db8:1::1"));
+            IfaceMgr::instance().addInterface(eth0);
+        }
+        return (false);
+    };
+    IfaceMgr::instance().setDetectCallback(detectIfaces);
+    IfaceMgr::instance().clearIfaces();
+    IfaceMgr::instance().closeSockets();
+    IfaceMgr::instance().detectIfaces();
+    createUnixChannelServer();
+    std::string response;
+
+    // This is normally set to whatever value is passed to -c when the server is
+    // started, but we're not starting it that way, so need to set it by hand.
+    server_->setConfigFile("test8.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    const std::string cfg_txt =
+        "{ \"Dhcp4\": {"
+        "    \"interfaces-config\": {"
+        "        \"interfaces\": [ \"eth1\" ]"
+        "    },"
+        "    \"subnet4\": ["
+        "        { \"id\": 1, \"subnet\": \"192.0.2.0/24\" },"
+        "        { \"id\": 2, \"subnet\": \"192.0.3.0/24\" }"
+        "     ],"
+        "    \"valid-lifetime\": 4000,"
+        "    \"lease-database\": {"
+        "       \"type\": \"memfile\", \"persist\": false }"
+        "} }";
+    ofstream f("test8.json", ios::trunc);
+    f << cfg_txt;
+    f.close();
+
+    IfacePtr eth1 = IfaceMgrTestConfig::createIface("eth1", ETH1_INDEX,
+                                                    "AA:BB:CC:DD:EE:FF");
+    auto detectUpdateIfaces = [&](bool update_only) {
+        if (!update_only) {
+            eth1->addAddress(IOAddress("192.0.2.3"));
+            eth1->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
+            eth1->addAddress(IOAddress("3001:db8:100::1"));
+            IfaceMgr::instance().addInterface(eth1);
+        }
+        return (false);
+    };
+    IfaceMgr::instance().setDetectCallback(detectUpdateIfaces);
+
+    // This command should reload test8.json config.
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
     // Verify the configuration was successful. The config contains random
-    // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+    // socket name (/tmp/kea-<value-changing-each-time>/kea4.sock), so the
     // hash will be different each time. As such, we can do simplified checks:
     // - verify the "result": 0 is there
     // - verify the "text": "Configuration successful." is there
index 337371c22323ed4ff380b348c86fbb0deff14188..dd11de07d46d4cc162e4387711074307219935ae 100644 (file)
@@ -425,6 +425,10 @@ void configureCommandChannel() {
     }
 }
 
+/// @brief Process a DHCPv6 confguration and return an answer stating if the
+/// configuration is valid, or specifying details about the error otherwise.
+///
+/// @param config_set the configuration being processed
 isc::data::ConstElementPtr
 processDhcp6Config(isc::data::ConstElementPtr config_set) {
     // Before starting any subnet operations, let's reset the subnet-id counter,
@@ -570,14 +574,6 @@ processDhcp6Config(isc::data::ConstElementPtr config_set) {
             parser.parse(cfg, server_id);
         }
 
-        ConstElementPtr ifaces_config = mutable_cfg->get("interfaces-config");
-        if (ifaces_config) {
-            parameter_name = "interfaces-config";
-            IfacesConfigParser parser(AF_INET6, true);
-            CfgIfacePtr cfg_iface = srv_config->getCfgIface();
-            parser.parse(cfg_iface, ifaces_config);
-        }
-
         ConstElementPtr sanity_checks = mutable_cfg->get("sanity-checks");
         if (sanity_checks) {
             parameter_name = "sanity-checks";
@@ -934,9 +930,6 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
                 }
             }
         } else {
-            string parameter_name;
-            ElementPtr mutable_cfg;
-
             // disable multi-threading (it will be applied by new configuration)
             // this must be done in order to properly handle MT to ST transition
             // when 'multi-threading' structure is missing from new config and
@@ -950,9 +943,12 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
             TimerMgr::instance()->unregisterTimers();
             server.discardPackets();
             server.getCBControl()->reset();
+        }
 
+        if (status_code == CONTROL_RESULT_SUCCESS) {
+            string parameter_name;
+            ElementPtr mutable_cfg;
             try {
-
                 // Get the staging configuration.
                 srv_config = CfgMgr::instance().getStagingCfg();
 
@@ -964,7 +960,7 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
                 ConstElementPtr ifaces_config = mutable_cfg->get("interfaces-config");
                 if (ifaces_config) {
                     parameter_name = "interfaces-config";
-                    IfacesConfigParser parser(AF_INET6, false);
+                    IfacesConfigParser parser(AF_INET6, check_only);
                     CfgIfacePtr cfg_iface = srv_config->getCfgIface();
                     cfg_iface->reset();
                     parser.parse(cfg_iface, ifaces_config);
index f39062b9494f76e5a4c03a5af83b8fa65ac13040..f05a40212b526655a3d398d92ccb9aaeae06ea9c 100644 (file)
@@ -86,7 +86,7 @@ private:
 class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
     // "Naked" DHCPv6 server, exposes internal fields
 public:
-    NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) {
+    NakedControlledDhcpv6Srv() : ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) {
         CfgMgr::instance().setFamily(AF_INET6);
     }
 
@@ -114,7 +114,6 @@ public:
         reset();
     };
 
-
     /// @brief Reset hooks data
     ///
     /// Resets the data for the hooks-related portion of the test by ensuring
@@ -139,13 +138,16 @@ public:
     /// @brief Path to the UNIX socket being used to communicate with the server
     std::string socket_path_;
 
+    /// @brief List of interfaces (defaults to "*").
+    std::string interfaces_;
+
     /// @brief Pointer to the tested server object
     boost::shared_ptr<NakedControlledDhcpv6Srv> server_;
 
     /// @brief Default constructor
     ///
     /// Sets socket path to its default value.
-    CtrlChannelDhcpv6SrvTest() {
+    CtrlChannelDhcpv6SrvTest() : interfaces_("\"*\"") {
         const char* env = getenv("KEA_SOCKET_TEST_DIR");
         if (env) {
             socket_path_ = string(env) + "/kea6.sock";
@@ -153,12 +155,21 @@ public:
             socket_path_ = sandbox.join("/kea6.sock");
         }
         reset();
+        IfaceMgr::instance().setTestMode(false);
+        IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
+                                               IfaceMgr::instancePtr().get(), ph::_1));
     }
 
     /// @brief Destructor
     ~CtrlChannelDhcpv6SrvTest() {
         server_.reset();
         reset();
+        IfaceMgr::instance().setTestMode(false);
+        IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
+                                               IfaceMgr::instancePtr().get(), ph::_1));
+        IfaceMgr::instance().clearIfaces();
+        IfaceMgr::instance().closeSockets();
+        IfaceMgr::instance().detectIfaces();
     };
 
     /// @brief Returns pointer to the server's IO service.
@@ -177,7 +188,9 @@ public:
         std::string header =
             "{"
             "    \"interfaces-config\": {"
-            "        \"interfaces\": [ \"*\" ]"
+            "        \"interfaces\": [";
+
+        std::string body = "]"
             "    },"
             "    \"expired-leases-processing\": {"
             "         \"reclaim-timer-wait-time\": 60,"
@@ -205,8 +218,7 @@ public:
 
         // Fill in the socket-name value with socket_path_  to
         // make the actual configuration text.
-        std::string config_txt = header + socket_path_  + footer;
-
+        std::string config_txt = header + interfaces_ + body + socket_path_  + footer;
         ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
 
         ConstElementPtr config;
@@ -1537,6 +1549,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configWriteFilename) {
 
     sendUnixCommand("{ \"command\": \"config-write\", "
                     "\"arguments\": { \"filename\": \"test2.json\" } }", response);
+
     checkConfigWrite(response, CONTROL_RESULT_SUCCESS, "test2.json");
     ::remove("test2.json");
 }
@@ -1636,6 +1649,81 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configReloadValid) {
     ::remove("test8.json");
 }
 
+// Tests if config-reload attempts to reload a file and reports that the
+// file is loaded correctly.
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadDetectInterfaces) {
+    interfaces_ = "\"eth0\"";
+    IfacePtr eth0 = IfaceMgrTestConfig::createIface("eth0", ETH0_INDEX,
+                                                    "11:22:33:44:55:66");
+    auto detectIfaces = [&](bool update_only) {
+        if (!update_only) {
+            eth0->addAddress(IOAddress("10.0.0.1"));
+            eth0->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+            eth0->addAddress(IOAddress("2001:db8:1::1"));
+            IfaceMgr::instance().addInterface(eth0);
+        }
+        return (false);
+    };
+    IfaceMgr::instance().setDetectCallback(detectIfaces);
+    IfaceMgr::instance().clearIfaces();
+    IfaceMgr::instance().closeSockets();
+    IfaceMgr::instance().detectIfaces();
+    createUnixChannelServer();
+    std::string response;
+
+    // This is normally set to whatever value is passed to -c when the server is
+    // started, but we're not starting it that way, so need to set it by hand.
+    server_->setConfigFile("test8.json");
+
+    // Ok, enough fooling around. Let's create a valid config.
+    const std::string cfg_txt =
+        "{ \"Dhcp6\": {"
+        "    \"interfaces-config\": {"
+        "        \"interfaces\": [ \"eth1\" ]"
+        "    },"
+        "    \"subnet6\": ["
+        "        { \"subnet\": \"2001:db8:1::/64\", \"id\": 1 },"
+        "        { \"subnet\": \"2001:db8:2::/64\", \"id\": 2 }"
+        "     ],"
+        "    \"lease-database\": {"
+        "       \"type\": \"memfile\", \"persist\": false }"
+        "} }";
+    ofstream f("test8.json", ios::trunc);
+    f << cfg_txt;
+    f.close();
+
+    IfacePtr eth1 = IfaceMgrTestConfig::createIface("eth1", ETH1_INDEX,
+                                                    "AA:BB:CC:DD:EE:FF");
+    auto detectUpdateIfaces = [&](bool update_only) {
+        if (!update_only) {
+            eth1->addAddress(IOAddress("192.0.2.3"));
+            eth1->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
+            eth1->addAddress(IOAddress("3001:db8:100::1"));
+            IfaceMgr::instance().addInterface(eth1);
+        }
+        return (false);
+    };
+    IfaceMgr::instance().setDetectCallback(detectUpdateIfaces);
+
+    // This command should reload test8.json config.
+    sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+    // Verify the configuration was successful. The config contains random
+    // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+    // hash will be different each time. As such, we can do simplified checks:
+    // - verify the "result": 0 is there
+    // - verify the "text": "Configuration successful." is there
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+    // Check that the config was indeed applied.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(2, subnets->size());
+
+    ::remove("test8.json");
+}
+
 // This test verifies that disable DHCP service command performs sanity check on
 // parameters.
 TEST_F(CtrlChannelDhcpv6SrvTest, dhcpDisableBadParam) {
index 9b34d15d796966be9bf4687ef64bf2908578ffbe..1a023445bd2eb99601167663b516b6f13c3e85f3 100644 (file)
@@ -296,7 +296,7 @@ DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier,
     // used for generating DUID-LLT.
     if (identifier.empty()) {
         isc_throw(Unexpected, "unable to find suitable interface for "
-                  " generating a DUID-LLT");
+                  "generating a DUID-LLT");
     }
 }
 
index 6bf1a0853f5c9438aafd9cffcda0bfb6e5bc3a3e..c23a9e9836cf29bf50c2fa1dbd8035da895f799b 100644 (file)
@@ -45,6 +45,7 @@ using namespace isc::asiolink;
 using namespace isc::util;
 using namespace isc::util::io;
 using namespace isc::util::io::internal;
+namespace ph = std::placeholders;
 
 namespace isc {
 namespace dhcp {
@@ -185,8 +186,7 @@ bool Iface::delSocket(const uint16_t sockfd) {
 IfaceMgr::IfaceMgr()
     : packet_filter_(new PktFilterInet()),
       packet_filter6_(new PktFilterInet6()),
-      test_mode_(false),
-      allow_loopback_(false) {
+      test_mode_(false), allow_loopback_(false) {
 
     // Ensure that PQMs have been created to guarantee we have
     // default packet queues in place.
@@ -197,6 +197,8 @@ IfaceMgr::IfaceMgr()
         isc_throw(Unexpected, "Failed to create PacketQueueManagers: " << ex.what());
     }
 
+    detect_callback_ = std::bind(&IfaceMgr::checkDetectIfaces, this, ph::_1);
+
     try {
 
         // required for sending/receiving packets
@@ -477,44 +479,6 @@ IfaceMgr::hasOpenSocket(const IOAddress& addr) const {
     return (false);
 }
 
-void IfaceMgr::stubDetectIfaces() {
-    string ifaceName;
-    const string v4addr("127.0.0.1"), v6addr("::1");
-
-    // This is a stub implementation for interface detection. Actual detection
-    // is faked by detecting loopback interface (lo or lo0). It will eventually
-    // be removed once we have actual implementations for all supported systems.
-
-    if (if_nametoindex("lo") > 0) {
-        ifaceName = "lo";
-        // this is Linux-like OS
-    } else if (if_nametoindex("lo0") > 0) {
-        ifaceName = "lo0";
-        // this is BSD-like OS
-    } else {
-        // we give up. What OS is this, anyway? Solaris? Hurd?
-        isc_throw(NotImplemented,
-                  "Interface detection on this OS is not supported.");
-    }
-
-    IfacePtr iface(new Iface(ifaceName, if_nametoindex(ifaceName.c_str())));
-    iface->flag_up_ = true;
-    iface->flag_running_ = true;
-
-    // Note that we claim that this is not a loopback. iface_mgr tries to open a
-    // socket on all interfaces that are up, running and not loopback. As this is
-    // the only interface we were able to detect, let's pretend this is a normal
-    // interface.
-    iface->flag_loopback_ = false;
-    iface->flag_multicast_ = true;
-    iface->flag_broadcast_ = true;
-    iface->setHWType(HWTYPE_ETHERNET);
-
-    iface->addAddress(IOAddress(v4addr));
-    iface->addAddress(IOAddress(v6addr));
-    addInterface(iface);
-}
-
 bool
 IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
                        IfaceMgrErrorMsgCallback error_handler,
@@ -836,7 +800,6 @@ IfaceCollection::getIface(const unsigned int ifindex) {
     return (getIfaceInternal(ifindex, MultiThreadingMgr::instance().getMode()));
 }
 
-
 IfacePtr
 IfaceCollection::getIface(const std::string& ifname) {
     return (getIfaceInternal(ifname, MultiThreadingMgr::instance().getMode()));
@@ -1881,7 +1844,6 @@ IfaceMgr::getSocket(const isc::dhcp::Pkt6Ptr& pkt) {
         isc_throw(IfaceNotFound, "Tried to find socket for non-existent interface");
     }
 
-
     const Iface::SocketCollection& socket_collection = iface->getSockets();
 
     Iface::SocketCollection::const_iterator candidate = socket_collection.end();
@@ -2015,5 +1977,13 @@ Iface::getErrors() const {
     return errors_;
 }
 
+bool
+IfaceMgr::checkDetectIfaces(bool update_only) {
+    if (test_mode_ && update_only) {
+        return (false);
+    }
+    return (true);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc
index bccfeee01cb36d8d5d96a18a22bb67f10b51dd29..ca0a536096b71f369ca79e16cf282a55f81e956d 100644 (file)
@@ -312,7 +312,7 @@ public:
     /// @param addr address to be removed.
     ///
     /// @return true if removal was successful (address was in collection),
-    ///         false otherwise
+    /// false otherwise
     bool delAddress(const isc::asiolink::IOAddress& addr);
 
     /// @brief Adds socket descriptor to an interface.
@@ -655,10 +655,20 @@ std::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback;
 ///
 class IfaceMgr : public boost::noncopyable {
 public:
-    /// Defines callback used when data is received over external sockets.
+    /// @brief Defines callback used when data is received over external sockets.
+    ///
     /// @param fd socket descriptor of the ready socket
     typedef std::function<void (int fd)> SocketCallback;
 
+    /// @brief Defines callback used when detecting interfaces.
+    ///
+    /// @param update_only Only add interfaces that do not exist and update
+    /// existing interfaces.
+    ///
+    /// @return true if callback exited with no issue and @ref detectIfaces
+    /// should continue with specific system calls, false otherwise.
+    typedef std::function<bool (bool)> DetectCallback;
+
     /// Keeps callback information for external sockets.
     struct SocketCallbackInfo {
         /// Socket descriptor of the external socket.
@@ -788,11 +798,28 @@ public:
     /// @c PktFilter class to mimic socket operation on these interfaces.
     void clearIfaces();
 
+    /// @brief Set a callback to perform operations before executing specific
+    /// system calls.
+    ///
+    /// @param cb The callback used before executing specific system calls.
+    void setDetectCallback(const DetectCallback& cb) {
+        detect_callback_ = cb;
+    }
+
+    /// @brief Check if the specific system calls used to detect interfaces
+    /// should be executed.
+    ///
+    /// @param update_only Only add interfaces that do not exist and update
+    /// existing interfaces.
+    ///
+    /// @return true if the specific system calls should be executed, false
+    /// otherwise causing the @ref detectIfaces to return immediately.
+    bool checkDetectIfaces(bool update_only);
+
     /// @brief Detects network interfaces.
     ///
-    /// This method will eventually detect available interfaces. For now
-    /// it offers stub implementation. First interface name and link-local
-    /// IPv6 address is read from interfaces.txt file.
+    /// If the @ref detect_callback_ returns true, the specific system calls are
+    /// executed, otherwise the @ref detectIfaces will return immediately.
     ///
     /// @param update_only Only add interfaces that do not exist and update
     /// existing interfaces.
@@ -973,7 +1000,6 @@ public:
     int openSocketFromRemoteAddress(const isc::asiolink::IOAddress& remote_addr,
                                     const uint16_t port);
 
-
     /// @brief Opens IPv6 sockets on detected interfaces.
     ///
     /// This method opens sockets only on interfaces which have the
@@ -1444,15 +1470,6 @@ protected:
     /// @return Pkt6 object representing received packet (or null)
     Pkt6Ptr receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0);
 
-
-    /// @brief Stub implementation of network interface detection.
-    ///
-    /// This implementations reads a single line from interfaces.txt file
-    /// and pretends to detect such interface. First interface name and
-    /// link-local IPv6 address or IPv4 address is read from the
-    /// interfaces.txt file.
-    void stubDetectIfaces();
-
     /// @brief List of available interfaces
     IfaceCollection ifaces_;
 
@@ -1488,7 +1505,6 @@ private:
     getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
                     const uint16_t port);
 
-
     /// @brief Open an IPv6 socket with multicast support.
     ///
     /// This function opens a socket capable of receiving messages sent to
@@ -1586,6 +1602,13 @@ private:
     /// @brief Indicates if the IfaceMgr is in the test mode.
     bool test_mode_;
 
+    /// @brief Detect callback used to perform actions before system dependent
+    /// function calls.
+    ///
+    /// If the @ref detect_callback_ returns true, the specific system calls are
+    /// executed, otherwise the @ref detectIfaces will return immediately.
+    DetectCallback detect_callback_;
+
     /// @brief Allows to use loopback
     bool allow_loopback_;
 
index bc77d967f1f7a9bc481f96a5f5edf722e350a0b4..553842aa412676a3b5db7a1264afe920c1dd032a 100644 (file)
@@ -31,8 +31,10 @@ namespace dhcp {
 /// This is a BSD specific interface detection method.
 void
 IfaceMgr::detectIfaces(bool update_only) {
-    if (isTestMode() && update_only) {
-        return;
+    if (detect_callback_) {
+        if (!detect_callback_(update_only)) {
+            return;
+        }
     }
 
     struct ifaddrs* iflist = 0;// The whole interface list
index 4821da64b6058db10ede0316d41623be66387ea8..e4053b0664eae680ecff432faeb239f47b0c5490 100644 (file)
@@ -414,8 +414,10 @@ namespace dhcp {
 /// Uses the socket-based netlink protocol to retrieve the list of interfaces
 /// from the Linux kernel.
 void IfaceMgr::detectIfaces(bool update_only) {
-    if (isTestMode() && update_only) {
-        return;
+    if (detect_callback_) {
+        if (!detect_callback_(update_only)) {
+            return;
+        }
     }
 
     // Copies of netlink messages about links will be stored here.
index 56b36e027fca7887c8ee5e8059e3fb6e853a135f..2fd765f96f4d239a88c98f8ee957dadc2afb2faa 100644 (file)
@@ -31,8 +31,10 @@ namespace dhcp {
 /// only, as earlier versions did not support getifaddrs() API.
 void
 IfaceMgr::detectIfaces(bool update_only) {
-    if (isTestMode() && update_only) {
-        return;
+    if (detect_callback_) {
+        if (!detect_callback_(update_only)) {
+            return;
+        }
     }
 
     struct ifaddrs* iflist = 0;// The whole interface list
index a36786734c7250f9babdcc6cb1ee27c1860c3471..26191d5aa8f8227939ad09848f753db7bf97c84d 100644 (file)
@@ -67,12 +67,15 @@ IfaceMgrTestConfig::addIface(const IfacePtr& iface) {
 }
 
 void
-IfaceMgrTestConfig::addIface(const std::string& name, const unsigned int ifindex) {
+IfaceMgrTestConfig::addIface(const std::string& name,
+                             const unsigned int ifindex) {
     IfaceMgr::instance().addInterface(createIface(name, ifindex));
 }
 
 IfacePtr
-IfaceMgrTestConfig::createIface(const std::string &name, const unsigned int ifindex) {
+IfaceMgrTestConfig::createIface(const std::string& name,
+                                const unsigned int ifindex,
+                                const std::string& mac) {
     IfacePtr iface(new Iface(name, ifindex));
     if (name == "lo") {
         iface->flag_loopback_ = true;
@@ -93,8 +96,9 @@ IfaceMgrTestConfig::createIface(const std::string &name, const unsigned int ifin
     iface->flag_up_ = true;
     iface->flag_running_ = true;
 
-    // Set MAC address to 08:08:08:08:08:08.
-    std::vector<uint8_t> mac_vec(6, 8);
+    // Set MAC address.
+    HWAddr hwaddr = HWAddr::fromText(mac);
+    std::vector<uint8_t> mac_vec = hwaddr.hwaddr_;
     iface->setMac(&mac_vec[0], mac_vec.size());
     iface->setHWType(HTYPE_ETHER);
 
@@ -202,6 +206,6 @@ IfaceMgrTestConfig::unicastOpen(const std::string& iface_name) const {
     return (false);
 }
 
-}
-}
-}
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
index 108aca67b8a778011fe236e2a64c247e7bc6281c..2839e4e987d369be787edf6da9716c32c8c349a7 100644 (file)
@@ -191,14 +191,14 @@ public:
     /// - multicast always to true
     /// - broadcast always to false
     ///
-    /// If one needs to modify the default flag settings, the setIfaceFlags
-    /// function should be used.
-    ///
     /// @param name A name of the interface to be created.
     /// @param ifindex An index of the interface to be created.
+    /// @param mac The mac of the interface.
     ///
     /// @return An object representing interface.
-    static IfacePtr createIface(const std::string& name, const unsigned int ifindex);
+    static IfacePtr createIface(const std::string& name,
+                                const unsigned int ifindex,
+                                const std::string& mac = "08:08:08:08:08:08");
 
     /// @brief Creates a default (example) set of fake interfaces.
     void createIfaces();
@@ -258,7 +258,6 @@ public:
     /// @param iface_name Interface name.
     bool unicastOpen(const std::string& iface_name) const;
 
-
 private:
     /// @brief Currently used packet filter for DHCPv4.
     PktFilterPtr packet_filter4_;
@@ -267,8 +266,8 @@ private:
     PktFilter6Ptr packet_filter6_;
 };
 
-};
-};
-};
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
 
 #endif // IFACE_MGR_TEST_CONFIG_H
index 018506ed43f185bf3b6f34081c0009fab7582f6c..8387c39c8f8da6c4eddb14ecd4a120332c4cc69e 100644 (file)
@@ -5,12 +5,12 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
-#include <dhcp/tests/pkt_captures.h>
-#include <string>
 #include <asiolink/io_address.h>
+#include <dhcp/tests/pkt_captures.h>
 #include <util/encode/hex.h>
+#include <string>
 
-/// @file   wireshark.cc
+/// @file   pkt_captures4.cc
 ///
 /// @brief  contains packet captures imported from Wireshark
 ///
index d1fa4165b649d1991db1d2f1ee2f10d62965db2f..3b936aedf088b9f10ead15b4fd2875c9c945e120 100644 (file)
@@ -6,8 +6,8 @@
 
 #include <config.h>
 #include <dhcp/pkt6.h>
-#include <util/encode/hex.h>
 #include <dhcp/tests/pkt_captures.h>
+#include <util/encode/hex.h>
 #include <string>
 
 /// @file   pkt_captures6.cc
index adc821f2d62077a8ce44d8ed20e7dfc2f7e5ce30..77a23c281fdc8054838a38ab6e57ba8ddc514bbe 100644 (file)
@@ -59,7 +59,6 @@ IfacesConfigParser::parse(const CfgIfacePtr& cfg,
             if (element.first == "interfaces") {
                 parseInterfacesList(cfg, element.second);
                 continue;
-
             }
 
             if (element.first == "dhcp-socket-type") {