]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2974] Finished v6, tests and doc
authorFrancis Dupont <fdupont@isc.org>
Fri, 14 Jun 2024 08:12:57 +0000 (10:12 +0200)
committerRazvan Becheriu <razvan@isc.org>
Wed, 23 Oct 2024 14:42:09 +0000 (17:42 +0300)
changelog_unreleased/2974-localize [new file with mode: 0644]
doc/sphinx/arm/ctrl-channel.rst
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/bin/dhcp6/ctrl_dhcp6_srv.h
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
src/share/api/localize4.json
src/share/api/localize4o6.json
src/share/api/localize6.json

diff --git a/changelog_unreleased/2974-localize b/changelog_unreleased/2974-localize
new file mode 100644 (file)
index 0000000..75e03d4
--- /dev/null
@@ -0,0 +1,5 @@
+[func]         fdupont
+       Added new commands to simulate a subnet selection:
+       "localize4" and "localize4o6" for kea-dhcp4, and
+       "localize6" for kea-dhcp6.
+       (Gitlab #2974)
index a162cd0b808c7461b4044ba8cb5ffb23369c0ac3..11a621d70a5600457d8def969d98128e4d8f65d8 100644 (file)
@@ -874,6 +874,26 @@ The ``localize4`` Command
 -------------------------
 
 The :isccmd:`localize4` command returns the result of DHCPv4 subnet selection.
+Recognized entries take strings and are:
+
+ -  ``interface`` - the incoming interface name
+ -  ``address`` - the client address
+ -  ``relay`` - the relay/gateway address
+ -  ``local`` - the local/destination address
+ -  ``remote`` - the remote/source address
+ -  ``link`` - the RAI link-selection address
+ -  ``subnet`` - the subnet-selection address
+ -  ``classes`` - (list of strings) client classes (allowing to select a guarded subnet)
+
+The RAI link-selection is ignored when the ``ignore-rai-link-selection``
+compatibility flag is ``true``. When it is not ignored it has precedence
+over the subnet-selection.
+
+Outside of errors possible results are:
+
+ -  (empty) "no selected subnet"
+ -  "selected shared network '<name>' starting with subnet '<subnet>' id <id>"
+ -  "selected subnet '<subnet>' id <id>"
 
 .. isccmd:: localize4o6
 .. _command-localize4o6:
@@ -882,7 +902,29 @@ The ``localize4o6`` Command
 ---------------------------
 
 The :isccmd:`localize4o6` command returns the result of DHCPv4-over-DHCPv6
-subnet selection.
+subnet selection. Recognized entries take strings and are:
+
+ -  ``interface`` - the incoming interface name of the DHCPv6 server
+ -  ``interface-id`` - the binary content of the interface-id relay option
+ -  ``address`` - the client address
+ -  ``relay`` - the relay/gateway address
+ -  ``local`` - the local/destination address
+ -  ``remote`` - the remote/source IPv6 address of the DHCPv6 server
+ -  ``link`` - the first relay link IPv6 address
+ -  ``subnet`` - the subnet-selection address
+ -  ``classes`` - (list of strings) client classes (allowing to select a guarded subnet)
+
+According to the code only ``remote``, ``interface-id`` and ``interface``
+selectors are used. In  DHCPv4-over-DHCPv6 implementation ``interface` and
+``remote`` values are transmitted from the DHCPv6 server, ``interface-id``
+and ``link`` are carried in the relay info part of the DHCPv6 packet so
+are the same as for the DHCPv6 server.
+
+Outside of errors possible results are:
+
+ -  (empty) "no selected subnet"
+ -  "selected shared network '<name>' starting with subnet '<subnet>' id <id>"
+ -  "selected subnet '<subnet>' id <id>"
 
 Commands Supported by the DHCPv6 Server
 =======================================
@@ -894,6 +936,19 @@ The ``localize6`` Command
 -------------------------
 
 The :isccmd:`localize6` command returns the result of DHCPv6 subnet selection.
+Recognized entries take strings and are:
+
+ -  ``interface`` - the incoming interface name
+ -  ``interface-id`` - the binary content of the interface-id relay option
+ -  ``remote`` - the remote/source address
+ -  ``link`` - the first relay link address
+ -  ``classes`` - (list of strings) client classes (allowing to select a guarded subnet)
+
+Outside of errors possible results are:
+
+ -  (empty) "no selected subnet"
+ -  "selected shared network '<name>' starting with subnet '<subnet>' id <id>"
+ -  "selected subnet '<subnet>' id <id>"
 
 Commands Supported by the D2 Server
 ===================================
@@ -920,7 +975,7 @@ The D2 server supports only a subset of the DHCPv4/DHCPv6 server commands:
 
 -  :isccmd:`status-get`
 
-- :isccmd:`version-get`
+-  :isccmd:`version-get`
 
 .. _agent-commands:
 
index 4dab839f0a58f31253e12d7b1c573bd8abfe4988..8285d7a2d72fe1ab09153f31df7d0954eb606ac3 100644 (file)
@@ -851,7 +851,7 @@ ControlledDhcpv4Srv::commandLocalize4Handler(const string&,
         return (createAnswer(CONTROL_RESULT_EMPTY, "no selected subnet"));
     }
     SharedNetwork4Ptr network;
-    subnet->getSharedNetwork(subnet);
+    subnet->getSharedNetwork(network);
     ostringstream msg;
     if (network) {
         msg << "selected shared network '" << network->getName()
@@ -875,6 +875,8 @@ ControlledDhcpv4Srv::commandLocalize4o6Handler(const string&,
     }
     SubnetSelector selector;
     selector.dhcp4o6_ = true;
+    selector.local_address_ = IOAddress::IPV6_ZERO_ADDRESS();
+    selector.remote_address_ = IOAddress::IPV6_ZERO_ADDRESS();
     for (auto const& entry : args->mapValue()) {
         ostringstream errmsg;
         if (entry.first == "interface") {
@@ -884,6 +886,29 @@ ControlledDhcpv4Srv::commandLocalize4o6Handler(const string&,
             }
             selector.iface_name_ = entry.second->stringValue();
             continue;
+        } if (entry.first == "interface-id") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'interface-id' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                string str = entry.second->stringValue();
+                vector<uint8_t> id = util::str::quotedStringToBinary(str);
+                if (id.empty()) {
+                    util::str::decodeFormattedHexString(str, id);
+                }
+                if (id.empty()) {
+                    errmsg << "'interface-id' must be not empty";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.interface_id_ = OptionPtr(new Option(Option::V6,
+                                                              D6O_INTERFACE_ID,
+                                                              id));
+                continue;
+            } catch (...) {
+                errmsg << "value of 'interface-id' was not recognized";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
         } else if (entry.first == "address") {
             if (entry.second->getType() != Element::string) {
                 errmsg << "'address' entry must be a string";
@@ -925,8 +950,8 @@ ControlledDhcpv4Srv::commandLocalize4o6Handler(const string&,
             }
             try {
                 IOAddress addr(entry.second->stringValue());
-                if (!addr.isV4()) {
-                    errmsg << "bad 'local' entry: not IPv4";
+                if (!addr.isV6()) {
+                    errmsg << "bad 'local' entry: not IPv6";
                     return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
                 }
                 selector.local_address_ = addr;
@@ -942,8 +967,8 @@ ControlledDhcpv4Srv::commandLocalize4o6Handler(const string&,
             }
             try {
                 IOAddress addr(entry.second->stringValue());
-                if (!addr.isV4()) {
-                    errmsg << "bad 'remote' entry: not IPv4";
+                if (!addr.isV6()) {
+                    errmsg << "bad 'remote' entry: not IPv6";
                     return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
                 }
                 selector.remote_address_ = addr;
@@ -1013,7 +1038,7 @@ ControlledDhcpv4Srv::commandLocalize4o6Handler(const string&,
         return (createAnswer(CONTROL_RESULT_EMPTY, "no selected subnet"));
     }
     SharedNetwork4Ptr network;
-    subnet->getSharedNetwork(subnet);
+    subnet->getSharedNetwork(network);
     ostringstream msg;
     if (network) {
         msg << "selected shared network '" << network->getName()
index 3208afd8f3fa13d978174f2dc773286eba6380f0..847cbf832f894a78ae3aeb59bc21bc18de5c9fbc 100644 (file)
@@ -2056,6 +2056,1125 @@ TEST_F(CtrlChannelDhcpv4SrvTest, dhcpEnableOriginId) {
     EXPECT_TRUE(server_->network_state_->isServiceEnabled());
 }
 
+// This test verifies that localize4 command performs sanity check parameters.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4BadParam) {
+    createUnixChannelServer();
+    std::string response;
+    ConstElementPtr rsp;
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\""
+                    "}", response);
+
+    // The response should be a valid JSON.
+    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+    ASSERT_TRUE(rsp);
+
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"empty arguments\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": [ ]"
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"arguments must be a map\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"foo\": \"bar\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"unknown entry 'foo'\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"interface\": 1"
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'interface' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'address' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'address' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'address' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'relay' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'relay' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'relay' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"local\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'local' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"local\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'local' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"local\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'local' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"remote\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'remote' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'remote' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'remote' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"link\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'link' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'link' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'link' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'subnet' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'subnet' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'subnet' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"classes\": \"foo\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'classes' entry must be a list";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"classes\": [ 1 ]"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'classes' entry must be a list of strings";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4 command returns proper subnet for a given
+// relay link select address.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4RAILinkSelect) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"10.0.1.1\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4 command returns proper subnet for a given
+// subnet select address.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4SubnetSelect) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": \"10.0.1.1\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4 command returns proper subnet for a given
+// relay address.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4RelayAddress) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    subnet->addRelayAddress(IOAddress("192.0.3.1"));
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"10.0.1.1\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"192.0.3.1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"192.0.3.1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4 command returns proper subnet for a given
+// gateway address.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4Gateway) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"10.0.1.1\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4 command returns proper subnet for a given
+// client address.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4Client) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"10.0.1.1\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4 command returns proper subnet for a given
+// remote/source address.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4Remote) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"10.0.1.1\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4 command returns proper subnet for a given
+// incoming interface.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4Iface) {
+    isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    // Beware that the interface for 192.0.2.0/24 is eth1.
+    subnet->setIface("eth1");
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Different interface: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"eth0\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Same interface: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"eth1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"eth1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4 command returns proper guarded subnet.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4Class) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    subnet->allowClientClass("foobar");
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address in range but not in guard: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"192.0.2.1\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range and in guard: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"192.0.2.1\","
+                    "        \"classes\": [ \"foobar\" ]"
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"192.0.2.1\","
+                    "        \"classes\": [ \"foobar\" ]"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies that localize4o6 command performs sanity check parameters.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4o6BadParam) {
+    createUnixChannelServer();
+    std::string response;
+    ConstElementPtr rsp;
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\""
+                    "}", response);
+
+    // The response should be a valid JSON.
+    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+    ASSERT_TRUE(rsp);
+
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"empty arguments\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": [ ]"
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"arguments must be a map\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"foo\": \"bar\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"unknown entry 'foo'\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"interface\": 1"
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'interface' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"address\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'address' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'address' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"address\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'address' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"relay\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'relay' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'relay' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"relay\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'relay' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"local\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'local' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"local\": \"192.2.1.2\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'local' entry: not IPv6";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"local\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'local' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'remote' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"192.2.1.2\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'remote' entry: not IPv6";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'remote' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"link\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'link' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"192.2.1.2\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'link' entry: not IPv6";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'link' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'subnet' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'subnet' entry: not IPv4";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"subnet\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'subnet' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"classes\": \"foo\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'classes' entry must be a list";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"classes\": [ 1 ]"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'classes' entry must be a list of strings";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4o6 command returns proper subnet for a given
+// remote/source address.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4o6Remote) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    subnet->get4o6().setSubnet4o6(IOAddress("2001:db8:1::"), 64);
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:2::2\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4o6 command returns proper subnet for a given
+// relay interface id.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4o6RelayInterfaceId) {
+    isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    string iface_id("relay");
+    vector<uint8_t> bin(iface_id.cbegin(), iface_id.cend());
+    OptionPtr id(new Option(Option::V6, D6O_INTERFACE_ID, bin));
+    subnet->get4o6().setInterfaceId(id);
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Different interface: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"interface-id\": \"'foobar'\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Same interface: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"interface-id\": \"'relay'\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"interface-id\": \"'relay'\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize4o6 command returns proper subnet for a given
+// incoming interface.
+TEST_F(CtrlChannelDhcpv4SrvTest, localize4o6Iface) {
+    isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+                                  24, 1, 2, 3, SubnetID(1));
+    subnet->get4o6().setIface4o6("eth0");
+    SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->add(network);
+    CfgMgr::instance().commit();
+
+    // Different interface: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"foobar\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Same interface: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"eth0\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize4o6\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"eth0\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '192.0.2.0/24' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
 /// Verify that concurrent connections over the control channel can be
 ///  established.
 /// @todo Future Kea 1.3 tickets will modify the behavior of the CommandMgr
index 580852f1b3869c5ac578295c6584e74dfb4b9773..f6d5b32751b17ac3c163a0f9cce65fe8286d6798 100644 (file)
@@ -697,6 +697,123 @@ ControlledDhcpv6Srv::commandLeasesReclaimHandler(const string&,
     return (answer);
 }
 
+ConstElementPtr
+ControlledDhcpv6Srv::commandLocalize6Handler(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.remote_address_ = IOAddress::IPV6_ZERO_ADDRESS();
+    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;
+        } if (entry.first == "interface-id") {
+            if (entry.second->getType() != Element::string) {
+                errmsg << "'interface-id' entry must be a string";
+                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+            }
+            try {
+                string str = entry.second->stringValue();
+                vector<uint8_t> id = util::str::quotedStringToBinary(str);
+                if (id.empty()) {
+                    util::str::decodeFormattedHexString(str, id);
+                }
+                if (id.empty()) {
+                    errmsg << "'interface-id' must be not empty";
+                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
+                }
+                selector.interface_id_ = OptionPtr(new Option(Option::V6,
+                                                              D6O_INTERFACE_ID,
+                                                              id));
+                continue;
+            } catch (...) {
+                errmsg << "value of 'interface-id' was not recognized";
+                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.isV6()) {
+                    errmsg << "bad 'remote' entry: not IPv6";
+                    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 == "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()));
+        }
+    }
+    ConstSubnet6Ptr subnet = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->selectSubnet(selector);
+    if (!subnet) {
+        return (createAnswer(CONTROL_RESULT_EMPTY, "no selected subnet"));
+    }
+    SharedNetwork6Ptr network;
+    subnet->getSharedNetwork(network);
+    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
 ControlledDhcpv6Srv::commandServerTagGetHandler(const std::string&,
                                                 ConstElementPtr) {
@@ -1160,6 +1277,9 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port /*= DHCP6_SERVER_P
     CommandMgr::instance().registerCommand("leases-reclaim",
         std::bind(&ControlledDhcpv6Srv::commandLeasesReclaimHandler, this, ph::_1, ph::_2));
 
+    CommandMgr::instance().registerCommand("localize6",
+        std::bind(&ControlledDhcpv6Srv::commandLocalize6Handler, this, ph::_1, ph::_2));
+
     CommandMgr::instance().registerCommand("server-tag-get",
         std::bind(&ControlledDhcpv6Srv::commandServerTagGetHandler, this, ph::_1, ph::_2));
 
@@ -1242,6 +1362,7 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
         CommandMgr::instance().deregisterCommand("dhcp-disable");
         CommandMgr::instance().deregisterCommand("dhcp-enable");
         CommandMgr::instance().deregisterCommand("leases-reclaim");
+        CommandMgr::instance().deregisterCommand("localize6");
         CommandMgr::instance().deregisterCommand("server-tag-get");
         CommandMgr::instance().deregisterCommand("shutdown");
         CommandMgr::instance().deregisterCommand("statistic-get");
index 3d75bae6f9d1a00e6e5985cf776472bc9d6d0644..e6227bbe3e0046b644c3a19453aaba0d3b177214 100644 (file)
@@ -279,6 +279,19 @@ private:
     commandLeasesReclaimHandler(const std::string& command,
                                 isc::data::ConstElementPtr args);
 
+    /// @brief Handler for processing 'localize6' command
+    ///
+    /// This handler processes localize6 command, which returns
+    /// the result of DHCPv6 subnet selected.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args arguments map { <selector>: <value> }
+    ///
+    /// @return status of the command with the selection result
+    isc::data::ConstElementPtr
+    commandLocalize6Handler(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 131bf179aadfb751df00b5c78af1982106ecc218..1fd21ab3f0d6c4af8a715b261270d78473de9121 100644 (file)
@@ -509,6 +509,7 @@ TEST_F(CtrlDhcpv6SrvTest, 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("\"localize6\"") != 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);
@@ -2089,6 +2090,442 @@ TEST_F(CtrlChannelDhcpv6SrvTest, dhcpEnableOriginId) {
     EXPECT_TRUE(server_->network_state_->isServiceEnabled());
 }
 
+// This test verifies that localize6 command performs sanity check parameters.
+TEST_F(CtrlChannelDhcpv6SrvTest, localize6BadParam) {
+    createUnixChannelServer();
+    std::string response;
+    ConstElementPtr rsp;
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\""
+                    "}", response);
+
+    // The response should be a valid JSON.
+    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+    ASSERT_TRUE(rsp);
+
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"empty arguments\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": [ ]"
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"arguments must be a map\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"foo\": \"bar\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"unknown entry 'foo'\" }",
+              response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"interface\": 1"
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'interface' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"interface-id\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'interface-id' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"interface-id\": \"\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'interface-id' must be not empty";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"interface-id\": \"foo\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "value of 'interface-id' was not recognized";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'remote' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"192.2.1.2\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'remote' entry: not IPv6";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'remote' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": 1"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'link' entry must be a string";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"192.2.1.2\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'link' entry: not IPv6";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"foobar\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "bad 'link' entry: Failed to convert string to address ";
+    expected += "'foobar': Invalid argument";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"classes\": \"foo\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'classes' entry must be a list";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"classes\": [ 1 ]"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 1, \"text\": \"";
+    expected += "'classes' entry must be a list of strings";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize6 command returns proper subnet for a given
+// remote/source address.
+TEST_F(CtrlChannelDhcpv6SrvTest, localize6Addr) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+                                  64, 1, 2, 3, 4, SubnetID(1));
+    SharedNetwork6Ptr network(new SharedNetwork6("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"fe80::abcd\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize6 command returns proper subnet for a given
+// incoming interface.
+TEST_F(CtrlChannelDhcpv6SrvTest, localize6Iface) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+                                  64, 1, 2, 3, 4, SubnetID(1));
+    subnet->setIface("eth0");
+    SharedNetwork6Ptr network(new SharedNetwork6("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6()->add(network);
+    CfgMgr::instance().commit();
+
+    // Different interface: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"bar\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Same interface: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"eth0\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"interface\": \"eth0\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize6 command returns proper subnet for a given
+// relay link address.
+TEST_F(CtrlChannelDhcpv6SrvTest, localize6RelayLinkaddr) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+                                  64, 1, 2, 3, 4, SubnetID(1));
+    SharedNetwork6Ptr network(new SharedNetwork6("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address not in range: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"2001:db8:2::2\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize6 command returns proper subnet for a given
+// relay interface id.
+TEST_F(CtrlChannelDhcpv6SrvTest, localize6RelayInterfaceId) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+                                  64, 1, 2, 3, 4, SubnetID(1));
+    string iface_id("relay");
+    vector<uint8_t> bin(iface_id.cbegin(), iface_id.cend());
+    OptionPtr id(new Option(Option::V6, D6O_INTERFACE_ID, bin));
+    subnet->setInterfaceId(id);
+    SharedNetwork6Ptr network(new SharedNetwork6("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6()->add(network);
+    CfgMgr::instance().commit();
+
+    // Note that below a relay link address is required: it says the client
+    // is behind a relay.
+    // Different interface id: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"2001:db8:2::2\","
+                    "        \"interface-id\": \"'foobar'\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Same interface id: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"2001:db8:2::2\","
+                    "        \"interface-id\": \"'relay'\""
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"link\": \"2001:db8:2::2\","
+                    "        \"interface-id\": \"'relay'\""
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
+// This test verifies if localize6 command returns proper guarded subnet.
+TEST_F(CtrlChannelDhcpv6SrvTest, localize6Class) {
+    createUnixChannelServer();
+    std::string response;
+
+    auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+                                  64, 1, 2, 3, 4, SubnetID(1));
+    subnet->allowClientClass("foobar");
+    SharedNetwork6Ptr network(new SharedNetwork6("foo"));
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet);
+    CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6()->add(network);
+    CfgMgr::instance().commit();
+
+    // Address in range but not in guard: nothing can be selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:1::1\""
+                    "    }"
+                    "}", response);
+    EXPECT_EQ("{ \"result\": 3, \"text\": \"no selected subnet\" }",
+              response);
+
+    // Address in range and in guard: the subnet is selected.
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:1::1\","
+                    "        \"classes\": [ \"foobar\" ]"
+                    "    }"
+                    "}", response);
+    string expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+
+    // Add the subnet to the shared network.
+    subnet->setSharedNetwork(network);
+    sendUnixCommand("{"
+                    "    \"command\": \"localize6\","
+                    "    \"arguments\": {"
+                    "        \"remote\": \"2001:db8:1::1\","
+                    "        \"classes\": [ \"foobar\" ]"
+                    "    }"
+                    "}", response);
+    expected = "{ \"result\": 0, \"text\": \"";
+    expected += "selected shared network 'foo' ";
+    expected += "starting with subnet '2001:db8:1::/64' id 1";
+    expected += "\" }";
+    EXPECT_EQ(expected, response);
+}
+
 /// Verify that concurrent connections over the control channel can be
 ///  established.
 /// @todo Future Kea 1.3 tickets will modify the behavior of the CommandMgr
index aa1e790020b3a9f7c4a4bd04cda61e617a11cc34..6b19b0bc1658430bf739e058c0b9e5e6fdb09677 100644 (file)
@@ -4,15 +4,19 @@
     "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> ]",
+        "{",
+        "    \"command\": \"localize4\",",
+        "    \"arguments\": {",
+        "        \"interface\": <string>,",
+        "        \"address\": <string>,",
+        "        \"relay\": <string>,",
+        "        \"local\": <string>,",
+        "        \"remote\": <string>,",
+        "        \"link\": <string>,",
+        "        \"subnet\": <string>,",
+        "        \"classes\": [ <strings> ]",
         "    }",
-       "}"
+        "}"
     ],
     "description": [ "See <xref linkend=\"command-localize4\"/>" ],
     "name": "localize4",
index 7f182375f35628bf7b0c2c06e2a546c2975f747f..ecaa42741da2994312a1778e8fca3382d5c9b261 100644 (file)
@@ -2,17 +2,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-comment": [ "Possible parameters are the remote address, interface id option, interface name (the three from the DHCPv6 server), etc." ],
     "cmd-syntax": [
-       "{",
-       "    \"command\": \"localize4o6\",",
-       "    \"arguments\": {",
-       "        \"interface\": <string>,",
-       "        \"address\": <string>,",
-       "        \"relay\": <string>,",
-       "        \"classes\": [ <strings> ]",
+        "{",
+        "    \"command\": \"localize4o6\",",
+        "    \"arguments\": {",
+        "        \"interface\": <string>,",
+        "        \"interface-id\": <string>,",
+        "        \"address\": <string>,",
+        "        \"relay\": <string>,",
+        "        \"local\": <string>,",
+        "        \"remote\": <string>,",
+        "        \"link\": <string>,",
+        "        \"subnet\": <string>,",
+        "        \"classes\": [ <strings> ]",
         "    }",
-       "}"
+        "}"
     ],
     "description": [ "See <xref linkend=\"command-localize4o6\"/>" ],
     "name": "localize4o6",
index 7d082d282e1a751d8323a04a5b906b9257f01795..dfe8718c4a2e35b15f524d65ea059140cafc55ee 100644 (file)
@@ -2,17 +2,18 @@
     "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-comment": [ "Possible parameters are the incoming interface name, remote address, relay link address, client classes, etc." ],
     "cmd-syntax": [
-       "{",
-       "    \"command\": \"localize6\",",
-       "    \"arguments\": {",
-       "        \"interface\": <string>,",
-       "        \"address\": <string>,",
-       "        \"relay\": <string>,",
-       "        \"classes\": [ <strings> ]",
+        "{",
+        "    \"command\": \"localize6\",",
+        "    \"arguments\": {",
+        "        \"interface\": <string>,",
+        "        \"interface-id\": <string>,",
+        "        \"remote\": <string>,",
+        "        \"link\": <string>,",
+        "        \"classes\": [ <strings> ]",
         "    }",
-       "}"
+        "}"
     ],
     "description": [ "See <xref linkend=\"command-localize6\"/>" ],
     "name": "localize6",