From: Razvan Becheriu Date: Mon, 13 Nov 2023 12:05:50 +0000 (+0200) Subject: [#3145] backport #3017 to v2_4 X-Git-Tag: Kea-2.4.1~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0f180bd92f3235d58cbbe7078bdcedb1d65e4edd;p=thirdparty%2Fkea.git [#3145] backport #3017 to v2_4 --- diff --git a/ChangeLog b/ChangeLog index 7364c98e7f..69ec83340d 100644 --- 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) diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index b62bbe116f..d958c415ea 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -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); diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc index 88d685f3e6..ee0e28a31b 100644 --- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc @@ -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 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-/kea6.sock), so the + // socket name (/tmp/kea-/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-/kea6.sock), so the + // socket name (/tmp/kea-/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-/kea6.sock), so the + // socket name (/tmp/kea-/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-/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-/kea6.sock), so the + // socket name (/tmp/kea-/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 diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 337371c223..dd11de07d4 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -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); diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc index f39062b949..f05a40212b 100644 --- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc @@ -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 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-/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) { diff --git a/src/lib/dhcp/duid_factory.cc b/src/lib/dhcp/duid_factory.cc index 9b34d15d79..1a023445bd 100644 --- a/src/lib/dhcp/duid_factory.cc +++ b/src/lib/dhcp/duid_factory.cc @@ -296,7 +296,7 @@ DUIDFactory::createLinkLayerId(std::vector& 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"); } } diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index 6bf1a0853f..c23a9e9836 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/src/lib/dhcp/iface_mgr.cc @@ -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 diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h index bccfeee01c..ca0a536096 100644 --- a/src/lib/dhcp/iface_mgr.h +++ b/src/lib/dhcp/iface_mgr.h @@ -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 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 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 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_; diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc index bc77d967f1..553842aa41 100644 --- a/src/lib/dhcp/iface_mgr_bsd.cc +++ b/src/lib/dhcp/iface_mgr_bsd.cc @@ -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 diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc index 4821da64b6..e4053b0664 100644 --- a/src/lib/dhcp/iface_mgr_linux.cc +++ b/src/lib/dhcp/iface_mgr_linux.cc @@ -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. diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc index 56b36e027f..2fd765f96f 100644 --- a/src/lib/dhcp/iface_mgr_sun.cc +++ b/src/lib/dhcp/iface_mgr_sun.cc @@ -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 diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.cc b/src/lib/dhcp/tests/iface_mgr_test_config.cc index a36786734c..26191d5aa8 100644 --- a/src/lib/dhcp/tests/iface_mgr_test_config.cc +++ b/src/lib/dhcp/tests/iface_mgr_test_config.cc @@ -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 mac_vec(6, 8); + // Set MAC address. + HWAddr hwaddr = HWAddr::fromText(mac); + std::vector 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 diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.h b/src/lib/dhcp/tests/iface_mgr_test_config.h index 108aca67b8..2839e4e987 100644 --- a/src/lib/dhcp/tests/iface_mgr_test_config.h +++ b/src/lib/dhcp/tests/iface_mgr_test_config.h @@ -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 diff --git a/src/lib/dhcp/tests/pkt_captures4.cc b/src/lib/dhcp/tests/pkt_captures4.cc index 018506ed43..8387c39c8f 100644 --- a/src/lib/dhcp/tests/pkt_captures4.cc +++ b/src/lib/dhcp/tests/pkt_captures4.cc @@ -5,12 +5,12 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include -#include -#include #include +#include #include +#include -/// @file wireshark.cc +/// @file pkt_captures4.cc /// /// @brief contains packet captures imported from Wireshark /// diff --git a/src/lib/dhcp/tests/pkt_captures6.cc b/src/lib/dhcp/tests/pkt_captures6.cc index d1fa4165b6..3b936aedf0 100644 --- a/src/lib/dhcp/tests/pkt_captures6.cc +++ b/src/lib/dhcp/tests/pkt_captures6.cc @@ -6,8 +6,8 @@ #include #include -#include #include +#include #include /// @file pkt_captures6.cc diff --git a/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc index adc821f2d6..77a23c281f 100644 --- a/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc +++ b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc @@ -59,7 +59,6 @@ IfacesConfigParser::parse(const CfgIfacePtr& cfg, if (element.first == "interfaces") { parseInterfacesList(cfg, element.second); continue; - } if (element.first == "dhcp-socket-type") {