]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3477] Added DHCPv6 TLS UTs
authorFrancis Dupont <fdupont@isc.org>
Fri, 9 Aug 2024 13:36:15 +0000 (15:36 +0200)
committerFrancis Dupont <fdupont@isc.org>
Fri, 20 Sep 2024 11:46:27 +0000 (13:46 +0200)
src/bin/dhcp6/tests/Makefile.am
src/bin/dhcp6/tests/http_control_socket_unittest.cc

index 067539c8fbc8e380a1039b7907ebf68880ca9d30..54632a2c010e654e5f476cb38707a73329bcccbd 100644 (file)
@@ -25,6 +25,7 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/kea6\"
 AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../dhcp6_parser.yy\"
 AM_CPPFLAGS += -DKEA_LFC_EXECUTABLE=\"$(abs_top_builddir)/src/bin/lfc/kea-lfc\"
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(srcdir)/../../lib/asiolink/testutils/ca\"
 
 AM_CXXFLAGS = $(KEA_CXXFLAGS)
 
@@ -160,6 +161,7 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/database/testutils/libdatabaset
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
index fbb20ab581e1c0502920d027b7464c3aa3dddfdf..8d35675153d7bbcd0cb967250495699627ad3f54 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <asiolink/interval_timer.h>
 #include <asiolink/io_address.h>
+#include <asiolink/testutils/test_tls.h>
 #include <cc/command_interpreter.h>
 #include <config/command_mgr.h>
 #include <config/http_command_mgr.h>
@@ -49,6 +50,7 @@
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
+using namespace isc::asiolink::test;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::dhcp;
@@ -119,7 +121,8 @@ public:
 
 };
 
-class HttpCtrlChannelDhcpv6Test : public HttpCtrlDhcpv6Test {
+/// @brief Base fixture class intended for testing HTTP/HTTPS control channel.
+class BaseCtrlChannelDhcpv6Test : public HttpCtrlDhcpv6Test {
 public:
     /// @brief List of interfaces (defaults to "*").
     std::string interfaces_;
@@ -130,7 +133,7 @@ public:
     /// @brief Default constructor
     ///
     /// Sets socket path to its default value.
-    HttpCtrlChannelDhcpv6Test() : interfaces_("\"*\"") {
+    BaseCtrlChannelDhcpv6Test() : interfaces_("\"*\"") {
         reset();
         IfaceMgr::instance().setTestMode(false);
         IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
@@ -138,7 +141,7 @@ public:
     }
 
     /// @brief Destructor
-    ~HttpCtrlChannelDhcpv6Test() {
+    virtual ~BaseCtrlChannelDhcpv6Test() {
         LeaseMgrFactory::destroy();
         StatsMgr::instance().removeAll();
 
@@ -183,7 +186,7 @@ public:
         IOServicePtr io_service = getIOService();
         ASSERT_TRUE(io_service);
         IntervalTimer test_timer(io_service);
-        test_timer.setup(std::bind(&HttpCtrlChannelDhcpv6Test::timeoutHandler,
+        test_timer.setup(std::bind(&BaseCtrlChannelDhcpv6Test::timeoutHandler,
                                    this, true),
                          TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
         // Run until the client stops the service or an error occurs.
@@ -196,73 +199,9 @@ public:
     }
 
     /// @brief Create a server with a HTTP command channel.
-    void createHttpChannelServer() {
-        // Just a simple config. The important part here is the socket
-        // location information.
-        std::string header =
-            "{"
-            "    \"interfaces-config\": {"
-            "        \"interfaces\": [";
-
-        std::string body = "]"
-            "    },"
-            "    \"expired-leases-processing\": {"
-            "         \"reclaim-timer-wait-time\": 60,"
-            "         \"hold-reclaimed-time\": 500,"
-            "         \"flush-reclaimed-timer-wait-time\": 60"
-            "    },"
-            "    \"rebind-timer\": 2000, "
-            "    \"renew-timer\": 1000, "
-            "    \"subnet6\": [ ],"
-            "    \"valid-lifetime\": 4000,"
-            "    \"control-socket\": {"
-            "        \"socket-type\": \"http\","
-            "        \"socket-address\": \"::1\","
-            "        \"socket-port\": 18126"
-            "    },"
-            "    \"lease-database\": {"
-            "       \"type\": \"memfile\", \"persist\": false },"
-            "    \"loggers\": [ {"
-            "       \"name\": \"kea-dhcp6\","
-            "       \"severity\": \"INFO\","
-            "       \"debuglevel\": 0"
-            "       } ]"
-            "}";
-
-        std::string config_txt = header + interfaces_ + body;
-        ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
-
-        ConstElementPtr config;
-        ASSERT_NO_THROW(config = parseDHCP6(config_txt));
-
-        // Parse the logger configuration explicitly into the staging config.
-        // Note this does not alter the current loggers, they remain in
-        // effect until we apply the logging config below.  If no logging
-        // is supplied logging will revert to default logging.
-        server_->configureLogger(config, CfgMgr::instance().getStagingCfg());
-
-        // Let's apply the new logging. We do it early, so we'll be able to print
-        // out what exactly is wrong with the new config in case of problems.
-        CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
-
-        ConstElementPtr answer = server_->processConfig(config);
-
-        // Commit the configuration so any subsequent reconfigurations
-        // will only close the command channel if its configuration has
-        // changed.
-        CfgMgr::instance().commit();
-
-        ASSERT_TRUE(answer);
-
-        int status = 0;
-        ConstElementPtr txt = isc::config::parseAnswer(status, answer);
-        // This should succeed. If not, print the error message.
-        ASSERT_EQ(0, status) << txt->str();
-
-        // Now check that the socket was indeed open.
-        ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
-    }
+    virtual void createHttpChannelServer() = 0;
 
+    /// @brief Create a server with a HTTP command channel.
     /// @brief Reset
     void reset() override {
         HttpCtrlDhcpv6Test::reset();
@@ -315,32 +254,8 @@ public:
     /// @param command the command text to execute in JSON form.
     /// @param response variable into which the received response should be
     /// placed.
-    void sendHttpCommand(const std::string& command, std::string& response) {
-        response = "";
-        IOServicePtr io_service = getIOService();
-        ASSERT_TRUE(io_service);
-        boost::scoped_ptr<TestHttpClient> client;
-        client.reset(new TestHttpClient(io_service, SERVER_ADDRESS,
-                                        SERVER_PORT));
-        ASSERT_TRUE(client);
-
-        // Send the command. This will trigger server's handler which receives
-        // data over the HTTP socket. The server will start sending response
-        // to the client.
-        ASSERT_NO_THROW(client->startRequest(buildPostStr(command)));
-        runIOService();
-        ASSERT_TRUE(client->receiveDone());
-
-        // Read the response generated by the server.
-        HttpResponsePtr hr;
-        ASSERT_NO_THROW(hr = parseResponse(client->getResponse()));
-        response = hr->getBody();
-
-        // Now close client.
-        client->close();
-
-        ASSERT_NO_THROW(io_service->poll());
-    }
+    virtual void sendHttpCommand(const std::string& command,
+                                 std::string& response) = 0;
 
     /// @brief Parse list answer.
     ///
@@ -532,267 +447,968 @@ public:
         }
         return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
     }
-};
 
-// Tests that the server properly responds to invalid commands sent
-// via ControlChannel
-TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelNegative) {
-    createHttpChannelServer();
-    std::string response;
+    // Tests that the server properly responds to invalid commands sent
+    // via ControlChannel.
+    void testControlChannelNegative();
 
-    sendHttpCommand("{ \"command\": \"bogus\" }", response);
-    EXPECT_EQ("[ { \"result\": 2,"
-              " \"text\": \"'bogus' command not supported.\" } ]",
-              response);
+    // Tests that the server properly responds to shutdown command sent
+    // via ControlChannel.
+    void testControlChannelShutdown();
 
-    sendHttpCommand("utter nonsense", response);
-    EXPECT_EQ("{ \"result\": 400, \"text\": \"Bad Request\" }", response);
-}
+    // Tests that the server properly responds to statistics commands.
+    void testControlChannelStats();
 
-// Tests that the server properly responds to shutdown command sent
-// via ControlChannel
-TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelShutdown) {
-    createHttpChannelServer();
-    std::string response;
+    // Tests if the server returns its configuration using config-get.
+    void testConfigGet();
 
-    sendHttpCommand("{ \"command\": \"shutdown\" }", response);
-    EXPECT_EQ("[ { \"result\": 0, \"text\": \"Shutting down.\" } ]", response);
+    // Tests if the server returns the hash of its configuration using
+    // config-hash-get.
+    void testConfigHashGet();
 
-    EXPECT_EQ(EXIT_SUCCESS, server_->getExitValue());
-}
+    // This test verifies that the DHCP server handles version-get commands.
+    void testGetVersion();
 
-// Check that the "config-set" command will replace current configuration
-TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
-    createHttpChannelServer();
+    // This test verifies that the DHCP server handles server-tag-get command.
+    void testServerTagGet();
 
-    // 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 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 subnet2 =
-        "               {\"subnet\": \"3003::/64\", \"id\": 2, \n"
-        "                \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
-    string bad_subnet =
-        "               {\"comment\": \"3005::/64\", \"id\": 10, \n"
-        "                \"pools\": [{ \"pool\": \"3005::100-3005::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";
+    // This test verifies that the DHCP server handles status-get commands.
+    void testStatusGet();
 
-    std::ostringstream os;
+    // Check that status is returned even if LeaseMgr and HostMgr are
+    // not created.
+    void testNoManagers();
 
-    // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
-        << args_txt
-        << dhcp6_cfg_txt
-        << subnet1
-        << subnet_footer
-        << option_def
-        << option_data
-        << control_socket
-        << logger_txt
-        << "}\n"                      // close dhcp6
-        << "}}";
+    // Checks that socket status exists in status-get responses.
+    void testStatusGetSockets();
 
-    // Send the config-set command
-    std::string response;
-    sendHttpCommand(os.str(), response);
-    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
-              response);
+    // Checks that socket status includes errors in status-get responses.
+    void testStatusGetSocketsErrors();
 
-    // Check that the config was indeed applied.
-    const Subnet6Collection* subnets =
-        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
-    EXPECT_EQ(1, subnets->size());
+    // This test verifies that the DHCP server handles
+    // config-backend-pull command.
+    void testConfigBackendPull();
 
-    OptionDefinitionPtr def =
-        LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
-    ASSERT_TRUE(def);
+    // This test verifies that the DHCP server immediately reclaims expired
+    // leases on leases-reclaim command.
+    void testControlLeasesReclaim();
 
-    // Create a config with malformed subnet that should fail to parse.
-    os.str("");
-    os << set_config_txt << ","
-        << args_txt
-        << dhcp6_cfg_txt
-        << bad_subnet
-        << subnet_footer
-        << control_socket
-        << "}\n"                      // close dhcp6
-        << "}}";
+    // This test verifies that the DHCP server immediately reclaims expired
+    // leases on leases-reclaim command with remove = true.
+    void testControlLeasesReclaimRemove();
 
-    // Send the config-set command
-    sendHttpCommand(os.str(), response);
+    // Tests that the server properly responds to list-commands command sent
+    // via ControlChannel.
+    void testListCommands();
 
-    // Should fail with a syntax error
-    EXPECT_EQ("[ { \"result\": 1, "
-              "\"text\": \"subnet configuration failed: mandatory 'subnet' "
-              "parameter is missing for a subnet being configured "
-              "(<string>:21:17)\" } ]",
-              response);
+    // Tests if config-write can be called without any parameters.
+    void testConfigWriteNoFilename();
 
-    // Check that the config was not lost
-    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
-    EXPECT_EQ(1, subnets->size());
+    // Tests if config-write can be called with a valid filename as parameter.
+    void testConfigWriteFilename();
 
-    def = LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
-    ASSERT_TRUE(def);
+    // Tests if config-reload attempts to reload a file and reports that the
+    // file is missing.
+    void testConfigReloadMissingFile();
 
-    // 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 << ","
-        << args_txt
-        << dhcp6_cfg_txt
-        << subnet1
-        << ",\n"
-        << subnet2
-        << subnet_footer
-        << "}\n"                      // close dhcp6
-        << "}}";
+    // Tests if config-reload attempts to reload a file and reports that the
+    // file is not a valid JSON.
+    void testConfigReloadBrokenFile();
 
-    // Verify the HTTP control channel socket exists.
-    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    // Tests if config-reload attempts to reload a file and reports that the
+    // file is loaded correctly.
+    void testConfigReloadValid();
 
-    // Send the config-set command.
-    sendHttpCommand(os.str(), response);
+    // Tests if config-reload attempts to reload a file and reports that the
+    // file is loaded correctly.
+    void testConfigReloadDetectInterfaces();
 
-    // Verify the HTTP control channel socket no longer exists.
-    ASSERT_NO_THROW(HttpCommandMgr::instance().garbageCollectListeners());
-    EXPECT_FALSE(HttpCommandMgr::instance().getHttpListener());
+    // This test verifies that disable DHCP service command performs
+    // sanity check on parameters.
+    void testDhcpDisableBadParam();
 
-    // With no command channel, should still receive the response.
-    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"48035E8F9CC25FC1F6175B78CCC6B8A673CACBA9E956C0ED3079C478BF1F2D1A\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
-              response);
+    // This test verifies if it is possible to disable DHCP service
+    // via command.
+    void testDhcpDisable();
 
-    // Check that the config was not lost
-    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
-    EXPECT_EQ(2, subnets->size());
+    // This test verifies if it is possible to disable DHCP service using
+    // the origin-id.
+    void testDhcpDisableOriginId();
 
-    // Clean up after the test.
-    CfgMgr::instance().clear();
-}
+    // This test verifies that it is possible to disable DHCP service
+    // for a short period of time, after which the service is
+    // automatically enabled.
+    void testDhcpDisableTemporarily();
 
-// Tests if the server returns its configuration using config-get.
-// Note there are separate tests that verify if toElement() called by the
-// config-get handler are actually converting the configuration correctly.
-TEST_F(HttpCtrlChannelDhcpv6Test, configGet) {
-    createHttpChannelServer();
-    std::string response;
+    // This test verifies that enable DHCP service command performs
+    // sanity check on parameters.
+    void testDhcpEnableBadParam();
 
-    sendHttpCommand("{ \"command\": \"config-get\" }", response);
-    ConstElementPtr rsp;
+    // This test verifies if it is possible to enable DHCP service via command.
+    void testDhcpEnable();
 
-    // The response should be a valid JSON.
-    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
-    ASSERT_TRUE(rsp);
+    // This test verifies if it is possible to enable DHCP service using
+    // the origin-id.
+    void testDhcpEnableOriginId();
 
-    int status;
-    ConstElementPtr cfg = parseListAnswer(status, rsp);
-    EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+    // This test verifies that the server can receive and process a
+    // large command.
+    void testLongCommand();
 
-    // Ok, now roughly check if the response seems legit.
-    ASSERT_TRUE(cfg);
-    ASSERT_EQ(Element::map, cfg->getType());
-    EXPECT_TRUE(cfg->get("Dhcp6"));
-    EXPECT_TRUE(cfg->get("Dhcp6")->get("loggers"));
-}
+    // This test verifies that the server can send long response to the client.
+    void testLongResponse();
+
+    // This test verifies that the server signals timeout if the transmission
+    // takes too long, having received no data from the client.
+    void testConnectionTimeoutNoData();
+};
+
+/// @brief Fixture class intended for testing HTTP control channel.
+class HttpCtrlChannelDhcpv6Test : public BaseCtrlChannelDhcpv6Test {
+public:
+
+    virtual void createHttpChannelServer() override {
+        // Just a simple config. The important part here is the socket
+        // location information.
+        std::string header =
+            "{"
+            "    \"interfaces-config\": {"
+            "        \"interfaces\": [";
+
+        std::string body = "]"
+            "    },"
+            "    \"expired-leases-processing\": {"
+            "         \"reclaim-timer-wait-time\": 60,"
+            "         \"hold-reclaimed-time\": 500,"
+            "         \"flush-reclaimed-timer-wait-time\": 60"
+            "    },"
+            "    \"rebind-timer\": 2000, "
+            "    \"renew-timer\": 1000, "
+            "    \"subnet6\": [ ],"
+            "    \"valid-lifetime\": 4000,"
+            "    \"control-socket\": {"
+            "        \"socket-type\": \"http\","
+            "        \"socket-address\": \"::1\","
+            "        \"socket-port\": 18126"
+            "    },"
+            "    \"lease-database\": {"
+            "       \"type\": \"memfile\", \"persist\": false },"
+            "    \"loggers\": [ {"
+            "       \"name\": \"kea-dhcp6\","
+            "       \"severity\": \"INFO\","
+            "       \"debuglevel\": 0"
+            "       } ]"
+            "}";
+
+        std::string config_txt = header + interfaces_ + body;
+        ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
+
+        ConstElementPtr config;
+        ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+
+        // Parse the logger configuration explicitly into the staging config.
+        // Note this does not alter the current loggers, they remain in
+        // effect until we apply the logging config below.  If no logging
+        // is supplied logging will revert to default logging.
+        server_->configureLogger(config, CfgMgr::instance().getStagingCfg());
+
+        // Let's apply the new logging. We do it early, so we'll be able to print
+        // out what exactly is wrong with the new config in case of problems.
+        CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+        ConstElementPtr answer = server_->processConfig(config);
+
+        // Commit the configuration so any subsequent reconfigurations
+        // will only close the command channel if its configuration has
+        // changed.
+        CfgMgr::instance().commit();
+
+        ASSERT_TRUE(answer);
+
+        int status = 0;
+        ConstElementPtr txt = isc::config::parseAnswer(status, answer);
+        // This should succeed. If not, print the error message.
+        ASSERT_EQ(0, status) << txt->str();
+
+        // Now check that the socket was indeed open.
+        ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    }
+
+    /// @brief Conducts a command/response exchange via HttpCommandSocket.
+    ///
+    /// This method connects to the given server over the given address/port.
+    /// If successful, it then sends the given command and retrieves the
+    /// server's response.  Note that it polls the server's I/O service
+    /// where needed to cause the server to process IO events on
+    /// the control channel sockets.
+    ///
+    /// @param command the command text to execute in JSON form.
+    /// @param response variable into which the received response should be
+    /// placed.
+    virtual void sendHttpCommand(const std::string& command,
+                                 std::string& response) override {
+        response = "";
+        IOServicePtr io_service = getIOService();
+        ASSERT_TRUE(io_service);
+        boost::scoped_ptr<TestHttpClient> client;
+        client.reset(new TestHttpClient(io_service, SERVER_ADDRESS,
+                                        SERVER_PORT));
+        ASSERT_TRUE(client);
+
+        // Send the command. This will trigger server's handler which receives
+        // data over the HTTP socket. The server will start sending response
+        // to the client.
+        ASSERT_NO_THROW(client->startRequest(buildPostStr(command)));
+        runIOService();
+        ASSERT_TRUE(client->receiveDone());
+
+        // Read the response generated by the server.
+        HttpResponsePtr hr;
+        ASSERT_NO_THROW(hr = parseResponse(client->getResponse()));
+        response = hr->getBody();
+
+        // Now close client.
+        client->close();
+
+        ASSERT_NO_THROW(io_service->poll());
+    }
+};
+
+/// @brief Fixture class intended for testing HTTPS control channel.
+class HttpsCtrlChannelDhcpv6Test : public BaseCtrlChannelDhcpv6Test {
+public:
+
+    virtual void createHttpChannelServer() override {
+        // Just a simple config. The important part here is the socket
+        // location information.
+        string ca_dir(string(TEST_CA_DIR));
+        ostringstream config_st;
+        config_st
+            << "{"
+            << "    \"interfaces-config\": {"
+            << "        \"interfaces\": ["
+            << interfaces_
+            << "]"
+            << "    },"
+            << "    \"expired-leases-processing\": {"
+            << "         \"reclaim-timer-wait-time\": 60,"
+            << "         \"hold-reclaimed-time\": 500,"
+            << "         \"flush-reclaimed-timer-wait-time\": 60"
+            << "    },"
+            << "    \"rebind-timer\": 2000, "
+            << "    \"renew-timer\": 1000, "
+            << "    \"subnet6\": [ ],"
+            << "    \"valid-lifetime\": 4000,"
+            << "    \"control-socket\": {"
+            << "        \"socket-type\": \"http\","
+            << "        \"socket-address\": \"::1\","
+            << "        \"socket-port\": 18126,"
+            << "        \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\","
+            << "        \"cert-file\": \"" << ca_dir << "/kea-server.crt\","
+            << "        \"key-file\": \"" << ca_dir << "/kea-server.key\""
+            << "    },"
+            << "    \"lease-database\": {"
+            << "       \"type\": \"memfile\", \"persist\": false },"
+            << "    \"loggers\": [ {"
+            << "       \"name\": \"kea-dhcp6\","
+            << "       \"severity\": \"INFO\","
+            << "       \"debuglevel\": 0"
+            << "       } ]"
+            << "}";
+
+        ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
+
+        ConstElementPtr config;
+        ASSERT_NO_THROW(config = parseDHCP6(config_st.str()));
+
+        // Parse the logger configuration explicitly into the staging config.
+        // Note this does not alter the current loggers, they remain in
+        // effect until we apply the logging config below.  If no logging
+        // is supplied logging will revert to default logging.
+        server_->configureLogger(config, CfgMgr::instance().getStagingCfg());
+
+        // Let's apply the new logging. We do it early, so we'll be able to print
+        // out what exactly is wrong with the new config in case of problems.
+        CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+        ConstElementPtr answer = server_->processConfig(config);
+
+        // Commit the configuration so any subsequent reconfigurations
+        // will only close the command channel if its configuration has
+        // changed.
+        CfgMgr::instance().commit();
+
+        ASSERT_TRUE(answer);
+
+        int status = 0;
+        ConstElementPtr txt = isc::config::parseAnswer(status, answer);
+        // This should succeed. If not, print the error message.
+        ASSERT_EQ(0, status) << txt->str();
+
+        // Now check that the socket was indeed open.
+        ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
+    }
+
+    /// @brief Conducts a command/response exchange via HttpCommandSocket.
+    ///
+    /// This method connects to the given server over the given address/port.
+    /// If successful, it then sends the given command and retrieves the
+    /// server's response.  Note that it polls the server's I/O service
+    /// where needed to cause the server to process IO events on
+    /// the control channel sockets.
+    ///
+    /// @param command the command text to execute in JSON form.
+    /// @param response variable into which the received response should be
+    /// placed.
+    virtual void sendHttpCommand(const std::string& command,
+                                 std::string& response) override {
+        response = "";
+        IOServicePtr io_service = getIOService();
+        ASSERT_TRUE(io_service);
+        boost::scoped_ptr<TestHttpsClient> client;
+        TlsContextPtr client_tls_context;
+        configClient(client_tls_context);
+        client.reset(new TestHttpsClient(io_service, client_tls_context,
+                                         SERVER_ADDRESS, SERVER_PORT));
+        ASSERT_TRUE(client);
+
+        // Send the command. This will trigger server's handler which receives
+        // data over the HTTP socket. The server will start sending response
+        // to the client.
+        ASSERT_NO_THROW(client->startRequest(buildPostStr(command)));
+        runIOService();
+        ASSERT_TRUE(client->receiveDone());
+
+        // Read the response generated by the server.
+        HttpResponsePtr hr;
+        ASSERT_NO_THROW(hr = parseResponse(client->getResponse()));
+        response = hr->getBody();
+
+        // Now close client.
+        client->close();
+
+        ASSERT_NO_THROW(io_service->poll());
+    }
+};
+
+// Tests that the server properly responds to invalid commands sent
+// via ControlChannel.
+void
+BaseCtrlChannelDhcpv6Test::testControlChannelNegative() {
+    createHttpChannelServer();
+    std::string response;
+
+    sendHttpCommand("{ \"command\": \"bogus\" }", response);
+    EXPECT_EQ("[ { \"result\": 2,"
+              " \"text\": \"'bogus' command not supported.\" } ]",
+              response);
+
+    sendHttpCommand("utter nonsense", response);
+    EXPECT_EQ("{ \"result\": 400, \"text\": \"Bad Request\" }", response);
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelNegative) {
+    testControlChannelNegative();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlChannelNegative) {
+    testControlChannelNegative();
+}
+
+// Tests that the server properly responds to shutdown command sent
+// via ControlChannel.
+void
+BaseCtrlChannelDhcpv6Test::testControlChannelShutdown() {
+    createHttpChannelServer();
+    std::string response;
+
+    sendHttpCommand("{ \"command\": \"shutdown\" }", response);
+    EXPECT_EQ("[ { \"result\": 0, \"text\": \"Shutting down.\" } ]", response);
+
+    EXPECT_EQ(EXIT_SUCCESS, server_->getExitValue());
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelShutdown) {
+    testControlChannelShutdown();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlChannelShutdown) {
+    testControlChannelShutdown();
+}
+
+// Check that the "config-set" command will replace current configuration.
+TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
+    createHttpChannelServer();
+
+    // 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 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 subnet2 =
+        "               {\"subnet\": \"3003::/64\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"3005::/64\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"3005::100-3005::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 << set_config_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);
+
+    // Create a config with malformed subnet that should fail to parse.
+    os.str("");
+    os << set_config_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << bad_subnet
+       << subnet_footer
+       << control_socket
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    sendHttpCommand(os.str(), response);
+
+    // Should fail with a syntax error
+    EXPECT_EQ("[ { \"result\": 1, "
+              "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+              "parameter is missing for a subnet being configured "
+              "(<string>:21:17)\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    def = LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // 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 << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << ",\n"
+       << subnet2
+       << subnet_footer
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Send the config-set command.
+    sendHttpCommand(os.str(), response);
+
+    // Verify the HTTP control channel socket no longer exists.
+    ASSERT_NO_THROW(HttpCommandMgr::instance().garbageCollectListeners());
+    EXPECT_FALSE(HttpCommandMgr::instance().getHttpListener());
+
+    // With no command channel, should still receive the response.
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"48035E8F9CC25FC1F6175B78CCC6B8A673CACBA9E956C0ED3079C478BF1F2D1A\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(2, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
+// Check that the "config-set" command will replace current configuration.
+TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
+    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 set_config_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 subnet2 =
+        "               {\"subnet\": \"3003::/64\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"3005::/64\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"3005::100-3005::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"
+        "       \"socket-type\": \"http\", \n"
+        "       \"socket-address\": \"::1\", \n"
+        "       \"socket-port\": 18126, \n";
+    string control_socket_footer =
+        "    } \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 << set_config_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);
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"19DC7B91AB2806C11494FA4AB3D2ACF144E1F6CDDD24859F5E8F277D06B06454\" }, \"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);
+
+    // Create a config with malformed subnet that should fail to parse.
+    os.str("");
+    os << set_config_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << bad_subnet
+       << subnet_footer
+       << 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
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    sendHttpCommand(os.str(), response);
+
+    // Should fail with a syntax error
+    EXPECT_EQ("[ { \"result\": 1, "
+              "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+              "parameter is missing for a subnet being configured "
+              "(<string>:21:17)\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    def = LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+    ASSERT_TRUE(def);
+
+    // 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 << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << ",\n"
+       << subnet2
+       << subnet_footer
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Send the config-set command.
+    sendHttpCommand(os.str(), response);
+
+    // Verify the HTTP control channel socket no longer exists.
+    ASSERT_NO_THROW(HttpCommandMgr::instance().garbageCollectListeners());
+    EXPECT_FALSE(HttpCommandMgr::instance().getHttpListener());
+
+    // With no command channel, should still receive the response.
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"48035E8F9CC25FC1F6175B78CCC6B8A673CACBA9E956C0ED3079C478BF1F2D1A\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(2, subnets->size());
+
+    // Clean up after the test.
+    CfgMgr::instance().clear();
+}
+
+// Tests if the server returns its configuration using config-get.
+// Note there are separate tests that verify if toElement() called by the
+// config-get handler are actually converting the configuration correctly.
+void
+BaseCtrlChannelDhcpv6Test::testConfigGet() {
+    createHttpChannelServer();
+    std::string response;
+
+    sendHttpCommand("{ \"command\": \"config-get\" }", response);
+    ConstElementPtr rsp;
+
+    // The response should be a valid JSON.
+    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+    ASSERT_TRUE(rsp);
+
+    int status;
+    ConstElementPtr cfg = parseListAnswer(status, rsp);
+    EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+    // Ok, now roughly check if the response seems legit.
+    ASSERT_TRUE(cfg);
+    ASSERT_EQ(Element::map, cfg->getType());
+    EXPECT_TRUE(cfg->get("Dhcp6"));
+    EXPECT_TRUE(cfg->get("Dhcp6")->get("loggers"));
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, configGet) {
+    testConfigGet();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configGet) {
+    testConfigGet();
+}
 
 // Tests if the server returns the hash of its configuration using
 // config-hash-get.
-TEST_F(HttpCtrlChannelDhcpv6Test, configHashGet) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigHashGet() {
     createHttpChannelServer();
     std::string response;
 
     sendHttpCommand("{ \"command\": \"config-hash-get\" }", response);
     ConstElementPtr rsp;
 
-    // The response should be a valid JSON.
-    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
-    ASSERT_TRUE(rsp);
+    // The response should be a valid JSON.
+    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+    ASSERT_TRUE(rsp);
+
+    int status;
+    ConstElementPtr args = parseListAnswer(status, rsp);
+    EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+    // The parseListAnswer is trying to be smart with ignoring hash.
+    // But this time we really want to see the hash, so we'll retrieve
+    // the arguments manually.
+    args = rsp->get(0)->get(CONTROL_ARGUMENTS);
+
+    // Ok, now roughly check if the response seems legit.
+    ASSERT_TRUE(args);
+    ASSERT_EQ(Element::map, args->getType());
+    ConstElementPtr hash = args->get("hash");
+    ASSERT_TRUE(hash);
+    ASSERT_EQ(Element::string, hash->getType());
+    // SHA-256 -> 64 hex digits.
+    EXPECT_EQ(64, hash->stringValue().size());
+}
+
+TEST_F(HttpCtrlChannelDhcpv6Test, configHashGet) {
+    testConfigHashGet();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configHashGet) {
+    testConfigHashGet();
+}
+
+// Verify that the "config-test" command will do what we expect.
+TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
+    createHttpChannelServer();
+
+    // 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_test_txt = "{ \"command\": \"config-test\" \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 subnet2 =
+        "               {\"subnet\": \"3003::/64\", \"id\": 2, \n"
+        "                \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"3005::/64\", \"id\": 10, \n"
+        "                \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
+    string subnet_footer =
+        "          ] \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 << set_config_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << subnet_footer
+       << control_socket
+       << logger_txt
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-set command
+    std::string response;
+    sendHttpCommand(os.str(), response);
+
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"DE129E7DD1C402721C83B74C5BBD7C330A0B705108A198CB868377031169BBC2\" }, \"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());
+
+    // Create a config with malformed subnet that should fail to parse.
+    os.str("");
+    os << config_test_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << bad_subnet
+       << subnet_footer
+       << control_socket
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Send the config-test command
+    sendHttpCommand(os.str(), response);
+
+    // Should fail with a syntax error
+    EXPECT_EQ("[ { \"result\": 1, "
+              "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+              "parameter is missing for a subnet being configured "
+              "(<string>:21:17)\" } ]",
+              response);
+
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
+
+    // Create a valid config with two subnets and no command channel.
+    os.str("");
+    os << config_test_txt << ","
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << ",\n"
+       << subnet2
+       << subnet_footer
+       << "}\n"                      // close dhcp6
+       << "}}";
+
+    // Verify the HTTP control channel socket exists.
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Send the config-test command.
+    sendHttpCommand(os.str(), response);
+
+    // Verify the HTTP control channel socket still exists.
+    ASSERT_NO_THROW(HttpCommandMgr::instance().garbageCollectListeners());
+    EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
+
+    // Verify the configuration was successful.
+    EXPECT_EQ("[ { \"result\": 0, \"text\": \"Configuration seems sane. "
+              "Control-socket, hook-libraries, and D2 configuration were "
+              "sanity checked, but not applied.\" } ]",
+              response);
 
-    int status;
-    ConstElementPtr args = parseListAnswer(status, rsp);
-    EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
-    // The parseListAnswer is trying to be smart with ignoring hash.
-    // But this time we really want to see the hash, so we'll retrieve
-    // the arguments manually.
-    args = rsp->get(0)->get(CONTROL_ARGUMENTS);
+    // Check that the config was not applied.
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    EXPECT_EQ(1, subnets->size());
 
-    // Ok, now roughly check if the response seems legit.
-    ASSERT_TRUE(args);
-    ASSERT_EQ(Element::map, args->getType());
-    ConstElementPtr hash = args->get("hash");
-    ASSERT_TRUE(hash);
-    ASSERT_EQ(Element::string, hash->getType());
-    // SHA-256 -> 64 hex digits.
-    EXPECT_EQ(64, hash->stringValue().size());
+    // Clean up after the test.
+    CfgMgr::instance().clear();
 }
 
 // Verify that the "config-test" command will do what we expect.
-TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
+TEST_F(HttpsCtrlChannelDhcpv6Test, configTest) {
     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 set_config_txt = "{ \"command\": \"config-set\" \n";
     string config_test_txt = "{ \"command\": \"config-test\" \n";
     string args_txt = " \"arguments\": { \n";
@@ -827,11 +1443,12 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
         "                \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
     string subnet_footer =
         "          ] \n";
-    string control_socket =
+    string control_socket_header =
         "    ,\"control-socket\": { \n"
         "       \"socket-type\": \"http\", \n"
         "       \"socket-address\": \"::1\", \n"
-        "       \"socket-port\": 18126 \n"
+        "       \"socket-port\": 18126, \n";
+    string control_socket_footer =
         "    } \n";
     string logger_txt =
         "       ,\"loggers\": [ { \n"
@@ -847,20 +1464,24 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
 
     // Create a valid config with all the parts should parse
     os << set_config_txt << ","
-        << args_txt
-        << dhcp6_cfg_txt
-        << subnet1
-        << subnet_footer
-        << control_socket
-        << logger_txt
-        << "}\n"                      // close dhcp6
-        << "}}";
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << subnet_footer
+       << 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);
 
-    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"DE129E7DD1C402721C83B74C5BBD7C330A0B705108A198CB868377031169BBC2\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
+    EXPECT_EQ("[ { \"arguments\": { \"hash\": \"F985ED10D250FDF13F10DF7E409EA4F467D46BC77F8FACE95AF7CB5208E6B6B2\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
               response);
 
     // Check that the config was indeed applied.
@@ -871,13 +1492,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
     // Create a config with malformed subnet that should fail to parse.
     os.str("");
     os << config_test_txt << ","
-        << args_txt
-        << dhcp6_cfg_txt
-        << bad_subnet
-        << subnet_footer
-        << control_socket
-        << "}\n"                      // close dhcp6
-        << "}}";
+       << args_txt
+       << dhcp6_cfg_txt
+       << bad_subnet
+       << subnet_footer
+       << 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
+       << "}\n"                      // close dhcp6
+       << "}}";
 
     // Send the config-test command
     sendHttpCommand(os.str(), response);
@@ -896,14 +1521,14 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
     // Create a valid config with two subnets and no command channel.
     os.str("");
     os << config_test_txt << ","
-        << args_txt
-        << dhcp6_cfg_txt
-        << subnet1
-        << ",\n"
-        << subnet2
-        << subnet_footer
-        << "}\n"                      // close dhcp6
-        << "}}";
+       << args_txt
+       << dhcp6_cfg_txt
+       << subnet1
+       << ",\n"
+       << subnet2
+       << subnet_footer
+       << "}\n"                      // close dhcp6
+       << "}}";
 
     // Verify the HTTP control channel socket exists.
     EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
@@ -930,7 +1555,8 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
 }
 
 // This test verifies that the DHCP server handles version-get commands
-TEST_F(HttpCtrlChannelDhcpv6Test, getVersion) {
+void
+BaseCtrlChannelDhcpv6Test::testGetVersion() {
     createHttpChannelServer();
 
     std::string response;
@@ -947,8 +1573,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, getVersion) {
     EXPECT_TRUE(response.find("GTEST_VERSION") != string::npos);
 }
 
-// This test verifies that the DHCP server handles status-get commands
-TEST_F(HttpCtrlChannelDhcpv6Test, statusGet) {
+TEST_F(HttpCtrlChannelDhcpv6Test, getVersion) {
+    testGetVersion();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, getVersion) {
+    testGetVersion();
+}
+
+// This test verifies that the DHCP server handles status-get commands.
+void
+BaseCtrlChannelDhcpv6Test::testStatusGet() {
     createHttpChannelServer();
 
     // start_ is initialized by init.
@@ -1069,8 +1704,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, statusGet) {
     EXPECT_EQ(3, found_queue_stats->size());
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, statusGet) {
+    testStatusGet();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, statusGet) {
+    testStatusGet();
+}
+
 // Check that status is returned even if LeaseMgr and HostMgr are not created.
-TEST_F(HttpCtrlChannelDhcpv6Test, noManagers) {
+void
+BaseCtrlChannelDhcpv6Test::testNoManagers() {
     // Send the status-get command.
     createHttpChannelServer();
     LeaseMgrFactory::destroy();
@@ -1094,8 +1738,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, noManagers) {
     ASSERT_EQ(Element::map, arguments->getType());
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, noManagers) {
+    testNoManagers();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, noManagers) {
+    testNoManagers();
+}
+
 // Checks that socket status exists in status-get responses.
-TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSockets) {
+void
+BaseCtrlChannelDhcpv6Test::testStatusGetSockets() {
     // Create dummy interfaces to test socket status.
     isc::dhcp::test::IfaceMgrTestConfig test_config(true);
 
@@ -1132,8 +1785,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSockets) {
     ASSERT_FALSE(errors);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSockets) {
+    testStatusGetSockets();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, statusGetSockets) {
+    testStatusGetSockets();
+}
+
 // Checks that socket status includes errors in status-get responses.
-TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSocketsErrors) {
+void
+BaseCtrlChannelDhcpv6Test::testStatusGetSocketsErrors() {
     // Create dummy interfaces to test socket status and add a custom down interface.
     isc::dhcp::test::IfaceMgrTestConfig test_config(true);
     test_config.addIface("down_interface", 4);
@@ -1181,8 +1843,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSocketsErrors) {
     ASSERT_EQ("the interface down_interface is down", error->stringValue());
 }
 
-// This test verifies that the DHCP server handles server-tag-get command
-TEST_F(HttpCtrlChannelDhcpv6Test, serverTagGet) {
+TEST_F(HttpCtrlChannelDhcpv6Test, statusGetSocketsErrors) {
+    testStatusGetSocketsErrors();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, statusGetSocketsErrors) {
+    testStatusGetSocketsErrors();
+}
+
+// This test verifies that the DHCP server handles server-tag-get command.
+void
+BaseCtrlChannelDhcpv6Test::testServerTagGet() {
     createHttpChannelServer();
 
     std::string response;
@@ -1204,8 +1875,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, serverTagGet) {
     EXPECT_EQ(expected, response);
 }
 
-// This test verifies that the DHCP server handles config-backend-pull command
-TEST_F(HttpCtrlChannelDhcpv6Test, configBackendPull) {
+TEST_F(HttpCtrlChannelDhcpv6Test, serverTagGet) {
+    testServerTagGet();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, serverTagGet) {
+    testServerTagGet();
+}
+
+// This test verifies that the DHCP server handles config-backend-pull command.
+void
+BaseCtrlChannelDhcpv6Test::testConfigBackendPull() {
     createHttpChannelServer();
 
     std::string response;
@@ -1217,9 +1897,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configBackendPull) {
     EXPECT_EQ(expected, response);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, configBackendPull) {
+    testConfigBackendPull();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configBackendPull) {
+    testConfigBackendPull();
+}
+
 // This test verifies that the DHCP server immediately reclaims expired
-// leases on leases-reclaim command
-TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaim) {
+// leases on leases-reclaim command.
+void
+BaseCtrlChannelDhcpv6Test::testControlLeasesReclaim() {
     createHttpChannelServer();
 
     // Create expired leases. Leases are expired by 40 seconds ago
@@ -1283,9 +1972,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaim) {
     EXPECT_TRUE(lease1->stateExpiredReclaimed());
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaim) {
+    testControlLeasesReclaim();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlLeasesReclaim) {
+    testControlLeasesReclaim();
+}
+
 // This test verifies that the DHCP server immediately reclaims expired
-// leases on leases-reclaim command with remove = true
-TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaimRemove) {
+// leases on leases-reclaim command with remove = true.
+void
+BaseCtrlChannelDhcpv6Test::testControlLeasesReclaimRemove() {
     createHttpChannelServer();
 
     // Create expired leases. Leases are expired by 40 seconds ago
@@ -1327,11 +2025,20 @@ TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaimRemove) {
     ASSERT_FALSE(lease1);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, controlLeasesReclaimRemove) {
+    testControlLeasesReclaimRemove();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlLeasesReclaimRemove) {
+    testControlLeasesReclaimRemove();
+}
+
 // Tests that the server properly responds to statistics commands.  Note this
 // is really only intended to verify that the appropriate Statistics handler
 // is called based on the command.  It is not intended to be an exhaustive
 // test of Dhcpv6 statistics.
-TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelStats) {
+void
+BaseCtrlChannelDhcpv6Test::testControlChannelStats() {
     createHttpChannelServer();
     std::string response;
 
@@ -1447,9 +2154,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelStats) {
               response);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, controlChannelStats) {
+    testControlChannelStats();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, controlChannelStats) {
+    testControlChannelStats();
+}
+
 // Tests that the server properly responds to list-commands command sent
-// via ControlChannel
-TEST_F(HttpCtrlChannelDhcpv6Test, listCommands) {
+// via ControlChannel.
+void
+BaseCtrlChannelDhcpv6Test::testListCommands() {
     createHttpChannelServer();
     std::string response;
 
@@ -1484,8 +2200,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, listCommands) {
     checkListCommands(rsp, "statistic-sample-count-set-all");
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, listCommands) {
+    testListCommands();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, listCommands) {
+    testListCommands();
+}
+
 // Tests if config-write can be called without any parameters.
-TEST_F(HttpCtrlChannelDhcpv6Test, configWriteNoFilename) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigWriteNoFilename() {
     createHttpChannelServer();
     std::string response;
 
@@ -1500,8 +2225,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configWriteNoFilename) {
     ::remove("test1.json");
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, configWriteNoFilename) {
+    testConfigWriteNoFilename();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configWriteNoFilename) {
+    testConfigWriteNoFilename();
+}
+
 // Tests if config-write can be called with a valid filename as parameter.
-TEST_F(HttpCtrlChannelDhcpv6Test, configWriteFilename) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigWriteFilename() {
     createHttpChannelServer();
     std::string response;
 
@@ -1513,9 +2247,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configWriteFilename) {
     ::remove("test2.json");
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, configWriteFilename) {
+    testConfigWriteFilename();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configWriteFilename) {
+    testConfigWriteFilename();
+}
+
 // Tests if config-reload attempts to reload a file and reports that the
 // file is missing.
-TEST_F(HttpCtrlChannelDhcpv6Test, configReloadMissingFile) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadMissingFile() {
     createHttpChannelServer();
     std::string response;
 
@@ -1534,9 +2277,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configReloadMissingFile) {
               response);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadMissingFile) {
+    testConfigReloadMissingFile();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadMissingFile) {
+    testConfigReloadMissingFile();
+}
+
 // Tests if config-reload attempts to reload a file and reports that the
 // file is not a valid JSON.
-TEST_F(HttpCtrlChannelDhcpv6Test, configReloadBrokenFile) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadBrokenFile() {
     createHttpChannelServer();
     std::string response;
 
@@ -1562,9 +2314,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configReloadBrokenFile) {
     ::remove("test7.json");
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadBrokenFile) {
+    testConfigReloadBrokenFile();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadBrokenFile) {
+    testConfigReloadBrokenFile();
+}
+
 // Tests if config-reload attempts to reload a file and reports that the
 // file is loaded correctly.
-TEST_F(HttpCtrlChannelDhcpv6Test, configReloadValid) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadValid() {
     createHttpChannelServer();
     std::string response;
 
@@ -1603,9 +2364,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configReloadValid) {
     ::remove("test8.json");
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadValid) {
+    testConfigReloadValid();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadValid) {
+    testConfigReloadValid();
+}
+
 // Tests if config-reload attempts to reload a file and reports that the
 // file is loaded correctly.
-TEST_F(HttpCtrlChannelDhcpv6Test, configReloadDetectInterfaces) {
+void
+BaseCtrlChannelDhcpv6Test::testConfigReloadDetectInterfaces() {
     interfaces_ = "\"eth0\"";
     IfacePtr eth0 = IfaceMgrTestConfig::createIface("eth0", ETH0_INDEX,
                                                     "11:22:33:44:55:66");
@@ -1673,9 +2443,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configReloadDetectInterfaces) {
     ::remove("test8.json");
 }
 
-// This test verifies that disable DHCP service command performs sanity check on
-// parameters.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableBadParam) {
+TEST_F(HttpCtrlChannelDhcpv6Test, configReloadDetectInterfaces) {
+    testConfigReloadDetectInterfaces();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, configReloadDetectInterfaces) {
+    testConfigReloadDetectInterfaces();
+}
+
+// This test verifies that disable DHCP service command performs
+// sanity check on parameters.
+void
+BaseCtrlChannelDhcpv6Test::testDhcpDisableBadParam() {
     createHttpChannelServer();
     std::string response;
 
@@ -1756,8 +2535,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableBadParam) {
               response);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableBadParam) {
+    testDhcpDisableBadParam();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpDisableBadParam) {
+    testDhcpDisableBadParam();
+}
+
 // This test verifies if it is possible to disable DHCP service via command.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisable) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpDisable() {
     createHttpChannelServer();
     std::string response;
 
@@ -1839,9 +2627,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisable) {
     EXPECT_TRUE(server_->network_state_->isServiceEnabled());
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisable) {
+    testDhcpDisable();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpDisable) {
+    testDhcpDisable();
+}
+
 // This test verifies if it is possible to disable DHCP service using
 // the origin-id.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableOriginId) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpDisableOriginId() {
     createHttpChannelServer();
     std::string response;
 
@@ -1872,9 +2669,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableOriginId) {
     EXPECT_TRUE(server_->network_state_->isServiceEnabled());
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableOriginId) {
+    testDhcpDisableOriginId();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpDisableOriginId) {
+    testDhcpDisableOriginId();
+}
+
 // This test verifies that it is possible to disable DHCP service for a short
 // period of time, after which the service is automatically enabled.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableTemporarily) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpDisableTemporarily() {
     createHttpChannelServer();
     std::string response;
 
@@ -1902,9 +2708,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableTemporarily) {
     EXPECT_TRUE(server_->network_state_->isDelayedEnableService());
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpDisableTemporarily) {
+    testDhcpDisableTemporarily();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpDisableTemporarily) {
+    testDhcpDisableTemporarily();
+}
+
 // This test verifies that enable DHCP service command performs sanity check on
 // parameters.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableBadParam) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpEnableBadParam() {
     createHttpChannelServer();
     std::string response;
     ConstElementPtr rsp;
@@ -1970,8 +2785,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableBadParam) {
               response);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableBadParam) {
+    testDhcpEnableBadParam();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpEnableBadParam) {
+    testDhcpEnableBadParam();
+}
+
 // This test verifies if it is possible to enable DHCP service via command.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnable) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpEnable() {
     createHttpChannelServer();
     std::string response;
 
@@ -2049,9 +2873,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnable) {
     EXPECT_TRUE(server_->network_state_->isServiceEnabled());
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnable) {
+    testDhcpEnable();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpEnable) {
+    testDhcpEnable();
+}
+
 // This test verifies if it is possible to enable DHCP service using
 // the origin-id.
-TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableOriginId) {
+void
+BaseCtrlChannelDhcpv6Test::testDhcpEnableOriginId() {
     createHttpChannelServer();
     std::string response;
 
@@ -2104,6 +2937,14 @@ TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableOriginId) {
     EXPECT_TRUE(server_->network_state_->isServiceEnabled());
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, dhcpEnableOriginId) {
+    testDhcpEnableOriginId();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, dhcpEnableOriginId) {
+    testDhcpEnableOriginId();
+}
+
 /// Verify that concurrent connections over the HTTP control channel can be
 /// established.
 TEST_F(HttpCtrlChannelDhcpv6Test, concurrentConnections) {
@@ -2168,8 +3009,79 @@ TEST_F(HttpCtrlChannelDhcpv6Test, concurrentConnections) {
     }
 }
 
+/// Verify that concurrent connections over the HTTPS control channel can be
+/// established.
+TEST_F(HttpsCtrlChannelDhcpv6Test, concurrentConnections) {
+    EXPECT_NO_THROW(createHttpChannelServer());
+
+    const size_t NB = 5;
+    vector<IOServicePtr> io_services;
+    vector<TestHttpsClientPtr> clients;
+    vector<TlsContextPtr> tls_contexts;
+
+    // Create clients.
+    for (size_t i = 0; i < NB; ++i) {
+        IOServicePtr io_service(new IOService());
+        io_services.push_back(io_service);
+        TlsContextPtr tls_context;
+        configClient(tls_context);
+        tls_contexts.push_back(tls_context);
+        TestHttpsClientPtr client(new TestHttpsClient(io_service,
+                                                      tls_context,
+                                                      SERVER_ADDRESS,
+                                                      SERVER_PORT));
+        clients.push_back(client);
+    }
+    ASSERT_EQ(NB, io_services.size());
+    ASSERT_EQ(NB, clients.size());
+
+    // Send requests and receive responses.
+    atomic<size_t> terminated;
+    terminated = 0;
+    vector<thread> threads;
+    const string command = "{ \"command\": \"list-commands\" }";
+    for (size_t i = 0; i < NB; ++i) {
+        threads.push_back(thread([&, i] () {
+            TestHttpsClientPtr client = clients[i];
+            ASSERT_TRUE(client);
+            client->startRequest(buildPostStr(command));
+            IOServicePtr io_service = io_services[i];
+            ASSERT_TRUE(io_service);
+            io_service->run();
+            ASSERT_TRUE(client->receiveDone());
+            HttpResponsePtr hr;
+            ASSERT_NO_THROW(hr = parseResponse(client->getResponse()));
+            string response = hr->getBody();
+            EXPECT_TRUE(response.find("\"result\": 0") != std::string::npos);
+            client->close();
+            ++terminated;
+        }));
+    }
+    ASSERT_EQ(NB, threads.size());
+
+    // Run the service IO services with a timeout.
+    IntervalTimer test_timer(getIOService());
+    bool timeout = false;
+    test_timer.setup([&timeout] () { timeout = true; },
+                     TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+    while (!timeout && (terminated < NB)) {
+        getIOService()->poll();
+    }
+    test_timer.cancel();
+    EXPECT_FALSE(timeout);
+
+    // Cleanup clients.
+    for (IOServicePtr io_service : io_services) {
+        io_service->stopAndPoll();
+    }
+    for (auto th = threads.begin(); th != threads.end(); ++th) {
+        th->join();
+    }
+}
+
 // This test verifies that the server can receive and process a large command.
-TEST_F(HttpCtrlChannelDhcpv6Test, longCommand) {
+void
+BaseCtrlChannelDhcpv6Test::testLongCommand() {
 
     ostringstream command;
 
@@ -2217,8 +3129,17 @@ TEST_F(HttpCtrlChannelDhcpv6Test, longCommand) {
               response);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, longCommand) {
+    testLongCommand();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, longCommand) {
+    testLongCommand();
+}
+
 // This test verifies that the server can send long response to the client.
-TEST_F(HttpCtrlChannelDhcpv6Test, longResponse) {
+void
+BaseCtrlChannelDhcpv6Test::testLongResponse() {
     // We need to generate large response. The simplest way is to create
     // a command and a handler which will generate some static response
     // of a desired size.
@@ -2245,9 +3166,18 @@ TEST_F(HttpCtrlChannelDhcpv6Test, longResponse) {
     EXPECT_EQ(reference_response, response);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, longResponse) {
+    testLongResponse();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, longResponse) {
+    testLongResponse();
+}
+
 // This test verifies that the server signals timeout if the transmission
 // takes too long, having received no data from the client.
-TEST_F(HttpCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
+void
+BaseCtrlChannelDhcpv6Test::testConnectionTimeoutNoData() {
     // Set connection timeout to 2s to prevent long waiting time for the
     // timeout during this test.
     const unsigned short timeout = 2000;
@@ -2261,4 +3191,12 @@ TEST_F(HttpCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
     EXPECT_EQ("{ \"result\": 400, \"text\": \"Bad Request\" }", response);
 }
 
+TEST_F(HttpCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
+    testConnectionTimeoutNoData();
+}
+
+TEST_F(HttpsCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
+    testConnectionTimeoutNoData();
+}
+
 } // End of anonymous namespace