]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3144] addressed review comments
authorRazvan Becheriu <razvan@isc.org>
Fri, 15 May 2026 15:43:37 +0000 (18:43 +0300)
committerRazvan Becheriu <razvan@isc.org>
Mon, 18 May 2026 11:22:02 +0000 (14:22 +0300)
doc/sphinx/arm/ctrl-channel.rst
doc/sphinx/arm/dhcp4-srv.rst
doc/sphinx/arm/dhcp6-srv.rst
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/lib/dhcp/iface_mgr.h
src/lib/dhcp/iface_mgr_bsd.cc
src/lib/dhcp/iface_mgr_linux.cc
src/lib/dhcp/tests/iface_mgr_unittest.cc

index 03928567d05366b7373fdad2a0f99da4587e8c80..28a8ef7f75511434cc676ade64fced3e42907403 100644 (file)
@@ -484,26 +484,6 @@ returning client that previously used that lease. See :ref:`lease-affinity`
 for details. Also, see :ref:`lease-reclamation` for general
 information about the processing of expired leases (lease reclamation).
 
-.. isccmd:: interface-list
-.. _command-interface-list:
-
-The ``interface-list`` Command
-------------------------------
-
-The :isccmd:`interface-list` command retrieves the list of detected interfaces.
-This command does not take any parameters.
-
-.. isccmd:: interface-redetect
-.. _command-interface-redetect:
-
-The ``interface-redetect`` Command
-----------------------------------
-
-The :isccmd:`interface-redetect` command retrieves the list of detected interfaces
-after performing a re-detect procedure which only adds newly discovered interfaces,
-without removing any previously detected interfaces.
-This command does not take any parameters."
-
 .. isccmd:: interface-add
 .. _command-interface-add:
 
@@ -514,7 +494,9 @@ The :isccmd:`interface-add` command updates the list of interfaces used
 to process DHCP traffic.
 The command takes as parameter the list of interfaces with respective
 addresses (if specified) on which the server should start listening for
-DHCP traffic.
+DHCP traffic. If there are duplicate entries in the command list or they
+are duplicating the entries in the running configuration, these entries
+will have no effect on the new configuration.
 
 .. note::
 
@@ -531,11 +513,31 @@ string, ``text``, describing the outcome:
 
 ::
 
-       {"result": 0, "text": "Configuration successful." }
+       { "result": 0, "text": "Interface configuration successfully updated." }
 
 The updated configuration can also be retrieved using the :isccmd:`config-get`
 command (inside the "interfaces-config" configuration map).
 
+.. isccmd:: interface-list
+.. _command-interface-list:
+
+The ``interface-list`` Command
+------------------------------
+
+The :isccmd:`interface-list` command retrieves the list of detected interfaces.
+This command does not take any parameters.
+
+.. isccmd:: interface-redetect
+.. _command-interface-redetect:
+
+The ``interface-redetect`` Command
+----------------------------------
+
+The :isccmd:`interface-redetect` command retrieves the list of detected interfaces
+after performing a re-detect procedure which only adds newly discovered interfaces,
+without removing any previously detected interfaces.
+This command does not take any parameters."
+
 .. isccmd:: list-commands
 .. _command-list-commands:
 
index e49094c4cacd49c6c0f39af137bbb3b5da7898e9..0a88bb197a2e91a6d63bfaf793af344b04be2739 100644 (file)
@@ -8007,9 +8007,9 @@ The DHCPv4 server supports the following operational commands:
 - :isccmd:`config-write`
 - :isccmd:`dhcp-disable`
 - :isccmd:`dhcp-enable`
+- :isccmd:`interface-add`
 - :isccmd:`interface-list`
 - :isccmd:`interface-redetect`
-- :isccmd:`interface-add`
 - :isccmd:`leases-reclaim`
 - :isccmd:`list-commands`
 - :isccmd:`shutdown`
index dea03cf992f9c421b92c7dcbfcc8655a000d0a58..98836532d789fada26237197352e492ebad21623 100644 (file)
@@ -7986,9 +7986,9 @@ The DHCPv6 server supports the following operational commands:
 - :isccmd:`config-write`
 - :isccmd:`dhcp-disable`
 - :isccmd:`dhcp-enable`
+- :isccmd:`interface-add`
 - :isccmd:`interface-list`
 - :isccmd:`interface-redetect`
-- :isccmd:`interface-add`
 - :isccmd:`leases-reclaim`
 - :isccmd:`list-commands`
 - :isccmd:`shutdown`
index 5ec8c0b1f463d7ed33289fbb5a1988a387e6fdfc..a7dcaaff54ee284772c8200435e9708ebc115779 100644 (file)
@@ -679,8 +679,10 @@ ControlledDhcpv4Srv::commandDhcpEnableHandler(const std::string&,
 ConstElementPtr
 ControlledDhcpv4Srv::commandInterfaceListHandler(const std::string&,
                                                  ConstElementPtr) {
-    // stop thread pool (if running)
-    MultiThreadingCriticalSection cs;
+    if (!IfaceMgr::instance().isMainThread()) {
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+            "Illegal operation executing 'interface-list' on a different thread than main thread"));
+    }
     ElementPtr ifaces = Element::createMap();
     std::string message;
     bool error = false;
@@ -708,11 +710,15 @@ ControlledDhcpv4Srv::commandInterfaceListHandler(const std::string&,
 ConstElementPtr
 ControlledDhcpv4Srv::commandInterfaceRedetectHandler(const std::string&,
                                                      ConstElementPtr args) {
-    // stop thread pool (if running)
-    MultiThreadingCriticalSection cs;
+    if (!IfaceMgr::instance().isMainThread()) {
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+            "Illegal operation executing 'interface-redetect' on a different thread than main thread"));
+    }
     std::string message;
     bool error = false;
     try {
+        // stop thread pool (if running)
+        MultiThreadingCriticalSection cs;
         IfaceMgr::instance().detectIfaces(true);
     } catch (const std::exception& ex) {
         error = true;
@@ -734,8 +740,10 @@ ControlledDhcpv4Srv::commandInterfaceRedetectHandler(const std::string&,
 ConstElementPtr
 ControlledDhcpv4Srv::commandInterfaceAddHandler(const std::string&,
                                                 ConstElementPtr args) {
-    // stop thread pool (if running)
-    MultiThreadingCriticalSection cs;
+    if (!IfaceMgr::instance().isMainThread()) {
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+            "Illegal operation executing 'interface-add' on a different thread than main thread"));
+    }
     string message;
     ConstElementPtr ifaces_config;
     if (!args) {
@@ -766,31 +774,29 @@ ControlledDhcpv4Srv::commandInterfaceAddHandler(const std::string&,
     }
     bool error = false;
     try {
-        ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(args);
-        CfgIfacePtr running_cfg_iface = CfgMgr::instance().getCurrentCfg()->getCfgIface();
-        ElementPtr mutable_running_cfg = running_cfg_iface->toElement();
-        auto const& element_empty = [](ElementPtr&) {
-            return (true);
-        };
-        auto const& element_match_any = [](ElementPtr&, ElementPtr&) -> bool {
-            return (true);
-        };
-        auto const& element_match = [](ElementPtr& left, ElementPtr& right) -> bool {
-            return (left->stringValue() == right->stringValue());
-        };
-        auto const& element_is_key = [](const std::string& key) -> bool {
-            return (key == "interfaces");
-        };
-        isc::data::HierarchyDescriptor hierarchy = {
-            { { "interfaces-config", { element_match_any, element_empty, element_is_key } } },
-            { { "interfaces", { element_match, element_empty, element_is_key } } }
-        };
-        mergeDiffAdd(mutable_running_cfg, mutable_cfg, hierarchy, "interfaces");
+        CfgIfacePtr running_cfg = CfgMgr::instance().getCurrentCfg()->getCfgIface();
+        ElementPtr ifaces = Element::createList();
+        std::set<std::string> seen;
+        auto running_ifaces = running_cfg->toElement()->get("interfaces");
+        if (running_ifaces && (running_ifaces->getType() == Element::list)) {
+            for (auto const& item : running_ifaces->listValue()) {
+                seen.insert(item->stringValue());
+                ifaces->add(item);
+            }
+        }
+        for (auto const& item : ifaces_config->listValue()) {
+            auto const& str = item->stringValue();
+            if (seen.find(item->stringValue()) != seen.end()) {
+                continue;
+            }
+            seen.insert(item->stringValue());
+            ifaces->add(item);
+        }
         IfacesConfigParser parser(AF_INET, true);
         CfgIfacePtr cfg_iface(new CfgIface());
-        parser.parseInterfacesList(cfg_iface, mutable_running_cfg->get("interfaces"));
-        CfgMgr::instance().getCurrentCfg()->getCfgIface()->update(*cfg_iface);
-        running_cfg_iface->triggerOpenSocketsWithRetry(AF_INET, getServerPort(), useBroadcast());
+        parser.parseInterfacesList(cfg_iface, ifaces);
+        running_cfg->update(*cfg_iface);
+        running_cfg->triggerOpenSocketsWithRetry(AF_INET, getServerPort(), useBroadcast());
     } catch (const std::exception& ex) {
         error = true;
         message = ex.what();
index 3c3041f73c67a920a4fe6a97ac94505055d289dc..635904f4251af70779b86c43304f4c2db35801ac 100644 (file)
@@ -682,8 +682,10 @@ ControlledDhcpv6Srv::commandDhcpEnableHandler(const std::string&,
 ConstElementPtr
 ControlledDhcpv6Srv::commandInterfaceListHandler(const std::string&,
                                                  ConstElementPtr) {
-    // stop thread pool (if running)
-    MultiThreadingCriticalSection cs;
+    if (!IfaceMgr::instance().isMainThread()) {
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+            "Illegal operation executing 'interface-list' on a different thread than main thread"));
+    }
     ElementPtr ifaces = Element::createMap();
     std::string message;
     bool error = false;
@@ -711,11 +713,15 @@ ControlledDhcpv6Srv::commandInterfaceListHandler(const std::string&,
 ConstElementPtr
 ControlledDhcpv6Srv::commandInterfaceRedetectHandler(const std::string&,
                                                      ConstElementPtr args) {
-    // stop thread pool (if running)
-    MultiThreadingCriticalSection cs;
+    if (!IfaceMgr::instance().isMainThread()) {
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+            "Illegal operation executing 'interface-redetect' on a different thread than main thread"));
+    }
     std::string message;
     bool error = false;
     try {
+        // stop thread pool (if running)
+        MultiThreadingCriticalSection cs;
         IfaceMgr::instance().detectIfaces(true);
     } catch (const std::exception& ex) {
         error = true;
@@ -737,8 +743,10 @@ ControlledDhcpv6Srv::commandInterfaceRedetectHandler(const std::string&,
 ConstElementPtr
 ControlledDhcpv6Srv::commandInterfaceAddHandler(const std::string&,
                                                 ConstElementPtr args) {
-    // stop thread pool (if running)
-    MultiThreadingCriticalSection cs;
+    if (!IfaceMgr::instance().isMainThread()) {
+        return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+            "Illegal operation executing 'interface-add' on a different thread than main thread"));
+    }
     string message;
     ConstElementPtr ifaces_config;
     if (!args) {
@@ -769,31 +777,29 @@ ControlledDhcpv6Srv::commandInterfaceAddHandler(const std::string&,
     }
     bool error = false;
     try {
-        ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(args);
-        CfgIfacePtr running_cfg_iface = CfgMgr::instance().getCurrentCfg()->getCfgIface();
-        ElementPtr mutable_running_cfg = running_cfg_iface->toElement();
-        auto const& element_empty = [](ElementPtr&) {
-            return (true);
-        };
-        auto const& element_match_any = [](ElementPtr&, ElementPtr&) -> bool {
-            return (true);
-        };
-        auto const& element_match = [](ElementPtr& left, ElementPtr& right) -> bool {
-            return (left->stringValue() == right->stringValue());
-        };
-        auto const& element_is_key = [](const std::string& key) -> bool {
-            return (key == "interfaces");
-        };
-        isc::data::HierarchyDescriptor hierarchy = {
-            { { "interfaces-config", { element_match_any, element_empty, element_is_key } } },
-            { { "interfaces", { element_match, element_empty, element_is_key } } }
-        };
-        mergeDiffAdd(mutable_running_cfg, mutable_cfg, hierarchy, "interfaces");
+        CfgIfacePtr running_cfg = CfgMgr::instance().getCurrentCfg()->getCfgIface();
+        ElementPtr ifaces = Element::createList();
+        std::set<std::string> seen;
+        auto running_ifaces = running_cfg->toElement()->get("interfaces");
+        if (running_ifaces && (running_ifaces->getType() == Element::list)) {
+            for (auto const& item : running_ifaces->listValue()) {
+                seen.insert(item->stringValue());
+                ifaces->add(item);
+            }
+        }
+        for (auto const& item : ifaces_config->listValue()) {
+            auto const& str = item->stringValue();
+            if (seen.find(item->stringValue()) != seen.end()) {
+                continue;
+            }
+            seen.insert(item->stringValue());
+            ifaces->add(item);
+        }
         IfacesConfigParser parser(AF_INET6, true);
         CfgIfacePtr cfg_iface(new CfgIface());
-        parser.parseInterfacesList(cfg_iface, mutable_running_cfg->get("interfaces"));
-        CfgMgr::instance().getCurrentCfg()->getCfgIface()->update(*cfg_iface);
-        running_cfg_iface->triggerOpenSocketsWithRetry(AF_INET6, getServerPort());
+        parser.parseInterfacesList(cfg_iface, ifaces);
+        running_cfg->update(*cfg_iface);
+        running_cfg->triggerOpenSocketsWithRetry(AF_INET6, getServerPort());
     } catch (const std::exception& ex) {
         error = true;
         message = ex.what();
index 0b9dfa6dee4f8345d826cfd085fb0aa23f61c8ec..9a48fac82bb588dcce45b5fb1357360be6f3c80e 100644 (file)
@@ -869,6 +869,13 @@ public:
         check_thread_id_ = check;
     }
 
+    /// @brief Check if called from the main thread.
+    ///
+    /// @return true if the current thread is the main thread, false otherwise.
+    bool isMainThread() const {
+        return (std::this_thread::get_id() == id_);
+    }
+
     /// @brief Allows or disallows the loopback interface
     ///
     /// By default the loopback interface is not considered when opening
@@ -1851,6 +1858,40 @@ private:
     uint16_t family_;
 };
 
+/// @brief RAII class creating a critical section for the receiver thread.
+class ReceiverCriticalSection : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Entering the critical section: if running, the receiver
+    /// is stopped not clearing the packet queue.
+    ReceiverCriticalSection(IfaceMgr& iface_mgr)
+        : iface_mgr_(iface_mgr), is_running_(iface_mgr.isDHCPReceiverRunning()) {
+        if (is_running_) {
+            iface_mgr_.stopDHCPReceiver(false);
+        }
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Leaving the critical section: if it was running, the receiver
+    /// is started.
+    ~ReceiverCriticalSection() {
+        if (is_running_) {
+            auto family = iface_mgr_.getFamily();
+            iface_mgr_.startDHCPReceiver(family);
+        }
+    }
+
+private:
+    /// @brief The IfaceMgr instance.
+    IfaceMgr& iface_mgr_;
+
+    /// @brief Is running flag.
+    bool is_running_;
+};
+
 }  // namespace isc::dhcp
 }  // namespace isc
 
index e0c4eba240a125d2e1cdd72af1c6dd5c10f0e6f9..a29ab6a6087a6d07c07e9ef81628af114800d5b2 100644 (file)
@@ -31,17 +31,7 @@ namespace dhcp {
 /// This is a BSD specific interface detection method.
 void
 IfaceMgr::detectIfaces(bool update_only) {
-    IfaceMgr* mgr_p = 0;
-    if (isDHCPReceiverRunning()) {
-        mgr_p = this;
-        stopDHCPReceiver(false);
-    }
-    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(mgr_p), [](void* m) {
-        if (m) {
-            IfaceMgr* mgr = reinterpret_cast<IfaceMgr*>(m);
-            mgr->startDHCPReceiver(mgr->getFamily());
-        }
-    });
+    ReceiverCriticalSection rcs(*this);
     if (detect_callback_) {
         if (!detect_callback_(update_only)) {
             return;
index af1473898d73c387b80e831fd3d8b733ae38d530..8be49e15ecd005b573ec9b720e397b8bf818bcec 100644 (file)
@@ -413,17 +413,7 @@ namespace dhcp {
 /// Uses the socket-based netlink protocol to retrieve the list of interfaces
 /// from the Linux kernel.
 void IfaceMgr::detectIfaces(bool update_only) {
-    IfaceMgr* mgr_p = 0;
-    if (isDHCPReceiverRunning()) {
-        mgr_p = this;
-        stopDHCPReceiver(false);
-    }
-    std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(mgr_p), [](void* m) {
-        if (m) {
-            IfaceMgr* mgr = reinterpret_cast<IfaceMgr*>(m);
-            mgr->startDHCPReceiver(mgr->getFamily());
-        }
-    });
+    ReceiverCriticalSection rcs(*this);
     if (detect_callback_) {
         if (!detect_callback_(update_only)) {
             return;
index 96afc67c1aeb7f89afbbed0c6ce51e630eacf20f..105e413acc66f69c96a4a4b2400c96b8d2dacd8b 100644 (file)
@@ -4821,4 +4821,26 @@ TEST_F(IfaceMgrTest, indirectReceive6RotateIfaces) {
     testReceive6RotateIfaces(false);
 }
 
+TEST_F(IfaceMgrTest, receiverCS) {
+    ASSERT_FALSE(IfaceMgr::instance().isDHCPReceiverRunning());
+    {
+        ReceiverCriticalSection rcs(IfaceMgr::instance());
+        ASSERT_FALSE(IfaceMgr::instance().isDHCPReceiverRunning());
+    }
+    ASSERT_FALSE(IfaceMgr::instance().isDHCPReceiverRunning());
+    bool queue_enabled = false;
+    data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500);
+    ASSERT_NO_THROW(queue_enabled = IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, config));
+    ASSERT_TRUE(queue_enabled);
+
+    // Thread should only start when there is a packet queue.
+    ASSERT_NO_THROW(IfaceMgr::instance().startDHCPReceiver(AF_INET));
+    ASSERT_TRUE(IfaceMgr::instance().isDHCPReceiverRunning());
+    {
+        ReceiverCriticalSection rcs(IfaceMgr::instance());
+        ASSERT_FALSE(IfaceMgr::instance().isDHCPReceiverRunning());
+    }
+    ASSERT_TRUE(IfaceMgr::instance().isDHCPReceiverRunning());
+}
+
 }