]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3694] apply tls settings on reload
authorRazvan Becheriu <razvan@isc.org>
Wed, 8 Jan 2025 18:53:31 +0000 (20:53 +0200)
committerRazvan Becheriu <razvan@isc.org>
Fri, 7 Feb 2025 12:11:10 +0000 (14:11 +0200)
18 files changed:
src/bin/agent/ca_messages.mes
src/bin/agent/ca_process.cc
src/bin/agent/tests/Makefile.am
src/bin/agent/tests/ca_controller_unittests.cc
src/bin/d2/d2_process.cc
src/bin/d2/tests/d2_http_command_unittest.cc
src/bin/dhcp4/json_config_parser.cc
src/bin/dhcp4/tests/http_control_socket_unittest.cc
src/bin/dhcp6/json_config_parser.cc
src/bin/dhcp6/tests/http_control_socket_unittest.cc
src/lib/config/config_messages.cc
src/lib/config/config_messages.h
src/lib/config/config_messages.mes
src/lib/config/http_command_mgr.cc
src/lib/http/listener.cc
src/lib/http/listener.h
src/lib/http/listener_impl.cc
src/lib/http/listener_impl.h

index f592955f3dd2a5a2e1bbf3f8986c3f15a1e2e344..fce41e1b6b30a171f034f9a862d0df80b69d73c4 100644 (file)
@@ -55,7 +55,7 @@ address and port over a TLS channel.
 
 % CTRL_AGENT_HTTP_SERVICE_REUSED reused HTTP service bound to address %1:%2
 This informational message indicates that the server has reused existing
-HTTPS service on the specified address and port.
+HTTP service on the specified address and port.
 
 % CTRL_AGENT_HTTP_SERVICE_STARTED HTTP service bound to address %1:%2
 This informational message indicates that the server has started HTTP service
index a736ccdb4e67f7e72ae1f9da6eb448744dc20cee..7ee3e142fba483f9f479722bdccec16374f6db52 100644 (file)
@@ -139,14 +139,31 @@ CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set,
         // Search for the specific connection and reuse the existing one if found.
         auto it = sockets_.find(std::make_pair(server_address, server_port));
         if (it != sockets_.end()) {
-            auto listener = getHttpListener();
+            auto listener = it->second->listener_;
             if (listener) {
                 // Reconfig keeping the same address and port.
                 if (listener->getTlsContext()) {
-                    LOG_INFO(agent_logger, CTRL_AGENT_HTTPS_SERVICE_REUSED)
-                        .arg(server_address.toText())
-                        .arg(server_port);
-                } else {
+                    if (ctx->getTrustAnchor().empty()) {
+                        // Can not switch from HTTPS to HTTP
+                        LOG_INFO(agent_logger, CTRL_AGENT_HTTPS_SERVICE_REUSED)
+                            .arg(server_address.toText())
+                            .arg(server_port);
+                    } else {
+                        // Apply TLS settings each time.
+                        TlsContextPtr tls_context;
+                        TlsContext::configure(tls_context,
+                                              TlsRole::SERVER,
+                                              ctx->getTrustAnchor(),
+                                              ctx->getCertFile(),
+                                              ctx->getKeyFile(),
+                                              ctx->getCertRequired());
+                        // Overwrite the authentication setup and the http headers in the response creator config.
+                        it->second->config_->setAuthConfig(ctx->getAuthConfig());
+                        it->second->config_->setHttpHeaders(ctx->getHttpHeaders());
+                        getIOService()->post([listener, tls_context]() { listener->setTlsContext(tls_context); });
+                    }
+                } else if (!ctx->getTrustAnchor().empty()) {
+                    // Can not switch from HTTP to HTTPS
                     LOG_INFO(agent_logger, CTRL_AGENT_HTTP_SERVICE_REUSED)
                         .arg(server_address.toText())
                         .arg(server_port);
index 100d2467df3c38e0a1d8355a3809452bdc476c07..5497c9ef60d25f7be554efaeb57a1c1ece66c5e8 100644 (file)
@@ -30,6 +30,7 @@ AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/agent/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/agent\"
 AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../agent_parser.yy\"
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(abs_top_srcdir)/src/lib/asiolink/testutils/ca\"
 
 AM_CXXFLAGS = $(KEA_CXXFLAGS)
 
index 9a18c27a0a0fbb671dec336fd0f9d7c40c98e820..3890606e63f2998b7ccf93410db458741c8d538d 100644 (file)
@@ -21,6 +21,7 @@
 
 #include <unistd.h>
 
+using namespace isc::asiolink;
 using namespace isc::asiolink::test;
 using namespace isc::agent;
 using namespace isc::data;
@@ -402,7 +403,7 @@ TEST_F(CtrlAgentControllerTest, unsuccessfulConfigUpdate) {
 // Tests that it is possible to update the configuration in such a way that the
 // listener configuration remains the same. The server should continue using the
 // listener instance it has been using prior to the reconfiguration.
-TEST_F(CtrlAgentControllerTest, noListenerChange) {
+TEST_F(CtrlAgentControllerTest, noListenerChangeHttp) {
     // This configuration should be used to override the initial configuration.
     const char* second_config =
         "{"
@@ -420,6 +421,8 @@ TEST_F(CtrlAgentControllerTest, noListenerChange) {
         "  }"
         "}";
 
+    const HttpListener* listener_ptr = 0;
+
     // This check callback is called before the shutdown.
     auto check_callback = [&] {
         CtrlAgentProcessPtr process = getCtrlAgentProcess();
@@ -428,6 +431,8 @@ TEST_F(CtrlAgentControllerTest, noListenerChange) {
         // Check that the HTTP listener still exists after reconfiguration.
         ConstHttpListenerPtr listener = process->getHttpListener();
         ASSERT_TRUE(listener);
+        ASSERT_EQ(listener_ptr, listener.get());
+        ASSERT_FALSE(listener->getTlsContext());
         EXPECT_TRUE(process->isListening());
 
         EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
@@ -439,6 +444,202 @@ TEST_F(CtrlAgentControllerTest, noListenerChange) {
     // Schedule SIGHUP signal to trigger reconfiguration.
     TimedSignal sighup(getIOService(), SIGHUP, 200);
 
+    IntervalTimer timer(getIOService());
+    timer.setup([&] {
+        CtrlAgentProcessPtr process = getCtrlAgentProcess();
+        ASSERT_TRUE(process);
+        ConstHttpListenerPtr listener = process->getHttpListener();
+        ASSERT_TRUE(listener);
+        listener_ptr = listener.get();
+        ASSERT_FALSE(listener->getTlsContext());
+    }, 50, IntervalTimer::ONE_SHOT);
+
+    // Start the server.
+    time_duration elapsed_time;
+    runWithConfig(valid_agent_config, 500,
+                  static_cast<const TestCallback&>(check_callback),
+                  elapsed_time);
+
+    CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+    ASSERT_TRUE(ctx);
+
+    // The server should use a correct listener configuration.
+    EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
+    EXPECT_EQ(8081, ctx->getHttpPort());
+
+    // The forwarding configuration should have been updated.
+    testUnixSocketInfo("dhcp4", "/second/dhcp4/socket");
+    testUnixSocketInfo("dhcp6", "/second/dhcp6/socket");
+
+    CtrlAgentProcessPtr process = getCtrlAgentProcess();
+    ASSERT_TRUE(process);
+    ConstHttpListenerPtr listener = process->getHttpListener();
+    ASSERT_FALSE(listener);
+    EXPECT_FALSE(process->isListening());
+}
+
+// Tests that it is possible to update the configuration in such a way that the
+// listener configuration remains the same. The server should continue using the
+// listener instance it has been using prior to the reconfiguration.
+TEST_F(CtrlAgentControllerTest, noListenerChangeHttps) {
+    // This configuration should be used to override the initial configuration.
+    string ca_dir(string(TEST_CA_DIR));
+    ostringstream agent_st;
+    agent_st << "{"
+             << "  \"http-host\": \"127.0.0.1\","
+             << "  \"http-port\": 8081,"
+             << "  \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+             << "  \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+             << "  \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+             << "  \"control-sockets\": {"
+             << "    \"dhcp4\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/first/dhcp4/socket\""
+             << "    },"
+             << "    \"dhcp6\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/first/dhcp6/socket\""
+             << "    }"
+             << "  }"
+             << "}";
+
+    ostringstream second_config_st;
+    second_config_st << "{"
+             << "  \"http-host\": \"127.0.0.1\","
+             << "  \"http-port\": 8081,"
+             << "  \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+             << "  \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+             << "  \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+             << "  \"control-sockets\": {"
+             << "    \"dhcp4\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/second/dhcp4/socket\""
+             << "    },"
+             << "    \"dhcp6\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/second/dhcp6/socket\""
+             << "    }"
+             << "  }"
+             << "}";
+
+    const HttpListener* listener_ptr = 0;
+    TlsContext* context = 0;
+
+    // This check callback is called before the shutdown.
+    auto check_callback = [&] {
+        CtrlAgentProcessPtr process = getCtrlAgentProcess();
+        ASSERT_TRUE(process);
+
+        // Check that the HTTP listener still exists after reconfiguration.
+        ConstHttpListenerPtr listener = process->getHttpListener();
+        ASSERT_TRUE(listener);
+        ASSERT_EQ(listener_ptr, listener.get());
+        ASSERT_TRUE(listener->getTlsContext());
+        // The TLS settings have been applied
+        ASSERT_NE(context, listener->getTlsContext().get());
+        EXPECT_TRUE(process->isListening());
+
+        EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
+        EXPECT_EQ(8081, listener->getLocalPort());
+    };
+
+    // Schedule reconfiguration.
+    scheduleTimedWrite(second_config_st.str(), 100);
+    // Schedule SIGHUP signal to trigger reconfiguration.
+    TimedSignal sighup(getIOService(), SIGHUP, 200);
+
+    IntervalTimer timer(getIOService());
+    timer.setup([&] {
+        CtrlAgentProcessPtr process = getCtrlAgentProcess();
+        ASSERT_TRUE(process);
+        ConstHttpListenerPtr listener = process->getHttpListener();
+        ASSERT_TRUE(listener);
+        listener_ptr = listener.get();
+        ASSERT_TRUE(listener->getTlsContext());
+        context = listener->getTlsContext().get();
+    }, 50, IntervalTimer::ONE_SHOT);
+
+    // Start the server.
+    time_duration elapsed_time;
+    runWithConfig(agent_st.str(), 500,
+                  static_cast<const TestCallback&>(check_callback),
+                  elapsed_time);
+
+    CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+    ASSERT_TRUE(ctx);
+
+    // The server should use a correct listener configuration.
+    EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
+    EXPECT_EQ(8081, ctx->getHttpPort());
+
+    // The forwarding configuration should have been updated.
+    testUnixSocketInfo("dhcp4", "/second/dhcp4/socket");
+    testUnixSocketInfo("dhcp6", "/second/dhcp6/socket");
+
+    CtrlAgentProcessPtr process = getCtrlAgentProcess();
+    ASSERT_TRUE(process);
+    ConstHttpListenerPtr listener = process->getHttpListener();
+    ASSERT_FALSE(listener);
+    EXPECT_FALSE(process->isListening());
+}
+
+// Verify that the reload will reuse listener
+TEST_F(CtrlAgentControllerTest, ignoreHttpToHttpsSwitch) {
+    string ca_dir(string(TEST_CA_DIR));
+
+    // This configuration should be used to override the initial configuration.
+    ostringstream second_config_st;
+    second_config_st << "{"
+             << "  \"http-host\": \"127.0.0.1\","
+             << "  \"http-port\": 8081,"
+             << "  \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+             << "  \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+             << "  \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+             << "  \"control-sockets\": {"
+             << "    \"dhcp4\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/second/dhcp4/socket\""
+             << "    },"
+             << "    \"dhcp6\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/second/dhcp6/socket\""
+             << "    }"
+             << "  }"
+             << "}";
+
+    const HttpListener* listener_ptr = 0;
+
+    // This check callback is called before the shutdown.
+    auto check_callback = [&] {
+        CtrlAgentProcessPtr process = getCtrlAgentProcess();
+        ASSERT_TRUE(process);
+
+        // Check that the HTTP listener still exists after reconfiguration.
+        ConstHttpListenerPtr listener = process->getHttpListener();
+        ASSERT_TRUE(listener);
+        ASSERT_EQ(listener_ptr, listener.get());
+        ASSERT_FALSE(listener->getTlsContext());
+        EXPECT_TRUE(process->isListening());
+
+        EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
+        EXPECT_EQ(8081, listener->getLocalPort());
+    };
+
+    // Schedule reconfiguration.
+    scheduleTimedWrite(second_config_st.str(), 100);
+    // Schedule SIGHUP signal to trigger reconfiguration.
+    TimedSignal sighup(getIOService(), SIGHUP, 200);
+
+    IntervalTimer timer(getIOService());
+    timer.setup([&] {
+        CtrlAgentProcessPtr process = getCtrlAgentProcess();
+        ASSERT_TRUE(process);
+        ConstHttpListenerPtr listener = process->getHttpListener();
+        ASSERT_TRUE(listener);
+        listener_ptr = listener.get();
+        ASSERT_FALSE(listener->getTlsContext());
+    }, 50, IntervalTimer::ONE_SHOT);
+
     // Start the server.
     time_duration elapsed_time;
     runWithConfig(valid_agent_config, 500,
@@ -463,6 +664,106 @@ TEST_F(CtrlAgentControllerTest, noListenerChange) {
     EXPECT_FALSE(process->isListening());
 }
 
+// Verify that the reload will reuse listener
+TEST_F(CtrlAgentControllerTest, ignoreHttpsToHttpSwitch) {
+    string ca_dir(string(TEST_CA_DIR));
+    ostringstream agent_st;
+    agent_st << "{"
+             << "  \"http-host\": \"127.0.0.1\","
+             << "  \"http-port\": 8081,"
+             << "  \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+             << "  \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+             << "  \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+             << "  \"control-sockets\": {"
+             << "    \"dhcp4\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/first/dhcp4/socket\""
+             << "    },"
+             << "    \"dhcp6\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/first/dhcp6/socket\""
+             << "    }"
+             << "  }"
+             << "}";
+
+    // This configuration should be used to override the initial configuration.
+    ostringstream second_config_st;
+    second_config_st << "{"
+             << "  \"http-host\": \"127.0.0.1\","
+             << "  \"http-port\": 8081,"
+             << "  \"control-sockets\": {"
+             << "    \"dhcp4\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/second/dhcp4/socket\""
+             << "    },"
+             << "    \"dhcp6\": {"
+             << "      \"socket-type\": \"unix\","
+             << "      \"socket-name\": \"/second/dhcp6/socket\""
+             << "    }"
+             << "  }"
+             << "}";
+
+    const HttpListener* listener_ptr = 0;
+    TlsContext* context = 0;
+
+    // This check callback is called before the shutdown.
+    auto check_callback = [&] {
+        CtrlAgentProcessPtr process = getCtrlAgentProcess();
+        ASSERT_TRUE(process);
+
+        // Check that the HTTP listener still exists after reconfiguration.
+        ConstHttpListenerPtr listener = process->getHttpListener();
+        ASSERT_TRUE(listener);
+        ASSERT_EQ(listener_ptr, listener.get());
+        ASSERT_TRUE(listener->getTlsContext());
+        // The TLS settings have not changed
+        ASSERT_EQ(context, listener->getTlsContext().get());
+        EXPECT_TRUE(process->isListening());
+
+        EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
+        EXPECT_EQ(8081, listener->getLocalPort());
+    };
+
+    // Schedule reconfiguration.
+    scheduleTimedWrite(second_config_st.str(), 100);
+    // Schedule SIGHUP signal to trigger reconfiguration.
+    TimedSignal sighup(getIOService(), SIGHUP, 200);
+
+    IntervalTimer timer(getIOService());
+    timer.setup([&] {
+        CtrlAgentProcessPtr process = getCtrlAgentProcess();
+        ASSERT_TRUE(process);
+        ConstHttpListenerPtr listener = process->getHttpListener();
+        ASSERT_TRUE(listener);
+        listener_ptr = listener.get();
+        ASSERT_TRUE(listener->getTlsContext());
+        context = listener->getTlsContext().get();
+    }, 50, IntervalTimer::ONE_SHOT);
+
+    // Start the server.
+    time_duration elapsed_time;
+    runWithConfig(agent_st.str(), 500,
+                  static_cast<const TestCallback&>(check_callback),
+                  elapsed_time);
+
+    CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+    ASSERT_TRUE(ctx);
+
+    // The server should use a correct listener configuration.
+    EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
+    EXPECT_EQ(8081, ctx->getHttpPort());
+
+    // The forwarding configuration should have been updated.
+    testUnixSocketInfo("dhcp4", "/second/dhcp4/socket");
+    testUnixSocketInfo("dhcp6", "/second/dhcp6/socket");
+
+    CtrlAgentProcessPtr process = getCtrlAgentProcess();
+    ASSERT_TRUE(process);
+    ConstHttpListenerPtr listener = process->getHttpListener();
+    ASSERT_FALSE(listener);
+    EXPECT_FALSE(process->isListening());
+}
+
 // Tests that registerCommands actually registers anything.
 TEST_F(CtrlAgentControllerTest, registeredCommands) {
     ASSERT_NO_THROW(initProcess());
index 617686b9c109078e81f6288a03c1337881a7684b..7d17961b8fe05e647d8ffd2a821a89d94558bab1 100644 (file)
@@ -522,16 +522,11 @@ D2Process::reconfigureCommandChannel() {
     ConstElementPtr http_config =
         getD2CfgMgr()->getHttpControlSocketInfo();
 
-    sock_changed = (http_config && current_http_control_socket_ &&
-                    !http_config->equals(*current_http_control_socket_));
-
-    if (!http_config || !current_http_control_socket_ || sock_changed) {
-        // Open the new sockets and close old ones, keeping reused.
-        if (http_config) {
-            HttpCommandMgr::instance().openCommandSockets(http_config);
-        } else if (current_http_control_socket_) {
-            HttpCommandMgr::instance().closeCommandSockets();
-        }
+    // Open the new sockets and close old ones, keeping reused.
+    if (http_config) {
+        HttpCommandMgr::instance().openCommandSockets(http_config);
+    } else if (current_http_control_socket_) {
+        HttpCommandMgr::instance().closeCommandSockets();
     }
 
     // Commit the new socket configuration.
index 0f8999af3de006c9a19de26722c6016593e4d142..00c2afcbf9b659d16a1da9fd9fa1c3d12f70d5d5 100644 (file)
@@ -1218,7 +1218,7 @@ TEST_F(HttpCtrlChannelD2Test, configSet) {
     ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
 
     // Create a config with invalid content that should fail to parse.
-    string config_test_txt =
+    string config_set_txt =
         "{ \"command\": \"config-set\", \n"
         "  \"arguments\": { \n"
         "    \"DhcpDdns\": \n"
@@ -1242,7 +1242,7 @@ TEST_F(HttpCtrlChannelD2Test, configSet) {
 
     // Send the config-set command.
     string response;
-    sendHttpCommand(config_test_txt, response);
+    sendHttpCommand(config_set_txt, response);
 
     // Should fail with a syntax error.
     EXPECT_EQ("[ { \"result\": 1, \"text\": \"missing parameter 'name' (<string>:10:14)\" } ]",
@@ -1255,7 +1255,7 @@ TEST_F(HttpCtrlChannelD2Test, configSet) {
     EXPECT_EQ(1, keys->size());
 
     // Create a valid config with two keys and no command channel.
-    config_test_txt =
+    config_set_txt =
         "{ \"command\": \"config-set\", \n"
         "  \"arguments\": { \n"
         "    \"DhcpDdns\": \n"
@@ -1280,7 +1280,7 @@ TEST_F(HttpCtrlChannelD2Test, configSet) {
     EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
 
     // Send the config-set command.
-    sendHttpCommand(config_test_txt, response);
+    sendHttpCommand(config_set_txt, response);
 
     // Verify the HTTP control channel socket no longer exists.
     ASSERT_NO_THROW(HttpCommandMgr::instance().closeCommandSockets());
@@ -1355,7 +1355,7 @@ TEST_F(HttpsCtrlChannelD2Test, configSet) {
     ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
 
     // Create a config with invalid content that should fail to parse.
-    string config_test_txt =
+    string config_set_txt =
         "{ \"command\": \"config-set\", \n"
         "  \"arguments\": { \n"
         "    \"DhcpDdns\": \n"
@@ -1379,7 +1379,7 @@ TEST_F(HttpsCtrlChannelD2Test, configSet) {
 
     // Send the config-set command.
     string response;
-    sendHttpCommand(config_test_txt, response);
+    sendHttpCommand(config_set_txt, response);
 
     // Should fail with a syntax error.
     EXPECT_EQ("[ { \"result\": 1, \"text\": \"missing parameter 'name' (<string>:10:14)\" } ]",
@@ -1392,7 +1392,7 @@ TEST_F(HttpsCtrlChannelD2Test, configSet) {
     EXPECT_EQ(1, keys->size());
 
     // Create a valid config with two keys and no command channel.
-    config_test_txt =
+    config_set_txt =
         "{ \"command\": \"config-set\", \n"
         "  \"arguments\": { \n"
         "    \"DhcpDdns\": \n"
@@ -1417,7 +1417,7 @@ TEST_F(HttpsCtrlChannelD2Test, configSet) {
     EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
 
     // Send the config-set command.
-    sendHttpCommand(config_test_txt, response);
+    sendHttpCommand(config_set_txt, response);
 
     // Verify the HTTP control channel socket no longer exists.
     ASSERT_NO_THROW(HttpCommandMgr::instance().closeCommandSockets());
@@ -1984,4 +1984,374 @@ TEST_F(HttpsCtrlChannelD2Test, connectionTimeoutNoData) {
     testConnectionTimeoutNoData();
 }
 
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpCtrlChannelD2Test, noListenerChange) {
+
+    string d2_cfg_txt =
+        "    { \n"
+        "        \"ip-address\": \"192.168.77.1\", \n"
+        "        \"port\": 777, \n"
+        "        \"forward-ddns\" : {}, \n"
+        "        \"reverse-ddns\" : {}, \n"
+        "        \"tsig-keys\": [ \n"
+        "            {\"name\": \"d2_key.example.com\", \n"
+        "             \"algorithm\": \"hmac-md5\", \n"
+        "             \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
+        "          ], \n"
+        "        \"control-socket\": { \n"
+        "           \"socket-type\": \"http\", \n"
+        "           \"socket-address\": \"127.0.0.1\", \n"
+        "           \"socket-port\": 18125 \n"
+        "        } \n"
+        "    } \n";
+
+    ASSERT_TRUE(server_);
+
+    ConstElementPtr config;
+    ASSERT_NO_THROW(config = parseDHCPDDNS(d2_cfg_txt, true));
+    ASSERT_NO_THROW(d2Controller()->initProcess());
+    D2ProcessPtr proc = d2Controller()->getProcess();
+    ASSERT_TRUE(proc);
+    ConstElementPtr answer = proc->configure(config, false);
+    ASSERT_TRUE(answer);
+    EXPECT_EQ("{ \"arguments\": { \"hash\": \"029AE1208415D6911B5651A6F82D054F55B7877D2589CFD1DCEB5BFFCD3B13A3\" }, \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
+              answer->str());
+    ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+    // Check that the config was indeed applied.
+    D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
+    ASSERT_TRUE(cfg_mgr);
+    D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
+    ASSERT_TRUE(d2_context);
+    TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+    ASSERT_TRUE(keys);
+    EXPECT_EQ(1, keys->size());
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    // Create a config with same content that should not recreate listener.
+    string config_set_txt =
+        "{ \"command\": \"config-set\", \n"
+        "  \"arguments\": { \n"
+        "    \"DhcpDdns\": \n";
+
+    config_set_txt += d2_cfg_txt;
+    config_set_txt += "}} \n";
+
+    // Send the config-set command.
+    string response;
+    sendHttpCommand(config_set_txt, response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    // Verify the configuration was successful.
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"029AE1208415D6911B5651A6F82D054F55B7877D2589CFD1DCEB5BFFCD3B13A3\" }, \"result\": 0, \"text\": \"Configuration applied successfully.\" } ]",
+              response);
+
+    // Check that the config was applied.
+    d2_context = cfg_mgr->getD2CfgContext();
+    keys = d2_context->getKeys();
+    ASSERT_TRUE(keys);
+    EXPECT_EQ(1, keys->size());
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpsCtrlChannelD2Test, noListenerChange) {
+
+    string ca_dir(string(TEST_CA_DIR));
+    ostringstream d2_st;
+    d2_st << "    { \n"
+          << "        \"ip-address\": \"192.168.77.1\", \n"
+          << "        \"port\": 777, \n"
+          << "        \"forward-ddns\" : {}, \n"
+          << "        \"reverse-ddns\" : {}, \n"
+          << "        \"tsig-keys\": [ \n"
+          << "            {\"name\": \"d2_key.example.com\", \n"
+          << "             \"algorithm\": \"hmac-md5\", \n"
+          << "             \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
+          << "          ], \n"
+          << "        \"control-socket\": { \n"
+          << "           \"socket-type\": \"https\", \n"
+          << "           \"socket-address\": \"127.0.0.1\", \n"
+          << "           \"socket-port\": 18125, \n"
+          << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+          << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+          << "        \"key-file\": \"" << ca_dir << "/kea-server.key\" \n"
+          << "        } \n"
+          << "    } \n";
+
+    ASSERT_TRUE(server_);
+
+    ConstElementPtr config;
+    ASSERT_NO_THROW(config = parseDHCPDDNS(d2_st.str(), true));
+    ASSERT_NO_THROW(d2Controller()->initProcess());
+    D2ProcessPtr proc = d2Controller()->getProcess();
+    ASSERT_TRUE(proc);
+    ConstElementPtr answer = proc->configure(config, false);
+    ASSERT_TRUE(answer);
+    // Verify the configuration was successful. The config contains random
+    // file paths (CA directory), 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 applied successfully." is there
+    string answer_txt = answer->str();
+    EXPECT_NE(answer_txt.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(answer_txt.find("\"text\": \"Configuration applied successfully.\""),
+              std::string::npos);
+    ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+    // Check that the config was indeed applied.
+    D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
+    ASSERT_TRUE(cfg_mgr);
+    D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
+    ASSERT_TRUE(d2_context);
+    TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+    ASSERT_TRUE(keys);
+    EXPECT_EQ(1, keys->size());
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
+
+    // Create a config with same content that should not recreate listener.
+    string config_set_txt =
+        "{ \"command\": \"config-set\", \n"
+        "  \"arguments\": { \n"
+        "    \"DhcpDdns\": \n";
+
+    config_set_txt += d2_st.str();
+    config_set_txt += "}} \n";
+
+    // Send the config-set command.
+    string response;
+    sendHttpCommand(config_set_txt, response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    // The TLS settings have been applied
+    EXPECT_NE(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
+
+    // Verify the configuration was successful.
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration applied successfully.\""),
+              std::string::npos);
+
+    // Check that the config was applied.
+    d2_context = cfg_mgr->getD2CfgContext();
+    keys = d2_context->getKeys();
+    ASSERT_TRUE(keys);
+    EXPECT_EQ(1, keys->size());
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpCtrlChannelD2Test, ignoreHttpToHttpsSwitch) {
+
+    string d2_cfg_txt =
+        "    { \n"
+        "        \"ip-address\": \"192.168.77.1\", \n"
+        "        \"port\": 777, \n"
+        "        \"forward-ddns\" : {}, \n"
+        "        \"reverse-ddns\" : {}, \n"
+        "        \"tsig-keys\": [ \n"
+        "            {\"name\": \"d2_key.example.com\", \n"
+        "             \"algorithm\": \"hmac-md5\", \n"
+        "             \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
+        "          ], \n"
+        "        \"control-socket\": { \n"
+        "           \"socket-type\": \"http\", \n"
+        "           \"socket-address\": \"127.0.0.1\", \n"
+        "           \"socket-port\": 18125 \n"
+        "        } \n"
+        "    } \n";
+
+    ASSERT_TRUE(server_);
+
+    ConstElementPtr config;
+    ASSERT_NO_THROW(config = parseDHCPDDNS(d2_cfg_txt, true));
+    ASSERT_NO_THROW(d2Controller()->initProcess());
+    D2ProcessPtr proc = d2Controller()->getProcess();
+    ASSERT_TRUE(proc);
+    ConstElementPtr answer = proc->configure(config, false);
+    ASSERT_TRUE(answer);
+    EXPECT_EQ("{ \"arguments\": { \"hash\": \"029AE1208415D6911B5651A6F82D054F55B7877D2589CFD1DCEB5BFFCD3B13A3\" }, \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
+              answer->str());
+    ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+    // Check that the config was indeed applied.
+    D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
+    ASSERT_TRUE(cfg_mgr);
+    D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
+    ASSERT_TRUE(d2_context);
+    TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+    ASSERT_TRUE(keys);
+    EXPECT_EQ(1, keys->size());
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    string ca_dir(string(TEST_CA_DIR));
+    ostringstream d2_st;
+    d2_st << "    { \n"
+          << "        \"ip-address\": \"192.168.77.1\", \n"
+          << "        \"port\": 777, \n"
+          << "        \"forward-ddns\" : {}, \n"
+          << "        \"reverse-ddns\" : {}, \n"
+          << "        \"tsig-keys\": [ \n"
+          << "            {\"name\": \"d2_key.example.com\", \n"
+          << "             \"algorithm\": \"hmac-md5\", \n"
+          << "             \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
+          << "          ], \n"
+          << "        \"control-socket\": { \n"
+          << "           \"socket-type\": \"https\", \n"
+          << "           \"socket-address\": \"127.0.0.1\", \n"
+          << "           \"socket-port\": 18125, \n"
+          << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+          << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+          << "        \"key-file\": \"" << ca_dir << "/kea-server.key\" \n"
+          << "        } \n"
+          << "    } \n";
+
+    // Create a config with HTTPS and same content that should not recreate listener.
+    string config_set_txt =
+        "{ \"command\": \"config-set\", \n"
+        "  \"arguments\": { \n"
+        "    \"DhcpDdns\": \n";
+
+    config_set_txt += d2_st.str();
+    config_set_txt += "}} \n";
+
+    // Send the config-set command.
+    string response;
+    sendHttpCommand(config_set_txt, response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    // Verify the configuration was successful.
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration applied successfully.\""),
+              std::string::npos);
+
+    // Check that the config was applied.
+    d2_context = cfg_mgr->getD2CfgContext();
+    keys = d2_context->getKeys();
+    ASSERT_TRUE(keys);
+    EXPECT_EQ(1, keys->size());
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpsCtrlChannelD2Test, ignoreHttpsToHttpSwitch) {
+
+    string ca_dir(string(TEST_CA_DIR));
+    ostringstream d2_st;
+    d2_st << "    { \n"
+          << "        \"ip-address\": \"192.168.77.1\", \n"
+          << "        \"port\": 777, \n"
+          << "        \"forward-ddns\" : {}, \n"
+          << "        \"reverse-ddns\" : {}, \n"
+          << "        \"tsig-keys\": [ \n"
+          << "            {\"name\": \"d2_key.example.com\", \n"
+          << "             \"algorithm\": \"hmac-md5\", \n"
+          << "             \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
+          << "          ], \n"
+          << "        \"control-socket\": { \n"
+          << "           \"socket-type\": \"https\", \n"
+          << "           \"socket-address\": \"127.0.0.1\", \n"
+          << "           \"socket-port\": 18125, \n"
+          << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+          << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+          << "        \"key-file\": \"" << ca_dir << "/kea-server.key\" \n"
+          << "        } \n"
+          << "    } \n";
+
+    ASSERT_TRUE(server_);
+
+    ConstElementPtr config;
+    ASSERT_NO_THROW(config = parseDHCPDDNS(d2_st.str(), true));
+    ASSERT_NO_THROW(d2Controller()->initProcess());
+    D2ProcessPtr proc = d2Controller()->getProcess();
+    ASSERT_TRUE(proc);
+    ConstElementPtr answer = proc->configure(config, false);
+    ASSERT_TRUE(answer);
+    // Verify the configuration was successful. The config contains random
+    // file paths (CA directory), 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 applied successfully." is there
+    string answer_txt = answer->str();
+    EXPECT_NE(answer_txt.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(answer_txt.find("\"text\": \"Configuration applied successfully.\""),
+              std::string::npos);
+    ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+    // Check that the config was indeed applied.
+    D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
+    ASSERT_TRUE(cfg_mgr);
+    D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
+    ASSERT_TRUE(d2_context);
+    TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+    ASSERT_TRUE(keys);
+    EXPECT_EQ(1, keys->size());
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
+
+    string d2_cfg_txt =
+        "    { \n"
+        "        \"ip-address\": \"192.168.77.1\", \n"
+        "        \"port\": 777, \n"
+        "        \"forward-ddns\" : {}, \n"
+        "        \"reverse-ddns\" : {}, \n"
+        "        \"tsig-keys\": [ \n"
+        "            {\"name\": \"d2_key.example.com\", \n"
+        "             \"algorithm\": \"hmac-md5\", \n"
+        "             \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
+        "          ], \n"
+        "        \"control-socket\": { \n"
+        "           \"socket-type\": \"http\", \n"
+        "           \"socket-address\": \"127.0.0.1\", \n"
+        "           \"socket-port\": 18125 \n"
+        "        } \n"
+        "    } \n";
+
+    // Create a config with HTTP and same content that should not recreate listener.
+    string config_set_txt =
+        "{ \"command\": \"config-set\", \n"
+        "  \"arguments\": { \n"
+        "    \"DhcpDdns\": \n";
+
+    config_set_txt += d2_cfg_txt;
+    config_set_txt += "}} \n";
+
+    // Send the config-set command.
+    string response;
+    sendHttpCommand(config_set_txt, response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    // The TLS settings have not changed
+    EXPECT_EQ(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
+
+    // Verify the configuration was successful.
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"029AE1208415D6911B5651A6F82D054F55B7877D2589CFD1DCEB5BFFCD3B13A3\" }, \"result\": 0, \"text\": \"Configuration applied successfully.\" } ]",
+              response);
+
+    // Check that the config was applied.
+    d2_context = cfg_mgr->getD2CfgContext();
+    keys = d2_context->getKeys();
+    ASSERT_TRUE(keys);
+    EXPECT_EQ(1, keys->size());
+}
+
 } // end of anonymous namespace
index df0ac5b54a6616f1925f7eb7ee09c01a539e6f46..3f0e252cd64391d05d2d1c6944b516297cb84a4b 100644 (file)
@@ -338,15 +338,10 @@ void configureCommandChannel() {
     ConstElementPtr current_http_config =
         CfgMgr::instance().getCurrentCfg()->getHttpControlSocketInfo();
 
-    sock_changed = (http_config && current_http_config &&
-                    !http_config->equals(*current_http_config));
-
-    if (!http_config || !current_http_config || sock_changed) {
-        if (http_config) {
-            HttpCommandMgr::instance().openCommandSockets(http_config);
-        } else if (current_http_config) {
-            HttpCommandMgr::instance().closeCommandSockets();
-        }
+    if (http_config) {
+        HttpCommandMgr::instance().openCommandSockets(http_config);
+    } else if (current_http_config) {
+        HttpCommandMgr::instance().closeCommandSockets();
     }
 }
 
index 4b6d690221b93fdcf4df158295819751967a9153..1a43c8205eb8b1d12dc9950ab6f37503d3f1a7df 100644 (file)
@@ -983,7 +983,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp4_cfg_txt =
         "    \"Dhcp4\": { \n"
@@ -1056,7 +1056,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
     std::ostringstream os;
 
     // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp4_cfg_txt
        << subnet1
@@ -1085,7 +1085,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
 
     // Create a config with malformed subnet that should fail to parse.
     os.str("");
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp4_cfg_txt
        << bad_subnet
@@ -1114,7 +1114,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
     // Create a valid config with two subnets and no command channel.
     // It should succeed, client should still receive the response
     os.str("");
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp4_cfg_txt
        << subnet1
@@ -1153,7 +1153,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
     string ca_dir(string(TEST_CA_DIR));
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp4_cfg_txt =
         "    \"Dhcp4\": { \n"
@@ -1227,7 +1227,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
     std::ostringstream os;
 
     // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp4_cfg_txt
        << subnet1
@@ -1266,7 +1266,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
 
     // Create a config with malformed subnet that should fail to parse.
     os.str("");
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp4_cfg_txt
        << bad_subnet
@@ -1299,7 +1299,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
     // Create a valid config with two subnets and no command channel.
     // It should succeed, client should still receive the response
     os.str("");
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp4_cfg_txt
        << subnet1
@@ -1411,7 +1411,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configTest) {
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
     string config_test_txt = "{ \"command\": \"config-test\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp4_cfg_txt =
@@ -1463,7 +1463,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configTest) {
     std::ostringstream os;
 
     // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp4_cfg_txt
        << subnet1
@@ -1552,7 +1552,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configTest) {
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
     string ca_dir(string(TEST_CA_DIR));
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
     string config_test_txt = "{ \"command\": \"config-test\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp4_cfg_txt =
@@ -1605,7 +1605,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configTest) {
     std::ostringstream os;
 
     // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp4_cfg_txt
        << subnet1
@@ -3392,4 +3392,567 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, connectionTimeoutNoData) {
     testConnectionTimeoutNoData();
 }
 
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpCtrlChannelDhcpv4Test, noListenerChange) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp4_cfg_txt =
+        "    \"Dhcp4\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet4\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket =
+        "    ,\"control-socket\": { \n"
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"127.0.0.1\", \n"
+        "       \"socket-port\": 18124 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp4_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket
+       << logger_txt
+       << "}\n"                      // close dhcp4
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"F6137301FF10D81585E041FD5FD8E91347ACADDE64F92ED03432FB100874DE02\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was indeed applied.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    // Send the config-set command.
+    sendHttpCommand(os.str(), response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"F6137301FF10D81585E041FD5FD8E91347ACADDE64F92ED03432FB100874DE02\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpsCtrlChannelDhcpv4Test, noListenerChange) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string ca_dir(string(TEST_CA_DIR));
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp4_cfg_txt =
+        "    \"Dhcp4\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet4\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "    ,\"control-socket\": { \n";
+    string control_socket_footer =
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"127.0.0.1\", \n"
+        "       \"socket-port\": 18124 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp4_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket_header
+       << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+       << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+       << "        \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+       << control_socket_footer
+       << logger_txt
+       << "}\n"                      // close dhcp4
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    // Verify the configuration was successful. The config contains random
+    // file paths (CA directory), 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(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
+
+    // Send the config-set command.
+    sendHttpCommand(os.str(), response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    // The TLS settings have been applied
+    EXPECT_NE(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
+
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
+              std::string::npos);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpCtrlChannelDhcpv4Test, ignoredHttpToHttpsSwitch) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string ca_dir(string(TEST_CA_DIR));
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp4_cfg_txt =
+        "    \"Dhcp4\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet4\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "    ,\"control-socket\": { \n";
+    string control_socket_footer =
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"127.0.0.1\", \n"
+        "       \"socket-port\": 18124 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp4_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket_header
+       << control_socket_footer
+       << logger_txt
+       << "}\n"                      // close dhcp4
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"F6137301FF10D81585E041FD5FD8E91347ACADDE64F92ED03432FB100874DE02\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was indeed applied.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    std::ostringstream second_config_os;
+
+    // Create a valid config with all the parts should parse
+    second_config_os << config_set_txt << ","
+        << args_txt
+        << dhcp4_cfg_txt
+        << subnet1
+        << subnet_footer
+        << option_def
+        << option_data
+        << control_socket_header
+        << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+        << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+        << "        \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+        << control_socket_footer
+        << logger_txt
+        << "}\n"                      // close dhcp4
+        << "}}";
+
+    // Send the config-set command.
+    sendHttpCommand(second_config_os.str(), response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
+              std::string::npos);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpsCtrlChannelDhcpv4Test, ignoreHttpsToHttpSwitch) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string ca_dir(string(TEST_CA_DIR));
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp4_cfg_txt =
+        "    \"Dhcp4\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet4\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "    ,\"control-socket\": { \n";
+    string control_socket_footer =
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"127.0.0.1\", \n"
+        "       \"socket-port\": 18124 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp4_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket_header
+       << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+       << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+       << "        \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+       << control_socket_footer
+       << logger_txt
+       << "}\n"                      // close dhcp4
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    // Verify the configuration was successful. The config contains random
+    // file paths (CA directory), 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(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    // The TLS settings have not changed
+    auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
+
+    std::ostringstream second_config_os;
+
+    // Create a valid config with all the parts should parse
+    second_config_os << config_set_txt << ","
+        << args_txt
+        << dhcp4_cfg_txt
+        << subnet1
+        << subnet_footer
+        << option_def
+        << option_data
+        << control_socket_header
+        << control_socket_footer
+        << logger_txt
+        << "}\n"                      // close dhcp4
+        << "}}";
+
+    // Send the config-set command.
+    sendHttpCommand(second_config_os.str(), response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    EXPECT_EQ(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
+
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
+              std::string::npos);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
 } // End of anonymous namespace
index cecf3536e4fdfa9b4455de2eaffd956c2589a700..b65faec7092f1e413e4916bbb966a7b02bca7b6d 100644 (file)
@@ -440,15 +440,10 @@ void configureCommandChannel() {
     ConstElementPtr current_http_config =
         CfgMgr::instance().getCurrentCfg()->getHttpControlSocketInfo();
 
-    sock_changed = (http_config && current_http_config &&
-                    !http_config->equals(*current_http_config));
-
-    if (!http_config || !current_http_config || sock_changed) {
-        if (http_config) {
-            HttpCommandMgr::instance().openCommandSockets(http_config);
-        } else if (current_http_config) {
-            HttpCommandMgr::instance().closeCommandSockets();
-        }
+    if (http_config) {
+        HttpCommandMgr::instance().openCommandSockets(http_config);
+    } else if (current_http_config) {
+        HttpCommandMgr::instance().closeCommandSockets();
     }
 }
 
index 7a64400268dfabe0be061e16047b9bbe764fc8da..b2cc74ef26433eb6b64fa3160a0c9a11d5ae13e1 100644 (file)
@@ -1013,7 +1013,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp6_cfg_txt =
         "    \"Dhcp6\": { \n"
@@ -1087,7 +1087,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
     std::ostringstream os;
 
     // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp6_cfg_txt
        << subnet1
@@ -1116,7 +1116,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
 
     // Create a config with malformed subnet that should fail to parse.
     os.str("");
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp6_cfg_txt
        << bad_subnet
@@ -1145,7 +1145,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
     // Create a valid config with two subnets and no command channel.
     // It should succeed, client should still receive the response
     os.str("");
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp6_cfg_txt
        << subnet1
@@ -1184,7 +1184,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
     string ca_dir(string(TEST_CA_DIR));
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp6_cfg_txt =
         "    \"Dhcp6\": { \n"
@@ -1259,7 +1259,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
     std::ostringstream os;
 
     // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp6_cfg_txt
        << subnet1
@@ -1298,7 +1298,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
 
     // Create a config with malformed subnet that should fail to parse.
     os.str("");
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp6_cfg_txt
        << bad_subnet
@@ -1331,7 +1331,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
     // Create a valid config with two subnets and no command channel.
     // It should succeed, client should still receive the response
     os.str("");
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp6_cfg_txt
        << subnet1
@@ -1443,7 +1443,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
     string config_test_txt = "{ \"command\": \"config-test\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp6_cfg_txt =
@@ -1496,7 +1496,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
     std::ostringstream os;
 
     // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp6_cfg_txt
        << subnet1
@@ -1585,7 +1585,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configTest) {
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
     string ca_dir(string(TEST_CA_DIR));
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
     string config_test_txt = "{ \"command\": \"config-test\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp6_cfg_txt =
@@ -1639,7 +1639,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configTest) {
     std::ostringstream os;
 
     // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
+    os << config_set_txt << ","
        << args_txt
        << dhcp6_cfg_txt
        << subnet1
@@ -3401,4 +3401,571 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
     testConnectionTimeoutNoData();
 }
 
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpCtrlChannelDhcpv6Test, noListenerChange) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp6_cfg_txt =
+        "    \"Dhcp6\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"preferred-lifetime\": 3000, \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet6\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket =
+        "    ,\"control-socket\": { \n"
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"::1\", \n"
+        "       \"socket-port\": 18126 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket
+       << logger_txt
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was indeed applied.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    // Send the config-set command.
+    sendHttpCommand(os.str(), response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpsCtrlChannelDhcpv6Test, noListenerChange) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string ca_dir(string(TEST_CA_DIR));
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp6_cfg_txt =
+        "    \"Dhcp6\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"preferred-lifetime\": 3000, \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet6\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "    ,\"control-socket\": { \n";
+    string control_socket_footer =
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"::1\", \n"
+        "       \"socket-port\": 18126 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket_header
+       << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+       << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+       << "        \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+       << control_socket_footer
+       << logger_txt
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    // Verify the configuration was successful. The config contains random
+    // file paths (CA directory), 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(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
+
+    // Send the config-set command.
+    sendHttpCommand(os.str(), response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    // The TLS settings have been applied
+    EXPECT_NE(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
+
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
+              std::string::npos);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpCtrlChannelDhcpv6Test, ignoreHttpToHttpsSwitch) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string ca_dir(string(TEST_CA_DIR));
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp6_cfg_txt =
+        "    \"Dhcp6\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"preferred-lifetime\": 3000, \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet6\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "    ,\"control-socket\": { \n";
+    string control_socket_footer =
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"::1\", \n"
+        "       \"socket-port\": 18126 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket_header
+       << control_socket_footer
+       << logger_txt
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was indeed applied.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    std::ostringstream second_config_os;
+
+    // Create a valid config with all the parts should parse
+    second_config_os << config_set_txt << ","
+        << args_txt
+        << dhcp6_cfg_txt
+        << subnet1
+        << subnet_footer
+        << option_def
+        << option_data
+        << control_socket_header
+        << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+        << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+        << "        \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+        << control_socket_footer
+        << logger_txt
+        << "}\n"                      // close dhcp6
+        << "}}";
+
+    // Send the config-set command.
+    sendHttpCommand(second_config_os.str(), response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
+              std::string::npos);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
+// Verify that the "config-set" command will reuse listener
+TEST_F(HttpsCtrlChannelDhcpv6Test, ignoreHttpsToHttpSwitch) {
+    createHttpChannelServer();
+
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string ca_dir(string(TEST_CA_DIR));
+    string config_set_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp6_cfg_txt =
+        "    \"Dhcp6\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"preferred-lifetime\": 3000, \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "        \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet6\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+        "                \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "    ,\"control-socket\": { \n";
+    string control_socket_footer =
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"::1\", \n"
+        "       \"socket-port\": 18126 \n"
+        "    } \n";
+    string logger_txt =
+        "       ,\"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output-options\": [{ \n"
+        "                \"output\": \"/dev/null\", \n"
+        "                \"maxsize\": 0"
+        "            }] \n"
+        "        }] \n";
+
+    std::ostringstream os;
+
+    // Create a valid config with all the parts should parse
+    os << config_set_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << subnet_footer
+       << option_def
+       << option_data
+       << control_socket_header
+       << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
+       << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
+       << "        \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
+       << control_socket_footer
+       << logger_txt
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+    // Verify the configuration was successful. The config contains random
+    // file paths (CA directory), 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(1, subnets->size());
+
+    OptionDefinitionPtr def =
+        LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // Verify the HTTP control channel socket exists.
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    auto const listener = HttpCommandMgr::instance().getHttpListener().get();
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    // The TLS settings have not changed
+    auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
+
+    std::ostringstream second_config_os;
+
+    // Create a valid config with all the parts should parse
+    second_config_os << config_set_txt << ","
+        << args_txt
+        << dhcp6_cfg_txt
+        << subnet1
+        << subnet_footer
+        << option_def
+        << option_data
+        << control_socket_header
+        << control_socket_footer
+        << logger_txt
+        << "}\n"                      // close dhcp6
+        << "}}";
+
+    // Send the config-set command.
+    sendHttpCommand(second_config_os.str(), response);
+
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
+    ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
+    EXPECT_EQ(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
+
+    EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+    EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
+              std::string::npos);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
 } // End of anonymous namespace
index b63a98c3b80ef7877946633a0a6251534b84f98c..0e9c07473f8cf38c22fb6d553b35b9e039bc35f9 100644 (file)
@@ -34,7 +34,8 @@ extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL = "COMMAND_SOCKET_WRI
 extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR = "COMMAND_WATCH_SOCKET_CLEAR_ERROR";
 extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR = "COMMAND_WATCH_SOCKET_CLOSE_ERROR";
 extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR = "COMMAND_WATCH_SOCKET_MARK_READY_ERROR";
-extern const isc::log::MessageID HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES = "HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES";
+extern const isc::log::MessageID HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED = "HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED";
+extern const isc::log::MessageID HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED = "HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED";
 extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STARTED = "HTTP_COMMAND_MGR_SERVICE_STARTED";
 extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING = "HTTP_COMMAND_MGR_SERVICE_STOPPING";
 extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING_ALL = "HTTP_COMMAND_MGR_SERVICE_STOPPING_ALL";
@@ -73,7 +74,8 @@ const char* values[] = {
     "COMMAND_WATCH_SOCKET_CLEAR_ERROR", "watch socket failed to clear: %1",
     "COMMAND_WATCH_SOCKET_CLOSE_ERROR", "watch socket failed to close: %1",
     "COMMAND_WATCH_SOCKET_MARK_READY_ERROR", "watch socket failed to mark ready: %1",
-    "HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES", "ignore a change in TLS setup of the http control socket",
+    "HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED", "reused HTTPS service bound to address %1:%2",
+    "HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED", "reused HTTP service bound to address %1:%2",
     "HTTP_COMMAND_MGR_SERVICE_STARTED", "started %1 service bound to address %2 port %3",
     "HTTP_COMMAND_MGR_SERVICE_STOPPING", "Server is stopping %1 service %2",
     "HTTP_COMMAND_MGR_SERVICE_STOPPING_ALL", "stopping %1 service %2",
index 6ab6b17c3bc41e16b09b31b37fd53cbf8d41b16e..fd55744e49dc1d650e58c730164c8dded2bc3b32 100644 (file)
@@ -35,7 +35,8 @@ extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL;
 extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR;
 extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR;
 extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR;
-extern const isc::log::MessageID HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES;
+extern const isc::log::MessageID HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED;
+extern const isc::log::MessageID HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED;
 extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STARTED;
 extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING;
 extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING_ALL;
index 2c268cd7e936f1e841d745ecc682c14cfc3c7ace..7c4fd50dc3e4f1a20211c2ef08ee8d1c95c28099 100644 (file)
@@ -153,11 +153,14 @@ ready status after scheduling asynchronous send. This is programmatic error
 that should be reported. The command manager may or may not continue
 to operate correctly.
 
-% HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES ignore a change in TLS setup of the http control socket
-The warning message is issued when the HTTP/HTTPS control socket was
-reconfigured with a different TLS setup but keeping the address and port.
-These changes are ignored because they can't be applied without opening a new
-socket which will conflict with the existing one.
+% HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED reused HTTPS service bound to address %1:%2
+This informational message indicates that the server has reused existing
+HTTPS service on the specified address and port. Note that any change in
+the TLS setup was ignored.
+
+% HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED reused HTTP service bound to address %1:%2
+This informational message indicates that the server has reused existing
+HTTP service on the specified address and port.
 
 % HTTP_COMMAND_MGR_SERVICE_STARTED started %1 service bound to address %2 port %3
 This informational message indicates that the server has started
index 9fcc44d11fe232644069e7b81459b5aaadf7e196..0725d7821e498d3f34d33ef87453646b0ff0b74f 100644 (file)
@@ -127,15 +127,37 @@ HttpCommandMgrImpl::openCommandSocket(const isc::data::ConstElementPtr config) {
     // Search for the specific connection and reuse the existing one if found.
     auto it = sockets_.find(std::make_pair(server_address, server_port));
     if (it != sockets_.end()) {
-        if ((cmd_config->getTrustAnchor() != it->second->config_->getTrustAnchor()) ||
-            (cmd_config->getCertFile() != it->second->config_->getCertFile()) ||
-            (cmd_config->getKeyFile() != it->second->config_->getKeyFile()) ||
-            (cmd_config->getCertRequired() != it->second->config_->getCertRequired())) {
-            LOG_WARN(command_logger, HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES);
-            // Overwrite the authentication setup and the emulation flag
-            // in the response creator config.
-            it->second->config_->setAuthConfig(cmd_config->getAuthConfig());
-            it->second->config_->setEmulateAgentResponse(cmd_config->getEmulateAgentResponse());
+        auto listener = it->second->listener_;
+        if (listener) {
+            // Reconfig keeping the same address and port.
+            if (listener->getTlsContext()) {
+                if (cmd_config->getTrustAnchor().empty()) {
+                    // Can not switch from HTTPS to HTTP
+                    LOG_INFO(command_logger, HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED)
+                        .arg(server_address.toText())
+                        .arg(server_port);
+                } else {
+                    // Apply TLS settings each time.
+                    TlsContextPtr tls_context;
+                    TlsContext::configure(tls_context,
+                                          TlsRole::SERVER,
+                                          cmd_config->getTrustAnchor(),
+                                          cmd_config->getCertFile(),
+                                          cmd_config->getKeyFile(),
+                                          cmd_config->getCertRequired());
+                    // Overwrite the authentication setup, the http headers and the emulation flag
+                    // in the response creator config.
+                    it->second->config_->setAuthConfig(cmd_config->getAuthConfig());
+                    it->second->config_->setHttpHeaders(cmd_config->getHttpHeaders());
+                    it->second->config_->setEmulateAgentResponse(cmd_config->getEmulateAgentResponse());
+                    io_service_->post([listener, tls_context]() { listener->setTlsContext(tls_context); });
+                }
+            } else if (!cmd_config->getTrustAnchor().empty()) {
+                // Can not switch from HTTP to HTTPS
+                LOG_INFO(command_logger, HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED)
+                    .arg(server_address.toText())
+                    .arg(server_port);
+            }
         }
         // If the connection can be reused, mark it as usable.
         it->second->usable_ = true;
index 9c05e996898b1ec0e7b88bdbb529681d4364545e..3b137c37c53f2b33c4e7c80e7878a1e9eaeba420 100644 (file)
@@ -47,6 +47,11 @@ HttpListener::getTlsContext() const {
     return (impl_->getTlsContext());
 }
 
+void
+HttpListener::setTlsContext(const TlsContextPtr& context) {
+    impl_->setTlsContext(context);
+}
+
 int
 HttpListener::getNative() const {
     return (impl_->getNative());
index 9f35e626426077f8ffccf5663de83e4bfac32827..0180eb0d9e6977f7ca519a5d7e8fbffe126a247f 100644 (file)
@@ -118,6 +118,9 @@ public:
     /// @brief Returns reference to the current TLS context.
     const asiolink::TlsContextPtr& getTlsContext() const;
 
+    /// @brief Sets reference of the current TLS context.
+    void setTlsContext(const asiolink::TlsContextPtr& context);
+
     /// @brief file descriptor of the underlying acceptor socket.
     int getNative() const;
 
index 85fd74ee3dcfe825a1e654db11156e621f82cd66..58754cb34993ab3ac170e7795c7cb4bd252941cf 100644 (file)
@@ -75,6 +75,11 @@ HttpListenerImpl::getTlsContext() const {
     return (tls_context_);
 }
 
+void
+HttpListenerImpl::setTlsContext(const TlsContextPtr& context) {
+    tls_context_ = context;
+}
+
 int
 HttpListenerImpl::getNative() const {
     return (acceptor_ ? acceptor_->getNative() : -1);
index f36a1330b81b791e05b928faec8bdc3105c03617..6ae863b109aa7edc4171fda2585af00440fa55e6 100644 (file)
@@ -63,6 +63,9 @@ public:
     /// @brief Returns reference to the current TLS context.
     const asiolink::TlsContextPtr& getTlsContext() const;
 
+    /// @brief Sets reference of the current TLS context.
+    void setTlsContext(const asiolink::TlsContextPtr& context);
+
     /// @brief file descriptor of the underlying acceptor socket.
     int getNative() const;