]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2974] Checkpoint: did v4 code, need UTs, v6 and doc
authorFrancis Dupont <fdupont@isc.org>
Thu, 13 Jun 2024 19:50:09 +0000 (21:50 +0200)
committerRazvan Becheriu <razvan@isc.org>
Wed, 23 Oct 2024 14:42:09 +0000 (17:42 +0300)
doc/sphinx/api-files.txt
doc/sphinx/arm/ctrl-channel.rst
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/ctrl_dhcp4_srv.h
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
src/share/api/api_files.mk
src/share/api/localize4.json [new file with mode: 0644]
src/share/api/localize4o6.json [new file with mode: 0644]
src/share/api/localize6.json [new file with mode: 0644]

index 4e363d09b4af4f9312986a1255c8bda8139bce58..ea026dfd67599d17c304a5e69b6d919c205b8e08 100644 (file)
@@ -69,6 +69,9 @@ src/share/api/lease6-wipe.json
 src/share/api/lease6-write.json
 src/share/api/leases-reclaim.json
 src/share/api/list-commands.json
+src/share/api/localize4.json
+src/share/api/localize4o6.json
+src/share/api/localize6.json
 src/share/api/network4-add.json
 src/share/api/network4-del.json
 src/share/api/network4-get.json
index 24bdf59e87945bd367e671dabea8e2a0013735fc..a162cd0b808c7461b4044ba8cb5ffb23369c0ac3 100644 (file)
@@ -864,6 +864,37 @@ command-line argument. This command does not take any parameters.
        "command": "version-get"
    }
 
+Commands Supported by the DHCPv4 Server
+=======================================
+
+.. isccmd:: localize4
+.. _command-localize4:
+
+The ``localize4`` Command
+-------------------------
+
+The :isccmd:`localize4` command returns the result of DHCPv4 subnet selection.
+
+.. isccmd:: localize4o6
+.. _command-localize4o6:
+
+The ``localize4o6`` Command
+---------------------------
+
+The :isccmd:`localize4o6` command returns the result of DHCPv4-over-DHCPv6
+subnet selection.
+
+Commands Supported by the DHCPv6 Server
+=======================================
+
+.. isccmd:: localize6
+.. _command-localize6:
+
+The ``localize6`` Command
+-------------------------
+
+The :isccmd:`localize6` command returns the result of DHCPv6 subnet selection.
+
 Commands Supported by the D2 Server
 ===================================
 
index 8e7d785d878e4210f6a33fd0451a510cfef835b7..4dab839f0a58f31253e12d7b1c573bd8abfe4988 100644 (file)
@@ -695,6 +695,337 @@ ControlledDhcpv4Srv::commandLeasesReclaimHandler(const string&,
     return (answer);
 }
 
+ConstElementPtr
+ControlledDhcpv4Srv::commandLocalize4Handler(const string&,
+                                             ConstElementPtr args) {
+    if (!args) {
+        return (createAnswer(CONTROL_RESULT_ERROR, "empty arguments"));
+    }
+    if (args->getType() != Element::map) {
+        return (createAnswer(CONTROL_RESULT_ERROR, "arguments must be a map"));
+    }
+    bool ignore_link_sel =
+        CfgMgr::instance().getCurrentCfg()->getIgnoreRAILinkSelection();
+    SubnetSelector selector;
+    for (auto const& entry : args->mapValue()) {
+        ostringstream errmsg;
+        if (entry.first == "interface") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'interface' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            selector.iface_name_ = entry.second->stringValue();
+            continue;
+        } else if (entry.first == "address") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'address' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'address' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.ciaddr_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'address' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "relay") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'relay' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'relay' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.giaddr_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'relay' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "local") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'local' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'local' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.local_address_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'local' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "remote") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'remote' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'remote' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.remote_address_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'remote' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "link") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'link' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'link' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                if (!ignore_link_sel) {
+                    selector.option_select_ = addr;
+                }
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'link' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "subnet") {
+            // RAI link-selection has precedence over subnet-selection.
+            if (args->contains("link") && !ignore_link_sel) {
+                continue;
+            }
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'subnet' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'subnet' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.option_select_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'subnet' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "classes") {
+            if (entry.second->getType() != Element::list) {
+                return (createAnswer(CONTROL_RESULT_ERROR,
+                                     "'classes' entry must be a list"));
+            }
+            for (auto const& item : entry.second->listValue()) {
+                if (!item || (item->getType() != Element::string)) {
+                    errmsg << "'classes' entry must be a list of strings";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                // Skip empty client classes.
+                if (!item->stringValue().empty()) {
+                    selector.client_classes_.insert(item->stringValue());
+                }
+            }
+            continue;
+        } else {
+            errmsg << "unknown entry '" << entry.first << "'";
+            return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+        }
+    }
+    ConstSubnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets4()->selectSubnet(selector);
+    if (!subnet) {
+        return (createAnswer(CONTROL_RESULT_EMPTY, "no selected subnet"));
+    }
+    SharedNetwork4Ptr network;
+    subnet->getSharedNetwork(subnet);
+    ostringstream msg;
+    if (network) {
+        msg << "selected shared network '" << network->getName()
+            << "' starting with subnet '" << subnet->toText()
+            << "' id " << subnet->getID();
+    } else {
+        msg << "selected subnet '" << subnet->toText()
+            << "' id " << subnet->getID();
+    }
+    return (createAnswer(CONTROL_RESULT_SUCCESS, msg.str()));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandLocalize4o6Handler(const string&,
+                                               ConstElementPtr args) {
+    if (!args) {
+        return (createAnswer(CONTROL_RESULT_ERROR, "empty arguments"));
+    }
+    if (args->getType() != Element::map) {
+        return (createAnswer(CONTROL_RESULT_ERROR, "arguments must be a map"));
+    }
+    SubnetSelector selector;
+    selector.dhcp4o6_ = true;
+    for (auto const& entry : args->mapValue()) {
+        ostringstream errmsg;
+        if (entry.first == "interface") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'interface' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            selector.iface_name_ = entry.second->stringValue();
+            continue;
+        } else if (entry.first == "address") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'address' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'address' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.ciaddr_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'address' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "relay") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'relay' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'relay' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.giaddr_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'relay' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "local") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'local' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'local' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.local_address_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'local' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "remote") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'remote' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'remote' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.remote_address_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'remote' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "link") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'link' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV6()) {
+                    errmsg << "bad 'link' entry: not IPv6";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.first_relay_linkaddr_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'link' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "subnet") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'subnet' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                IOAddress addr(entry.second->stringValue());
+                if (!addr.isV4()) {
+                    errmsg << "bad 'subnet' entry: not IPv4";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.option_select_ = addr;
+                continue;
+            } catch (const exception& ex) {
+                errmsg << "bad 'subnet' entry: " << ex.what();
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+        } else if (entry.first == "classes") {
+            if (entry.second->getType() != Element::list) {
+                return (createAnswer(CONTROL_RESULT_ERROR,
+                                     "'classes' entry must be a list"));
+            }
+            for (auto const& item : entry.second->listValue()) {
+                if (!item || (item->getType() != Element::string)) {
+                    errmsg << "'classes' entry must be a list of strings";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                // Skip empty client classes.
+                if (!item->stringValue().empty()) {
+                    selector.client_classes_.insert(item->stringValue());
+                }
+            }
+            continue;
+        } else {
+            errmsg << "unknown entry '" << entry.first << "'";
+            return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+        }
+    }
+    ConstSubnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets4()->selectSubnet4o6(selector);
+    if (!subnet) {
+        return (createAnswer(CONTROL_RESULT_EMPTY, "no selected subnet"));
+    }
+    SharedNetwork4Ptr network;
+    subnet->getSharedNetwork(subnet);
+    ostringstream msg;
+    if (network) {
+        msg << "selected shared network '" << network->getName()
+            << "' starting with subnet '" << subnet->toText()
+            << "' id " << subnet->getID();
+    } else {
+        msg << "selected subnet '" << subnet->toText()
+            << "' id " << subnet->getID();
+    }
+    return (createAnswer(CONTROL_RESULT_SUCCESS, msg.str()));
+}
+
 ConstElementPtr
 ControlledDhcpv4Srv::commandServerTagGetHandler(const std::string&,
                                                 ConstElementPtr) {
@@ -1133,6 +1464,12 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_P
     CommandMgr::instance().registerCommand("leases-reclaim",
         std::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, ph::_1, ph::_2));
 
+    CommandMgr::instance().registerCommand("localize4",
+        std::bind(&ControlledDhcpv4Srv::commandLocalize4Handler, this, ph::_1, ph::_2));
+
+    CommandMgr::instance().registerCommand("localize4o6",
+        std::bind(&ControlledDhcpv4Srv::commandLocalize4o6Handler, this, ph::_1, ph::_2));
+
     CommandMgr::instance().registerCommand("server-tag-get",
         std::bind(&ControlledDhcpv4Srv::commandServerTagGetHandler, this, ph::_1, ph::_2));
 
@@ -1216,6 +1553,8 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
         CommandMgr::instance().deregisterCommand("dhcp-enable");
         CommandMgr::instance().deregisterCommand("leases-reclaim");
         CommandMgr::instance().deregisterCommand("server-tag-get");
+        CommandMgr::instance().deregisterCommand("localize4");
+        CommandMgr::instance().deregisterCommand("localize4o6");
         CommandMgr::instance().deregisterCommand("shutdown");
         CommandMgr::instance().deregisterCommand("statistic-get");
         CommandMgr::instance().deregisterCommand("statistic-get-all");
index 1c8e6dc8adbe8bbb42e20c97e881ef54f05684f9..0b826ad7c62c1bb537b1d92dc93ac2f034d85a51 100644 (file)
@@ -279,6 +279,32 @@ private:
     commandLeasesReclaimHandler(const std::string& command,
                                 isc::data::ConstElementPtr args);
 
+    /// @brief Handler for processing 'localize4' command
+    ///
+    /// This handler processes localize4 command, which returns
+    /// the result of DHCPv4 subnet selected.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args arguments map { <selector>: <value> }
+    ///
+    /// @return status of the command with the selection result
+    isc::data::ConstElementPtr
+    commandLocalize4Handler(const std::string& command,
+                            isc::data::ConstElementPtr args);
+
+    /// @brief Handler for processing 'localize4o6' command
+    ///
+    /// This handler processes localize4o6 command, which returns
+    /// the result of DHCP4o6 subnet selected.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args arguments map { <selector>: <value> }
+    ///
+    /// @return status of the command with the selection result
+    isc::data::ConstElementPtr
+    commandLocalize4o6Handler(const std::string& command,
+                              isc::data::ConstElementPtr args);
+
     /// @brief handler for server-tag-get command
     ///
     /// This method handles the server-tag-get command, which retrieves
index 632e1df759d5d82e7e9c64760dfe59e21be1d262..3208afd8f3fa13d978174f2dc773286eba6380f0 100644 (file)
@@ -482,6 +482,8 @@ TEST_F(CtrlChannelDhcpv4SrvTest, commandsRegistration) {
     EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"localize4\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"localize4o6\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"server-tag-get\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
index cc3fc5f555eda1ab0b0ad2ee2a9af787283073cc..44596f86bfb20320078fe8538d576fa98f1ae368 100644 (file)
@@ -69,6 +69,9 @@ api_files += $(top_srcdir)/src/share/api/lease6-wipe.json
 api_files += $(top_srcdir)/src/share/api/lease6-write.json
 api_files += $(top_srcdir)/src/share/api/leases-reclaim.json
 api_files += $(top_srcdir)/src/share/api/list-commands.json
+api_files += $(top_srcdir)/src/share/api/localize4.json
+api_files += $(top_srcdir)/src/share/api/localize4o6.json
+api_files += $(top_srcdir)/src/share/api/localize6.json
 api_files += $(top_srcdir)/src/share/api/network4-add.json
 api_files += $(top_srcdir)/src/share/api/network4-del.json
 api_files += $(top_srcdir)/src/share/api/network4-get.json
diff --git a/src/share/api/localize4.json b/src/share/api/localize4.json
new file mode 100644 (file)
index 0000000..aa1e790
--- /dev/null
@@ -0,0 +1,22 @@
+{
+    "access": "read",
+    "avail": "2.7.0",
+    "brief": [ "This command returns the result of DHCPv4 subnet selection" ],
+    "cmd-comment": [ "Possible parameters are the incoming interface name, client address, relay address, client classes, etc." ],
+    "cmd-syntax": [
+       "{",
+       "    \"command\": \"localize4\",",
+       "    \"arguments\": {",
+       "        \"interface\": <string>,",
+       "        \"address\": <string>,",
+       "        \"relay\": <string>,",
+       "        \"classes\": [ <strings> ]",
+        "    }",
+       "}"
+    ],
+    "description": [ "See <xref linkend=\"command-localize4\"/>" ],
+    "name": "localize4",
+    "resp-comment": [ "3 forms for selected shared network, subnet or nothing.." ],
+    "resp-syntax": [ "{ \"result\": 1, \"text\": <localization result> }" ],
+    "support": [ "kea-dhcp4" ]
+}
diff --git a/src/share/api/localize4o6.json b/src/share/api/localize4o6.json
new file mode 100644 (file)
index 0000000..7f18237
--- /dev/null
@@ -0,0 +1,22 @@
+{
+    "access": "read",
+    "avail": "2.7.0",
+    "brief": [ "This command returns the result of DHCPv4o6 subnet selection" ],
+    "cmd-comment": [ "Possible parameters are the incoming interface name, client address, relay address, client classes, etc." ],
+    "cmd-syntax": [
+       "{",
+       "    \"command\": \"localize4o6\",",
+       "    \"arguments\": {",
+       "        \"interface\": <string>,",
+       "        \"address\": <string>,",
+       "        \"relay\": <string>,",
+       "        \"classes\": [ <strings> ]",
+        "    }",
+       "}"
+    ],
+    "description": [ "See <xref linkend=\"command-localize4o6\"/>" ],
+    "name": "localize4o6",
+    "resp-comment": [ "3 forms for selected shared network, subnet or nothing.." ],
+    "resp-syntax": [ "{ \"result\": 1, \"text\": <localization result> }" ],
+    "support": [ "kea-dhcp4" ]
+}
diff --git a/src/share/api/localize6.json b/src/share/api/localize6.json
new file mode 100644 (file)
index 0000000..7d082d2
--- /dev/null
@@ -0,0 +1,22 @@
+{
+    "access": "read",
+    "avail": "2.7.0",
+    "brief": [ "This command returns the result of DHCPv6 subnet selection" ],
+    "cmd-comment": [ "Possible parameters are the incoming interface name, client address, relay address, client classes, etc." ],
+    "cmd-syntax": [
+       "{",
+       "    \"command\": \"localize6\",",
+       "    \"arguments\": {",
+       "        \"interface\": <string>,",
+       "        \"address\": <string>,",
+       "        \"relay\": <string>,",
+       "        \"classes\": [ <strings> ]",
+        "    }",
+       "}"
+    ],
+    "description": [ "See <xref linkend=\"command-localize6\"/>" ],
+    "name": "localize6",
+    "resp-comment": [ "3 forms for selected shared network, subnet or nothing.." ],
+    "resp-syntax": [ "{ \"result\": 1, \"text\": <localization result> }" ],
+    "support": [ "kea-dhcp6" ]
+}