]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[3543] Finished
authorFrancis Dupont <fdupont@isc.org>
Thu, 28 Jun 2018 00:15:25 +0000 (02:15 +0200)
committerFrancis Dupont <fdupont@isc.org>
Thu, 27 Dec 2018 20:00:17 +0000 (21:00 +0100)
doc/examples/agent/simple.json
doc/guide/agent.xml
src/bin/agent/ca_command_mgr.cc
src/bin/agent/tests/ca_command_mgr_unittests.cc
src/bin/d2/d2_controller.h
src/bin/d2/tests/d2_command_unittest.cc
src/bin/keactrl/kea-ctrl-agent.conf.pre
src/bin/keactrl/kea-dhcp-ddns.conf.pre

index 15be619f690d48c2c87033f2174ee2d17cba47cd..b64a647a62f01a5e4a7acb295623e89b59f0cb88 100644 (file)
@@ -42,9 +42,7 @@
                 "socket-name": "/path/to/the/unix/socket-v6"
             },
 
-            // Currently DHCP-DDNS (nicknamed D2) does not support
-            // command channel yet, but we hope this will change in the
-            // future.
+            // Location of the D2 command channel socket.
             "d2":
             {
                 "socket-type": "unix",
index 6a45c84353db49569def535581eead6a0e0c36cd..e11b8a42ec92378842d7d286626c542fc14d8cb4 100644 (file)
                 "socket-type": "unix",
                 "socket-name": "/path/to/the/unix/socket-v6",
                 "user-context": { "version": 3 }
-            }
+            },
+            "d2": {
+                "socket-type": "unix",
+                "socket-name": "/path/to/the/unix/socket-d2"
+            },
         },
 
         "hooks-libraries": [
       commands to it. Obviously, the DHCPv4 server must be configured to
       listen to connections via this same socket. In other words, the command
       socket configuration for the DHCPv4 server and CA (for this server)
-      must match. Consult the <xref linkend="dhcp4-ctrl-channel"/> and the
-      <xref linkend="dhcp6-ctrl-channel"/> to learn how the socket
-      configuration is specified for the DHCPv4 and DHCPv6 services.
+      must match. Consult the <xref linkend="dhcp4-ctrl-channel"/>, the
+      <xref linkend="dhcp6-ctrl-channel"/> and
+      <xref linkend="d2-ctrl-channel"/> to learn how the socket
+      configuration is specified for the DHCPv4, DHCPv6 and D2 services.
     </para>
 
     <warning>
index 6386527b4cb038c6981edfc365d6a84dfbb41342..3371bf53e6c8fbc994e38c0eb4d417e8f0e6e5a2 100644 (file)
@@ -118,12 +118,12 @@ CtrlAgentCommandMgr::handleCommandInternal(std::string cmd_name,
                 s << text->stringValue();
                 s << " You did not include \"service\" parameter in the command,"
                     " which indicates that Kea Control Agent should process this"
-                " command rather than forward it to one or more DHCP servers. If you"
+                    " command rather than forward it to one or more DHCP servers. If you"
                     " aimed to send this command to one of the DHCP servers you"
                     " should include the \"service\" parameter in your request, e.g."
                     " \"service\": [ \"dhcp4\" ] to forward the command to the DHCPv4"
-                    " server, or \"service\": [ \"dhcp4\", \"dhcp6\" ] to forward it to"
-                    " both DHCPv4 and DHCPv6 servers etc.";
+                    " server, or \"service\": [ \"dhcp4\", \"dhcp6\", \"d2\" ] to forward it to"
+                    " DHCPv4, DHCPv6 and D2 servers etc.";
 
                 answer->set(CONTROL_TEXT, Element::create(s.str()));
             }
index ba692c457377d1f0c0cfb7019402dd1ced87b402..f8a7833a7142f270d12db229619fb1aa275837ba 100644 (file)
@@ -296,6 +296,11 @@ TEST_F(CtrlAgentCommandMgrTest, forwardToDHCPv6Server) {
     testForward("dhcp6", "dhcp6", isc::config::CONTROL_RESULT_SUCCESS);
 }
 
+/// Check that control command is successfully forwarded to the D2 server.
+TEST_F(CtrlAgentCommandMgrTest, forwardToD2Server) {
+    testForward("d2", "d2", isc::config::CONTROL_RESULT_SUCCESS);
+}
+
 /// Check that the same command is forwarded to multiple servers.
 TEST_F(CtrlAgentCommandMgrTest, forwardToBothDHCPServers) {
     configureControlSocket("dhcp6");
@@ -304,6 +309,16 @@ TEST_F(CtrlAgentCommandMgrTest, forwardToBothDHCPServers) {
                 isc::config::CONTROL_RESULT_SUCCESS, -1, 2);
 }
 
+/// Check that the same command is forwarded to all servers.
+TEST_F(CtrlAgentCommandMgrTest, forwardToAllServers) {
+    configureControlSocket("dhcp6");
+    configureControlSocket("d2");
+
+    testForward("dhcp4", "dhcp4,dhcp6,d2", isc::config::CONTROL_RESULT_SUCCESS,
+                isc::config::CONTROL_RESULT_SUCCESS,
+                isc::config::CONTROL_RESULT_SUCCESS, 3);
+}
+
 /// Check that the command may forwarded to the second server even if
 /// forwarding to a first server fails.
 TEST_F(CtrlAgentCommandMgrTest, failForwardToServer) {
index 3ec84b03fbcb187b4b405c638ddf9b9432e111fe..f53cf6eb5117d4490dc46d52dfac903f71678f5a 100644 (file)
@@ -53,6 +53,14 @@ public:
     void deregisterCommands();
 
 protected:
+    /// @brief Returns version info specific to D2
+    virtual std::string getVersionAddendum();
+
+    /// @brief Constructor is declared protected to maintain the integrity of
+    /// the singleton instance.
+    D2Controller();
+
+private:
     /// @brief Creates an instance of the DHCP-DDNS specific application
     /// process.  This method is invoked during the process initialization
     /// step of the controller launch.
@@ -63,14 +71,6 @@ protected:
     /// pointer.
     virtual process::DProcessBase* createProcess();
 
-    /// @brief Returns version info specific to D2
-    virtual std::string getVersionAddendum();
-
-    /// @brief Constructor is declared protected to maintain the integrity of
-    /// the singleton instance.
-    D2Controller();
-
-private:
     ///@brief Parse a given file into Elements
     ///
     /// Uses bison parsing to parse a JSON configuration file into an
index 42374496d6ade09d751bd9a3c8f46972749db2ce..9f9328819a615a8d88deb82d4bec339395fa9b80 100644 (file)
@@ -6,6 +6,8 @@
 
 #include <config.h>
 
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
 #include <cc/command_interpreter.h>
 #include <config/command_mgr.h>
 #include <config/timeouts.h>
@@ -19,6 +21,7 @@
 #include <fstream>
 #include <iostream>
 #include <sstream>
+#include <thread>
 
 using namespace std;
 using namespace isc;
@@ -32,6 +35,32 @@ using namespace boost::asio;
 
 namespace {
 
+/// @brief Simple RAII class which stops IO service upon destruction
+/// of the object.
+class IOServiceWork {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service Pointer to the IO service to be stopped.
+    explicit IOServiceWork(const IOServicePtr& io_service)
+        : io_service_(io_service) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Stops IO service.
+    ~IOServiceWork() {
+        io_service_->stop();
+    }
+
+private:
+
+    /// @brief Pointer to the IO service to be stopped upon destruction.
+    IOServicePtr io_service_;
+
+};
+
 class NakedD2Controller;
 typedef boost::shared_ptr<NakedD2Controller> NakedD2ControllerPtr;
 
@@ -97,6 +126,10 @@ public:
         // Remove files.
         ::remove(CFG_TEST_FILE);
         ::remove(socket_path_.c_str());
+
+        // Reset command manager.
+        CommandMgr::instance().deregisterAll();
+        CommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
     }
 
     /// @brief Returns pointer to the server's IO service.
@@ -125,42 +158,6 @@ public:
         }
     }
 
-    /// @brief Convenience method for invoking standard, valid launch.
-    ///
-    /// This method sets up a timed run of the D2Controller::launch.
-    /// It does the following:
-    ///  - It creates command line argument variables argc/argv
-    ///  - Creates the config file with the given content.
-    ///  - Schedules a shutdown time timer to call D2ontroller::executeShutdown
-    ///    after the interval
-    ///  - Invokes D2Controller::launch() with the command line arguments
-    ///
-    /// @param config configuration file content to write before calling launch
-
-    /// @param run_time_ms  maximum amount of time to allow runProcess()
-    ///        to continue.
-    void runWithConfig(const string& config, int run_time_ms) {
-        /// write config file.
-        ofstream out(CFG_TEST_FILE, ios::trunc);
-        ASSERT_TRUE(out.is_open());
-        out << "{ \"DhcpDdns\":\n" << config << "\n}\n";
-        out.close();
-
-        // Shutdown (without error) after runtime.
-        IntervalTimer timer(*getIOService());
-        auto genShutdownCallback = [this]() {
-            ElementPtr arg_set;
-            server_->shutdownHandler(SHUT_DOWN_COMMAND, arg_set);
-        };
-        timer.setup(genShutdownCallback, run_time_ms);
-        
-        char* argv[] = { const_cast<char*>("progName"),
-                         const_cast<char*>("-c"),
-                         const_cast<char*>(CFG_TEST_FILE),
-                         const_cast<char*>("-d") };
-        server_->launch(4, argv, false);
-    }
-
     /// @brief Create a server with a command channel.
     void createUnixChannelServer() {
         ::remove(socket_path_.c_str());
@@ -332,6 +329,51 @@ public:
             ADD_FAILURE() << "Invalid expected status: " << exp_status;
         }
     }
+
+    /// @brief Handler for long command.
+    ///
+    /// It checks whether the received command is equal to the one specified
+    /// as an argument.
+    ///
+    /// @param expected_command String representing an expected command.
+    /// @param command_name Command name received by the handler.
+    /// @param arguments Command arguments received by the handler.
+    ///
+    /// @returns Success answer.
+    static ConstElementPtr
+    longCommandHandler(const string& expected_command,
+                       const string& command_name,
+                       const ConstElementPtr& arguments) {
+        // The handler is called with a command name and the structure holding
+        // command arguments. We have to rebuild the command from those
+        // two arguments so as it can be compared against expected_command.
+        ElementPtr entire_command = Element::createMap();
+        entire_command->set("command", Element::create(command_name));
+        entire_command->set("arguments", (arguments));
+
+        // The rebuilt command will have a different order of parameters so
+        // let's parse expected_command back to JSON to guarantee that
+        // both structures are built using the same order.
+        EXPECT_EQ(Element::fromJSON(expected_command)->str(),
+                 entire_command->str());
+        return (createAnswer(0, "long command received ok"));
+    }
+
+    /// @brief Command handler which generates long response.
+    ///
+    /// This handler generates a large response (over 400kB). It includes
+    /// a list of randomly generated strings to make sure that the test
+    /// can catch out of order delivery.
+    static ConstElementPtr
+    longResponseHandler(const string&, const ConstElementPtr&) {
+        ElementPtr arguments = Element::createList();
+        for (unsigned i = 0; i < 80000; ++i) {
+            std::ostringstream s;
+            s << std::setw(5) << i;
+            arguments->add(Element::create(s.str()));
+        }
+        return (createAnswer(0, arguments));
+    }
 };
 
 const char* CtrlChannelD2Test::CFG_TEST_FILE = "d2-test-config.json";
@@ -533,6 +575,25 @@ TEST_F(CtrlChannelD2Test, getversion) {
     EXPECT_TRUE(response.find("GTEST_VERSION") != string::npos);
 }
 
+// Tests that the server properly responds to list-commands command.
+TEST_F(CtrlChannelD2Test, listCommands) {
+    EXPECT_NO_THROW(createUnixChannelServer());
+    string response;
+
+    sendUnixCommand("{ \"command\": \"list-commands\" }", response);
+
+    ConstElementPtr rsp;
+    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+
+    // We expect the server to report at least the following commands:
+    checkListCommands(rsp, "build-report");
+    checkListCommands(rsp, "config-get");
+    checkListCommands(rsp, "config-write");
+    checkListCommands(rsp, "list-commands");
+    checkListCommands(rsp, "shutdown");
+    checkListCommands(rsp, "version-get");
+}
+
 // 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.
@@ -647,7 +708,6 @@ TEST_F(CtrlChannelD2Test, configTest) {
     sendUnixCommand(os.str(), response);
 
     // Should fail with a syntax error.
-    cerr << os.str();
     EXPECT_EQ("{ \"result\": 1, \"text\": \"element: tsig-keys : missing parameter 'name' (<wire>:9:14)<wire>:8:23\" }",
               response);
 
@@ -716,7 +776,293 @@ TEST_F(CtrlChannelD2Test, writeConfigFilename) {
     ::remove("test2.json");
 }
 
-// TODO: concurrentConnections, longCommand, longResponse,
-//       connectionTimeoutPartialCommand, connectionTimeoutNoData
+/// Verify that concurrent connections over the control channel can be
+/// established. (@todo change when response will be sent in multiple chunks)
+TEST_F(CtrlChannelD2Test, concurrentConnections) {
+    EXPECT_NO_THROW(createUnixChannelServer());
+
+    boost::scoped_ptr<UnixControlClient> client1(new UnixControlClient());
+    ASSERT_TRUE(client1);
+
+    boost::scoped_ptr<UnixControlClient> client2(new UnixControlClient());
+    ASSERT_TRUE(client2);
+
+    // Client 1 connects.
+    ASSERT_TRUE(client1->connectToServer(socket_path_));
+    ASSERT_NO_THROW(getIOService()->poll());
+
+    // Client 2 connects.
+    ASSERT_TRUE(client2->connectToServer(socket_path_));
+    ASSERT_NO_THROW(getIOService()->poll());
+
+    // Send the command while another client is connected.
+    ASSERT_TRUE(client2->sendCommand("{ \"command\": \"list-commands\" }"));
+    ASSERT_NO_THROW(getIOService()->poll());
+
+    string response;
+    // The server should respond ok.
+    ASSERT_TRUE(client2->getResponse(response));
+    EXPECT_TRUE(response.find("\"result\": 0") != std::string::npos);
+
+    // Disconnect the servers.
+    client1->disconnectFromServer();
+    client2->disconnectFromServer();
+    ASSERT_NO_THROW(getIOService()->poll());
+}
+
+// This test verifies that the server can receive and process a large command.
+TEST_F(CtrlChannelD2Test, longCommand) {
+
+    ostringstream command;
+
+    // This is the desired size of the command sent to the server (1MB).
+    // The actual size sent will be slightly greater than that.
+    const size_t command_size = 1024 * 1000;
+
+    while (command.tellp() < command_size) {
+
+        // We're sending command 'foo' with arguments being a list of
+        // strings. If this is the first transmission, send command name
+        // and open the arguments list. Also insert the first argument
+        // so as all subsequent arguments can be prefixed with a comma.
+        if (command.tellp() == 0) {
+            command << "{ \"command\": \"foo\", \"arguments\": [ \"begin\"";
+
+        } else {
+            // Generate a random number and insert it into the stream as
+            // 10 digits long string.
+            ostringstream arg;
+            arg << setw(10) << std::rand();
+            // Append the argument in the command.
+            command << ", \"" << arg.str() << "\"\n";
+
+            // If we have hit the limit of the command size, close braces to
+            // get appropriate JSON.
+            if (command.tellp() > command_size) {
+                command << "] }";
+            }
+        }
+    }
+
+    ASSERT_NO_THROW(
+        CommandMgr::instance().registerCommand("foo",
+            boost::bind(&CtrlChannelD2Test::longCommandHandler,
+                        command.str(), _1, _2));
+    );
+
+    createUnixChannelServer();
+
+    string response;
+    std::thread th([this, &response, &command]() {
+
+        // IO service will be stopped automatically when this object goes
+        // out of scope and is destroyed. This is useful because we use
+        // asserts which may break the thread in various exit points.
+        IOServiceWork work(getIOService());
+
+        // Create client which we will use to send command to the server.
+        boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+        ASSERT_TRUE(client);
+
+        // Connect to the server. This will trigger acceptor handler on the
+        // server side and create a new connection.
+        ASSERT_TRUE(client->connectToServer(socket_path_));
+
+        // Initially the remaining_string holds the entire command and we
+        // will be erasing the portions that we have sent.
+        string remaining_data = command.str();
+        while (!remaining_data.empty()) {
+            // Send the command in chunks of 1024 bytes.
+            const size_t l = remaining_data.size() < 1024 ? remaining_data.size() : 1024;
+            ASSERT_TRUE(client->sendCommand(remaining_data.substr(0, l)));
+            remaining_data.erase(0, l);
+        }
+
+        // Set timeout to 5 seconds to allow the time for the server to send
+        // a response.
+        const unsigned int timeout = 5;
+        ASSERT_TRUE(client->getResponse(response, timeout));
+
+        // We're done. Close the connection to the server.
+        client->disconnectFromServer();
+        });
+
+    // Run the server until the command has been processed and response
+    // received.
+    getIOService()->run();
+
+    // Wait for the thread to complete.
+    th.join();
+
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"long command received ok\" }",
+              response);
+}
+
+// This test verifies that the server can send long response to the client.
+TEST_F(CtrlChannelD2Test, longResponse) {
+    // 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
+    ASSERT_NO_THROW(
+        CommandMgr::instance().registerCommand("foo",
+            boost::bind(&CtrlChannelD2Test::longResponseHandler, _1, _2));
+    );
+
+    createUnixChannelServer();
+
+    // The UnixControlClient doesn't have any means to check that the entire
+    // response has been received. What we want to do is to generate a
+    // reference response using our command handler and then compare
+    // what we have received over the unix domain socket with this reference
+    // response to figure out when to stop receiving.
+    string reference_response = longResponseHandler("foo", ConstElementPtr())->str();
+
+    // In this stream we're going to collect out partial responses.
+    ostringstream response;
+
+    // The client is synchronous so it is useful to run it in a thread.
+    std::thread th([this, &response, reference_response]() {
+
+        // IO service will be stopped automatically when this object goes
+        // out of scope and is destroyed. This is useful because we use
+        // asserts which may break the thread in various exit points.
+        IOServiceWork work(getIOService());
+
+        // Remember the response size so as we know when we should stop
+        // receiving.
+        const size_t long_response_size = reference_response.size();
+
+        // Create the client and connect it to the server.
+        boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+        ASSERT_TRUE(client);
+        ASSERT_TRUE(client->connectToServer(socket_path_));
+
+        // Send the stub command.
+        std::string command = "{ \"command\": \"foo\", \"arguments\": { }  }";
+        ASSERT_TRUE(client->sendCommand(command));
+
+        // Keep receiving response data until we have received the full answer.
+        while (response.tellp() < long_response_size) {
+            std::string partial;
+            const unsigned int timeout = 5;
+            ASSERT_TRUE(client->getResponse(partial, timeout));
+            response << partial;
+        }
+
+        // We have received the entire response, so close the connection and
+        // stop the IO service.
+        client->disconnectFromServer();
+        });
+
+    // Run the server until the entire response has been received.
+    getIOService()->run();
+
+    // Wait for the thread to complete.
+    th.join();
+
+    // Make sure we have received correct response.
+    EXPECT_EQ(reference_response, response.str());
+}
+
+// This test verifies that the server signals timeout if the transmission
+// takes too long, after receiving a partial command
+TEST_F(CtrlChannelD2Test, connectionTimeoutPartialCommand) {
+    createUnixChannelServer();
+
+    // Set connection timeout to 2s to prevent long waiting time for the
+    // timeout during this test.
+    const unsigned short timeout = 2000;
+    CommandMgr::instance().setConnectionTimeout(timeout);
+
+    // Server's response will be assigned to this variable.
+    string response;
+
+    // It is useful to create a thread and run the server and the client
+    // at the same time and independently.
+    std::thread th([this, &response]() {
+
+        // IO service will be stopped automatically when this object goes
+        // out of scope and is destroyed. This is useful because we use
+        // asserts which may break the thread in various exit points.
+        IOServiceWork work(getIOService());
+
+        // Create the client and connect it to the server.
+        boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+        ASSERT_TRUE(client);
+        ASSERT_TRUE(client->connectToServer(socket_path_));
+
+        // Send partial command. The server will be waiting for the remaining
+        // part to be sent and will eventually signal a timeout.
+        string command = "{ \"command\": \"foo\" ";
+        ASSERT_TRUE(client->sendCommand(command));
+
+        // Let's wait up to 15s for the server's response. The response
+        // should arrive sooner assuming that the timeout mechanism for
+        // the server is working properly.
+        const unsigned int timeout = 15;
+        ASSERT_TRUE(client->getResponse(response, timeout));
+
+        // Explicitly close the client's connection.
+        client->disconnectFromServer();
+        });
+
+    // Run the server until stopped.
+    getIOService()->run();
+
+    // Wait for the thread to return.
+    th.join();
+
+    // Check that the server has signalled a timeout.
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"Connection over control channel timed out, discarded partial command of 19 bytes\" }" ,
+              response);
+}
+
+// This test verifies that the server signals timeout if the transmission
+// takes too long, having received no data from the client.
+TEST_F(CtrlChannelD2Test, connectionTimeoutNoData) {
+    createUnixChannelServer();
+
+    // Set connection timeout to 2s to prevent long waiting time for the
+    // timeout during this test.
+    const unsigned short timeout = 2000;
+    CommandMgr::instance().setConnectionTimeout(timeout);
+
+    // Server's response will be assigned to this variable.
+    string response;
+
+    // It is useful to create a thread and run the server and the client
+    // at the same time and independently.
+    std::thread th([this, &response]() {
+
+        // IO service will be stopped automatically when this object goes
+        // out of scope and is destroyed. This is useful because we use
+        // asserts which may break the thread in various exit points.
+        IOServiceWork work(getIOService());
+
+        // Create the client and connect it to the server.
+        boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+        ASSERT_TRUE(client);
+        ASSERT_TRUE(client->connectToServer(socket_path_));
+
+        // Let's wait up to 15s for the server's response. The response
+        // should arrive sooner assuming that the timeout mechanism for
+        // the server is working properly.
+        const unsigned int timeout = 15;
+        ASSERT_TRUE(client->getResponse(response, timeout));
+
+        // Explicitly close the client's connection.
+        client->disconnectFromServer();
+        });
+
+    // Run the server until stopped.
+    getIOService()->run();
+
+    // Wait for the thread to return.
+    th.join();
+
+    // Check that the server has signalled a timeout.
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"Connection over control channel timed out\" }",
+              response);
+}
 
 } // End of anonymous namespace
index 0619408b963066a2f5aac86ad76715dbf99823f7..94177a7068a6035c7fad06902f94fd7d9aab5564 100644 (file)
@@ -21,8 +21,8 @@
     "http-port": 8080,
 
     // Specify location of the files to which the Control Agent
-    // should connect to forward commands to the DHCPv4 and DHCPv6
-    // server via unix domain socket.
+    // should connect to forward commands to the DHCPv4, DHCPv6
+    // and D2 servers via unix domain sockets.
     "control-sockets": {
         "dhcp4": {
             "socket-type": "unix",
         "dhcp6": {
             "socket-type": "unix",
             "socket-name": "/tmp/kea-dhcp6-ctrl.sock"
+        },
+        "d2": {
+            "socket-type": "unix",
+            "socket-name": "/tmp/kea-dhcp-ddns-ctrl.sock"
         }
     },
 
index b1910974d9739c903495f4c40652bfb4ff89058b..cec180d9ba8294c9c574eec71dc158f7f8f0dfbf 100644 (file)
 {
   "ip-address": "127.0.0.1",
   "port": 53001,
+  "control-socket": {
+      "socket-type": "unix",
+      "socket-name": "/tmp/kea-dhcp-ddns-ctrl.sock"
+  },
   "tsig-keys": [],
   "forward-ddns" : {},
-  "reverse-ddns" : {}
+  "reverse-ddns" : {},
 },
 
 // Logging configuration starts here. Kea uses different loggers to log various