Browse Source

[master] Merge branch 'trac3565' (host reservations mac+dhcpv6)

Conflicts:
	src/bin/dhcp6/dhcp6_srv.cc
Tomek Mrugalski 10 years ago
parent
commit
cfbe13ad05

+ 70 - 0
doc/guide/dhcp4-srv.xml

@@ -2061,6 +2061,76 @@ temporarily override a list of interface names and listen on all interfaces.
       reservation. Such a feature will be added in the upcoming Kea
       releases.</para>
     </section>
+
+    <section id="reservation4-mode">
+      <title>Fine Tuning IPv4 Host Reservation</title>
+
+      <note>
+        <para><command>reservation-mode</command> configuration parameter in DHCPv4
+        server is accepted, but not used in the Kea 0.9.1 beta. Full implementation
+        will be available in the upcoming releases.</para>
+      </note>
+
+      <para>Host reservation capability introduces additional restrictions for the
+      allocation engine during lease selection and renewal. In particular, three
+      major checks are necessary. First, when selecting a new lease, it is not
+      sufficient for a candidate lease to be not used by another DHCP client. It
+      also must not be reserved for another client. Second, when renewing a lease,
+      additional check must be performed whether the address being renewed is not
+      reserved for another client. Finally, when a host renews an address, the server
+      has to check whether there's a reservation for this host, so the exisiting
+      (dynamically allocated) address should be revoked and the reserved one be
+      used instead.
+      </para>
+      <para>Some of those checks may be unnecessary in certain deployments. Not
+      performing them may improve performance. The Kea server provides the
+      <command>reservation-mode</command> configuration parameter to select the
+      types of reservations allowed for the particular subnet. Each reservation
+      type has different constraints for the checks to be performed by the
+      server when allocating or renewing a lease for the client.
+      Allowed values are:
+
+      <itemizedlist>
+      <listitem><simpara> <command>all</command> - enables all host reservation
+      types. This is the default value. This setting is the safest and the most
+      flexible. It allows in-pool and out-of-pool reservations. As all checks
+      are conducted, it is also the slowest.
+      </simpara></listitem>
+
+      <listitem><simpara> <command>out-of-pool</command> - allows only out of
+      pool host reservations.  With this setting in place, the server may assume
+      that all host reservations are for addresses that do not belong to the
+      dynamic pool. Therefore it can skip the reservation checks when dealing
+      with in-pool addresses, thus improving performance. Do not use this mode
+      if any of your reservations use in-pool address. Caution is advised when
+      using this setting. Kea 0.9.1 does not sanity check the reservations against
+      <command>reservation-mode</command>. Misconfiguration may cause problems.
+      </simpara></listitem>
+
+      <listitem><simpara>
+      <command>disabled</command> - host reservation support is disabled. As there
+      are no reservations, the server will skip all checks. Any reservations defined
+      will be completely ignored. As the checks are skipped, the server may
+      operate faster in this mode.
+      </simpara></listitem>
+
+      </itemizedlist>
+      </para>
+
+      <para>
+        An example configuration that disables reservation looks like follows:
+	<screen>
+"Dhcp4": {
+    "subnet4": [
+        "subnet": "192.0.2.0/24",
+        <userinput>"reservation-mode": "disabled"</userinput>,
+        ...
+    ]
+}
+</screen>
+      </para>        
+   </section>
+
   </section>
   <!-- end of host reservations section -->
 

+ 68 - 0
doc/guide/dhcp6-srv.xml

@@ -1976,6 +1976,74 @@ should include options from the isc option space:
       releases.</para>
     </section>
 
+    <section id="reservation6-mode">
+      <title>Fine Tuning IPv6 Host Reservation</title>
+
+      <note>
+        <para><command>reservation-mode</command> in the DHCPv6 server is
+        implemented in Kea 0.9.1 beta, but has not been tested and is
+        considered experimental.</para>
+      </note>
+
+      <para>Host reservation capability introduces additional restrictions for the
+      allocation engine during lease selection and renewal. In particular, three
+      major checks are necessary. First, when selecting a new lease, it is not
+      sufficient for a candidate lease to be not used by another DHCP client. It
+      also must not be reserved for another client. Second, when renewing a lease,
+      additional check must be performed whether the address being renewed is not
+      reserved for another client. Finally, when a host renews an address or a
+      prefix, the server has to check whether there's a reservation for this host,
+      so the existing (dynamically allocated) address should be revoked and the
+      reserved one be used instead.</para>
+      <para>Some of those checks may be unnecessary in certain deployments. Not
+      performing them may improve performance. The Kea server provides the
+      <command>reservation-mode</command> configuration parameter to select the
+      types of reservations allowed for the particular subnet. Each reservation
+      type has different constraints for the checks to be performed by the
+      server when allocating or renewing a lease for the client.
+      Allowed values are:
+
+      <itemizedlist>
+      <listitem><simpara> <command>all</command> - enables all host reservation
+      types. This is the default value. This setting is the safest and the most
+      flexible. It allows in-pool and out-of-pool reservations. As all checks
+      are conducted, it is also the slowest.
+      </simpara></listitem>
+
+      <listitem><simpara> <command>out-of-pool</command> - allows only out of
+      pool host reservations.  With this setting in place, the server may assume
+      that all host reservations are for addresses that do not belong to the
+      dynamic pool. Therefore it can skip the reservation checks when dealing
+      with in-pool addresses, thus improving performance. Do not use this mode
+      if any of your reservations use in-pool address. Caution is advised when
+      using this setting. Kea 0.9.1 does not sanity check the reservations against
+      <command>reservation-mode</command>. Misconfiguration may cause problems.
+      </simpara></listitem>
+
+      <listitem><simpara>
+      <command>disabled</command> - host reservation support is disabled. As there
+      are no reservations, the server will skip all checks. Any reservations defined
+      will be completely ignored. As the checks are skipped, the server may
+      operate faster in this mode.
+      </simpara></listitem>
+
+      </itemizedlist>
+      </para>
+
+      <para>
+        An example configuration that disables reservation looks like follows:
+	<screen>
+"Dhcp6": {
+    "subnet6": [
+        "subnet": "2001:db8:1::/64",
+        <userinput>"reservation-mode": "disabled"</userinput>,
+        ...
+    ]
+}
+</screen>
+      </para>        
+   </section>
+
     <!-- @todo: add support for per IA reservation (that specifies IAID in
                 the ip-addresses and prefixes) -->
   </section>

+ 9 - 1
src/bin/dhcp4/dhcp4.spec

@@ -381,7 +381,15 @@
                         "item_default": "0.0.0.0"
                       } ]
                   }
-                } ]
+                },
+                {
+                  "item_name": "reservation-mode",
+                  "item_type": "string",
+                  "item_optional": true,
+                  "item_default": "all",
+                  "item_description": "Specifies allowed host reservation types. Disabling unused modes may improve performance. Allowed values: disabled, off, out-of-pool, all"
+                }
+             ]
          }
       },
 

+ 2 - 1
src/bin/dhcp4/json_config_parser.cc

@@ -183,7 +183,8 @@ protected:
         } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0) ||
                    (config_id.compare("client-class") == 0) ||
-                   (config_id.compare("next-server") == 0)) {
+                   (config_id.compare("next-server") == 0) ||
+                   (config_id.compare("reservation-mode") == 0)) {
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pools") == 0) {
             parser = new Pools4ListParser(config_id, pools_);

+ 71 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -3496,8 +3496,79 @@ TEST_F(Dhcp4ParserTest, reservationBogus) {
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 1);
+}
 
+/// The goal of this test is to verify that Host Reservation modes can be
+/// specified on a per-subnet basis.
+TEST_F(Dhcp4ParserTest, hostReservationPerSubnet) {
 
+    /// - Configuration:
+    ///   - only addresses (no prefixes)
+    ///   - 4 subnets with:
+    ///       - 192.0.2.0/24 (all reservations enabled)
+    ///       - 192.0.3.0/24 (out-of-pool reservations)
+    ///       - 192.0.4.0/24 (reservations disabled)
+    ///       - 192.0.5.0/24 (reservations not specified)
+    const char* hr_config =
+        "{ "
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"reservation-mode\": \"all\""
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"192.0.3.0/24\" } ],"
+        "    \"subnet\": \"192.0.3.0/24\", "
+        "    \"reservation-mode\": \"out-of-pool\""
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"192.0.4.0/24\" } ],"
+        "    \"subnet\": \"192.0.4.0/24\", "
+        "    \"reservation-mode\": \"disabled\""
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"192.0.5.0/24\" } ],"
+        "    \"subnet\": \"192.0.5.0/24\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(hr_config);
+    ConstElementPtr result;
+    EXPECT_NO_THROW(result = configureDhcp4Server(*srv_, json));
+
+    // returned value should be 0 (success)
+    checkResult(result, 0);
+
+    // Let's get all subnets and check that there are 4 of them.
+    ConstCfgSubnets4Ptr subnets = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+    ASSERT_TRUE(subnets);
+    const Subnet4Collection* subnet_col = subnets->getAll();
+    ASSERT_EQ(4, subnet_col->size()); // We expect 4 subnets
+
+    // Let's check if the parsed subnets have correct HR modes.
+
+    // Subnet 1
+    Subnet4Ptr subnet;
+    subnet = subnets->selectSubnet(IOAddress("192.0.2.1"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
+
+    // Subnet 2
+    subnet = subnets->selectSubnet(IOAddress("192.0.3.1"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(Subnet::HR_OUT_OF_POOL, subnet->getHostReservationMode());
+
+    // Subnet 3
+    subnet = subnets->selectSubnet(IOAddress("192.0.4.1"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(Subnet::HR_DISABLED, subnet->getHostReservationMode());
+
+    // Subnet 4
+    subnet = subnets->selectSubnet(IOAddress("192.0.5.1"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
 }
 
 }

+ 7 - 0
src/bin/dhcp6/dhcp6.spec

@@ -433,6 +433,13 @@
                         }
                       } ]
                   }
+                },
+                {
+                  "item_name": "reservation-mode",
+                  "item_type": "string",
+                  "item_optional": true,
+                  "item_default": "all",
+                  "item_description": "Specifies allowed host reservation types. Disabling unused modes may improve performance. Allowed values: disabled, off, out-of-pool, all"
                 } ]
             }
       },

+ 15 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -1580,6 +1580,15 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // we extend, cancel or otherwise deal with the leases.
     bool hints_present = !ctx.hints_.empty();
 
+    /// @todo: This was clarfied in draft-ietf-dhc-dhcpv6-stateful-issues that
+    /// the server is allowed to assign new leases in both Renew and Rebind. For
+    /// now, we only support it in Renew, because it breaks a lot of Rebind
+    /// unit-tests. Ultimately, whether we allow it or not, should be exposed
+    /// as configurable policy. See ticket #3717.
+    if (query->getType() == DHCPV6_RENEW) {
+        ctx.allow_new_leases_in_renewals_ = true;
+    }
+
     Lease6Collection leases = alloc_engine_->renewLeases6(ctx);
 
     // Ok, now we have the leases extended. We have:
@@ -1747,6 +1756,12 @@ Dhcpv6Srv::extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // we extend, cancel or otherwise deal with the leases.
     bool hints_present = !ctx.hints_.empty();
 
+    /// @todo: The draft-ietf-dhc-dhcpv6-stateful-issues added a new capability
+    /// of the server to to assign new PD leases in both Renew and Rebind.
+    /// There's allow_new_leases_in_renewals_ in the ClientContext6, but we
+    /// currently not use it in PD yet. This should be implemented as part
+    /// of the stateful-issues implementation effort. See ticket #3718.
+
     // Call Allocation Engine and attempt to renew leases. Number of things
     // may happen. Leases may be extended, revoked (if the lease is no longer
     // valid or reserved for someone else), or new leases may be added.

+ 2 - 1
src/bin/dhcp6/json_config_parser.cc

@@ -390,7 +390,8 @@ protected:
         } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0) ||
                    (config_id.compare("client-class") == 0) ||
-                   (config_id.compare("interface-id") == 0)) {
+                   (config_id.compare("interface-id") == 0) ||
+                   (config_id.compare("reservation-mode") == 0)) {
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pools") == 0) {
             parser = new Pools6ListParser(config_id, pools_);

+ 76 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -3703,4 +3703,80 @@ TEST_F(Dhcp6ParserTest, macSourcesBogus) {
     checkResult(status, 1);
 }
 
+/// The goal of this test is to verify that Host Reservation modes can be
+/// specified on a per-subnet basis.
+TEST_F(Dhcp6ParserTest, hostReservationPerSubnet) {
+
+    /// - Configuration:
+    ///   - only addresses (no prefixes)
+    ///   - 4 subnets with:
+    ///       - 2001:db8:1::/64 (all reservations enabled)
+    ///       - 2001:db8:2::/64 (out-of-pool reservations)
+    ///       - 2001:db8:3::/64 (reservations disabled)
+    ///       - 2001:db8:3::/64 (reservations not specified)
+    const char* HR_CONFIG =
+        "{"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"reservation-mode\": \"all\""
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"reservation-mode\": \"out-of-pool\""
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"2001:db8:3::/64\" } ],"
+        "    \"subnet\": \"2001:db8:3::/48\", "
+        "    \"reservation-mode\": \"disabled\""
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"2001:db8:4::/64\" } ],"
+        "    \"subnet\": \"2001:db8:4::/48\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr status;
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
+                    Element::fromJSON(HR_CONFIG)));
+
+    // returned value should be 0 (success)
+    checkResult(status, 0);
+    CfgMgr::instance().commit();
+
+    // Let's get all subnets and check that there are 4 of them.
+    ConstCfgSubnets6Ptr subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
+    ASSERT_TRUE(subnets);
+    const Subnet6Collection* subnet_col = subnets->getAll();
+    ASSERT_EQ(4, subnet_col->size()); // We expect 4 subnets
+
+    // Let's check if the parsed subnets have correct HR modes.
+
+    // Subnet 1
+    Subnet6Ptr subnet;
+    subnet = subnets->selectSubnet(IOAddress("2001:db8:1::1"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
+
+    // Subnet 2
+    subnet = subnets->selectSubnet(IOAddress("2001:db8:2::1"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(Subnet::HR_OUT_OF_POOL, subnet->getHostReservationMode());
+
+    // Subnet 3
+    subnet = subnets->selectSubnet(IOAddress("2001:db8:3::1"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(Subnet::HR_DISABLED, subnet->getHostReservationMode());
+
+    // Subnet 4
+    subnet = subnets->selectSubnet(IOAddress("2001:db8:4::1"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
+}
+
 };

+ 31 - 13
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -905,20 +905,38 @@ TEST_F(Dhcpv6SrvTest, pdRenewBasic) {
 }
 
 // This test verifies that incoming (invalid) RENEW with an address
-// can be handled properly.
-//
-// This test checks 3 scenarios:
-// 1. there is no such lease at all
-// 2. there is such a lease, but it is assigned to a different IAID
-// 3. there is such a lease, but it belongs to a different client
+// can be handled properly. This has changed with #3565. The server
+// is now able to allocate a lease in Renew if it's available.
+// Previous testRenewReject is now split into 3 tests.
 //
-// expected:
-// - returned REPLY message has copy of client-id
-// - returned REPLY message has server-id
-// - returned REPLY message has IA_NA that includes STATUS-CODE
-// - No lease in LeaseMgr
-TEST_F(Dhcpv6SrvTest, RenewReject) {
-    testRenewReject(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead"));
+// This test checks the first scenario: There is no lease at all.
+// The server will try to assign it. Since it is not used by anyone else,
+// the server will assign it. This is convenient for various types
+// of recoveries, e.g. when the server lost its database.
+TEST_F(Dhcpv6SrvTest, RenewUnknown) {
+    // False means that the lease should not be created before renewal attempt
+    testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::abc", "2001:db8:1:1::abc",
+                   128, false);
+}
+
+// This test checks that a client that renews existing lease, but uses
+// a wrong IAID, will be processed correctly. As there is no lease for
+// this (duid, type, iaid) tuple, this is treated as a new IA, regardless
+// if the client inserted an address that is used in a different IA.
+// After #3565 was implemented, the server will attempt to assign a lease.
+// The one that client requested is already used with different IAID, so
+// it will just pick a different lease. This is the second out of three
+// scenarios tests by old RenewReject test.
+TEST_F(Dhcpv6SrvTest, RenewWrongIAID) {
+    testRenewWrongIAID(Lease::TYPE_NA, IOAddress("2001:db8:1:1::abc"));
+}
+
+// This test checks whether client A can renew an address that is currently
+// leased by client B. The server should detect that the lease belong to
+// someone else and assign a different lease. This is the third out of three
+// scenarios tests by old RenewReject test.
+TEST_F(Dhcpv6SrvTest, RenewSomeoneElesesLease) {
+    testRenewSomeoneElsesLease(Lease::TYPE_NA, IOAddress("2001:db8::1"));
 }
 
 // This test verifies that incoming (invalid) RENEW with a prefix

+ 179 - 31
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -200,7 +200,7 @@ Dhcpv6SrvTest::createIA(isc::dhcp::Lease::Type lease_type,
 void
 Dhcpv6SrvTest::testRenewBasic(Lease::Type type, const std::string& existing_addr,
                               const std::string& renew_addr,
-                              const uint8_t prefix_len) {
+                              const uint8_t prefix_len, bool insert_before_renew) {
     NakedDhcpv6Srv srv(0);
 
     const IOAddress existing(existing_addr);
@@ -213,24 +213,27 @@ Dhcpv6SrvTest::testRenewBasic(Lease::Type type, const std::string& existing_addr
     // Check that the address we are about to use is indeed in pool
     ASSERT_TRUE(subnet_->inPool(type, existing));
 
-    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
-    // value on purpose. They should be updated during RENEW.
-    Lease6Ptr lease(new Lease6(type, existing, duid_, iaid, 501, 502, 503, 504,
-                               subnet_->getID(), HWAddrPtr(), prefix_len));
-    lease->cltt_ = 1234;
-    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+    Lease6Ptr l;
+    if (insert_before_renew) {
+        // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+        // value on purpose. They should be updated during RENEW.
+        Lease6Ptr lease(new Lease6(type, existing, duid_, iaid, 501, 502, 503, 504,
+                                   subnet_->getID(), HWAddrPtr(), prefix_len));
+        lease->cltt_ = 1234;
+        ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
 
-    // Check that the lease is really in the database
-    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(type, existing);
-    ASSERT_TRUE(l);
+        // Check that the lease is really in the database
+        l = LeaseMgrFactory::instance().getLease6(type, existing);
+        ASSERT_TRUE(l);
 
-    // Check that T1, T2, preferred, valid and cltt really set and not using
-    // previous (500, 501, etc.) values
-    EXPECT_NE(l->t1_, subnet_->getT1());
-    EXPECT_NE(l->t2_, subnet_->getT2());
-    EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
-    EXPECT_NE(l->valid_lft_, subnet_->getValid());
-    EXPECT_NE(l->cltt_, time(NULL));
+        // Check that T1, T2, preferred, valid and cltt really set and not using
+        // previous (500, 501, etc.) values
+        EXPECT_NE(l->t1_, subnet_->getT1());
+        EXPECT_NE(l->t2_, subnet_->getT2());
+        EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+        EXPECT_NE(l->valid_lft_, subnet_->getValid());
+        EXPECT_NE(l->cltt_, time(NULL));
+    }
 
     Pkt6Ptr req = createMessage(DHCPV6_RENEW, type, IOAddress(renew_addr),
                                 prefix_len, iaid);
@@ -300,6 +303,114 @@ Dhcpv6SrvTest::testRenewBasic(Lease::Type type, const std::string& existing_addr
 }
 
 void
+Dhcpv6SrvTest::testRenewWrongIAID(Lease::Type type, const IOAddress& addr) {
+
+    NakedDhcpv6Srv srv(0);
+
+    const uint32_t transid = 1234;
+    const uint32_t valid_iaid = 234;
+    const uint32_t bogus_iaid = 456;
+
+    uint8_t prefix_len = (type == Lease::TYPE_PD) ? 128 : pd_pool_->getLength();
+
+    // Quick sanity check that the address we're about to use is ok
+    ASSERT_TRUE(subnet_->inPool(type, addr));
+
+    // Check that the lease is NOT in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(type, addr);
+    ASSERT_FALSE(l);
+
+    // GenerateClientId() also sets duid_
+    OptionPtr clientid = generateClientId();
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during RENEW.
+    Lease6Ptr lease(new Lease6(type, addr, duid_, valid_iaid,
+                               501, 502, 503, 504, subnet_->getID(),
+                               HWAddrPtr(), prefix_len));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Pass it to the server and hope for a REPLY
+    // Let's create a RENEW
+    Pkt6Ptr renew = createMessage(DHCPV6_RENEW, type, IOAddress(addr), prefix_len,
+                                  bogus_iaid);
+    renew->addOption(clientid);
+    renew->addOption(srv.getServerID());
+
+    // The duid and address matches, but the iaid is different. The server could
+    // respond with NoBinding. However, according to
+    // draft-ietf-dhc-dhcpv6-stateful-issues-10, the server can also assign a
+    // new address. And that's what we expect here.
+    Pkt6Ptr reply = srv.processRenew(renew);
+    checkResponse(reply, DHCPV6_REPLY, transid);
+
+    // Check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr>
+        addr_opt = checkIA_NA(reply, bogus_iaid, subnet_->getT1(), subnet_->getT2());
+
+    ASSERT_TRUE(addr_opt);
+
+    // Check that we've got the an address
+    checkIAAddr(addr_opt, addr_opt->getAddress(), Lease::TYPE_NA);
+
+    // Check that we got a different address than was in the database.
+    EXPECT_NE(addr_opt->getAddress().toText(), addr.toText());
+
+    // Check that the lease is really in the database
+    l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+    ASSERT_TRUE(l);
+}
+
+void
+Dhcpv6SrvTest::testRenewSomeoneElsesLease(Lease::Type type, const IOAddress& addr) {
+
+    NakedDhcpv6Srv srv(0);
+    const uint32_t valid_iaid = 234;
+    const uint32_t transid = 1234;
+
+    uint8_t prefix_len = (type == Lease::TYPE_PD) ? 128 : pd_pool_->getLength();
+
+    // GenerateClientId() also sets duid_
+    OptionPtr clientid = generateClientId();
+
+    // Let's create a lease.
+    Lease6Ptr lease(new Lease6(type, addr, duid_, valid_iaid,
+                               501, 502, 503, 504, subnet_->getID(),
+                               HWAddrPtr(), prefix_len));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // CASE 3: Lease belongs to a client with different client-id
+    Pkt6Ptr renew = createMessage(DHCPV6_RENEW, type, IOAddress(addr), prefix_len,
+                                  valid_iaid);
+    renew->addOption(generateClientId(13)); // generate different DUID (length 13)
+    renew->addOption(srv.getServerID());
+
+    // The iaid and address matches, but the duid is different.
+    // The server should not renew it, but assign something else.
+    Pkt6Ptr reply = srv.processRenew(renew);
+    checkResponse(reply, DHCPV6_REPLY, transid);
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    // Check that IA_?? was returned and that there's proper status code
+    // Check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr>
+        addr_opt = checkIA_NA(reply, valid_iaid, subnet_->getT1(), subnet_->getT2());
+
+    ASSERT_TRUE(addr_opt);
+
+    // Check that we've got the an address
+    checkIAAddr(addr_opt, addr_opt->getAddress(), Lease::TYPE_NA);
+
+    // Check that we got a different address than was in the database.
+    EXPECT_NE(addr_opt->getAddress().toText(), addr.toText());
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+    ASSERT_TRUE(l);
+}
+
+void
 Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
 
     NakedDhcpv6Srv srv(0);
@@ -349,7 +460,41 @@ Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
+
+    if (type == Lease::TYPE_PD) {
+        // For PD, the check is easy. NoBinding and no prefixes
+        checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
+    } else {
+        // For IA, it's more involved, as the server will reject the address
+        // (and send it with 0 lifetimes), but will also assign a new address.
+
+        // First, check that the requested address is rejected.
+        bool found = false;
+
+        dhcp::OptionCollection options = ia->getOptions();
+        for (isc::dhcp::OptionCollection::iterator opt = options.begin();
+             opt != options.end(); ++opt) {
+            if (opt->second->getType() != D6O_IAADDR) {
+                continue;
+            }
+
+            dhcp::Option6IAAddrPtr opt_addr =
+                boost::dynamic_pointer_cast<isc::dhcp::Option6IAAddr>(opt->second);
+            ASSERT_TRUE(opt_addr);
+
+            if (opt_addr->getAddress() != addr) {
+                // There may be other addresses, e.g. the newly assigned one
+                continue;
+            }
+
+            found = true;
+            EXPECT_NE(0, opt_addr->getPreferred());
+            EXPECT_NE(0, opt_addr->getValid());
+        }
+
+        EXPECT_TRUE(found) << "Expected address " << addr.toText()
+                           << " with zero lifetimes not found.";
+    }
 
     // Check that there is no lease added
     l = LeaseMgrFactory::instance().getLease6(type, addr);
@@ -654,24 +799,27 @@ Dhcpv6SrvTest::compareOptions(const isc::dhcp::OptionPtr& option1,
 void
 NakedDhcpv6SrvTest::checkIA_NAStatusCode(
     const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
-    uint16_t expected_status_code, uint32_t expected_t1, uint32_t expected_t2)
+    uint16_t expected_status_code, uint32_t expected_t1, uint32_t expected_t2,
+    bool check_addr)
 {
     // Make sure there is no address assigned. Depending on the situation,
     // the server will either not return the address at all and sometimes
     // it will return it with zeroed lifetimes.
-    dhcp::OptionCollection options = ia->getOptions();
-    for (isc::dhcp::OptionCollection::iterator opt = options.begin();
-         opt != options.end(); ++opt) {
-        if (opt->second->getType() != D6O_IAADDR) {
-            continue;
+    if (check_addr) {
+        dhcp::OptionCollection options = ia->getOptions();
+        for (isc::dhcp::OptionCollection::iterator opt = options.begin();
+             opt != options.end(); ++opt) {
+            if (opt->second->getType() != D6O_IAADDR) {
+                continue;
+            }
+
+            dhcp::Option6IAAddrPtr addr =
+                boost::dynamic_pointer_cast<isc::dhcp::Option6IAAddr>(opt->second);
+            ASSERT_TRUE(addr);
+
+            EXPECT_EQ(0, addr->getPreferred());
+            EXPECT_EQ(0, addr->getValid());
         }
-
-        dhcp::Option6IAAddrPtr addr =
-            boost::dynamic_pointer_cast<isc::dhcp::Option6IAAddr>(opt->second);
-        ASSERT_TRUE(addr);
-
-        EXPECT_EQ(0, addr->getPreferred());
-        EXPECT_EQ(0, addr->getValid());
     }
 
     // T1, T2 should NOT be zeroed. draft-ietf-dhc-dhcpv6-stateful-issues-10,

+ 37 - 10
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -231,18 +231,26 @@ public:
                              expected_t2);
     }
 
-    // Checks that server rejected IA_NA, i.e. that it has no addresses and
-    // that expected status code really appears there. In some limited cases
-    // (reply to RELEASE) it may be used to verify positive case, where
-    // IA_NA response is expected to not include address.
-    //
-    // Status code indicates type of error encountered (in theory it can also
-    // indicate success, but servers typically don't send success status
-    // as this is the default result and it saves bandwidth)
+    /// @brief Checks that the server inserted expected status code in IA_NA
+    ///
+    /// Check that the server used status code as expected, i.e. that it has
+    /// no addresses (if status code is non-zero) and that expected status
+    /// code really appears there. In some limited cases (reply to RELEASE)
+    /// it may be used to verify positive case, where IA_NA response is
+    /// expected to not include address.
+    ///
+    /// Status code indicates type of error encountered (in theory it can also
+    /// indicate success, but servers typically don't send success status
+    /// as this is the default result and it saves bandwidth)
+    /// @param ia IA_NA container to be checked
+    /// @param expected_status_code expected value in status-code option
+    /// @param expected_t1 expected T1 in IA_NA option
+    /// @param expected_t2 expected T2 in IA_NA option
+    /// @param check_addr whether to check for address with 0 lifetimes
     void checkIA_NAStatusCode
         (const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
          uint16_t expected_status_code, uint32_t expected_t1,
-         uint32_t expected_t2);
+         uint32_t expected_t2, bool check_addr = true);
 
     void checkMsgStatusCode(const isc::dhcp::Pkt6Ptr& msg,
                             uint16_t expected_status)
@@ -464,10 +472,29 @@ public:
     /// @param existing_addr address to be preinserted into the database
     /// @param renew_addr address being sent in RENEW
     /// @param prefix_len length of the prefix (128 for addresses)
+    /// @param insert_before_renew should the lease be inserted into the database
+    ///        before we try renewal?
     void
     testRenewBasic(isc::dhcp::Lease::Type type,
                    const std::string& existing_addr,
-                   const std::string& renew_addr, const uint8_t prefix_len);
+                   const std::string& renew_addr, const uint8_t prefix_len,
+                   bool insert_before_renew = true);
+
+    /// @brief Checks if RENEW with invalid IAID is processed correctly.
+    ///
+    /// @param type lease type (currently only IA_NA is supported)
+    /// @param addr address to be renewed
+    void
+    testRenewWrongIAID(isc::dhcp::Lease::Type type,
+                       const asiolink::IOAddress& addr);
+
+    /// @brief Checks if client A can renew address used by client B
+    ///
+    /// @param type lease type (currently only IA_NA is supported)
+    /// @param addr address to be renewed
+    void
+    testRenewSomeoneElsesLease(isc::dhcp::Lease::Type type,
+                               const asiolink::IOAddress& addr);
 
     /// @brief Performs negative RENEW test
     ///

+ 1 - 1
src/lib/dhcpsrv/alloc_engine.cc

@@ -588,7 +588,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
         /// In-pool reservations: Check if this address is reserved for someone
         /// else. There is no need to check for whom it is reserved, because if
         /// it has been reserved for us we would have already allocated a lease.
-        if (hr_mode == Subnet::HR_IN_POOL &&
+        if (hr_mode == Subnet::HR_ALL &&
             HostMgr::instance().get6(ctx.subnet_->getID(), candidate)) {
 
             // Don't allocate.

+ 1 - 1
src/lib/dhcpsrv/lease.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015 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

+ 28 - 0
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -1089,6 +1089,21 @@ SubnetConfigParser::build(ConstElementPtr subnet) {
     }
 }
 
+Subnet::HRMode
+SubnetConfigParser::hrModeFromText(const std::string& txt) {
+    if ( (txt.compare("disabled") == 0) ||
+         (txt.compare("off") == 0) )  {
+        return (Subnet::HR_DISABLED);
+    } else if (txt.compare("out-of-pool") == 0) {
+        return (Subnet::HR_OUT_OF_POOL);
+    } else if (txt.compare("all") == 0) {
+        return (Subnet::HR_ALL);
+    } else {
+        isc_throw(BadValue, "Can't convert '" << txt
+                  << "' into any valid reservation-mode values");
+    }
+}
+
 void
 SubnetConfigParser::createSubnet() {
     std::string subnet_txt;
@@ -1142,6 +1157,19 @@ SubnetConfigParser::createSubnet() {
         // iface not mandatory so swallow the exception
     }
 
+
+    // Let's set host reservation mode. If not specified, the default value of
+    // all will be used.
+    std::string hr_mode;
+    try {
+        hr_mode = string_values_->getOptionalParam("reservation-mode", "all");
+        subnet_->setHostReservationMode(hrModeFromText(hr_mode));
+    } catch (const BadValue& ex) {
+        isc_throw(DhcpConfigError, "Failed to process specified value "
+                  " of reservation-mode parameter: " << ex.what()
+                  << string_values_->getPosition("reservation-mode"));
+    }
+
     if (!iface.empty()) {
         if (!IfaceMgr::instance().getIface(iface)) {
             isc_throw(DhcpConfigError, "Specified interface name " << iface

+ 11 - 0
src/lib/dhcpsrv/parsers/dhcp_parsers.h

@@ -1052,6 +1052,17 @@ protected:
     /// unspecified value.
     isc::dhcp::Triplet<uint32_t> getOptionalParam(const std::string& name);
 
+    /// @brief Attempts to convert text representation to HRMode enum.
+    ///
+    /// Allowed values are "disabled", "off" (alias for disabled),
+    /// "out-of-pool" and "all". See Subnet::HRMode for their exact meaning.
+    ///
+    /// @throw BadValue if the text cannot be converted.
+    ///
+    /// @param text representation for conversion
+    /// @return one of allowed HRMode values
+    static Subnet::HRMode hrModeFromText(const std::string& txt);
+
 private:
 
     /// @brief Create a new subnet using a data from child parsers.

+ 1 - 2
src/lib/dhcpsrv/subnet.cc

@@ -38,8 +38,7 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
      last_allocated_ia_(lastAddrInPrefix(prefix, len)),
      last_allocated_ta_(lastAddrInPrefix(prefix, len)),
      last_allocated_pd_(lastAddrInPrefix(prefix, len)), relay_(relay),
-     cfg_option_(new CfgOption())
-
+     host_reservation_mode_(HR_ALL), cfg_option_(new CfgOption())
       {
     if ((prefix.isV6() && len > 128) ||
         (prefix.isV4() && len > 32)) {

+ 16 - 5
src/lib/dhcpsrv/subnet.h

@@ -84,7 +84,7 @@ public:
         /// there is a non-trivial performance penalty for it, as the
         /// AllocEngine code has to check whether there are reservations, even
         /// when dealing with reservations from within the dynamic pools.
-        HR_IN_POOL
+        HR_ALL
     } HRMode;
 
     /// Pointer to the RelayInfo structure
@@ -324,12 +324,19 @@ public:
     /// not in the dynamic pool). HR may also be completely disabled for
     /// performance reasons.
     ///
-    /// @todo: implement this.
-    ///
     /// @return whether in-pool host reservations are allowed.
     HRMode
-    getHostReservationMode() {
-        return (Subnet::HR_IN_POOL);
+    getHostReservationMode() const {
+        return (host_reservation_mode_);
+    }
+
+    /// @brief Sets host reservation mode.
+    ///
+    /// See @getHostReservationMode for details.
+    ///
+    /// @param mode mode to be set
+    void setHostReservationMode(HRMode mode) {
+        host_reservation_mode_ = mode;
     }
 
 protected:
@@ -477,6 +484,10 @@ protected:
     /// so it may be a while until we support this.
     ClientClasses white_list_;
 
+    /// @brief Specifies host reservation mode
+    ///
+    /// See @ref HRMode type for details.
+    HRMode host_reservation_mode_;
 private:
 
     /// @brief Pointer to the option data configuration for this subnet.

+ 99 - 0
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc

@@ -1233,6 +1233,105 @@ TEST_F(AllocEngine6Test, reservedAddressRenewReserved) {
 /// - AllocEngine::removeLeases
 /// - AllocEngine::removeNonreservedLeases6
 
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressByMacInPoolSolicitNoHint) {
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
+                      IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressByMacInPoolRequestNoHint) {
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
+                      IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressByMacInPoolSolicitValidHint) {
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
+                      IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressByMacInPoolRequestValidHint) {
+    // Create reservation for the client This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
+                      IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
 }; // namespace test
 }; // namespace dhcp
 }; // namespace isc

+ 23 - 0
src/lib/dhcpsrv/tests/alloc_engine_utils.cc

@@ -49,6 +49,11 @@ AllocEngine6Test::AllocEngine6Test() {
     duid_ = DuidPtr(new DUID(std::vector<uint8_t>(8, 0x42)));
     iaid_ = 42;
 
+    // Let's use odd hardware type to check if there is no Ethernet
+    // hardcoded anywhere.
+    const uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
+    hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
+
     // Initialize a subnet and short address pool.
     initSubnet(IOAddress("2001:db8:1::"),
                IOAddress("2001:db8:1::10"),
@@ -77,6 +82,23 @@ AllocEngine6Test::initSubnet(const asiolink::IOAddress& subnet,
     cfg_mgr.commit();
 }
 
+HostPtr
+AllocEngine6Test::createHost6HWAddr(bool add_to_host_mgr, IPv6Resrv::Type type,
+                                    HWAddrPtr& hwaddr, const asiolink::IOAddress& addr,
+                                    uint8_t prefix_len) {
+    HostPtr host(new Host(&hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
+                          Host::IDENT_HWADDR, SubnetID(0), subnet_->getID(),
+                          asiolink::IOAddress("0.0.0.0")));
+    IPv6Resrv resv(type, addr, prefix_len);
+    host->addReservation(resv);
+
+    if (add_to_host_mgr) {
+        CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+        CfgMgr::instance().commit();
+    }
+    return (host);
+}
+
 Lease6Collection
 AllocEngine6Test::allocateTest(AllocEngine& engine, const Pool6Ptr& pool,
                                const asiolink::IOAddress& hint, bool fake,
@@ -142,6 +164,7 @@ AllocEngine6Test::simpleAlloc6Test(const Pool6Ptr& pool, const IOAddress& hint,
     Lease6Ptr lease;
     AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type,
                                     false, false, "", fake);
+    ctx.hwaddr_ = hwaddr_;
 
     EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
 

+ 15 - 1
src/lib/dhcpsrv/tests/alloc_engine_utils.h

@@ -271,7 +271,7 @@ public:
     void allocBogusHint6(Lease::Type type, asiolink::IOAddress hint,
                          uint8_t expected_pd_len);
 
-    /// @brief Utility function that creates a host reservation
+    /// @brief Utility function that creates a host reservation (duid)
     ///
     /// @param add_to_host_mgr true if the reservation should be added
     /// @param type specifies reservation type
@@ -294,11 +294,25 @@ public:
         return (host);
     }
 
+    /// @brief Utility function that creates a host reservation (hwaddr)
+    ///
+    /// @param add_to_host_mgr true if the reservation should be added
+    /// @param type specifies reservation type
+    /// @param hwaddr hardware address to be reserved to
+    /// @param addr specifies reserved address or prefix
+    /// @param prefix_len prefix length (should be 128 for addresses)
+    /// @return created Host object.
+    HostPtr
+    createHost6HWAddr(bool add_to_host_mgr, IPv6Resrv::Type type,
+                      HWAddrPtr& hwaddr, const asiolink::IOAddress& addr,
+                      uint8_t prefix_len);
+
     virtual ~AllocEngine6Test() {
         factory_.destroy();
     }
 
     DuidPtr duid_;            ///< client-identifier (value used in tests)
+    HWAddrPtr hwaddr_;        ///< client's hardware address
     uint32_t iaid_;           ///< IA identifier (value used in tests)
     Subnet6Ptr subnet_;       ///< subnet6 (used in tests)
     Pool6Ptr pool_;           ///< NA pool belonging to subnet_

+ 1 - 1
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 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

+ 1 - 1
src/lib/dhcpsrv/tests/test_utils.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015 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