Browse Source

[3269] Implemented processing of the Confirm message.

Marcin Siodelski 10 years ago
parent
commit
69a5eafcb6

+ 71 - 2
src/bin/dhcp6/dhcp6_srv.cc

@@ -2314,9 +2314,78 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
 
 Pkt6Ptr
 Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
-    /// @todo: Implement this
+    // Get IA_NAs from the Confirm. If there are none, the message is
+    // invalid and must be discarded. There is nothing more to do.
+    OptionCollection ias = confirm->getOptions(D6O_IA_NA);
+    if (ias.empty()) {
+        return (Pkt6Ptr());
+    }
+
+    // The server sends Reply message in response to Confirm.
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, confirm->getTransid()));
-    return reply;
+    // Append necessary options. e.g. server id.
+    appendDefaultOptions(confirm, reply);
+    // Number of addresses verified. If at the end it occurs that no addresses
+    // were verified we will need to dicard the message.
+    int verified = 0;
+    // Check if subnet can be selected for the message. If no subnet
+    // has been selected, the client is not on link.
+    SubnetPtr subnet = selectSubnet(confirm);
+    // Regardless if the subnet has been selected or not, we will iterate
+    // over the IA_NA options to check if they hold any addresses. If there
+    // are no, the Confirm is discarded.
+    // Check addresses in IA_NA options and make sure they are appropriate.
+    for (OptionCollection::const_iterator ia = ias.begin();
+         ia != ias.end(); ++ia) {
+        const OptionCollection& opts = ia->second->getOptions();
+        for (OptionCollection::const_iterator opt = opts.begin();
+             opt != opts.end(); ++opt) {
+            // Ignore options other than IAAddr.
+            if (opt->second->getType() != D6O_IAADDR) {
+                continue;
+            }
+            // Check that the address is in range in the subnet selected.
+            Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+                Option6IAAddr>(opt->second);
+            // If there is subnet selected and the address has been included
+            // in IA_NA, mark it verified and verify that it belongs to the
+            // subnet.
+            if (iaaddr && subnet) {
+                // There is an address so we increase the counter of
+                // addresses verified.
+                ++verified;
+                // If at least one address is not in range, then return
+                // the NotOnLink status code.
+                if (!subnet->inRange(iaaddr->getAddress())) {
+                    std::ostringstream status_msg;
+                        status_msg << "Address " << iaaddr->getAddress()
+                                   << " is not on link.";
+                        reply->addOption(createStatusCode(STATUS_NotOnLink,
+                                                          status_msg.str()));
+                        return (reply);
+                }
+            }
+        }
+    }
+
+    // It seems that the client hasn't included any addresses in which case
+    // the Confirm must be discarded.
+    if (verified == 0) {
+        return (Pkt6Ptr());
+    }
+
+    // If there is a subnet, there were addresses in IA_NA options and the
+    // addresses where consistent with the subnet then the client is on link.
+    if (subnet) {
+        // All addresses in range, so return success.
+        reply->addOption(createStatusCode(STATUS_Success,
+                                          "All addresses are on-link"));
+    } else {
+        reply->addOption(createStatusCode(STATUS_NotOnLink,
+                                          "No subnet selected"));
+    }
+
+    return (reply);
 }
 
 Pkt6Ptr

+ 22 - 2
src/bin/dhcp6/dhcp6_srv.h

@@ -221,9 +221,29 @@ protected:
     /// @param rebind message received from client
     Pkt6Ptr processRebind(const Pkt6Ptr& rebind);
 
-    /// @brief Stub function that will handle incoming CONFIRM messages.
+    /// @brief Processes incoming Confirm message and returns Reply.
     ///
-    /// @param confirm message received from client
+    /// This function processes Confirm message from the client according
+    /// to section 18.2.2. of RFC3315. It discards the Confirm message if
+    /// the message sent by the client contains no addresses, i.e. it has
+    /// no IA_NA options or all IA_NA options contain no IAAddr options.
+    ///
+    /// If the Confirm message contains addresses this function will perform
+    /// the following checks:
+    /// - check if there is appropriate subnet configured for the client
+    /// (e.g. subnet from which addresses are assigned for requests
+    /// received on the particular interface).
+    /// - check if all addresses sent in the Confirm message belong to the
+    /// selected subnet.
+    ///
+    /// If any of the checks above fails, the Reply message with the status
+    /// code NotOnLink is returned. Otherwise, the Reply message with the
+    /// status code Success is returned.
+    ///
+    /// @param confirm Confirm message sent by a client.
+    ///
+    /// @return Reply message from the server al NULL pointer if Confirm
+    /// message should be discarded by the server.
     Pkt6Ptr processConfirm(const Pkt6Ptr& confirm);
 
     /// @brief Stub function that will handle incoming RELEASE messages.

+ 1 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -88,6 +88,7 @@ dhcp6_unittests_SOURCES += rebind_unittest.cc
 dhcp6_unittests_SOURCES += sarr_unittest.cc
 dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
+dhcp6_unittests_SOURCES += confirm_unittest.cc
 
 if CONFIG_BACKEND_BUNDY
 # For Bundy backend, we only need to run the usual tests. There are no

+ 496 - 0
src/bin/dhcp6/tests/confirm_unittest.cc

@@ -0,0 +1,496 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Rebind tests.
+///
+/// - Configuration 0:
+///   - only addresses (no prefixes)
+///   - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::64
+///   - 1 subnet for eth0 and 1 subnet for eth1
+///
+/// - Configuration 1:
+///   - similar to Configuration 0 but different subnets
+///   - pools configured: 2001:db8:3::/64 and 2001:db8:4::/64
+///
+/// - Configuration 2:
+///   - similar to Configuration 0 and Configuration 1
+///   - pools configured: 3000:1::/64 and 3000:2::/64
+///   - this specific configuration is used by tests using relays
+///
+/// - Configuration 3:
+///   - similar to Configuration 2 but with different subnets
+///   - pools configured: 3000:3::/64 and 3000:4::/64
+///   - this specific configuration is used by tests using relays
+///
+/// - Configuration 5:
+///   - only prefixes (no addresses)
+///   - 2 subnets: 2001:db8:1::/40 and 2001:db8:2::/40
+///   - 2 prefix pools: 2001:db8:1::/72 and 2001:db8:2::/72
+///   - 1 subnet for eth0 and 1 subnet for eth1
+///   - this specific configuration is used by tests which don't use relays
+///
+/// - Configuration 6:
+///   - similar to Configuration 5 but with different subnets
+///   - 2 subnets: 2001:db8:3::/40 and 2001:db8:4::/40
+///   - 2 prefix pools: 2001:db8:3::/72 and 2001:db8:4::/72
+///   - delegated length /80
+///   - this specific configuration is used by tests which don't use relays
+const char* CONFIRM_CONFIGS[] = {
+// Configuration 0
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 1
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:3::/64\" ],"
+        "    \"subnet\": \"2001:db8:3::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:4::/64\" ],"
+        "    \"subnet\": \"2001:db8:4::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 2
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"3000:1::/64\" ],"
+        "    \"subnet\": \"3000:1::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " },"
+        " {"
+        "    \"pool\": [ \"3000:2::/64\" ],"
+        "    \"subnet\": \"3000:2::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 3
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"3000:3::/64\" ],"
+        "    \"subnet\": \"3000:3::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " },"
+        " {"
+        "    \"pool\": [ \"3000:4::/64\" ],"
+        "    \"subnet\": \"3000:4::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 4
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:1:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:1::/40\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " },"
+        " {"
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:2:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:2::/40\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 5
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:3:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:3::/40\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " },"
+        " {"
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:4:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:4::/40\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+};
+
+/// @brief Test fixture class for testing Rebind.
+class ConfirmTest : public Dhcpv6SrvTest {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    ConfirmTest()
+        : Dhcpv6SrvTest(),
+          iface_mgr_test_config_(true) {
+    }
+
+    /// @brief Increases last byte of the address.
+    ///
+    /// This function is helpful to find a different address that is within
+    /// the same subnet as the input address. It is achieved by increasing
+    /// the last byte of the input address by one.
+    ///
+    /// @param input_addr An input address.
+    ///
+    /// @return New address.
+    IOAddress bumpAddress(const IOAddress& input_addr);
+
+    /// @brief Increases specific byte in the address by one.
+    ///
+    /// This function is called by @c bumpAddress and @c bumpSubnet.
+    ///
+    /// @param input_addr An input address.
+    ///
+    /// @return New address.
+    IOAddress bumpByteInAddress(const IOAddress& input_addr,
+                                const size_t byte_num);
+
+    /// @brief Increases the first byte of the address.
+    ///
+    /// This function is helpful to find an address which belongs to the
+    /// different subnet than the input address. It is achived by increasing
+    /// the first byte of the input address.
+    ///
+    /// @param input_addr An input addres.
+    ///
+    /// @return New address.
+    IOAddress bumpSubnet(const IOAddress& input_addr);
+
+    /// @brief Make 4-way exchange to obtain a lease.
+    ///
+    /// @param config_index Index of the configuration held in @c CONFIRM_CONFIGS
+    /// to use to configure the server.
+    /// @param subnets_num Number of subnets being created with the specified
+    /// configuration.
+    /// @param client Object representing a test DHCPv6 client to use.
+    void requestLease(const int config_index, const int subnets_num,
+                      Dhcp6Client& client);
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+void
+ConfirmTest::requestLease(const int config_index, const int subnets_num,
+                         Dhcp6Client& client) {
+    // Check that the index is in the configuration table.
+    ASSERT_LT(config_index, sizeof(CONFIRM_CONFIGS)/sizeof(CONFIRM_CONFIGS[0]));
+    // Configure the server.
+    configure(CONFIRM_CONFIGS[config_index], *client.getServer());
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_EQ(subnets_num, subnets->size());
+    // Do the actual 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+    // Simulate aging of leases, by moving their cltt_ back by 1000s.
+    client.fastFwdTime(1000);
+    // Make sure that we have obtained a lease that belongs to one of the
+    // subnets.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client = client.getLease(0);
+    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client.addr_,
+                                              ClientClasses()));
+    // Check that the client's lease matches the information on the server
+    // side.
+    Lease6Ptr lease_server = checkLease(lease_client);
+    ASSERT_TRUE(lease_server);
+    // And that status code was OK.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(0));
+}
+
+IOAddress
+ConfirmTest::bumpAddress(const IOAddress& input_addr) {
+    return (bumpByteInAddress(input_addr, V6ADDRESS_LEN - 1));
+}
+
+IOAddress
+ConfirmTest::bumpByteInAddress(const IOAddress& input_addr,
+                               const size_t byte_num) {
+    std::vector<uint8_t> input_addr_buffer = input_addr.toBytes();
+    if (input_addr_buffer.size() > byte_num) {
+        ++input_addr_buffer[byte_num];
+        return (IOAddress::fromBytes(AF_INET6, &input_addr_buffer[0]));
+    }
+    return (input_addr);
+}
+
+
+IOAddress
+ConfirmTest::bumpSubnet(const IOAddress& input_addr) {
+    return (bumpByteInAddress(input_addr, 0));
+}
+
+// Test that directly connected client's Confirm message is processed and Reply
+// message is sent back. In this test case, the client sends Confirm for two
+// addresses that belong to the same IAID and are sent within the same IA_NA
+// option.
+TEST_F(ConfirmTest, directClientSameIAID) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(0, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client1 = client.getLease(0);
+    // Clone the lease and modify its address so as it is still in the range
+    // of the subnet to which the first lease belongs. When the client sends
+    // the Confirm it should include both addresses and the server should
+    // send Success because both of these addresses are on-link, regardless
+    // what the server has in the lease database.
+    Lease6 lease_client2 = lease_client1;
+    lease_client2.addr_ = bumpAddress(lease_client2.addr_);
+    client.createLease(lease_client2);
+    ASSERT_EQ(2, client.getLeaseNum());
+    // Send Confirm message to the server.
+    ASSERT_NO_THROW(client.doConfirm());
+    // Client should have received a status code option and this option should
+    // indicate the success.
+    ASSERT_TRUE(client.receivedStatusCode());
+    ASSERT_EQ(STATUS_Success, client.getStatusCode());
+
+    ASSERT_EQ(2, client.getLeaseNum());
+    lease_client2 = client.getLease(1);
+    lease_client2.addr_ = bumpSubnet(lease_client2.addr_);
+    client.createLease(lease_client2);
+    // Send confirm to the server. This time, one of the leases contains the
+    // address which doesn't belong to the configured subnet and the server
+    // should respond with STATUS_NotOnLink.
+    ASSERT_NO_THROW(client.doConfirm());
+    ASSERT_TRUE(client.receivedStatusCode());
+    ASSERT_EQ(STATUS_NotOnLink, client.getStatusCode());
+    // Make sure that the server id has been included.
+    EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+}
+
+// Test that directly connected client's Confirm message is processed and Reply
+// message is sent back. In this test case, the client sends Confirm for two
+// addresses that belong to different IAIDs and are sent within the different
+// IA_NA options.
+TEST_F(ConfirmTest, directClientDifferentIAID) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(0, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client1 = client.getLease(0);
+    // Clone the lease and modify its address so as it is still in the range
+    // of the subnet to which the first lease belongs. When the client sends
+    // the Confirm it should include both addresses and the server should
+    // send Success because both of these addresses are on-link, regardless
+    // what the server has in the lease database.
+    Lease6 lease_client2 = lease_client1;
+    ++lease_client2.iaid_;
+    lease_client2.addr_ = bumpAddress(lease_client2.addr_);
+    client.createLease(lease_client2);
+    ASSERT_EQ(2, client.getLeaseNum());
+    // Send Confirm message to the server.
+    ASSERT_NO_THROW(client.doConfirm());
+    // Client should have received a status code option and this option should
+    // indicate the success.
+    ASSERT_TRUE(client.receivedStatusCode());
+    ASSERT_EQ(STATUS_Success, client.getStatusCode());
+    // Make sure that the server id has been included.
+    EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+
+
+    ASSERT_EQ(2, client.getLeaseNum());
+    lease_client2 = client.getLease(1);
+    lease_client2.addr_ = bumpSubnet(lease_client2.addr_);
+    client.createLease(lease_client2);
+    // Send confirm to the server. This time, one of the leases contains the
+    // address which doesn't belong to the configured subnet and the server
+    // should respond with STATUS_NotOnLink.
+    ASSERT_NO_THROW(client.doConfirm());
+    ASSERT_TRUE(client.receivedStatusCode());
+    ASSERT_EQ(STATUS_NotOnLink, client.getStatusCode());
+    // Make sure that the server id has been included.
+    EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+}
+
+
+// Test that relayed client's Confirm message is processed and Reply message
+// is sent back.
+TEST_F(ConfirmTest, relayedClient) {
+    Dhcp6Client client;
+    // Client to send relayed message.
+    client.useRelay();
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(2, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client1 = client.getLease(0);
+    // Clone the lease and modify its address so as it is still in the range
+    // of the subnet to which the first lease belongs. When the client sends
+    // the Confirm it should include both addresses and the server should
+    // send Success because both of these addresses are on-link, regardless
+    // what the server has in the lease database.
+    Lease6 lease_client2 = lease_client1;
+    lease_client2.addr_ = bumpAddress(lease_client2.addr_);
+    ++lease_client2.iaid_;
+    client.createLease(lease_client2);
+    // Send Confirm message to the server.
+    ASSERT_NO_THROW(client.doConfirm());
+    // Client should have received a status code option and this option should
+    // indicate the success.
+    ASSERT_TRUE(client.receivedStatusCode());
+    ASSERT_EQ(STATUS_Success, client.getStatusCode());
+
+    lease_client2 = client.getLease(1);
+    lease_client2.addr_ = bumpSubnet(lease_client2.addr_);
+    client.createLease(lease_client2);
+    // Send confirm to the server. This time, one of the leases contains the
+    // address which doesn't belong to the configured subnet and the server
+    // should respond with STATUS_NotOnLink.
+    ASSERT_NO_THROW(client.doConfirm());
+    ASSERT_TRUE(client.receivedStatusCode());
+    ASSERT_EQ(STATUS_NotOnLink, client.getStatusCode());
+    // Make sure that the server id has been included.
+    EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+}
+
+// Test that the Confirm message without any addresses is discarded.
+TEST_F(ConfirmTest, relayedClientNoAddress) {
+    Dhcp6Client client;
+    // Configure the server.
+    configure(CONFIRM_CONFIGS[2], *client.getServer());
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_EQ(2, subnets->size());
+    // Client to send relayed message.
+    client.useRelay();
+    // Send Confirm message to the server. This message will contain no
+    // addresses because client has no leases.
+    ASSERT_NO_THROW(client.doConfirm());
+    EXPECT_FALSE(client.getContext().response_);
+}
+
+// This test checks that the relayed Confirm messsage is processed by the server
+// when sent to unicast address.
+TEST_F(ConfirmTest, relayedUnicast) {
+    Dhcp6Client client;
+    // Client to send relayed message.
+    client.useRelay();
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(2, 2, client));
+    // Make sure we have got the lease.
+    ASSERT_GT(client.getLeaseNum(), 0);
+    client.setDestAddress(IOAddress("2001:db8:1::1"));
+    // Send Confirm message to the server.
+    ASSERT_NO_THROW(client.doConfirm());
+    // Client should have received a response.
+    ASSERT_TRUE(client.getContext().response_);
+    // Client should have received a status code option and this option should
+    // indicate the success.
+    ASSERT_TRUE(client.receivedStatusCode());
+    ASSERT_EQ(STATUS_Success, client.getStatusCode());
+    // Make sure that the server id has been included.
+    EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+}
+
+// This test checks that the Confirm message is discarded by the server if it
+// has been sent to unicast address (RFC3315, section 15).
+TEST_F(ConfirmTest, unicast) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(0, 2, client));
+    // Make sure the client has got the lease.
+    ASSERT_GT(client.getLeaseNum(), 0);
+    // Send Confirm message to the server to the unicast address.
+    client.setDestAddress(IOAddress("2001:db8:1::1"));
+    ASSERT_NO_THROW(client.doConfirm());
+    // Mak sure that the server discarded client's Confirm message.
+    EXPECT_FALSE(client.getContext().response_);
+}
+
+} // end of anonymous namespace

+ 84 - 25
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -58,8 +58,11 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
 void
 Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
     typedef OptionCollection Opts;
-    // Get all options in the reply message and pick IA_NA and IA_PD.
+    // Get all options in the reply message and pick IA_NA, IA_PD and
+    // Status code.
     Opts opts = reply->options_;
+    // Set the global status code to default: success and not received.
+    config_.resetGlobalStatusCode();
     for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
         Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt->second);
         if (!ia) {
@@ -137,6 +140,16 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
             applyLease(lease_info);
         }
     }
+
+    // Get the global status code.
+    OptionCustomPtr status_code = boost::dynamic_pointer_cast<
+        OptionCustom>(reply->getOption(D6O_STATUS_CODE));
+    // If status code has been sent, we override the default status code:
+    // Success and record that we have received the status code.
+    if (status_code) {
+        config_.received_status_code_ = true;
+        config_.status_code_ = status_code->readInteger<uint16_t>(0);
+    }
 }
 
 void
@@ -150,7 +163,8 @@ Dhcp6Client::applyLease(const LeaseInfo& lease_info) {
         // lease assignment so we keep what we have.
         if ((existing_lease.iaid_ == lease_info.lease_.iaid_)
             && (existing_lease.type_ == lease_info.lease_.type_)
-            && (lease_info.lease_.addr_ != asiolink::IOAddress("::"))) {
+            && (lease_info.lease_.addr_ != asiolink::IOAddress("::"))
+            && (existing_lease.addr_ == lease_info.lease_.addr_)) {
             config_.leases_[i] = lease_info;
             return;
 
@@ -184,33 +198,41 @@ void
 Dhcp6Client::copyIAsFromLeases(const Pkt6Ptr& dest) const {
     // Go over leases and create IA_NA and IA_PD options from them.
     // Create one IA per lease.
-    for (std::vector<LeaseInfo>::const_iterator info = config_.leases_.begin();
-         info != config_.leases_.end(); ++info) {
-        Lease6 lease = info->lease_;
-        if (lease.type_ == Lease::TYPE_NA) {
-            Option6IAPtr opt(new Option6IA(D6O_IA_NA, lease.iaid_));
-            opt->setT1(lease.t1_);
-            opt->setT2(lease.t1_);
-            opt->addOption(Option6IAAddrPtr(new
-                                            Option6IAAddr(D6O_IAADDR,
-                                                          lease.addr_,
-                                                          lease.preferred_lft_,
-                                                          lease.valid_lft_)));
-            dest->addOption(opt);
-        } else if (lease.type_ == Lease::TYPE_PD) {
-            Option6IAPtr opt(new Option6IA(D6O_IA_PD, lease.iaid_));
-            opt->setT1(lease.t1_);
-            opt->setT2(lease.t1_);
-            opt->addOption(Option6IAPrefixPtr(new Option6IAPrefix(D6O_IAPREFIX,
-                                                                  lease.addr_,
-                                                                  lease.prefixlen_,
-                                                                  lease.preferred_lft_,
-                                                                  lease.valid_lft_)));
-            dest->addOption(opt);
+    std::set<uint32_t> iaids = getIAIDs();
+    for (std::set<uint32_t>::const_iterator iaid = iaids.begin();
+         iaid != iaids.end(); ++iaid) {
+        std::vector<Lease6> leases = getLeasesByIAID(*iaid);
+        Option6IAPtr opt(new Option6IA(leases[0].type_ == Lease::TYPE_NA ?
+                                       D6O_IA_NA : D6O_IA_PD, *iaid));
+        opt->setT1(leases[0].t1_);
+        opt->setT2(leases[0].t2_);
+        for (std::vector<Lease6>::const_iterator lease = leases.begin();
+             lease != leases.end(); ++lease) {
+            if (lease->type_ == Lease::TYPE_NA) {
+                opt->addOption(Option6IAAddrPtr(new Option6IAAddr(D6O_IAADDR,
+                                                          lease->addr_,
+                                                          lease->preferred_lft_,
+                                                          lease->valid_lft_)));
+            } else if (lease->type_ == Lease::TYPE_PD) {
+                opt->addOption(Option6IAAddrPtr(new Option6IAPrefix(D6O_IAPREFIX,
+                                                          lease->addr_,
+                                                          lease->prefixlen_,
+                                                          lease->preferred_lft_,
+                                                          lease->valid_lft_)));
+
+            }
         }
+        dest->addOption(opt);
     }
 }
 
+void
+Dhcp6Client::createLease(const Lease6& lease) {
+    LeaseInfo info;
+    info.lease_ = lease;
+    applyLease(info);
+}
+
 Pkt6Ptr
 Dhcp6Client::createMsg(const uint8_t msg_type) {
     Pkt6Ptr msg(new Pkt6(msg_type, curr_transid_++));
@@ -278,6 +300,19 @@ Dhcp6Client::doRebind() {
 }
 
 void
+Dhcp6Client::doConfirm() {
+    context_.query_ = createMsg(DHCPV6_CONFIRM);
+    copyIAsFromLeases(context_.query_);
+    sendMsg(context_.query_);
+    context_.response_ = receiveOneMsg();
+    // Set the global status code to default: success and not received.
+    config_.resetGlobalStatusCode();
+    if (context_.response_) {
+        applyRcvdConfiguration(context_.response_);
+    }
+}
+
+void
 Dhcp6Client::fastFwdTime(const uint32_t secs) {
     // Iterate over all leases and move their cltt backwards.
     for (int i = 0; i < config_.leases_.size(); ++i) {
@@ -314,6 +349,30 @@ Dhcp6Client::getClientId() const {
     return (opt_client_id);
 }
 
+std::set<uint32_t>
+Dhcp6Client::getIAIDs() const {
+    std::set<uint32_t> iaids;
+    for (std::vector<LeaseInfo>::const_iterator lease_info =
+             config_.leases_.begin(); lease_info != config_.leases_.end();
+         ++lease_info) {
+        iaids.insert(lease_info->lease_.iaid_);
+    }
+    return (iaids);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByIAID(const uint32_t iaid) const {
+    std::vector<Lease6> leases;
+    for (std::vector<LeaseInfo>::const_iterator lease_info =
+             config_.leases_.begin(); lease_info != config_.leases_.end();
+         ++lease_info) {
+        if (lease_info->lease_.iaid_ == iaid) {
+            leases.push_back(lease_info->lease_);
+        }
+    }
+    return (leases);
+}
+
 void
 Dhcp6Client::modifyDUID() {
     if (!duid_) {

+ 62 - 0
src/bin/dhcp6/tests/dhcp6_client.h

@@ -21,6 +21,7 @@
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
+#include <set>
 
 namespace isc {
 namespace dhcp {
@@ -76,9 +77,30 @@ public:
     struct Configuration {
         std::vector<LeaseInfo> leases_;
 
+        /// @brief Status code received in the global option scope.
+        uint16_t status_code_;
+
+        /// @brief Indicates if the status code has been received in the
+        /// last transaction.
+        bool received_status_code_;
+
+        /// @brief Constructor.
+        Configuration() {
+            clear();
+        }
+
         /// @brief Clears configuration.
         void clear() {
             leases_.clear();
+            resetGlobalStatusCode();
+        }
+
+        /// @brief Clears global status code.
+        ///
+        /// This function should be called before the new message is received.
+        void resetGlobalStatusCode() {
+            status_code_ = 0;
+            received_status_code_ = false;
         }
     };
 
@@ -121,6 +143,16 @@ public:
     /// @param srv Object representing server under test.
     Dhcp6Client(boost::shared_ptr<isc::test::NakedDhcpv6Srv>& srv);
 
+    /// @brief Create lease for the client.
+    ///
+    /// This function creates new lease on the client side without contacting
+    /// the server. This may be useful for the negative tests in which the
+    /// client is supposed to send invalid addresses/prefixes to the server
+    /// and expect certain responses.
+    ///
+    /// @param lease A lease to be applied for the client.
+    void createLease(const Lease6& lease);
+
     /// @brief Performs a 4-way echange between the client and the server.
     ///
     /// If the 4-way exchange is successful, the client should acquire leases
@@ -179,6 +211,12 @@ public:
     /// @todo Perform sanity checks on returned messages.
     void doRequest();
 
+    /// @brief Sends Confirm to the server and receives Reply.
+    ///
+    /// This function simulates sending the Confirm message to the server and
+    /// receiving server's response (if any).
+    void doConfirm();
+
     /// @brief Removes the stateful configuration obtained from the server.
     ///
     /// It removes all leases held by the client.
@@ -203,6 +241,9 @@ public:
         return (context_);
     }
 
+    /// @brief Returns the collection of IAIDs held by the client.
+    std::set<uint32_t> getIAIDs() const;
+
     /// @brief Returns lease at specified index.
     ///
     /// @warning This method doesn't check if the specified index is out of
@@ -215,6 +256,19 @@ public:
         return (config_.leases_[at].lease_);
     }
 
+    /// @brief Returns collection of leases for specified IAID.
+    ///
+    /// @param iaid IAID for which the leases should be returned.
+    ///
+    /// @return Vector containing leases for the IAID.
+    std::vector<Lease6> getLeasesByIAID(const uint32_t iaid) const;
+
+    /// @brief Returns the value of the global status code for the last
+    /// transaction.
+    uint16_t getStatusCode() const {
+        return (config_.status_code_);
+    }
+
     /// @brief Returns status code set by the server for the lease.
     ///
     /// @warning This method doesn't check if the specified index is out of
@@ -247,6 +301,14 @@ public:
     /// @c Dhcp6Client::getClientId
     void modifyDUID();
 
+    /// @brief Checks if the global status code was received in the response
+    /// from the server.
+    ///
+    /// @return true if the global status code option was received.
+    bool receivedStatusCode() const {
+        return (config_.received_status_code_);
+    }
+
     /// @brief Sets destination address for the messages being sent by the
     /// client.
     ///