summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTomek Mrugalski <tomasz@isc.org>2015-09-22 21:19:22 +0200
committerTomek Mrugalski <tomasz@isc.org>2015-09-22 21:19:22 +0200
commit11e2c4366d8624601172c01e95cff6a8b95833b3 (patch)
tree411d302b8662f239bf739da6657c096b519022a4 /src
parent[master] Removed extraneous variable. (diff)
parent[3982] Minor fix after review: (diff)
downloadkea-11e2c4366d8624601172c01e95cff6a8b95833b3.tar.xz
kea-11e2c4366d8624601172c01e95cff6a8b95833b3.zip
[master] Merge branch 'trac3982' (Decline v6)
Conflicts: src/bin/dhcp6/dhcp6_srv.cc src/lib/dhcpsrv/lease.cc src/lib/dhcpsrv/tests/lease_unittest.cc
Diffstat (limited to 'src')
-rw-r--r--src/bin/dhcp6/dhcp6_messages.mes43
-rw-r--r--src/bin/dhcp6/dhcp6_srv.cc204
-rw-r--r--src/bin/dhcp6/dhcp6_srv.h66
-rw-r--r--src/bin/dhcp6/tests/Makefile.am1
-rw-r--r--src/bin/dhcp6/tests/decline_unittest.cc274
-rw-r--r--src/bin/dhcp6/tests/dhcp6_client.cc56
-rw-r--r--src/bin/dhcp6/tests/dhcp6_client.h32
-rw-r--r--src/lib/dhcp/duid.cc12
-rw-r--r--src/lib/dhcp/duid.h21
-rw-r--r--src/lib/dhcp/tests/duid_unittest.cc13
-rw-r--r--src/lib/dhcpsrv/lease.cc14
-rw-r--r--src/lib/dhcpsrv/tests/lease_unittest.cc35
12 files changed, 757 insertions, 14 deletions
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 4cdf7eb302..25813d15c8 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -216,6 +216,49 @@ This message is printed when DHCPv6 server disables an interface from being
used to receive DHCPv6 traffic. Sockets on this interface will not be opened
by the Interface Manager until interface is enabled.
+% DHCP6_DECLINE_PROCESS_IA Processing of IA (IAID: %1) from client %2 started.
+This debug message is printed when the server starts processing an IA_NA option
+received in Decline message. It's expected that the option will contain an
+address that is being declined. Specific information will be printed in a
+separate message.
+
+% DHCP6_DECLINE_FAIL_DUID_MISMATCH Client %1 sent DECLINE for address %2, but it belongs to client with DUID %3
+This informational message is printed when a client attempts to decline
+a lease, but that lease belongs to a different client. The decline request
+will be rejected.
+
+% DHCP6_DECLINE_FAIL_IAID_MISMATCH Client %1 sent DECLINE for address %2, but used a wrong IAID (%3), instead of expected %4
+This informational message is printed when a client attempts to decline
+a lease. The server has a lease for this address, it belongs to this client,
+but the recorded IAID does not match what client has sent. This means
+the server will reject this Decline.
+
+% DHCP6_DECLINE_FAIL_LEASE_WITHOUT_DUID Client %1 sent DECLINE for address %2, but the associated lease has no DUID
+This error condition likely indicates database corruption, as every IPv6
+lease is supposed to have a DUID, even if it is an empty one.
+
+% DHCP6_DECLINE_FAIL_NO_LEASE Client %1 sent DECLINE for address %2, but there's no lease for it
+This informational message is printed when a client tried to decline an address,
+but the server has no lease for said address. This means that the server's
+and client's perception of the leases are different. The likely causes
+of this could be: a confused (e.g. skewed clock) or broken client (e.g. client
+moved to a different location and didn't notice) or possibly an attack
+(a rogue client is trying to decline random addresses). The server will
+inform the client that his decline request was rejected and client should
+be able to recover from that.
+
+% DHCP6_DECLINE_LEASE Client %1 sent DECLINE for address %2 and the server marked it as declined. The lease will be recovered in %3 seconds.
+This informational message indicates that the client leased an address, but
+discovered that it is being used by some other devicea and reported this to the
+server by sending a Decline message. The server marked the lease as
+declined. This likely indicates a misconfiguration in the network. Either
+the server is configured with an incorrect pool or there are devices that have
+statically assigned addresses that are supposed to be assigned by the DHCP
+server. Both client (will request a different address) and server (will recover
+the lease after decline-probation-time elapses) will recover automatically.
+However, if the underlying problem is not solved, the conditions leading
+to this message may reappear.
+
% DHCP6_DYNAMIC_RECONFIGURATION initiate server reconfiguration using file: %1, after receiving SIGHUP signal
This is the info message logged when the DHCPv6 server starts reconfiguration
as a result of receiving SIGHUP signal.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 43f1b22816..86569bb005 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -2584,13 +2584,215 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
Pkt6Ptr
Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) {
+ // Do sanity check.
sanityCheck(decline, MANDATORY, MANDATORY);
- /// @todo: Implement this
+ // Create an empty Reply message.
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, decline->getTransid()));
+
+ // Let's create a simplified client context here.
+ AllocEngine::ClientContext6 ctx = createContext(decline);
+
+ // Copy client options (client-id, also relay information if present)
+ copyClientOptions(decline, reply);
+
+ // Include server-id
+ appendDefaultOptions(decline, reply);
+
+ declineLeases(decline, reply, ctx);
+
return (reply);
}
+void
+Dhcpv6Srv::declineLeases(const Pkt6Ptr& decline, Pkt6Ptr& reply,
+ AllocEngine::ClientContext6& ctx) {
+
+ // We need to decline addresses for all IA_NA options in the client's
+ // RELEASE message.
+
+ // Let's set the status to be success by default. We can override it with
+ // error status if needed. The important thing to understand here is that
+ // the global status code may be set to success only if all IA options were
+ // handled properly. Therefore the declineIA options
+ // may turn the status code to some error, but can't turn it back to success.
+ int general_status = STATUS_Success;
+
+ for (OptionCollection::iterator opt = decline->options_.begin();
+ opt != decline->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = declineIA(decline, ctx.duid_, general_status,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ reply->addOption(answer_opt);
+ }
+ break;
+ }
+ default:
+ // We don't care for the remaining options
+ ;
+ }
+ }
+}
+
+OptionPtr
+Dhcpv6Srv::declineIA(const Pkt6Ptr& decline, const DuidPtr& duid,
+ int& general_status, boost::shared_ptr<Option6IA> ia) {
+
+ LOG_DEBUG(lease6_logger, DBG_DHCP6_DETAIL, DHCP6_DECLINE_PROCESS_IA)
+ .arg(decline->getLabel())
+ .arg(ia->getIAID());
+
+ // Decline can be done in one of two ways:
+ // Approach 1: extract address from client's IA_NA and see if it belongs
+ // to this particular client.
+ // Approach 2: find a subnet for this client, get a lease for
+ // this subnet/duid/iaid and check if its content matches to what the
+ // client is asking us to decline.
+ //
+ // This method implements approach 1.
+
+ // That's our response
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ const OptionCollection& opts = ia->getOptions();
+ int total_addrs = 0; // Let's count the total number of addresses.
+ for (OptionCollection::const_iterator opt = opts.begin(); opt != opts.end();
+ ++opt) {
+
+ // Let's ignore nested options other than IAADDR (there shouldn't be anything
+ // else in IA_NA in Decline message, but let's be on the safe side).
+ if (opt->second->getType() != D6O_IAADDR) {
+ continue;
+ }
+ Option6IAAddrPtr decline_addr = boost::dynamic_pointer_cast<Option6IAAddr>
+ (opt->second);
+ if (!decline_addr) {
+ continue;
+ }
+
+ total_addrs++;
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ decline_addr->getAddress());
+
+ if (!lease) {
+ // Client trying to decline a lease that we don't know about.
+ LOG_INFO(lease6_logger, DHCP6_DECLINE_FAIL_NO_LEASE)
+ .arg(decline->getLabel()).arg(decline_addr->getAddress().toText());
+
+ // RFC3315, section 18.2.7: "For each IA in the Decline message for
+ // which the server has no binding information, the server adds an
+ // IA option using the IAID from the Release message and includes
+ // a Status Code option with the value NoBinding in the IA option.
+ setStatusCode(ia_rsp, createStatusCode(*decline, *ia_rsp, STATUS_NoBinding,
+ "Server does not know about such an address."));
+
+ // RFC3315, section 18.2.7: The server ignores addresses not
+ // assigned to the IA (though it may choose to log an error if it
+ // finds such an address).
+ continue; // There may be other addresses.
+ }
+
+ if (!lease->duid_) {
+ // Something is gravely wrong here. We do have a lease, but it does not
+ // have mandatory DUID information attached. Someone was messing with our
+ // database.
+
+ LOG_ERROR(lease6_logger, DHCP6_DECLINE_FAIL_LEASE_WITHOUT_DUID)
+ .arg(decline->getLabel())
+ .arg(decline_addr->getAddress().toText());
+
+ ia_rsp->addOption(createStatusCode(*decline, *ia_rsp, STATUS_UnspecFail,
+ "Database consistency check failed when attempting Decline."));
+
+ continue;
+ }
+
+ // Ok, there's a sane lease with an address. Let's check if DUID matches first.
+ if (*duid != *(lease->duid_)) {
+
+ // Sorry, it's not your address. You can't release it.
+ LOG_INFO(lease6_logger, DHCP6_DECLINE_FAIL_DUID_MISMATCH)
+ .arg(decline->getLabel())
+ .arg(decline_addr->getAddress().toText())
+ .arg(lease->duid_->toText());
+
+ ia_rsp->addOption(createStatusCode(*decline, *ia_rsp, STATUS_NoBinding,
+ "This address does not belong to you, you can't decline it"));
+
+ continue;
+ }
+
+ // Let's check if IAID matches.
+ if (ia->getIAID() != lease->iaid_) {
+ // This address belongs to this client, but to a different IA
+ LOG_INFO(lease6_logger, DHCP6_DECLINE_FAIL_IAID_MISMATCH)
+ .arg(decline->getLabel())
+ .arg(lease->addr_.toText())
+ .arg(ia->getIAID())
+ .arg(lease->iaid_);
+ setStatusCode(ia_rsp, createStatusCode(*decline, *ia_rsp, STATUS_NoBinding,
+ "This is your address, but you used wrong IAID"));
+
+ continue;
+ }
+
+ // Ok, all is good. Decline this lease.
+ declineLease(decline, lease, ia_rsp);
+ }
+
+ if (total_addrs == 0) {
+ setStatusCode(ia_rsp, createStatusCode(*decline, *ia_rsp, STATUS_NoBinding,
+ "No addresses sent in IA_NA"));
+ general_status = STATUS_NoBinding;
+ }
+
+ return (ia_rsp);
+}
+
+void
+Dhcpv6Srv::setStatusCode(boost::shared_ptr<isc::dhcp::Option6IA>& container,
+ const OptionPtr& status) {
+ // Let's delete any old status code we may have.
+ container->delOption(D6O_STATUS_CODE);
+
+ container->addOption(status);
+}
+
+void
+Dhcpv6Srv::declineLease(const Pkt6Ptr& decline, const Lease6Ptr lease,
+ boost::shared_ptr<Option6IA> ia_rsp) {
+
+ // Check if a lease has flags indicating that the FQDN update has
+ // been performed. If so, create NameChangeRequest which removes
+ // the entries. This method does all necessary checks.
+ createRemovalNameChangeRequest(decline, lease);
+
+ // Bump up the subnet-specific statistic.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
+ static_cast<int64_t>(1));
+
+ // Global declined addresses counter.
+ StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
+
+ // @todo: Call hooks.
+
+ // We need to disassociate the lease from the client. Once we move a lease
+ // to declined state, it is no longer associated with the client in any
+ // way.
+ lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
+ LeaseMgrFactory::instance().updateLease6(lease);
+
+ LOG_INFO(dhcp6_logger, DHCP6_DECLINE_LEASE).arg(decline->getLabel())
+ .arg(lease->addr_.toText()).arg(lease->valid_lft_);
+
+ ia_rsp->addOption(createStatusCode(*decline, *ia_rsp, STATUS_Success,
+ "Lease declined. Hopefully the next one will be better."));
+}
+
Pkt6Ptr
Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) {
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index 2eb0044343..9d2ff2d510 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -88,7 +88,7 @@ public:
/// @brief returns Kea version on stdout and exit.
/// redeclaration/redefinition. @ref Daemon::getVersion()
static std::string getVersion(bool extended);
-
+
/// @brief Returns server-indentifier option.
///
/// @return server-id option
@@ -263,7 +263,15 @@ protected:
/// @return Reply message to be sent to the client.
Pkt6Ptr processRelease(const Pkt6Ptr& release);
- /// @brief Stub function that will handle incoming Decline.
+ /// @brief Process incoming Decline message.
+ ///
+ /// This method processes Decline message. It conducts standard sanity
+ /// checks, creates empty reply and copies the necessary options from
+ /// the client's message. Finally, it calls @ref declineLeases, where
+ /// the actual address processing takes place.
+ ///
+ /// @throw RFCViolation if Decline message is invalid (lacking mandatory
+ /// options)
///
/// @param decline message received from client
Pkt6Ptr processDecline(const Pkt6Ptr& decline);
@@ -693,6 +701,60 @@ protected:
/// will cause the packet to be assigned to class VENDOR_CLASS_FOO.
static const std::string VENDOR_CLASS_PREFIX;
+ /// @brief Attempts to decline all leases in specified Decline message.
+ ///
+ /// This method iterates over all IA_NA options and calls @ref declineIA on
+ /// each of them.
+ ///
+ /// @param decline Decline messege sent by a client
+ /// @param reply Server's response (IA_NA with status will be added here)
+ /// @param client context
+ void
+ declineLeases(const Pkt6Ptr& decline, Pkt6Ptr& reply,
+ AllocEngine::ClientContext6& ctx);
+
+ /// @brief Declines leases in a single IA_NA option
+ ///
+ /// This method iterates over all addresses in this IA_NA, verifies
+ /// whether they belong to the client and calls @ref declineLease. If there's
+ /// an error, general_status (a status put in the top level scope), will be
+ /// updated.
+ ///
+ /// @param decline client's Decline message
+ /// @param duid client's duid (used to verify if the client owns the lease)
+ /// @param general_status [out] status in top-level message (may be updated)
+ /// @param ia specific IA_NA option to process.
+ /// @return IA_NA option with response (to be included in Reply message)
+ OptionPtr
+ declineIA(const Pkt6Ptr& decline, const DuidPtr& duid, int& general_status,
+ boost::shared_ptr<Option6IA> ia);
+
+ /// @brief Declines specific IPv6 lease.
+ ///
+ /// This method performs the actual decline and all necessary operations:
+ /// - cleans up DNS, if necessary
+ /// - updates subnet[X].declined-addresses (per subnet stat)
+ /// - updates declined-addresses (global stat)
+ /// - deassociates client information from the lease
+ /// - moves the lease to DECLINED state
+ /// - sets lease expiration time to decline-probation-period
+ /// - adds status-code success
+ ///
+ /// @param decline used for generating removal Name Change Request.
+ /// @param lease lease to be declined
+ /// @param ia_rsp response IA_NA.
+ void
+ declineLease(const Pkt6Ptr& decline, const Lease6Ptr lease,
+ boost::shared_ptr<Option6IA> ia_rsp);
+
+ /// @brief A simple utility method that sets the status code
+ ///
+ /// Removes old status code and sets a new one.
+ /// @param container status code will be added here
+ /// @param status status code option
+ void setStatusCode(boost::shared_ptr<Option6IA>& container,
+ const OptionPtr& status);
+
private:
/// @brief Generate FQDN to be sent to a client if none exists.
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 3f74baba71..2f4331da5c 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -91,6 +91,7 @@ dhcp6_unittests_SOURCES += sarr_unittest.cc
dhcp6_unittests_SOURCES += config_parser_unittest.cc
dhcp6_unittests_SOURCES += confirm_unittest.cc
dhcp6_unittests_SOURCES += infrequest_unittest.cc
+dhcp6_unittests_SOURCES += decline_unittest.cc
dhcp6_unittests_SOURCES += dhcp6_message_test.cc dhcp6_message_test.h
dhcp6_unittests_SOURCES += kea_controller_unittest.cc
diff --git a/src/bin/dhcp6/tests/decline_unittest.cc b/src/bin/dhcp6/tests/decline_unittest.cc
new file mode 100644
index 0000000000..41d39d7ffd
--- /dev/null
+++ b/src/bin/dhcp6/tests/decline_unittest.cc
@@ -0,0 +1,274 @@
+// Copyright (C) 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
+// 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_message_test.h>
+#include <stats/stats_mgr.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;
+using namespace isc::stats;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Renew tests.
+///
+/// - Configuration 0:
+/// - only addresses (no prefixes)
+/// - 1 subnet with 2001:db8:1::/64 pool
+const char* DECLINE_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+};
+
+/// @brief Test fixture class for testing Renew.
+class DeclineTest : public Dhcpv6MessageTest {
+public:
+
+ /// @brief Specifies expected outcome
+ enum ExpectedResult {
+ SHOULD_PASS, // pass = accept decline, move lease to declined state.
+ SHOULD_FAIL // fail = reject the decline
+ };
+
+ /// @brief Specifies what address should the client include in its Decline
+ enum AddressInclusion {
+ VALID_ADDR, // Client will include its own, valid address
+ BOGUS_ADDR, // Client will include an address it doesn't own
+ NO_ADDR, // Client will send empty IA_NA (without address)
+ NO_IA // Client will not send IA_NA at all
+ };
+
+ /// @brief Tests if the acquired lease is or is not declined.
+ ///
+ /// @param duid1 DUID used during lease acquisition
+ /// @param iaid1 IAID used during lease acquisition
+ /// @param duid2 DUID used during Decline exchange
+ /// @param iaid2 IAID used during Decline exchange
+ /// @param addr_type specify what sort of address the client should
+ /// include (its own, a bogus one or no address at all)
+ /// @param expected_result SHOULD_PASS if the lease is expected to
+ /// be successfully declined, or SHOULD_FAIL if the lease is expected
+ /// to not be declined.
+ void acquireAndDecline(const std::string& duid1,
+ const uint32_t iaid1,
+ const std::string& duid2,
+ const uint32_t iaid2,
+ AddressInclusion addr_type,
+ ExpectedResult expected_result);
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ DeclineTest()
+ : Dhcpv6MessageTest(), na_iaid_(1234) {
+ }
+
+ /// @brief IAID used for IA_NA.
+ uint32_t na_iaid_;
+
+};
+
+void
+DeclineTest::acquireAndDecline(const std::string& duid1,
+ const uint32_t iaid1,
+ const std::string& duid2,
+ const uint32_t iaid2,
+ AddressInclusion addr_type,
+ ExpectedResult expected_result) {
+ // Set this global statistic explicitly to zero.
+ StatsMgr::instance().setValue("declined-addresses", static_cast<int64_t>(0));
+
+ Dhcp6Client client;
+ client.setDUID(duid1);
+ client.useNA(iaid1);
+
+ // Configure the server with a configuration.
+ ASSERT_NO_THROW(configure(DECLINE_CONFIGS[0], *client.getServer()));
+
+ // Let's get the subnet-id and generate statistics name out of it.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Let's generate the subnet specific statistic
+ std::string name = StatsMgr::generateName("subnet", subnets->at(0)->getID(),
+ "declined-addresses");
+
+ // Set this statistic explicitly to zero.
+ StatsMgr::instance().setValue(name, static_cast<int64_t>(0));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // Remember the acquired address.
+ IOAddress acquired_address = leases_client_na[0].addr_;
+
+ // Check the declined-addresses (subnet) before the Decline operation.
+ ObservationPtr declined_cnt = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(declined_cnt);
+ uint64_t before = declined_cnt->getInteger().first;
+
+ // Check the global declined-addresses statistic before the Decline.
+ ObservationPtr declined_global = StatsMgr::instance()
+ .getObservation("declined-addresses");
+ ASSERT_TRUE(declined_global);
+ uint64_t before_global = declined_cnt->getInteger().first;
+
+ // Let's tamper with the address if necessary.
+ switch (addr_type) {
+ case VALID_ADDR:
+ // Nothing to do, client will do its job correctly by default
+ break;
+ case BOGUS_ADDR:
+ // Simple increase by one.
+ client.config_.leases_[0].addr_ =
+ IOAddress::increase(client.config_.leases_[0].addr_);
+ break;
+ case NO_ADDR:
+ // Tell the client to not include an address in its IA_NA
+ client.includeAddress(false);
+ break;
+ case NO_IA:
+ // Tell the client to not include IA_NA at all
+ client.useNA(false);
+ }
+
+ // Use the second duid
+ client.setDUID(duid2);
+
+ // Use the second IAID
+ client.config_.leases_[0].iaid_ = iaid2;
+
+ // Ok, let's decline the lease.
+ ASSERT_NO_THROW(client.doDecline());
+
+ // Let's check if there's a lease
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ acquired_address);
+ ASSERT_TRUE(lease);
+
+ declined_cnt = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(declined_cnt);
+ uint64_t after = declined_cnt->getInteger().first;
+
+ declined_global = StatsMgr::instance().getObservation("declined-addresses");
+ ASSERT_TRUE(declined_global);
+ uint64_t after_global = declined_global->getInteger().first;
+
+ // We check if the decline process was successful by checking if the
+ // lease is in the database and what is its state.
+ if (expected_result == SHOULD_PASS) {
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+
+ // The decline succeded, so the declined-addresses statistic should
+ // be increased by one
+ EXPECT_EQ(after, before + 1);
+ EXPECT_EQ(after_global, before_global + 1);
+ } else {
+ // the decline was supposed, to be rejected.
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+
+ // The decline failed, so the declined-addresses should be the same
+ // as before
+ EXPECT_EQ(before, after);
+ EXPECT_EQ(before_global, after_global);
+ }
+}
+
+// This test checks that the client can acquire and decline the lease.
+TEST_F(DeclineTest, basic) {
+ acquireAndDecline("01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:06", 1234,
+ VALID_ADDR, SHOULD_PASS);
+}
+
+// This test verifies the decline is rejected in the following case:
+// - Client acquires new lease using duid, iaid
+// - Client sends the DECLINE with duid, iaid, but uses wrong address.
+// - The server rejects Decline due to address mismatch
+TEST_F(DeclineTest, addressMismatch) {
+ acquireAndDecline("01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:06", 1234,
+ BOGUS_ADDR, SHOULD_FAIL);
+}
+
+// This test verifies the decline is rejected in the following case:
+// - Client acquires new lease using duid, iaid1
+// - Client sends the DECLINE with duid, iaid2
+// - The server rejects Decline due to IAID mismatch
+TEST_F(DeclineTest, iaidMismatch) {
+ acquireAndDecline("01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:06", 1235,
+ VALID_ADDR, SHOULD_FAIL);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using duid1, iaid
+// - Client sends the DECLINE using duid2, iaid
+// - The server rejects the Decline due to DUID mismatch
+TEST_F(DeclineTest, duidMismatch) {
+ acquireAndDecline("01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:07", 1234,
+ VALID_ADDR, SHOULD_FAIL);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using duid1, iaid
+// - Client sends the DECLINE using valid duid/iaid, but does not
+// include the address in it
+// - The server rejects the Decline due to missing address
+TEST_F(DeclineTest, noAddrsSent) {
+ acquireAndDecline("01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:06", 1234,
+ NO_ADDR, SHOULD_FAIL);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using duid1, iaid
+// - Client sends the DECLINE using valid duid, but does not
+// include IA_NA at all
+// - The server rejects the Decline due to missing IA_NA
+TEST_F(DeclineTest, noIAs) {
+ acquireAndDecline("01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:06", 1234,
+ NO_IA, SHOULD_FAIL);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc
index d1b9a38995..9ca6282216 100644
--- a/src/bin/dhcp6/tests/dhcp6_client.cc
+++ b/src/bin/dhcp6/tests/dhcp6_client.cc
@@ -96,7 +96,8 @@ Dhcp6Client::Dhcp6Client() :
prefix_hint_(),
fqdn_(),
na_iaid_(1234),
- pd_iaid_(5678) {
+ pd_iaid_(5678),
+ include_address_(true) {
}
Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
@@ -117,11 +118,12 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
prefix_hint_(),
fqdn_(),
na_iaid_(1234),
- pd_iaid_(5678) {
+ pd_iaid_(5678),
+ include_address_(true) {
}
void
-Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
+Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply, uint32_t state) {
typedef OptionCollection Opts;
// Get all options in the reply message and pick IA_NA, IA_PD and
// Status code.
@@ -161,6 +163,7 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
ia->getT1(), ia->getT2(), 0,
hwaddr);
lease.cltt_ = time(NULL);
+ lease.state_ = state;
applyLease(lease);
}
}
@@ -528,6 +531,53 @@ Dhcp6Client::doConfirm() {
}
void
+Dhcp6Client::doDecline() {
+ Pkt6Ptr query = createMsg(DHCPV6_DECLINE);
+ if (!forced_server_id_) {
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ } else {
+ query->addOption(forced_server_id_);
+ }
+
+ generateIAFromLeases(query);
+
+ context_.query_ = query;
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // Apply new configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::generateIAFromLeases(const Pkt6Ptr& query) {
+ /// @todo: add support for IAPREFIX here.
+
+ if (!use_na_) {
+ // If we're told to not use IA_NA at all, there's nothing to be done here
+ return;
+ }
+
+ for (std::vector<Lease6>::const_iterator lease = config_.leases_.begin();
+ lease != config_.leases_.end(); ++lease) {
+ if (lease->type_ != Lease::TYPE_NA) {
+ continue;
+ }
+
+ Option6IAPtr ia(new Option6IA(D6O_IA_NA, lease->iaid_));
+
+ if (include_address_) {
+ ia->addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR,
+ lease->addr_, lease->preferred_lft_, lease->valid_lft_)));
+ }
+ query->addOption(ia);
+ }
+}
+
+void
Dhcp6Client::fastFwdTime(const uint32_t secs) {
// Iterate over all leases and move their cltt backwards.
for (size_t i = 0; i < config_.leases_.size(); ++i) {
diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h
index 9ebc9b31ae..c4c0750462 100644
--- a/src/bin/dhcp6/tests/dhcp6_client.h
+++ b/src/bin/dhcp6/tests/dhcp6_client.h
@@ -269,6 +269,11 @@ public:
/// receiving server's response (if any).
void doConfirm();
+ /// @brief Sends Decline to the server and receives Reply.
+ ///
+ /// This function simulates sending the Decline message to the server and
+ /// receiving the server's response.
+ void doDecline();
/// @brief Performs stateless (inf-request / reply) exchange.
///
@@ -495,6 +500,14 @@ public:
use_client_id_ = send;
}
+ /// @brief Controls whether the client should send an addres in IA_NA
+ ///
+ /// @todo: For now, this flag is only used in Decline
+ /// @param send should the address be included?
+ void includeAddress(const bool send) {
+ include_address_ = send;
+ }
+
/// @brief Specifies if the Rapid Commit option should be included in
/// the Solicit message.
///
@@ -560,6 +573,12 @@ public:
return (duid_);
}
+ /// @brief Generates IA_NA based on lease information
+ ///
+ /// @param query generated IA_NA options will be added here
+ void
+ generateIAFromLeases(const Pkt6Ptr& query);
+
private:
/// @brief Applies the new leases for the client.
@@ -571,11 +590,16 @@ private:
/// or Rebind.
///
/// @param reply Server response.
+ /// @param state specifies lease state (see Lease::STATE_* for details).
+ ///
+ /// The default for state is 0. We could have included dhcpsrv/lease.h
+ /// and used Lease::STATE_DEFAULT, but that would complicate the header
+ /// inclusion dependencies. It's easier to simply use 0 as the default.
///
/// @todo Currently this function supports one IAAddr or IAPrefix option
/// within IA. We will need to extend it to support multiple options
/// within a single IA once server supports that.
- void applyRcvdConfiguration(const Pkt6Ptr& reply);
+ void applyRcvdConfiguration(const Pkt6Ptr& reply, uint32_t state = 0);
/// @brief Applies configuration for the single lease.
///
@@ -735,6 +759,12 @@ private:
uint32_t na_iaid_;
/// @brief IAID used by the client when requesting prefix delegation.
uint32_t pd_iaid_;
+
+ /// @brief Determines if the client will include address in the messages
+ /// it sends.
+ ///
+ /// @todo this flag is currently supported in Decline only.
+ bool include_address_;
};
} // end of namespace isc::dhcp::test
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index 9afcf28555..6305df3d6a 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -123,6 +123,18 @@ DUID::fromText(const std::string& text) {
return DUID(binary);
}
+DuidPtr
+DUID::generateEmpty() {
+
+ // Technically this is a one byte DUID with value of 0. However, we do have
+ // a number of safety checks against invalid duids (too long or empty) and
+ // we should keep them. Therefore "empty" means a single byte with value of 0.
+ std::vector<uint8_t> empty_duid(1,0);
+
+ DuidPtr duid(new DUID(empty_duid));
+ return (duid);
+}
+
std::string DUID::toText() const {
std::stringstream tmp;
tmp << std::hex;
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 65a4ff08fb..9c587b8a59 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.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
@@ -25,6 +25,10 @@
namespace isc {
namespace dhcp {
+/// @brief Shared pointer to a DUID
+class DUID;
+typedef boost::shared_ptr<DUID> DuidPtr;
+
/// @brief Holds DUID (DHCPv6 Unique Identifier)
///
/// This class holds DUID, that is used in client-id, server-id and
@@ -66,6 +70,18 @@ class DUID {
/// @return A reference to a vector holding a DUID.
const std::vector<uint8_t>& getDuid() const;
+
+ /// @brief Returns an instance of empty DUID
+ ///
+ /// In general, empty DUID is not allowed. The only case where it is really
+ /// valid is to designate declined IPv6 Leases. We have a broad assumption
+ /// that the Lease->duid_ must always be set. However, declined lease
+ /// doesn't have any DUID associated with it. Hence we need a way to
+ /// indicate that fact.
+ ///
+ /// @return a smart pointer to an empty DUID
+ static DuidPtr generateEmpty();
+
/// @brief Returns the DUID type
DUIDType getType() const;
@@ -111,9 +127,6 @@ class DUID {
std::vector<uint8_t> duid_;
};
-/// @brief Shared pointer to a DUID
-typedef boost::shared_ptr<DUID> DuidPtr;
-
/// @brief Forward declaration to the @c ClientId class.
class ClientId;
/// @brief Shared pointer to a Client ID.
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index 6d7c6992a3..e663f4ebf9 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -165,6 +165,19 @@ TEST(DuidTest, toText) {
EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
}
+// This test verifies that empty DUID returns proper value
+TEST(DuidTest, empty) {
+ DuidPtr empty;
+ EXPECT_NO_THROW(empty = DUID::generateEmpty());
+
+ // This method must return something
+ ASSERT_TRUE(empty);
+
+ // Ok, technically empty is not really empty, it's just a single
+ // byte with value of 0.
+ EXPECT_EQ("00", empty->toText());
+}
+
// This test checks if the comparison operators are sane.
TEST(DuidTest, operators) {
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
diff --git a/src/lib/dhcpsrv/lease.cc b/src/lib/dhcpsrv/lease.cc
index 2320e51d74..0e77f21436 100644
--- a/src/lib/dhcpsrv/lease.cc
+++ b/src/lib/dhcpsrv/lease.cc
@@ -276,8 +276,18 @@ Lease6::getDuidVector() const {
}
void
-Lease6::decline(uint32_t ) {
- /// @todo: implement this
+Lease6::decline(uint32_t probation_period) {
+ hwaddr_.reset();
+ duid_ = DUID::generateEmpty();
+ t1_ = 0;
+ t2_ = 0;
+ preferred_lft_ = 0;
+ valid_lft_ = probation_period;
+ cltt_ = time(NULL);
+ hostname_ = string("");
+ fqdn_fwd_ = false;
+ fqdn_rev_ = false;
+ state_ = Lease::STATE_DECLINED;
}
std::string
diff --git a/src/lib/dhcpsrv/tests/lease_unittest.cc b/src/lib/dhcpsrv/tests/lease_unittest.cc
index 7b22f99a12..908c862970 100644
--- a/src/lib/dhcpsrv/tests/lease_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_unittest.cc
@@ -772,7 +772,40 @@ TEST(Lease6Test, getDuidVector) {
// Verify that decline() method properly clears up specific fields.
TEST(Lease6Test, decline) {
- /// @todo (see ticket 3981)
+
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
+
+ // Let's create a lease for 2001:db8::1, DUID, iaid=1234,
+ // t1=1000, t2=2000, pref=3000, valid=4000, subnet-id = 1
+ Lease6 lease(Lease::TYPE_NA, IOAddress("2001:db8::1"), duid,
+ 1234, 3000, 4000, 1000, 2000, 1, hwaddr);
+ lease.cltt_ = 12345678;
+ lease.hostname_ = "foo.example.org";
+ lease.fqdn_fwd_ = true;
+ lease.fqdn_rev_ = true;
+
+ time_t now = time(NULL);
+
+ // Move the lease to declined state and set probation-period to 123 seconds
+ lease.decline(123);
+
+ ASSERT_TRUE(lease.duid_);
+ ASSERT_EQ("00", lease.duid_->toText());
+ ASSERT_FALSE(lease.hwaddr_);
+ EXPECT_EQ(0, lease.t1_);
+ EXPECT_EQ(0, lease.t2_);
+ EXPECT_EQ(0, lease.preferred_lft_);
+
+ EXPECT_TRUE(now <= lease.cltt_);
+ EXPECT_TRUE(lease.cltt_ <= now + 1);
+ EXPECT_EQ("", lease.hostname_);
+ EXPECT_FALSE(lease.fqdn_fwd_);
+ EXPECT_FALSE(lease.fqdn_rev_);
+ EXPECT_EQ(Lease::STATE_DECLINED, lease.state_);
+ EXPECT_EQ(123, lease.valid_lft_);
}
// Verify the behavior of the function which checks FQDN data for equality.