From 1407676e7f2a71763eb760a5058281a5b22744eb Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Fri, 19 Jun 2015 15:23:23 +0200 Subject: [PATCH] [sedhcpv6] checkpoint (missing commits) --- doc/guide/dhcp6-srv.xml | 34 ++++ src/bin/dhcp6/dhcp6.spec | 6 + src/bin/dhcp6/dhcp6_messages.mes | 6 + src/bin/dhcp6/dhcp6_srv.cc | 99 ++++++----- src/bin/dhcp6/dhcp6_srv.h | 2 + src/bin/dhcp6/json_config_parser.cc | 19 ++- src/bin/dhcp6/tests/config_parser_unittest.cc | 83 ++++++++++ src/bin/dhcp6/tests/d2_unittest.cc | 1 + src/bin/dhcp6/tests/dhcp6_client.cc | 52 +++++- src/bin/dhcp6/tests/dhcp6_client.h | 43 ++++- src/bin/dhcp6/tests/sarr_unittest.cc | 154 +++++++++++++++++- src/lib/dhcpsrv/parsers/dhcp_parsers.cc | 3 +- src/lib/dhcpsrv/subnet.cc | 4 +- src/lib/dhcpsrv/subnet.h | 25 ++- src/lib/dhcpsrv/tests/subnet_unittest.cc | 18 ++ 15 files changed, 494 insertions(+), 55 deletions(-) diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml index 196e6da573..56deedce93 100644 --- a/doc/guide/dhcp6-srv.xml +++ b/doc/guide/dhcp6-srv.xml @@ -1315,6 +1315,40 @@ should include options from the isc option space: +
+ Rapid Commit + The Rapid Commit option, described in + RFC 3315, is supported + by the Kea DHCPv6 server. However, support is disabled by default for + all subnets. It can be enabled for a particular subnet using the + rapid-commit parameter as shown below: + +"Dhcp6": { + "subnet6": [ + { + "subnet": "2001:db8:beef::/48", + "rapid-commit": true, + "pools": [ + { + "pool": "2001:db8:beef::1-2001:db8:beef::10" + } + ], + } + ], + ... +} + + + + This setting only affects the subnet for which the + rapid-commit is set to true. + For clients connected to other subnets, the server will ignore the + Rapid Commit option sent by the client and will follow the 4-way + exchange procedure, i.e. respond with the Advertise for the Solicit + containing Rapid Commit option. + +
+
DHCPv6 Relays diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec index 8821639775..9709be7eb2 100644 --- a/src/bin/dhcp6/dhcp6.spec +++ b/src/bin/dhcp6/dhcp6.spec @@ -318,6 +318,12 @@ "item_default": "" }, + { "item_name": "rapid-commit", + "item_type": "boolean", + "item_optional": false, + "item_default": false + }, + { "item_name": "renew-timer", "item_type": "integer", "item_optional": false, diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index bd52a0405a..0b3f18feb4 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -429,6 +429,12 @@ as a hint for possible requested prefix. % DHCP6_QUERY_DATA received packet length %1, data length %2, data is %3 A debug message listing the data received from the client or relay. +% DHCP6_RAPID_COMMIT %1: Rapid Commit option received, following 2-way exchange +This debug message is issued when the server found a Rapid Commit option +in the client's message and 2-way exchanges are supported by the +server for the subnet on which the client is connected. The argument +specifies the client and transaction identification information. + % DHCP6_RELEASE_MISSING_CLIENTID client (address=%1) sent RELEASE message without mandatory client-id This warning message indicates that client sent RELEASE message without mandatory client-id option. This is most likely caused by a buggy client diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 9869eb8eaa..9a7c8db79a 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -1060,7 +1060,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) { @@ -1304,7 +1304,7 @@ Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer, const Subnet6Ptr& subnet = orig_ctx.subnet_; const DuidPtr& duid = orig_ctx.duid_; - // If there is no subnet selected for handling this IA_NA, the only thing to do left is + // If there is no subnet selected for handling this IA_NA, the only thing left to do is // to say that we are sorry, but the user won't get an address. As a convenience, we // use a different status text to indicate that (compare to the same status code, // but different wording below) @@ -1335,17 +1335,18 @@ Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer, .arg(duid ? duid->toText() : "(no-duid)").arg(ia->getIAID()) .arg(hint_opt ? hint.toText() : "(no hint)"); - // "Fake" allocation is processing of SOLICIT message. We pretend to do an - // allocation, but we do not put the lease in the database. That is ok, - // because we do not guarantee that the user will get that exact lease. If - // 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; - } + // "Fake" allocation is the case when the server is processing the Solicit + // message without the Rapid Commit option and advertises a lease to + // the client, but doesn't commit this lease to the lease database. If + // the Solicit contains the Rapid Commit option and the server is + // configured to honor the Rapid Commit option, or the client has sent + // the Request message, the lease will be committed to the lease + // database. The type of the server's response may be used to determine + // if this is the fake allocation case or not. When the server sends + // Reply message it means that it is committing leases. Other message + // type (Advertise) means that server is not committing leases (fake + // allocation). + bool fake_allocation = (answer->getType() != DHCPV6_REPLY); // Get DDNS update direction flags bool do_fwd = false; @@ -1456,7 +1457,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) { @@ -1468,8 +1469,8 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, // as we can initialize IAID using a constructor. boost::shared_ptr ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID())); - // If there is no subnet selected for handling this IA_PD, the only thing to - // do left is to say that we are sorry, but the user won't get an address. + // If there is no subnet selected for handling this IA_PD, the only thing + // left to do is to say that we are sorry, but the user won't get an address. // As a convenience, we use a different status text to indicate that // (compare to the same status code, but different wording below) if (!subnet) { @@ -1494,13 +1495,18 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, .arg(duid ? duid->toText() : "(no-duid)").arg(ia->getIAID()) .arg(hint_opt ? hint.toText() : "(no hint)"); - // "Fake" allocation is processing of SOLICIT message. We pretend to do an - // allocation, but we do not put the lease in the database. That is ok, - // because we do not guarantee that the user will get that exact lease. If - // 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); + // "Fake" allocation is the case when the server is processing the Solicit + // message without the Rapid Commit option and advertises a lease to + // the client, but doesn't commit this lease to the lease database. If + // the Solicit contains the Rapid Commit option and the server is + // configured to honor the Rapid Commit option, or the client has sent + // the Request message, the lease will be committed to the lease + // database. The type of the server's response may be used to determine + // if this is the fake allocation case or not. When the server sends + // Reply message it means that it is committing leases. Other message + // type (Advertise) means that server is not committing leases (fake + // allocation). + 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 @@ -2341,26 +2347,43 @@ 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())); - - copyClientOptions(solicit, advertise); - appendDefaultOptions(solicit, advertise); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid())); - if (!validateSeDhcpOptions(solicit, advertise, ctx)) { - return (advertise); + // Handle Rapid Commit option, if present. + if (ctx.subnet_ && ctx.subnet_->getRapidCommit()) { + OptionPtr opt_rapid_commit = solicit->getOption(D6O_RAPID_COMMIT); + if (opt_rapid_commit) { + + /// @todo uncomment when #3807 is merged! +/* LOG_DEBUG(options_logger, DBG_DHCP6_DETAIL, DHCP6_RAPID_COMMIT) + .arg(solicit->getLabel()); */ + + // 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); + } } - appendRequestedOptions(solicit, advertise, ctx); - appendRequestedVendorOptions(solicit, advertise, ctx); + + copyClientOptions(solicit, response); + appendDefaultOptions(solicit, response); - 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. + if (!validateSeDhcpOptions(solicit, response, ctx)) { + return (response); + } + appendRequestedOptions(solicit, response, ctx); + appendRequestedVendorOptions(solicit, response, ctx); - generateFqdn(advertise); + processClientFqdn(solicit, response, ctx); + assignLeases(solicit, response, ctx); - return (advertise); + // 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 (response); } Pkt6Ptr diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 7b0eea8349..8548f993e6 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -302,10 +302,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/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 37b32f0979..3a49ffba91 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -405,6 +405,8 @@ protected: parser = new PdPoolListParser(config_id, pools_); } else if (config_id.compare("option-data") == 0) { parser = new OptionDataListParser(config_id, options_, AF_INET6); + } else if (config_id.compare("rapid-commit") == 0) { + parser = new BooleanParser(config_id, boolean_values_); } else { isc_throw(NotImplemented, "unsupported parameter: " << config_id); } @@ -474,12 +476,16 @@ protected: } } - stringstream tmp; - tmp << addr << "/" << static_cast(len) - << " with params t1=" << t1 << ", t2=" << t2 << ", pref=" - << pref << ", valid=" << valid; + bool rapid_commit = boolean_values_->getOptionalParam("rapid-commit", false); - LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str()); + std::ostringstream output; + output << addr << "/" << static_cast(len) + << " with params t1=" << t1 << ", t2=" + << t2 << ", preferred-lifetime=" << pref + << ", valid-lifetime=" << valid + << ", rapid-commit is " << (rapid_commit ? "enabled" : "disabled"); + + LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(output.str()); // Create a new subnet. Subnet6* subnet6 = new Subnet6(addr, len, t1, t2, pref, valid, @@ -492,6 +498,9 @@ protected: subnet6->setInterfaceId(opt); } + // Enable or disable Rapid Commit option support for the subnet. + subnet6->setRapidCommit(rapid_commit); + // Try setting up client class (if specified) try { string client_class = string_values_->getParam("client-class"); diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index 50448ebe93..631dcf23c1 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -496,6 +496,41 @@ public: CfgMgr::instance().clear(); } + /// @brief Tests the Rapid Commit configuration for a subnet. + /// + /// This test configures the server with a given configuration and + /// verifies if the Rapid Commit has been configured successfully + /// for a subnet. + /// + /// @param config Server configuration, possibly including the + /// 'rapid-commit' parameter. + /// @param exp_rapid_commit Expected value of the Rapid Commit flag + /// within a subnet. + void testRapidCommit(const std::string& config, + const bool exp_rapid_commit) { + // Clear any existing configuration. + CfgMgr::instance().clear(); + + // Configure the server. + ElementPtr json = Element::fromJSON(config); + + // Make sure that the configuration was successful. + ConstElementPtr status; + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); + checkResult(status, 0); + + // Get the subnet. + Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()-> + selectSubnet(IOAddress("2001:db8:1::5"), classify_); + ASSERT_TRUE(subnet); + + // Check the Rapid Commit flag for the subnet. + EXPECT_EQ(exp_rapid_commit, subnet->getRapidCommit()); + + // Clear any existing configuration. + CfgMgr::instance().clear(); + } + int rcode_; ///< Return code (see @ref isc::config::parseAnswer) Dhcpv6Srv srv_; ///< Instance of the Dhcp6Srv used during tests ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer) @@ -1089,6 +1124,54 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) { EXPECT_TRUE(errorContainsPosition(status, "")); } +// This test checks the configuration of the Rapid Commit option +// support for the subnet. +TEST_F(Dhcp6ParserTest, subnetRapidCommit) { + { + // rapid-commit implicitly set to false. + SCOPED_TRACE("Default Rapid Commit setting"); + testRapidCommit("{ \"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::1 - " + "2001:db8:1::ffff\" } ]," + " \"subnet\": \"2001:db8:1::/64\" } ]," + "\"valid-lifetime\": 4000 }", + false); + } + + { + // rapid-commit explicitly set to true. + SCOPED_TRACE("Enable Rapid Commit"); + testRapidCommit("{ \"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::1 - " + "2001:db8:1::ffff\" } ]," + " \"rapid-commit\": True," + " \"subnet\": \"2001:db8:1::/64\" } ]," + "\"valid-lifetime\": 4000 }", + true); + } + + { + // rapid-commit explicitly set to false. + SCOPED_TRACE("Disable Rapid Commit"); + testRapidCommit("{ \"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::1 - " + "2001:db8:1::ffff\" } ]," + " \"rapid-commit\": False," + " \"subnet\": \"2001:db8:1::/64\" } ]," + "\"valid-lifetime\": 4000 }", + false); + } +} + // This test checks that multiple pools can be defined and handled properly. // The test defines 2 subnets, each with 2 pools. TEST_F(Dhcp6ParserTest, multiplePools) { 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..08f37a234f 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.cc +++ b/src/bin/dhcp6/tests/dhcp6_client.cc @@ -38,13 +38,16 @@ Dhcp6Client::Dhcp6Client() : dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS), duid_(generateDUID(DUID::DUID_LLT)), link_local_("fe80::3a60:77ff:fed5:cdef"), + iface_name_("eth0"), srv_(boost::shared_ptr(new NakedDhcpv6Srv(0))), use_na_(false), use_pd_(false), use_relay_(false), use_oro_(false), use_client_id_(true), - prefix_hint_() { + use_rapid_commit_(false), + prefix_hint_(), + fqdn_() { } Dhcp6Client::Dhcp6Client(boost::shared_ptr& srv) : @@ -53,13 +56,16 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr& srv) : dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS), duid_(generateDUID(DUID::DUID_LLT)), link_local_("fe80::3a60:77ff:fed5:cdef"), + iface_name_("eth0"), srv_(srv), use_na_(false), use_pd_(false), use_relay_(false), use_oro_(false), use_client_id_(true), - prefix_hint_() { + use_rapid_commit_(false), + prefix_hint_(), + fqdn_() { } void @@ -195,6 +201,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 +305,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 +328,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 +375,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 +392,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(); @@ -497,7 +534,7 @@ Dhcp6Client::sendMsg(const Pkt6Ptr& msg) { msg->getBuffer().getLength())); msg_copy->setRemoteAddr(link_local_); msg_copy->setLocalAddr(dest_addr_); - msg_copy->setIface("eth0"); + msg_copy->setIface(iface_name_); srv_->fakeReceive(msg_copy); srv_->run(); } @@ -510,6 +547,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..f4150ccd40 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 @@ -383,6 +384,13 @@ public: dest_addr_ = dest_addr; } + /// @brief Sets the interface to be used by the client. + /// + /// @param iface_name Interface name. + void setInterface(const std::string& iface_name) { + iface_name_ = iface_name; + } + /// @brief Sets a prefix hint to be sent to a server. /// /// @param pref_lft Preferred lifetime. @@ -427,10 +435,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 +518,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. @@ -550,7 +582,7 @@ private: /// @biref Current transaction id (altered on each send). uint32_t curr_transid_; - /// @brief Currently use destination address. + /// @brief Currently used destination address. asiolink::IOAddress dest_addr_; /// @brief Currently used DUID. @@ -559,6 +591,9 @@ private: /// @brief Currently used link local address. asiolink::IOAddress link_local_; + /// @brief Currently used interface. + std::string iface_name_; + /// @brief Pointer to the server that the client is communicating with. boost::shared_ptr srv_; @@ -568,6 +603,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 +613,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..2035521383 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,16 @@ 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: +/// - two subnets 2001:db8:1::/48 and 2001:db8:2::/48 +/// - first subnet assigned to interface eth0, another one assigned to eth1 +/// - one pool for subnet in a range of 2001:db8:X::1 - 2001:db8:X::10, +/// where X is 1 or 2 +/// - enables Rapid Commit for the first subnet and disables for the second +/// one +/// - DNS updates enabled +/// const char* CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -50,11 +63,36 @@ 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" + " }," + " {" + " \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ]," + " \"subnet\": \"2001:db8:2::/48\", " + " \"interface\": \"eth1\"," + " \"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 +103,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 +173,108 @@ 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(2, 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 the server responds with Advertise if the client hasn't +// included the Rapid Commit option in the Solicit. +TEST_F(SARRTest, rapidCommitNoOption) { + 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(2, subnets->size()); + // 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()); +} + +// 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; + // The subnet assigned to eth1 has Rapid Commit disabled. + client.setInterface("eth1"); + // 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(2, 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 diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index 68da05f44a..e822eec753 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -1033,7 +1033,8 @@ SubnetConfigParser::SubnetConfigParser(const std::string&, : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()), boolean_values_(new BooleanStorage()), - pools_(new PoolStorage()), global_context_(global_context), + pools_(new PoolStorage()), + global_context_(global_context), relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)), options_(new CfgOption()) { // The first parameter should always be "subnet", but we don't check diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index c5b775b302..36f1ae19ec 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -329,8 +329,8 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length, const Triplet& preferred_lifetime, const Triplet& valid_lifetime, const SubnetID id) -:Subnet(prefix, length, t1, t2, valid_lifetime, RelayInfo(IOAddress("::")), id), - preferred_(preferred_lifetime) { + :Subnet(prefix, length, t1, t2, valid_lifetime, RelayInfo(IOAddress("::")), id), + preferred_(preferred_lifetime), rapid_commit_(false) { if (!prefix.isV6()) { isc_throw(BadValue, "Non IPv6 prefix " << prefix << " specified in subnet6"); diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index 4d630cc50e..92c896db2a 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -639,7 +639,23 @@ public: return interface_id_; } -protected: + /// @brief Enables or disables Rapid Commit option support for the subnet. + /// + /// @param rapid_commit A boolean value indicating that the Rapid Commit + /// option support is enabled (if true), or disabled (if false). + void setRapidCommit(const bool rapid_commit) { + rapid_commit_ = rapid_commit; + }; + + /// @brief Returns boolean value indicating that the Rapid Commit option + /// is supported or unsupported for the subnet. + /// + /// @return true if the Rapid Commit option is supported, false otherwise. + bool getRapidCommit() const { + return (rapid_commit_); + } + +private: /// @brief Returns default address for pool selection /// @return ANY IPv6 address @@ -660,6 +676,13 @@ protected: /// @brief a triplet with preferred lifetime (in seconds) Triplet preferred_; + + /// @brief A flag indicating if Rapid Commit option is supported + /// for this subnet. + /// + /// It's default value is false, which indicates that the Rapid + /// Commit is disabled for the subnet. + bool rapid_commit_; }; /// @brief A pointer to a Subnet6 object diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index 58377ac396..8a5ad8af72 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -1041,6 +1041,24 @@ TEST(Subnet6Test, interfaceId) { } +// This test checks that the Rapid Commit support can be enabled or +// disabled for a subnet. It also checks that the Rapid Commit +// support is disabled by default. +TEST(Subnet6Test, rapidCommit) { + Subnet6 subnet(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4); + + // By default, the RC should be disabled. + EXPECT_FALSE(subnet.getRapidCommit()); + + // Enable Rapid Commit. + subnet.setRapidCommit(true); + EXPECT_TRUE(subnet.getRapidCommit()); + + // Disable again. + subnet.setRapidCommit(false); + EXPECT_FALSE(subnet.getRapidCommit()); +} + // Checks if last allocated address/prefix is stored/retrieved properly TEST(Subnet6Test, lastAllocated) { IOAddress ia("2001:db8:1::1"); -- 2.47.2