Parcourir la source

[5046] CommandMgr dups the connection socket prior to executing command

src/lib/config/command_mgr.cc
    CommandMgr::commandReader(int sockfd) - duplicates the connection
    socket to use for repsonding in case the command closes the channel.

src/lib/testutils/io_utils.cc
    fileExists() - now uses stat() function so one can use it on any
    type of file, like a unix socket

updated unit tests accordingly
Thomas Markwalder il y a 8 ans
Parent
commit
e5a6c2b4f8

+ 11 - 3
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -16,6 +16,7 @@
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
 #include <stats/stats_mgr.h>
 #include <stats/stats_mgr.h>
+#include <testutils/io_utils.h>
 #include <testutils/unix_control_client.h>
 #include <testutils/unix_control_client.h>
 
 
 #include "marker_file.h"
 #include "marker_file.h"
@@ -592,7 +593,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, set_config) {
     EXPECT_EQ(1, subnets->size());
     EXPECT_EQ(1, subnets->size());
 
 
     // Create a valid config with two subnets and no command channel.
     // Create a valid config with two subnets and no command channel.
-    // It should succeed but client will not receive a the response
+    // It should succeed, client should still receive the response
     os.str("");
     os.str("");
     os << set_config_txt << ","
     os << set_config_txt << ","
         << args_txt
         << args_txt
@@ -604,11 +605,18 @@ TEST_F(CtrlChannelDhcpv4SrvTest, set_config) {
         << "}\n"                      // close dhcp4
         << "}\n"                      // close dhcp4
         << "}}";
         << "}}";
 
 
+    /* Verify the control channel socket exists */
+    ASSERT_TRUE(fileExists(socket_path_));
+
     // Send the set-config command
     // Send the set-config command
     sendUnixCommand(os.str(), response);
     sendUnixCommand(os.str(), response);
 
 
-    // With no command channel, no response
-    EXPECT_EQ("", response);
+    /* Verify the control channel socket no longer exists */
+    EXPECT_FALSE(fileExists(socket_path_));
+
+    // With no command channel, should still receive the response.
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+              response);
 
 
     // Check that the config was not lost
     // Check that the config was not lost
     subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
     subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();

+ 11 - 2
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -18,6 +18,7 @@
 #include <log/logger_support.h>
 #include <log/logger_support.h>
 #include <stats/stats_mgr.h>
 #include <stats/stats_mgr.h>
 #include <testutils/unix_control_client.h>
 #include <testutils/unix_control_client.h>
+#include <testutils/io_utils.h>
 
 
 #include "marker_file.h"
 #include "marker_file.h"
 #include "test_libraries.h"
 #include "test_libraries.h"
@@ -26,6 +27,7 @@
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
 #include <sys/select.h>
 #include <sys/select.h>
+#include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/ioctl.h>
 #include <cstdlib>
 #include <cstdlib>
 
 
@@ -468,11 +470,18 @@ TEST_F(CtrlChannelDhcpv6SrvTest, set_config) {
         << "}\n"                      // close dhcp6
         << "}\n"                      // close dhcp6
         << "}}";
         << "}}";
 
 
+    /* Verify the control channel socket exists */
+    ASSERT_TRUE(fileExists(socket_path_));
+
     // Send the set-config command
     // Send the set-config command
     sendUnixCommand(os.str(), response);
     sendUnixCommand(os.str(), response);
 
 
-    // With no command channel, no response
-    EXPECT_EQ("", response);
+    /* Verify the control channel socket no longer exists */
+    EXPECT_FALSE(fileExists(socket_path_));
+
+    // With no command channel, should still receive the response.
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+              response);
 
 
     // Check that the config was not lost
     // Check that the config was not lost
     subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();

+ 28 - 2
src/lib/config/command_mgr.cc

@@ -11,6 +11,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <config/config_log.h>
 #include <config/config_log.h>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
+#include <unistd.h>
 
 
 using namespace isc::data;
 using namespace isc::data;
 
 
@@ -145,6 +146,19 @@ CommandMgr::commandReader(int sockfd) {
         return;
         return;
     }
     }
 
 
+    // Duplicate the connection's socket in the event, the command causes the
+    // channel to close (like a reconfig).  This permits us to always have
+    // a socket on which to respond. If for some reason  we can't fall back
+    // to the connection socket.
+    int rsp_fd = dup(sockfd);
+    if (rsp_fd < 0 ) {
+        // Highly unlikely
+        const char* errmsg = strerror(errno);
+        LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_DUP_WARN)
+                  .arg(errmsg);
+        rsp_fd = sockfd;
+    }
+
     LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ).arg(rval).arg(sockfd);
     LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ).arg(rval).arg(sockfd);
 
 
     // Ok, we received something. Let's see if we can make any sense of it.
     // Ok, we received something. Let's see if we can make any sense of it.
@@ -163,6 +177,11 @@ CommandMgr::commandReader(int sockfd) {
 
 
     if (!rsp) {
     if (!rsp) {
         LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR);
         LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR);
+        // Only close the dupped socket if it's different (should be)
+        if (rsp_fd != sockfd) {
+            close(rsp_fd);
+        }
+
         return;
         return;
     }
     }
 
 
@@ -179,7 +198,8 @@ CommandMgr::commandReader(int sockfd) {
     }
     }
 
 
     // Send the data back over socket.
     // Send the data back over socket.
-    rval = write(sockfd, txt.c_str(), len);
+    rval = write(rsp_fd, txt.c_str(), len);
+    int saverr = errno;
 
 
     LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE).arg(len).arg(sockfd);
     LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE).arg(len).arg(sockfd);
 
 
@@ -187,7 +207,13 @@ CommandMgr::commandReader(int sockfd) {
         // Response transmission failed. Since the response failed, it doesn't
         // Response transmission failed. Since the response failed, it doesn't
         // make sense to send any status codes. Let's log it and be done with
         // make sense to send any status codes. Let's log it and be done with
         // it.
         // it.
-        LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL).arg(len).arg(sockfd);
+        LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL)
+                  .arg(len).arg(sockfd).arg(strerror(saverr));
+    }
+
+    // Only close the dupped socket if it's different (should be)
+    if (rsp_fd != sockfd) {
+        close(rsp_fd);
     }
     }
 }
 }
 
 

+ 8 - 1
src/lib/config/config_messages.mes

@@ -57,6 +57,13 @@ This error message indicates that the server failed to set non-blocking mode
 on just created socket. That socket was created for accepting specific
 on just created socket. That socket was created for accepting specific
 incoming connection. Additional information may be provided as third parameter.
 incoming connection. Additional information may be provided as third parameter.
 
 
+% COMMAND_SOCKET_DUP_WARN Failed to duplicate socket for response: %1
+This debug message indicates that the commandReader was unable to duplicate
+the connection socket prior to executing the command. This is most likely a
+system resource issue.  The command should still be processed and the response
+sent, unless the command caused the command channel to be closed (e.g. a
+reconfiguration command).
+
 % COMMAND_SOCKET_READ Received %1 bytes over command socket %2
 % COMMAND_SOCKET_READ Received %1 bytes over command socket %2
 This debug message indicates that specified number of bytes was received
 This debug message indicates that specified number of bytes was received
 over command socket identified by specified file descriptor.
 over command socket identified by specified file descriptor.
@@ -86,6 +93,6 @@ descriptor and path specified.
 This debug message indicates that the specified number of bytes was sent
 This debug message indicates that the specified number of bytes was sent
 over command socket identifier by the specified file descriptor.
 over command socket identifier by the specified file descriptor.
 
 
-% COMMAND_SOCKET_WRITE_FAIL Error while writing %1 bytes to command socket %2
+% COMMAND_SOCKET_WRITE_FAIL Error while writing %1 bytes to command socket %2 : %3
 This error message indicates that an error was encountered while
 This error message indicates that an error was encountered while
 attempting to send a response to the command socket.
 attempting to send a response to the command socket.

+ 2 - 4
src/lib/testutils/io_utils.cc

@@ -15,10 +15,8 @@ namespace dhcp {
 namespace test {
 namespace test {
 
 
 bool fileExists(const std::string& file_path) {
 bool fileExists(const std::string& file_path) {
-    std::ifstream fs(file_path.c_str());
-    const bool file_exists = fs.good();
-    fs.close();
-    return (file_exists);
+    struct stat statbuf;
+    return(stat(file_path.c_str(), &statbuf) == 0);
 }
 }
 
 
 std::string readFile(const std::string& file_path) {
 std::string readFile(const std::string& file_path) {

+ 1 - 0
src/lib/testutils/io_utils.h

@@ -8,6 +8,7 @@
 #define TEST_IO_UTILS_H
 #define TEST_IO_UTILS_H
 
 
 #include <string>
 #include <string>
+#include <sys/stat.h>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {