Browse Source

[master] Merge trac4500 lease6_rebind

Francis Dupont 8 years ago
parent
commit
dedda6a36d
2 changed files with 369 additions and 17 deletions
  1. 32 0
      src/bin/dhcp6/dhcp6_hooks.dox
  2. 337 17
      src/bin/dhcp6/tests/hooks_unittest.cc

+ 32 - 0
src/bin/dhcp6/dhcp6_hooks.dox

@@ -167,6 +167,38 @@ packet processing. Hook points that are not specific to packet processing
    this fact; otherwise the client will think the lease was renewed and continue
    to operate under this assumption.
 
+@subsection dhcpv6HooksLease6Rebind lease6_rebind
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::PktPtr, direction: <b>in</b>
+   - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+   - name: @b ia_na, type: boost::shared_ptr<Option6IA>, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when the server engine is
+   about to rebind an existing lease. The client's request is provided as
+   the "query6" argument and the existing lease with the appropriate fields
+   already modified is given in the "lease6" argument. The final argument,
+   ia_na, is the IA_NA option that will be sent back to the client.
+   Callouts installed on the "lease6_rebind" may modify the content of
+   the "lease6" object. Care should be taken however, as that modified
+   information will be written to the database without any further
+   checking. \n\n Although the envisaged usage assumes modification of T1,
+   T2, preferred and valid lifetimes only, other parameters associated
+   with the lease may be modified as well. The only exception is the @c addr_
+   field, which must not be modified as it is used by the database to
+   select the existing lease to be updated. Care should also be taken to
+   modify the "ia_na" argument to match any changes in the "lease6" argument.
+   If a client sends more than one IA_NA option, callouts will be called
+   separately for each IA_NA instance. The callout will be called only
+   when the update is valid, i.e. conditions such as an invalid addresses
+   or invalid iaid rebinding attempts will not trigger this hook point.
+
+ - <b>Next step status</b>: If any callout installed on "lease6_rebind"
+   sets the status to SKIP, the server will not rebind the lease. Under these
+   circumstances, the callout should modify the "ia_na" argument to reflect
+   this fact; otherwise the client will think the lease was rebound and continue
+   to operate under this assumption.
+
 @subsection dhcpv6HooksLease6Decline lease6_decline
 
  - @b Arguments:

+ 337 - 17
src/bin/dhcp6/tests/hooks_unittest.cc

@@ -56,6 +56,8 @@ TEST_F(Dhcpv6SrvTest, Hooks) {
     int hook_index_buffer6_send    = -1;
     int hook_index_lease6_renew    = -1;
     int hook_index_lease6_release  = -1;
+    int hook_index_lease6_rebind   = -1;
+    int hook_index_lease6_decline  = -1;
     int hook_index_pkt6_received   = -1;
     int hook_index_select_subnet   = -1;
     int hook_index_pkt6_send       = -1;
@@ -69,6 +71,10 @@ TEST_F(Dhcpv6SrvTest, Hooks) {
                     .getIndex("lease6_renew"));
     EXPECT_NO_THROW(hook_index_lease6_release = ServerHooks::getServerHooks()
                     .getIndex("lease6_release"));
+    EXPECT_NO_THROW(hook_index_lease6_rebind = ServerHooks::getServerHooks()
+                    .getIndex("lease6_rebind"));
+    EXPECT_NO_THROW(hook_index_lease6_decline = ServerHooks::getServerHooks()
+                    .getIndex("lease6_decline"));
     EXPECT_NO_THROW(hook_index_pkt6_received = ServerHooks::getServerHooks()
                     .getIndex("pkt6_receive"));
     EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
@@ -83,6 +89,8 @@ TEST_F(Dhcpv6SrvTest, Hooks) {
     EXPECT_TRUE(hook_index_buffer6_send    > 0);
     EXPECT_TRUE(hook_index_lease6_renew    > 0);
     EXPECT_TRUE(hook_index_lease6_release  > 0);
+    EXPECT_TRUE(hook_index_lease6_rebind   > 0);
+    EXPECT_TRUE(hook_index_lease6_decline  > 0);
 }
 
 /// @brief a class dedicated to Hooks testing in DHCPv6 server
@@ -382,22 +390,6 @@ public:
         return (0);
     }
 
-    /// Test callback that stores received callout name and pkt6 value
-    /// @param callout_handle handle passed by the hooks framework
-    /// @return always 0
-    static int
-    lease6_rebind_callout(CalloutHandle& callout_handle) {
-        callback_name_ = string("lease6_rebind");
-
-        callout_handle.getArgument("query6", callback_qry_pkt6_);
-        callout_handle.getArgument("lease6", callback_lease6_);
-        callout_handle.getArgument("ia_na", callback_ia_na_);
-
-        callback_argument_names_ = callout_handle.getArgumentNames();
-        return (0);
-    }
-
-
     /// The following values are used by the callout to override
     /// renewed lease parameters
     static const uint32_t override_iaid_;
@@ -418,6 +410,8 @@ public:
         callout_handle.getArgument("lease6", callback_lease6_);
         callout_handle.getArgument("ia_na", callback_ia_na_);
 
+        // Should be an ASSERT but it is not allowed here
+        EXPECT_TRUE(callback_lease6_);
         // Let's override some values in the lease
         callback_lease6_->iaid_          = override_iaid_;
         callback_lease6_->t1_            = override_t1_;
@@ -425,6 +419,8 @@ public:
         callback_lease6_->preferred_lft_ = override_preferred_;
         callback_lease6_->valid_lft_     = override_valid_;
 
+        // Should be an ASSERT but it is not allowed here
+        EXPECT_TRUE(callback_ia_na_);
         // Override the values to be sent to the client as well
         callback_ia_na_->setIAID(override_iaid_);
         callback_ia_na_->setT1(override_t1_);
@@ -446,6 +442,76 @@ public:
         return (0);
     }
 
+    /// Test callback that stores received callout name and pkt6 value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_rebind_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("lease6_rebind");
+
+        callout_handle.getArgument("query6", callback_qry_pkt6_);
+        callout_handle.getArgument("lease6", callback_lease6_);
+        callout_handle.getArgument("ia_na", callback_ia_na_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// Test callback that overrides received lease. It updates
+    /// T1, T2, preferred and valid lifetimes
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_rebind_update_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("lease6_rebind");
+
+        callout_handle.getArgument("query6", callback_qry_pkt6_);
+        callout_handle.getArgument("lease6", callback_lease6_);
+        callout_handle.getArgument("ia_na", callback_ia_na_);
+
+        // Should be an ASSERT but it is not allowed here
+        EXPECT_TRUE(callback_lease6_);
+        // Let's override some values in the lease
+        callback_lease6_->iaid_          = override_iaid_;
+        callback_lease6_->t1_            = override_t1_;
+        callback_lease6_->t2_            = override_t2_;
+        callback_lease6_->preferred_lft_ = override_preferred_;
+        callback_lease6_->valid_lft_     = override_valid_;
+
+        // Should be an ASSERT but it is not allowed here
+        EXPECT_TRUE(callback_ia_na_);
+        // Override the values to be sent to the client as well
+        callback_ia_na_->setIAID(override_iaid_);
+        callback_ia_na_->setT1(override_t1_);
+        callback_ia_na_->setT2(override_t2_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// Lease6_rebind callout that sets status to SKIP
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_rebind_skip_callout(CalloutHandle& callout_handle) {
+        callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+        return (lease6_rebind_callout(callout_handle));
+    }
+
+    /// Lease6_rebind callout that sets status to DROP
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_rebind_drop_callout(CalloutHandle& callout_handle) {
+        callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+        return (lease6_rebind_callout(callout_handle));
+    }
+
+
     /// Test callback that stores received callout name passed parameters
     /// @param callout_handle handle passed by the hooks framework
     /// @return always 0
@@ -538,7 +604,7 @@ public:
     /// Pointer to lease6
     static Lease6Ptr callback_lease6_;
 
-    /// Pointer to IA_NA option being renewed
+    /// Pointer to IA_NA option being renewed or rebound
     static boost::shared_ptr<Option6IA> callback_ia_na_;
 
     /// Pointer to a subnet received by callout
@@ -1544,6 +1610,260 @@ TEST_F(HooksDhcpv6SrvTest, skipLease6Release) {
     ASSERT_TRUE(l);
 }
 
+// This test verifies that incoming (positive) REBIND can be handled properly,
+// and the lease6_rebind callouts are triggered.
+TEST_F(HooksDhcpv6SrvTest, basicLease6Rebind) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_rebind", lease6_rebind_callout));
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during REBIND.
+    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(),
+                               HWAddrPtr(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+                                                        addr);
+    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));
+
+    // Let's create a REBIND
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REBIND, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
+    boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+    OptionPtr rebound_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(rebound_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRebind(req);
+    ASSERT_TRUE(reply);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("lease6_rebind", callback_name_);
+
+    // Check that appropriate parameters are passed to the callouts
+    EXPECT_TRUE(callback_qry_pkt6_);
+    EXPECT_TRUE(callback_lease6_);
+    EXPECT_TRUE(callback_ia_na_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("lease6");
+    expected_argument_names.push_back("ia_na");
+
+    sort(callback_argument_names_.begin(), callback_argument_names_.end());
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    // Check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
+                                                           subnet_->getT2());
+
+    ASSERT_TRUE(addr_opt);
+    // Check that the lease is really in the database
+    l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+    ASSERT_TRUE(l);
+
+    // Check that the lease has been returned
+    ASSERT_TRUE(callback_lease6_);
+
+    // Check that the returned lease6 in callout is the same as the one in the
+    // database
+    EXPECT_TRUE(*callback_lease6_ == *l);
+}
+
+// This test verifies that incoming (positive) REBIND can be handled properly,
+// and the lease6_rebind callouts are able to change the lease being updated.
+TEST_F(HooksDhcpv6SrvTest, leaseUpdateLease6Rebind) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_rebind", lease6_rebind_update_callout));
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during REBIND.
+    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(),
+                               HWAddrPtr(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+                                                        addr);
+    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));
+
+    // Let's create a REBIND
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REBIND, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
+    boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+    OptionPtr rebound_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(rebound_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRebind(req);
+    ASSERT_TRUE(reply);
+
+    // Check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    // Check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 1000, 1001, 1002);
+
+    ASSERT_TRUE(addr_opt);
+    // Check that the lease is really in the database
+    l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+    ASSERT_TRUE(l);
+
+    // Check that we chose the distinct override values
+    ASSERT_NE(override_t1_,        subnet_->getT1());
+    ASSERT_NE(override_t2_,        subnet_->getT2());
+    ASSERT_NE(override_preferred_, subnet_->getPreferred());
+    EXPECT_NE(override_valid_,     subnet_->getValid());
+
+    // Check that T1, T2, preferred, valid were overridden the the callout
+    EXPECT_EQ(override_t1_, l->t1_);
+    EXPECT_EQ(override_t2_, l->t2_);
+    EXPECT_EQ(override_preferred_, l->preferred_lft_);
+    EXPECT_EQ(override_valid_, l->valid_lft_);
+
+    // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+    int32_t cltt = static_cast<int32_t>(l->cltt_);
+    int32_t expected = static_cast<int32_t>(time(NULL));
+    // Equality or difference by 1 between cltt and expected is ok.
+    EXPECT_GE(1, abs(cltt - expected));
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr_opt->getAddress()));
+}
+
+// This test verifies that incoming (positive) REBIND can be handled properly,
+// and the lease6_rebind callouts are able to set the skip flag that will
+// reject the rebinding
+TEST_F(HooksDhcpv6SrvTest, skipLease6Rebind) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_rebind", lease6_rebind_skip_callout));
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during REBIND.
+    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(),
+                               HWAddrPtr(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+                                                        addr);
+    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));
+
+    // Let's create a REBIND
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REBIND, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
+    boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+    OptionPtr rebound_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(rebound_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRebind(req);
+    ASSERT_TRUE(reply);
+
+    // Check that our callback was called
+    EXPECT_EQ("lease6_rebind", callback_name_);
+
+    l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+
+    // Check that the old values are still there and they were not
+    // updated by the rebinding
+    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));
+}
+
 // This test checks that the basic decline hook (lease6_decline) is
 // triggered properly.
 TEST_F(HooksDhcpv6SrvTest, basicLease6Decline) {