From: Marcin Siodelski Date: Tue, 9 Jun 2015 10:00:21 +0000 (+0200) Subject: [3070] Implemented Rapid Commit support for DHCPv6 server. X-Git-Tag: trac3732a_base~15^2~6 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e98d7592b25ffa2e1a84ce49488be4eccef7f826;p=thirdparty%2Fkea.git [3070] Implemented Rapid Commit support for DHCPv6 server. --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 0fdd23f207..57637b94ca 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -1037,7 +1037,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer, break; } case D6O_IA_PD: { - OptionPtr answer_opt = assignIA_PD(question, ctx, + OptionPtr answer_opt = assignIA_PD(question, answer, ctx, boost::dynamic_pointer_cast< Option6IA>(opt->second)); if (answer_opt) { @@ -1318,11 +1318,7 @@ Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer, // the user selects this server to do actual allocation (i.e. sends REQUEST) // it should include this hint. That will help us during the actual lease // allocation. - bool fake_allocation = false; - if (query->getType() == DHCPV6_SOLICIT) { - /// @todo: Check if we support rapid commit - fake_allocation = true; - } + bool fake_allocation = (answer->getType() != DHCPV6_REPLY); // Get DDNS update direction flags bool do_fwd = false; @@ -1433,7 +1429,7 @@ Dhcpv6Srv::conditionalNCRRemoval(Lease6Ptr& old_lease, Lease6Ptr& new_lease, } OptionPtr -Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, +Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, const Pkt6Ptr& answer, AllocEngine::ClientContext6& orig_ctx, boost::shared_ptr ia) { @@ -1477,7 +1473,7 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, // the user selects this server to do actual allocation (i.e. sends REQUEST) // it should include this hint. That will help us during the actual lease // allocation. - bool fake_allocation = (query->getType() == DHCPV6_SOLICIT); + bool fake_allocation = (answer->getType() != DHCPV6_REPLY); // Use allocation engine to pick a lease for this client. Allocation engine // will try to honour the hint, but it is just a hint - some other address @@ -2319,22 +2315,34 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx = createContext(solicit); - Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid())); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid())); - copyClientOptions(solicit, advertise); - appendDefaultOptions(solicit, advertise); - appendRequestedOptions(solicit, advertise, ctx); - appendRequestedVendorOptions(solicit, advertise, ctx); + // Handle Rapid Commit option, if prsent. + if (ctx.subnet_ && ctx.subnet_->getRapidCommit()) { + OptionPtr opt_rapid_commit = solicit->getOption(D6O_RAPID_COMMIT); + if (opt_rapid_commit) { + // If Rapid Commit has been sent by the client, change the + // response type to Reply and include Rapid Commit option. + response->setType(DHCPV6_REPLY); + response->addOption(opt_rapid_commit); + } + } - processClientFqdn(solicit, advertise, ctx); - assignLeases(solicit, advertise, ctx); - // Note, that we don't create NameChangeRequests here because we don't - // perform DNS Updates for Solicit. Client must send Request to update - // DNS. + copyClientOptions(solicit, response); + appendDefaultOptions(solicit, response); + appendRequestedOptions(solicit, response, ctx); + appendRequestedVendorOptions(solicit, response, ctx); - generateFqdn(advertise); + processClientFqdn(solicit, response, ctx); + assignLeases(solicit, response, ctx); + + // Only generate name change requests if sending a Reply as a result + // of receiving Rapid Commit option. + if (response->getType() == DHCPV6_REPLY) { + createNameChangeRequests(response); + } - return (advertise); + return (response); } Pkt6Ptr diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 0099b7ea86..61cdd03da1 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -298,10 +298,12 @@ protected: /// allocation failure. /// /// @param query client's message (typically SOLICIT or REQUEST) + /// @param answer server's response to the client's message. /// @param orig_ctx client context (contains subnet, duid and other parameters) /// @param ia pointer to client's IA_PD option (client's request) /// @return IA_PD option (server's response) OptionPtr assignIA_PD(const Pkt6Ptr& query, + const isc::dhcp::Pkt6Ptr& answer, AllocEngine::ClientContext6& orig_ctx, boost::shared_ptr ia); diff --git a/src/bin/dhcp6/tests/d2_unittest.cc b/src/bin/dhcp6/tests/d2_unittest.cc index e0d11f52df..d7f3b61787 100644 --- a/src/bin/dhcp6/tests/d2_unittest.cc +++ b/src/bin/dhcp6/tests/d2_unittest.cc @@ -45,6 +45,7 @@ const bool Dhcp6SrvD2Test::SHOULD_PASS; const bool Dhcp6SrvD2Test::SHOULD_FAIL; Dhcp6SrvD2Test::Dhcp6SrvD2Test() : rcode_(-1) { + reset(); } Dhcp6SrvD2Test::~Dhcp6SrvD2Test() { diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc index bbf80e9b5d..988d3c67f5 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.cc +++ b/src/bin/dhcp6/tests/dhcp6_client.cc @@ -44,7 +44,9 @@ Dhcp6Client::Dhcp6Client() : use_relay_(false), use_oro_(false), use_client_id_(true), - prefix_hint_() { + use_rapid_commit_(true), + prefix_hint_(), + fqdn_() { } Dhcp6Client::Dhcp6Client(boost::shared_ptr& srv) : @@ -59,7 +61,9 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr& srv) : use_relay_(false), use_oro_(false), use_client_id_(true), - prefix_hint_() { + use_rapid_commit_(true), + prefix_hint_(), + fqdn_() { } void @@ -195,6 +199,13 @@ Dhcp6Client::applyLease(const LeaseInfo& lease_info) { config_.leases_.push_back(lease_info); } +void +Dhcp6Client::appendFQDN() { + if (fqdn_) { + context_.query_->addOption(fqdn_); + } +} + void Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) { typedef OptionCollection Opts; @@ -292,10 +303,22 @@ Dhcp6Client::doSolicit() { } context_.query_->addOption(ia); } + if (use_rapid_commit_) { + context_.query_->addOption(OptionPtr(new Option(Option::V6, + D6O_RAPID_COMMIT))); + } + // Add Client FQDN if configured. + appendFQDN(); + sendMsg(context_.query_); context_.response_ = receiveOneMsg(); - /// @todo Sanity check here + // If using Rapid Commit and the server has responded with Reply, + // let's apply received configuration. + if (use_rapid_commit_ && context_.response_ && + context_.response_->getType() == DHCPV6_REPLY) { + applyRcvdConfiguration(context_.response_); + } } void @@ -303,6 +326,10 @@ Dhcp6Client::doRequest() { Pkt6Ptr query = createMsg(DHCPV6_REQUEST); query->addOption(context_.response_->getOption(D6O_SERVERID)); copyIAs(context_.response_, query); + + // Add Client FQDN if configured. + appendFQDN(); + context_.query_ = query; sendMsg(context_.query_); context_.response_ = receiveOneMsg(); @@ -346,6 +373,10 @@ Dhcp6Client::doRenew() { Pkt6Ptr query = createMsg(DHCPV6_RENEW); query->addOption(context_.response_->getOption(D6O_SERVERID)); copyIAsFromLeases(query); + + // Add Client FQDN if configured. + appendFQDN(); + context_.query_ = query; sendMsg(context_.query_); context_.response_ = receiveOneMsg(); @@ -359,6 +390,10 @@ void Dhcp6Client::doRebind() { Pkt6Ptr query = createMsg(DHCPV6_REBIND); copyIAsFromLeases(query); + + // Add Client FQDN if configured. + appendFQDN(); + context_.query_ = query; sendMsg(context_.query_); context_.response_ = receiveOneMsg(); @@ -510,6 +545,13 @@ Dhcp6Client::useHint(const uint32_t pref_lft, const uint32_t valid_lft, len, pref_lft, valid_lft)); } +void +Dhcp6Client::useFQDN(const uint8_t flags, const std::string& fqdn_name, + Option6ClientFqdn::DomainNameType fqdn_type) { + fqdn_.reset(new Option6ClientFqdn(flags, fqdn_name, fqdn_type)); +} + + } // end of namespace isc::dhcp::test } // end of namespace isc::dhcp diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h index e8e8526f58..9a77672b71 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.h +++ b/src/bin/dhcp6/tests/dhcp6_client.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -427,10 +428,28 @@ public: /// @brief Controls whether the client should send a client-id or not /// @param send should the client-id be sent? - void useClientId(bool send) { + void useClientId(const bool send) { use_client_id_ = send; } + /// @brief Specifies if the Rapid Commit option should be included in + /// the Solicit message. + /// + /// @param rapid_commit Boolean parameter controlling if the Rapid Commit + /// option must be included in the Solicit (if true), or not (if false). + void useRapidCommit(const bool rapid_commit) { + use_rapid_commit_ = rapid_commit; + } + + /// @brief Creates an instance of the Client FQDN option to be included + /// in the client's message. + /// + /// @param flags Flags. + /// @param fqdn_name Name in the textual format. + /// @param fqdn_type Type of the name (fully qualified or partial). + void useFQDN(const uint8_t flags, const std::string& fqdn_name, + Option6ClientFqdn::DomainNameType fqdn_type); + /// @brief Lease configuration obtained by the client. Configuration config_; @@ -492,6 +511,12 @@ private: /// @param lease_info Structure holding new lease information. void applyLease(const LeaseInfo& lease_info); + /// @brief Includes CLient FQDN in the client's message. + /// + /// This method checks if @c fqdn_ is specified and includes it in + /// the client's message. + void appendFQDN(); + /// @brief Copy IA options from one message to another. /// /// This method copies IA_NA and IA_PD options from one message to another. @@ -568,6 +593,7 @@ private: bool use_oro_; ///< Conth bool use_client_id_; + bool use_rapid_commit_; /// @brief Pointer to the option holding a prefix hint. Option6IAPrefixPtr prefix_hint_; @@ -577,6 +603,9 @@ private: /// Content of this vector will be sent as ORO if use_oro_ is set /// to true. See @ref sendORO for details. std::vector oro_; + + /// @brief FQDN requested by the client. + Option6ClientFqdnPtr fqdn_; }; } // end of namespace isc::dhcp::test diff --git a/src/bin/dhcp6/tests/sarr_unittest.cc b/src/bin/dhcp6/tests/sarr_unittest.cc index caec258eb1..93201c66fb 100644 --- a/src/bin/dhcp6/tests/sarr_unittest.cc +++ b/src/bin/dhcp6/tests/sarr_unittest.cc @@ -14,8 +14,11 @@ #include #include +#include #include #include +#include +#include using namespace isc; using namespace isc::dhcp; @@ -32,6 +35,21 @@ namespace { /// - the delegated prefix was intentionally selected to not match the /// subnet prefix, to test that the delegated prefix doesn't need to /// match the subnet prefix +/// +/// - Configuration 1: +/// - one subnet 2001:db8:1::/48 used on eth0 interface +/// - one pool in a range of 2001:db8:1::1 - 2001:db8:1::10 +/// - enables Rapid Commit for the subnet and can be used for testing +/// Rapid Commit option support +/// - DNS updates enabled +/// +/// - Configuration 2: +/// - one subnet 2001:db8:1::/48 used on eth0 interface +/// - one pool in a range of 2001:db8:1::1 - 2001:db8:1::10 +/// - disables Rapid Commit for the subnet and can be used for testing +/// that server ignores Rapid Commit option from the client. +/// - DNS updates enabled +/// const char* CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -50,11 +68,49 @@ const char* CONFIGS[] = { " \"interface-id\": \"\"," " \"interface\": \"eth0\"" " } ]," - "\"valid-lifetime\": 4000 }" + "\"valid-lifetime\": 4000 }", + +// Configuration 1 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth0\"," + " \"rapid-commit\": True" + " } ]," + "\"valid-lifetime\": 4000," + " \"dhcp-ddns\" : {" + " \"enable-updates\" : True, " + " \"qualifying-suffix\" : \"example.com\" }" + "}", + +// Configuration 2 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth0\"," + " \"rapid-commit\": False" + " } ]," + "\"valid-lifetime\": 4000," + " \"dhcp-ddns\" : {" + " \"enable-updates\" : True, " + " \"qualifying-suffix\" : \"example.com\" }" + "}" }; /// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise, -/// Request-Reply. +/// Request-Reply and 2-way exchange: Solicit-Reply. class SARRTest : public Dhcpv6SrvTest { public: /// @brief Constructor. @@ -65,6 +121,14 @@ public: iface_mgr_test_config_(true) { } + /// @brief Destructor. + /// + /// Clear the DHCP-DDNS configuration. + virtual ~SARRTest() { + D2ClientConfigPtr cfg(new D2ClientConfig()); + CfgMgr::instance().setD2ClientConfig(cfg); + } + /// @brief Interface Manager's fake configuration control. IfaceMgrTestConfig iface_mgr_test_config_; }; @@ -127,4 +191,76 @@ TEST_F(SARRTest, directClientPrefixHint) { ASSERT_TRUE(lease_server); } +// Check that when the client includes the Rapid Commit option in its +// Solicit, the server responds with Reply and commits the lease. +TEST_F(SARRTest, rapidCommitEnable) { + Dhcp6Client client; + // Configure client to request IA_NA + client.useNA(); + configure(CONFIGS[1], *client.getServer()); + ASSERT_NO_THROW(client.getServer()->startD2()); + // Make sure we ended-up having expected number of subnets configured. + const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()-> + getCfgSubnets6()->getAll(); + ASSERT_EQ(1, subnets->size()); + // Perform 2-way exchange. + client.useRapidCommit(true); + // Include FQDN to trigger generation of name change requests. + ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S, + "client-name.example.org", + Option6ClientFqdn::FULL)); + + ASSERT_NO_THROW(client.doSolicit()); + // Server should have committed a lease. + ASSERT_EQ(1, client.getLeaseNum()); + Lease6 lease_client = client.getLease(0); + // Make sure that the address belongs to the subnet configured. + ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()-> + selectSubnet(lease_client.addr_, ClientClasses())); + // Make sure that the server responded with Reply. + ASSERT_TRUE(client.getContext().response_); + EXPECT_EQ(DHCPV6_REPLY, client.getContext().response_->getType()); + // Rapid Commit option should be included. + EXPECT_TRUE(client.getContext().response_->getOption(D6O_RAPID_COMMIT)); + // Check that the lease has been committed. + Lease6Ptr lease_server = checkLease(lease_client); + EXPECT_TRUE(lease_server); + // There should be one name change request generated. + EXPECT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize()); +} + +// Check that when the Rapid Commit support is disabled for the subnet +// the server replies with an Advertise and ignores the Rapid Commit +// option sent by the client. +TEST_F(SARRTest, rapidCommitDisable) { + Dhcp6Client client; + // Configure client to request IA_NA + client.useNA(); + configure(CONFIGS[2], *client.getServer()); + ASSERT_NO_THROW(client.getServer()->startD2()); + // Make sure we ended-up having expected number of subnets configured. + const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()-> + getCfgSubnets6()->getAll(); + ASSERT_EQ(1, subnets->size()); + // Send Rapid Commit option to the server. + client.useRapidCommit(true); + // Include FQDN to test that the server will not create name change + // requests when it sends Advertise (Rapid Commit disabled). + ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S, + "client-name.example.org", + Option6ClientFqdn::FULL)); + ASSERT_NO_THROW(client.doSolicit()); + // There should be no lease because the server should have responded + // with Advertise. + ASSERT_EQ(0, client.getLeaseNum()); + // Make sure that the server responded. + ASSERT_TRUE(client.getContext().response_); + EXPECT_EQ(DHCPV6_ADVERTISE, client.getContext().response_->getType()); + // Make sure that the Rapid Commit option is not included. + EXPECT_FALSE(client.getContext().response_->getOption(D6O_RAPID_COMMIT)); + // There should be no name change request generated. + EXPECT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize()); +} + + } // end of anonymous namespace