// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc; using namespace isc::dhcp::test; using namespace isc::asiolink; using namespace isc::dhcp; using namespace isc::dhcp_ddns; using namespace isc::util; using namespace isc::hooks; using namespace std; namespace { /// @brief A test fixture class for testing DHCPv6 Client FQDN Option handling. class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest { public: // Bit Constants for turning on and off DDNS configuration options. // (Defined here as these are only meaningful to this class.) static const uint16_t OVERRIDE_NO_UPDATE = 1; static const uint16_t OVERRIDE_CLIENT_UPDATE = 2; static const uint16_t REPLACE_CLIENT_NAME = 4; // Enum used to specify whether a client (packet) should include // the hostname option enum ClientNameFlag { CLIENT_NAME_PRESENT, CLIENT_NAME_NOT_PRESENT }; // Enum used to specify whether the server should replace/supply // the hostname or not enum ReplacementFlag { NAME_REPLACED, NAME_NOT_REPLACED }; // Type used to indicate whether or not forward updates are expected struct ExpFwd { ExpFwd(bool exp_fwd) : value_(exp_fwd){}; bool value_; }; // Type used to indicate whether or not reverse updates are expected struct ExpRev { ExpRev(bool exp_rev) : value_(exp_rev){}; bool value_; }; /// @brief Constructor FqdnDhcpv6SrvTest() : Dhcpv6SrvTest(), srv_(new NakedDhcpv6Srv(0)), d2_mgr_(CfgMgr::instance().getD2ClientMgr()), iface_mgr_test_config_(true) { // generateClientId assigns DUID to duid_. generateClientId(); lease_.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), duid_, 1234, 501, 502, 1, HWAddrPtr())); // Config DDNS to be enabled, all controls off enableD2(); } /// @brief Destructor virtual ~FqdnDhcpv6SrvTest() { // Default constructor creates a config with DHCP-DDNS updates // disabled. D2ClientConfigPtr cfg(new D2ClientConfig()); CfgMgr::instance().setD2ClientConfig(cfg); } /// @brief Sets the server's DDNS configuration to ddns updates disabled. void disableD2() { // Default constructor creates a config with DHCP-DDNS updates // disabled. D2ClientConfigPtr cfg(new D2ClientConfig()); CfgMgr::instance().setD2ClientConfig(cfg); } /// @brief Enables DHCP-DDNS updates with the given options enabled. /// /// Replaces the current D2ClientConfiguration with a configuration /// which as updates enabled and the control options set based upon /// the bit mask of options. /// /// @param mask Bit mask of configuration options that should be enabled. void enableD2(const uint16_t mask = 0) { D2ClientConfigPtr cfg; ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true, isc::asiolink::IOAddress("::1"), 53001, isc::asiolink::IOAddress("::"), 0, 1024, dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON))); ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg)); // Now we'll set the DDNS parameters at the subnet level. // These should get fetched when getDdnsParams() is invoked. ASSERT_TRUE(subnet_) << "enableD2 called without subnet_ set"; subnet_->setDdnsSendUpdates(true); subnet_->setDdnsOverrideNoUpdate(mask & OVERRIDE_NO_UPDATE); subnet_->setDdnsOverrideClientUpdate(mask & OVERRIDE_CLIENT_UPDATE); subnet_->setDdnsReplaceClientNameMode((mask & REPLACE_CLIENT_NAME) ? D2ClientConfig::RCM_WHEN_PRESENT : D2ClientConfig::RCM_NEVER); subnet_->setDdnsGeneratedPrefix("myhost"); subnet_->setDdnsQualifyingSuffix("example.com"); subnet_->setHostnameCharSet("[^A-Za-z0-9-]"); subnet_->setHostnameCharReplacement("x"); subnet_->setDdnsUseConflictResolution(true); ASSERT_NO_THROW(srv_->startD2()); } // Fetch DDNS parameter set scoped to the current subnet_. DdnsParamsPtr getDdnsParams() { ConstElementPtr cfg = CfgMgr::instance().getCurrentCfg()->toElement(); if (!subnet_) { ADD_FAILURE() << "getDdnsParams() - subnet_ is empty!"; return (DdnsParamsPtr(new DdnsParams())); } return(CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_)); } /// @brief Construct the DHCPv6 Client FQDN option using flags and /// domain-name. /// /// @param flags Flags to be set for the created option. /// @param fqdn_name A name which should be stored in the option. /// @param fqdn_type A type of the name carried by the option: partial /// or fully qualified. /// /// @return A pointer to the created option. Option6ClientFqdnPtr createClientFqdn(const uint8_t flags, const std::string& fqdn_name, const Option6ClientFqdn::DomainNameType fqdn_type) { return (Option6ClientFqdnPtr(new Option6ClientFqdn(flags, fqdn_name, fqdn_type))); } /// @brief Create a message with or without DHCPv6 Client FQDN Option. /// /// @param msg_type A type of the DHCPv6 message to be created. /// @param fqdn_flags Flags to be carried in the FQDN option. /// @param fqdn_domain_name A name to be carried in the FQDN option. /// @param fqdn_type A type of the name carried by the option: partial /// or fully qualified. /// @param include_oro A boolean value which indicates whether the ORO /// option should be added to the message (if true). /// @param srvid server id to be stored in the message. /// /// @return An object representing the created message. Pkt6Ptr generateMessage(uint8_t msg_type, const uint8_t fqdn_flags, const std::string& fqdn_domain_name, const Option6ClientFqdn::DomainNameType fqdn_type, const bool include_oro, OptionPtr srvid = OptionPtr()) { Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234)); pkt->setIface("eth0"); pkt->setIndex(ETH0_INDEX); Option6IAPtr ia = generateIA(D6O_IA_NA, 234, 1500, 3000); if (msg_type != DHCPV6_REPLY) { IOAddress hint("2001:db8:1:1::dead:beef"); OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500)); ia->addOption(hint_opt); pkt->addOption(ia); } OptionPtr clientid = generateClientId(); pkt->addOption(clientid); if (srvid && (msg_type != DHCPV6_SOLICIT)) { pkt->addOption(srvid); } pkt->addOption(createClientFqdn(fqdn_flags, fqdn_domain_name, fqdn_type)); if (include_oro) { OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); oro->addValue(D6O_CLIENT_FQDN); pkt->addOption(oro); } return (pkt); } /// @brief Creates instance of the DHCPv6 message with client id and /// server id. /// /// @param msg_type A type of the message to be created. /// /// @return An object representing the created message. Pkt6Ptr generateMessageWithIds(const uint8_t msg_type) { Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234)); pkt->setIface("eth0"); pkt->setIndex(ETH0_INDEX); // Generate client-id. OptionPtr opt_clientid = generateClientId(); pkt->addOption(opt_clientid); if (msg_type != DHCPV6_SOLICIT) { // Generate server-id. pkt->addOption(srv_->getServerID()); } return (pkt); } /// @brief Returns an instance of the option carrying FQDN. /// /// @param pkt A message holding FQDN option to be returned. /// /// @return An object representing DHCPv6 Client FQDN option. Option6ClientFqdnPtr getClientFqdnOption(const Pkt6Ptr& pkt) { return (boost::dynamic_pointer_cast (pkt->getOption(D6O_CLIENT_FQDN))); } /// @brief Adds IA option to the message. /// /// Added option holds an address. /// /// @param iaid IAID /// @param pkt A DHCPv6 message to which the IA option should be added. void addIA(const uint32_t iaid, const IOAddress& addr, Pkt6Ptr& pkt) { Option6IAPtr opt_ia = generateIA(D6O_IA_NA, iaid, 1500, 3000); Option6IAAddrPtr opt_iaaddr(new Option6IAAddr(D6O_IAADDR, addr, 300, 500)); opt_ia->addOption(opt_iaaddr); pkt->addOption(opt_ia); } /// @brief Adds IA option to the message. /// /// Added option holds status code. /// /// @param iaid IAID /// @param status_code Status code /// @param pkt A DHCPv6 message to which the option should be added. void addIA(const uint32_t iaid, const uint16_t status_code, Pkt6Ptr& pkt) { Option6IAPtr opt_ia = generateIA(D6O_IA_NA, iaid, 1500, 3000); addStatusCode(status_code, "", opt_ia); pkt->addOption(opt_ia); } /// @brief Creates status code with the specified code and message. /// /// @param code A status code. /// @param msg A string representation of the message to be added to the /// Status Code option. /// /// @return An object representing the Status Code option. Option6StatusCodePtr createStatusCode(const uint16_t code, const std::string& msg) { Option6StatusCodePtr opt_status(new Option6StatusCode(code, msg)); return (opt_status); } /// @brief Adds Status Code option to the IA. /// /// @param code A status code value. /// @param msg A string representation of the message to be added to the /// Status Code option. void addStatusCode(const uint16_t code, const std::string& msg, Option6IAPtr& opt_ia) { opt_ia->addOption(createStatusCode(code, msg)); } /// @brief Verifies if the DHCPv6 server processes DHCPv6 Client FQDN option /// as expected and subsequent interpretation of this processing when /// creating NCRs, if any should be created. /// /// This function simulates generation of the client's message holding FQDN. /// It then calls the server's @c Dhcpv6Srv::processClientFqdn option to /// generate server's response to the FQDN. This function returns the FQDN /// which should be appended to the server's response to the client. /// This function verifies that the FQDN option returned is correct /// Optionally, this function then proceeds to call createNameChangeRequests /// to verify if that method interprets the FQDN information properly. /// /// @param msg_type A type of the client's message. /// @param in_flags A value of flags field to be set for the FQDN carried /// in the client's message. /// @param in_domain_name A domain name to be carried in the client's FQDN /// option. /// @param in_domain_type A type of the domain name to be carried in the /// client's FQDM option (partial or fully qualified). /// @param exp_flags A value of flags expected in the FQDN sent by a server. /// @param exp_domain_name A domain name expected in the FQDN sent by a /// server. /// @param create_ncr_check if true, calls createNameChangeRequests method /// and tests the outcome. /// @param exp_fwd indicates whether or not a forward change is expected /// @param exp_fwd indicates whether or not a reverse change is expected void testFqdn(const uint16_t msg_type, const uint8_t in_flags, const std::string& in_domain_name, const Option6ClientFqdn::DomainNameType in_domain_type, const uint8_t exp_flags, const std::string& exp_domain_name, const bool create_ncr_check, const ExpFwd& exp_fwd = ExpFwd(true), const ExpRev& exp_rev = ExpRev(true)) { Pkt6Ptr question = generateMessage(msg_type, in_flags, in_domain_name, in_domain_type, true); ASSERT_TRUE(getClientFqdnOption(question)); Pkt6Ptr answer = generateMessageWithIds(msg_type == DHCPV6_SOLICIT ? DHCPV6_ADVERTISE : DHCPV6_REPLY); // Create three IAs, each having different address. addIA(1234, IOAddress("2001:db8:1::1"), answer); AllocEngine::ClientContext6 ctx; // Set the selected subnet so ddns params get returned correctly. ctx.subnet_ = subnet_; ASSERT_NO_THROW(srv_->processClientFqdn(question, answer, ctx)); Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast< Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(answ_fqdn); const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0; const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0; const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0; EXPECT_EQ(flag_n, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_N)); EXPECT_EQ(flag_s, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_S)); EXPECT_EQ(flag_o, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_O)); EXPECT_EQ(exp_domain_name, answ_fqdn->getDomainName()); // If server is configured to generate full FQDN for a client, and/or // client sent empty FQDN the expected result of the processing by // processClientFqdn is an empty, partial FQDN. This is an indication // for the code which performs lease allocation that the FQDN has to // be generated from the lease address. if (exp_domain_name.empty()) { EXPECT_EQ(Option6ClientFqdn::PARTIAL, answ_fqdn->getDomainNameType()); } else { EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType()); } if (create_ncr_check) { // Context flags are normally set during lease allocation. Since that // hasn't occurred we'll set them here to match the expected values. // Call createNameChangeRequests ctx.fwd_dns_update_ = exp_fwd.value_; ctx.rev_dns_update_ = exp_rev.value_; ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx)); if (exp_fwd.value_ || exp_rev.value_) { // Should have created 1 NCR. NameChangeRequestPtr ncr; ASSERT_EQ(1, d2_mgr_.getQueueSize()); ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0)); ASSERT_TRUE(ncr); EXPECT_EQ(dhcp_ddns::CHG_ADD, ncr->getChangeType()); EXPECT_EQ(exp_fwd.value_, ncr->isForwardChange()); EXPECT_EQ(exp_rev.value_, ncr->isReverseChange()); ASSERT_NO_THROW(d2_mgr_.runReadyIO()); } else { // Should not have created any NCRs. EXPECT_EQ(0, d2_mgr_.getQueueSize()); } } } // Test that the server processes the FQDN option (or lack thereof) // in a client request correctly, according to the replace-client-name // mode configuration parameter. // // @param mode - value to use for replace-client-name mode // // @param client_name_flag - specifies whether or not the client request // should contain a hostname option // @param exp_replacement_flag - specifies whether or not the server is // expected to replace (or supply) the FQDN/name in its response void testReplaceClientNameMode(const char* mode, enum ClientNameFlag client_name_flag, enum ReplacementFlag exp_replacement_flag) { // Configuration "template" with a replaceable mode parameter const char* config_template = "{ \"interfaces-config\": { \n" " \"interfaces\": [ \"eth0\" ] \n" "}, \n" "\"valid-lifetime\": 4000, \n" "\"preferred-lifetime\": 3000, \n" "\"rebind-timer\": 2000, \n" "\"renew-timer\": 1000, \n" "\"subnet6\": [ { \n" " \"id\": 1, \n" " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n" " \"subnet\": \"2001:db8:1::/48\", \n" " \"interface\": \"eth0\" \n" " } ], \n" "\"dhcp-ddns\": { \n" "\"enable-updates\": true, \n" "\"qualifying-suffix\": \"fake-suffix.isc.org.\", \n" "\"replace-client-name\": \"%s\" \n" "}} \n"; // Create the configuration and configure the server char config_buf[1024]; snprintf(config_buf, 1024, config_template, mode); configure(config_buf, *srv_); // Build our client packet Pkt6Ptr query; if (client_name_flag == CLIENT_NAME_PRESENT) { query = generateMessage(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "my.example.com.", Option6ClientFqdn::FULL, true); } else { query = generateMessageWithIds(DHCPV6_SOLICIT); } AllocEngine::ClientContext6 ctx; bool drop = !srv_->earlyGHRLookup(query, ctx); ASSERT_FALSE(drop); srv_->initContext(query, ctx, drop); ASSERT_FALSE(drop); Pkt6Ptr answer = generateMessageWithIds(DHCPV6_ADVERTISE); ASSERT_NO_THROW(srv_->processClientFqdn(query, answer, ctx)); Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast< Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN)); // Verify the contents (or lack thereof) of the FQDN if (exp_replacement_flag == NAME_REPLACED) { ASSERT_TRUE(answ_fqdn); EXPECT_TRUE(answ_fqdn->getDomainName().empty()); EXPECT_EQ(Option6ClientFqdn::PARTIAL, answ_fqdn->getDomainNameType()); } else { if (client_name_flag == CLIENT_NAME_PRESENT) { ASSERT_TRUE(answ_fqdn); EXPECT_EQ("my.example.com.", answ_fqdn->getDomainName()); EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType()); } else { ASSERT_FALSE(answ_fqdn); } } } /// @brief Tests that the client's message holding an FQDN is processed /// and that lease is acquired. /// /// @param msg_type A type of the client's message. /// @param hostname A domain name in the client's FQDN. /// @param client_flags A bitmask of the client FQDN flags /// @param include_oro A boolean value which indicates whether the ORO /// option should be included in the client's message (if true) or not /// (if false). In the former case, the function will expect that server /// responds with the FQDN option. In the latter case, the function expects /// that the server doesn't respond with the FQDN. void testProcessMessage(const uint8_t msg_type, const std::string& hostname, const std::string& exp_hostname, const uint8_t client_flags = Option6ClientFqdn::FLAG_S, const IOAddress& expected_address = IOAddress("2001:db8:1:1::dead:beef"), const bool include_oro = true) { // Create a message of a specified type, add server id and // FQDN option. OptionPtr srvid = srv_->getServerID(); // Set the appropriate FQDN type. It must be partial if hostname is // empty. Option6ClientFqdn::DomainNameType fqdn_type = (hostname.empty() ? Option6ClientFqdn::PARTIAL : Option6ClientFqdn::FULL); Pkt6Ptr req = generateMessage(msg_type, client_flags, hostname, fqdn_type, include_oro, srvid); // For different client's message types we have to invoke different // functions to generate response. Pkt6Ptr reply; AllocEngine::ClientContext6 ctx; bool drop = !srv_->earlyGHRLookup(req, ctx); ASSERT_FALSE(drop); srv_->initContext(req, ctx, drop); ASSERT_FALSE(drop); if (msg_type == DHCPV6_SOLICIT) { ASSERT_NO_THROW(reply = srv_->processSolicit(ctx)); } else if (msg_type == DHCPV6_REQUEST) { ASSERT_NO_THROW(reply = srv_->processRequest(ctx)); } else if (msg_type == DHCPV6_RENEW) { ASSERT_NO_THROW(reply = srv_->processRenew(ctx)); } else if (msg_type == DHCPV6_RELEASE) { // For Release no lease will be acquired so we have to leave // function here. ASSERT_NO_THROW(reply = srv_->processRelease(ctx)); return; } else { // We are not interested in testing other message types. return; } // For Solicit, we will get different message type obviously. if (msg_type == DHCPV6_SOLICIT) { checkResponse(reply, DHCPV6_ADVERTISE, 1234); } else { checkResponse(reply, DHCPV6_REPLY, 1234); } // Check verify that IA_NA is correct. Option6IAAddrPtr addr = checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2()); ASSERT_TRUE(addr); // Check that we have got the address we requested. checkIAAddr(addr, expected_address, Lease::TYPE_NA); if (msg_type != DHCPV6_SOLICIT) { // Check that the lease exists. Lease6Ptr lease = checkLease(duid_, reply->getOption(D6O_IA_NA), addr); ASSERT_TRUE(lease); EXPECT_EQ(exp_hostname, lease->hostname_); } // The Client FQDN option should be always present in the server's // response, regardless if requested using ORO or not. Option6ClientFqdnPtr fqdn; ASSERT_TRUE(fqdn = boost::dynamic_pointer_cast< Option6ClientFqdn>(reply->getOption(D6O_CLIENT_FQDN))); EXPECT_EQ(exp_hostname, fqdn->getDomainName()); } /// @brief Verify that NameChangeRequest holds valid values. /// /// This function picks first NameChangeRequest from the internal server's /// queue and checks that it holds valid parameters. The NameChangeRequest /// is removed from the queue. /// /// @param type An expected type of the NameChangeRequest (Add or Remove). /// @param reverse An expected setting of the reverse update flag. /// @param forward An expected setting of the forward update flag. /// @param addr A string representation of the IPv6 address held in the /// NameChangeRequest. /// @param dhcid An expected DHCID value. Ignored if blank. /// @note This value is the value that is produced by /// dhcp_ddns::D2Dhcid::createDigest() with the appropriate arguments. This /// method uses encryption tools to produce the value which cannot be /// easily duplicated by hand. It is more or less necessary to generate /// these values programmatically and place them here. Should the /// underlying implementation of createDigest() change these test values /// will likely need to be updated as well. /// @param expires The cltt of the lease associated with the /// NameChangeRequest, and used to calculate NCR expires value. /// @param valid_lft the valid lifetime of the lease associated with the /// NameChangeRequest. /// @param fqdn The expected string value of the FQDN, if blank the /// check is skipped /// @param exp_use_cr expected value of NCR::conflict_resolution_ /// @param ddns_ttl_percent expected value of ddns_ttl_percent used for /// the NCR void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type, const bool reverse, const bool forward, const std::string& addr, const std::string& dhcid, const uint64_t expires, const uint16_t valid_lft, const std::string& fqdn = "", const bool exp_use_cr = true, util::Optional exp_ddns_ttl_percent = util::Optional()) { NameChangeRequestPtr ncr; ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0)); ASSERT_TRUE(ncr); EXPECT_EQ(type, ncr->getChangeType()); EXPECT_EQ(forward, ncr->isForwardChange()); EXPECT_EQ(reverse, ncr->isReverseChange()); EXPECT_EQ(addr, ncr->getIpAddress()); if (!dhcid.empty()) { EXPECT_EQ(dhcid, ncr->getDhcid().toStr()); } uint32_t ttl = calculateDdnsTtl(valid_lft, exp_ddns_ttl_percent); if (expires != 0) { EXPECT_EQ(expires + ttl, ncr->getLeaseExpiresOn()); } EXPECT_EQ(ttl, ncr->getLeaseLength()); EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus()); if (! fqdn.empty()) { EXPECT_EQ(fqdn, ncr->getFqdn()); } EXPECT_EQ(exp_use_cr, ncr->useConflictResolution()); // Process the message off the queue ASSERT_NO_THROW(d2_mgr_.runReadyIO()); } /// @brief Updates inherited subnet and pool members /// /// Hack added to set subnet_ and pool_ members that are buried into lower /// level tests such as checkLease(), so one can use "configure" functionality /// rather than hand-building configured objects /// @param subnet_idx Element index of the desired subnet /// @param pool_idx Element index of the desired pool within the desired subnet /// @param type lease type of desired pool /// void setSubnetAndPool(int subnet_idx, int pool_idx, Lease::Type type) { ConstCfgSubnets6Ptr subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6(); ASSERT_TRUE(subnets); const Subnet6Collection* subnet_col = subnets->getAll(); ASSERT_EQ(subnet_idx + 1, subnet_col->size()); auto subnet_it = subnet_col->begin(); // std::advance is not available for this iterator. for (int i = 0; i < subnet_idx; ++i) { subnet_it = std::next(subnet_it); } subnet_ = *subnet_it; ASSERT_TRUE(subnet_); const PoolCollection& pool_col = subnet_->getPools(type); ASSERT_EQ(pool_idx + 1, pool_col.size()); PoolPtr pool = (subnet_->getPools(type)).at(pool_idx); ASSERT_TRUE(pool); pool_ = boost::dynamic_pointer_cast(pool); ASSERT_TRUE(pool); } /// Pointer to Dhcpv6Srv that is used in tests boost::scoped_ptr srv_; // Reference to D2ClientMgr singleton D2ClientMgr& d2_mgr_; /// @brief Interface Manager's fake configuration control. IfaceMgrTestConfig iface_mgr_test_config_; // Holds a lease used by a test. Lease6Ptr lease_; }; // A set of tests verifying server's behavior when it receives the DHCPv6 // Client Fqdn Option. // Test server's response when client requests that server performs AAAA update. TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) { testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "myhost.example.com", Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S, "myhost.example.com.", true, ExpFwd(true), ExpRev(true)); } // Test server's response when client provides partial domain-name and requests // that server performs AAAA update. TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) { testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "myhost", Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S, "myhost.example.com.", true, ExpFwd(true), ExpRev(true)); } // Test server's response when client provides empty domain-name and requests // that server performs AAAA update. TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) { testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "", Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S, "", false); } // Test server's response when client requests no DNS update. TEST_F(FqdnDhcpv6SrvTest, noUpdate) { testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_N, "myhost.example.com", Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N, "myhost.example.com.", true, ExpFwd(false), ExpRev(false)); } // Test server's response when client requests no DNS update and // override-no-updates is true. TEST_F(FqdnDhcpv6SrvTest, overrideNoUpdate) { enableD2(OVERRIDE_NO_UPDATE); testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_N, "myhost.example.com", Option6ClientFqdn::FULL, (Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O), "myhost.example.com.", true, ExpFwd(true), ExpRev(true)); } // Test server's response when client requests that server delegates the AAAA // update to the client TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdate) { testFqdn(DHCPV6_SOLICIT, 0, "myhost.example.com.", Option6ClientFqdn::FULL, 0, "myhost.example.com.", true, ExpFwd(false), ExpRev(true)); } // Test server's response when client requests that server delegates the AAAA // update to the client and this delegation is not allowed. TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) { enableD2(OVERRIDE_CLIENT_UPDATE); testFqdn(DHCPV6_SOLICIT, 0, "myhost.example.com.", Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O, "myhost.example.com.", true, ExpFwd(true), ExpRev(true)); } // Test that exception is thrown if supplied NULL answer packet when // creating NameChangeRequests. TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) { Pkt6Ptr answer; AllocEngine::ClientContext6 ctx; ctx.subnet_ = subnet_; ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true; EXPECT_THROW(srv_->createNameChangeRequests(answer, ctx), isc::Unexpected); } // Test that exception is thrown if supplied answer from the server // contains no DUID when creating NameChangeRequests. TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) { Pkt6Ptr answer = Pkt6Ptr(new Pkt6(DHCPV6_REPLY, 1234)); Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S, "myhost.example.com", Option6ClientFqdn::FULL); answer->addOption(fqdn); AllocEngine::ClientContext6 ctx; ctx.subnet_ = subnet_; ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true; EXPECT_THROW(srv_->createNameChangeRequests(answer, ctx), isc::Unexpected); } // Test no NameChangeRequests if Client FQDN is not added to the server's // response. TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) { // Create Reply message with Client Id and Server id. Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY); AllocEngine::ClientContext6 ctx; ctx.subnet_ = subnet_; ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true; ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx)); // There should be no new NameChangeRequests. ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Test that NameChangeRequests are not generated if an answer message // contains no addresses. TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) { // Create Reply message with Client Id and Server id. Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY); // Add Client FQDN option. Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S, "myhost.example.com", Option6ClientFqdn::FULL); answer->addOption(fqdn); AllocEngine::ClientContext6 ctx; ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true; ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx)); // We didn't add any IAs, so there should be no NameChangeRequests in the // queue. ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Test that exactly one NameChangeRequest is created as a result of processing // the answer message which holds 3 IAs and when FQDN is specified. TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) { // Create Reply message with Client Id and Server id. Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY); // Create three IAs, each having different address. addIA(1234, IOAddress("2001:db8:1::1"), answer); addIA(2345, IOAddress("2001:db8:1::2"), answer); addIA(3456, IOAddress("2001:db8:1::3"), answer); // Use domain name in upper case. It should be converted to lower-case // before DHCID is calculated. So, we should get the same result as if // we typed domain name in lower-case. Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S, "MYHOST.EXAMPLE.COM", Option6ClientFqdn::FULL); answer->addOption(fqdn); // Create NameChangeRequest for the first allocated address. AllocEngine::ClientContext6 ctx; ctx.subnet_ = subnet_; ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true; ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx)); ASSERT_EQ(1, d2_mgr_.getQueueSize()); // Verify that NameChangeRequest is correct. verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1::1", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 500); } // Verify that conflict resolution is turned off when the // subnet has it disabled. TEST_F(FqdnDhcpv6SrvTest, noConflictResolution) { // Create Reply message with Client Id and Server id. Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY); // Create three IAs, each having different address. addIA(1234, IOAddress("2001:db8:1::1"), answer); // Use domain name in upper case. It should be converted to lower-case // before DHCID is calculated. So, we should get the same result as if // we typed domain name in lower-case. Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S, "MYHOST.EXAMPLE.COM", Option6ClientFqdn::FULL); answer->addOption(fqdn); // Create NameChangeRequest for the first allocated address. AllocEngine::ClientContext6 ctx; subnet_->setDdnsUseConflictResolution(false); ctx.subnet_ = subnet_; ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true; ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx)); ASSERT_EQ(1, d2_mgr_.getQueueSize()); // Verify that NameChangeRequest is correct. verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1::1", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 500, "", false); } // Checks that NameChangeRequests to add entries are not // created when ddns updates are disabled. TEST_F(FqdnDhcpv6SrvTest, noAddRequestsWhenDisabled) { // Disable DDNS updates. disableD2(); // Create Reply message with Client Id and Server id. Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY); // Create three IAs, each having different address. addIA(1234, IOAddress("2001:db8:1::1"), answer); // Use domain name in upper case. It should be converted to lower-case // before DHCID is calculated. So, we should get the same result as if // we typed domain name in lower-case. Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S, "MYHOST.EXAMPLE.COM", Option6ClientFqdn::FULL); answer->addOption(fqdn); // An attempt to send a NCR would throw. AllocEngine::ClientContext6 ctx; ctx.subnet_ = subnet_; ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true; ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx)); } // Test creation of the NameChangeRequest to remove both forward and reverse // mapping for the given lease. TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) { lease_->fqdn_fwd_ = true; lease_->fqdn_rev_ = true; // Part of the domain name is in upper case, to test that it gets converted // to lower case before DHCID is computed. So, we should get the same DHCID // as if we typed domain-name in lower case. lease_->hostname_ = "MYHOST.example.com."; ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_)); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true, "2001:db8:1::1", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", lease_->cltt_, lease_->valid_lft_); } // Checks that calling queueNCR would not result in error if DDNS updates are // disabled. TEST_F(FqdnDhcpv6SrvTest, noRemovalsWhenDisabled) { // Disable DDNS updates. disableD2(); lease_->fqdn_fwd_ = true; lease_->fqdn_rev_ = true; // Part of the domain name is in upper case, to test that it gets converted // to lower case before DHCID is computed. So, we should get the same DHCID // as if we typed domain-name in lower case. lease_->hostname_ = "MYHOST.example.com."; // When DDNS is disabled an attempt to send a request should not throw, but // nothing is generated. Unfortunately, we can't see if anything get // generated because getting anything from the queue when DDNS is disabled // would result in exception. ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_)); } // Test creation of the NameChangeRequest to remove reverse mapping for the // given lease. TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) { lease_->fqdn_fwd_ = false; lease_->fqdn_rev_ = true; lease_->hostname_ = "myhost.example.com."; ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_)); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, false, "2001:db8:1::1", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", lease_->cltt_, lease_->valid_lft_); } // Test that NameChangeRequest to remove DNS records is not generated when // neither forward nor reverse DNS update has been performed for a lease. TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoUpdate) { lease_->fqdn_fwd_ = false; lease_->fqdn_rev_ = false; ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_)); ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Test that NameChangeRequest is not generated if the hostname hasn't been // specified for a lease for which forward and reverse mapping has been set. TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoHostname) { lease_->fqdn_fwd_ = true; lease_->fqdn_rev_ = true; lease_->hostname_ = ""; Pkt6Ptr pkt(new Pkt6(DHCPREQUEST, 1234)); ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_)); ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Test that NameChangeRequest is not generated if the invalid hostname has // been specified for a lease for which forward and reverse mapping has been // set. TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestWrongHostname) { lease_->fqdn_fwd_ = true; lease_->fqdn_rev_ = true; lease_->hostname_ = "myhost..example.com."; ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_)); ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Test that Advertise message generated in a response to the Solicit will // not result in generation if the NameChangeRequests. TEST_F(FqdnDhcpv6SrvTest, processSolicit) { // Create a Solicit message with FQDN option and generate server's // response using processSolicit function. testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", "myhost.example.com."); ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Test that client may send two requests, each carrying FQDN option with // a different domain-name. Server should use existing lease for the second // request but modify the DNS entries for the lease according to the contents // of the FQDN sent in the second request. TEST_F(FqdnDhcpv6SrvTest, processTwoRequestsDiffFqdn) { // Create a Request message with FQDN option and generate server's // response using processRequest function. This will result in the // creation of a new lease and the appropriate NameChangeRequest // to add both reverse and forward mapping to DNS. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); // The lease should have been recorded in the database. lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef")); ASSERT_TRUE(lease_); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4000); // Client may send another request message with a new domain-name. In this // case the same lease will be returned. The existing DNS entry needs to // be replaced with a new one. Server should determine that the different // FQDN has been already added to the DNS. As a result, the old DNS // entries should be removed and the entries for the new domain-name // should be added. Therefore, we expect two NameChangeRequests. One to // remove the existing entries, one to add new entries. testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com", "otherhost.example.com."); ASSERT_EQ(2, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", lease_->cltt_, lease_->valid_lft_); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201D422AA463306223D269B6CB7AFE7AAD265FC" "EA97F93623019B2E0D14E5323D5A", 0, lease_->valid_lft_); } // Test that client may send two requests, each carrying FQDN option with // the same domain-name. Server should use existing lease for the second // request and not modify the DNS entries. TEST_F(FqdnDhcpv6SrvTest, processTwoRequestsSameFqdn) { // Create a Request message with FQDN option and generate server's // response using processRequest function. This will result in the // creation of a new lease and the appropriate NameChangeRequest // to add both reverse and forward mapping to DNS. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); // The lease should have been recorded in the database. lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef")); ASSERT_TRUE(lease_); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4000); // Client may send another request message with a same domain-name. In this // case the same lease will be returned. The existing DNS entry should be // left alone, so we expect no NameChangeRequests queued.. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Test that NameChangeRequest is not generated when Solicit message is sent. // The Solicit is here sent after a lease has been allocated for a client. // The Solicit conveys a different hostname which would trigger updates to // DNS if the Request was sent instead of Soicit. The code should differentiate // behavior depending whether Solicit or Request is sent. TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) { // Create a Request message with FQDN option and generate server's // response using processRequest function. This will result in the // creation of a new lease and the appropriate NameChangeRequest // to add both reverse and forward mapping to DNS. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4000); // When the returning client sends Solicit the code should never generate // NameChangeRequest and preserve existing DNS entries for the client. // The NameChangeRequest should only be generated when a client sends // Request or Renew. testProcessMessage(DHCPV6_SOLICIT, "otherhost.example.com", "otherhost.example.com."); ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Test that client may send Request followed by the Renew, both holding // FQDN options, but each option holding different domain-name. The Renew // should result in generation of the two NameChangeRequests, one to remove // DNS entry added previously when Request was processed, another one to // add a new entry for the FQDN held in the Renew. /// @todo: Fix will be available on trac3677 TEST_F(FqdnDhcpv6SrvTest, processRequestRenewDiffFqdn) { // Create a Request message with FQDN option and generate server's // response using processRequest function. This will result in the // creation of a new lease and the appropriate NameChangeRequest // to add both reverse and forward mapping to DNS. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); // The lease should have been recorded in the database. lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef")); ASSERT_TRUE(lease_); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4000); // Client may send Renew message with a new domain-name. In this // case the same lease will be returned. The existing DNS entry needs to // be replaced with a new one. Server should determine that the different // FQDN has been already added to the DNS. As a result, the old DNS // entries should be removed and the entries for the new domain-name // should be added. Therefore, we expect two NameChangeRequests. One to // remove the existing entries, one to add new entries. testProcessMessage(DHCPV6_RENEW, "otherhost.example.com", "otherhost.example.com."); ASSERT_EQ(2, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", lease_->cltt_, lease_->valid_lft_); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201D422AA463306223D269B6CB7AFE7AAD265FC" "EA97F93623019B2E0D14E5323D5A", 0, lease_->valid_lft_); } // Test that client may send Request followed by the Renew, both holding // FQDN options, but each option holding different domain-name. The Renew // should result in generation of the two NameChangeRequests, one to remove // DNS entry added previously when Request was processed, another one to // add a new entry for the FQDN held in the Renew. TEST_F(FqdnDhcpv6SrvTest, processRequestRenewSameFqdn) { // Create a Request message with FQDN option and generate server's // response using processRequest function. This will result in the // creation of a new lease and the appropriate NameChangeRequest // to add both reverse and forward mapping to DNS. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4000); ASSERT_EQ(0, d2_mgr_.getQueueSize()); // Client may send Renew message with a same domain-name. In this // case the same lease will be returned. No DNS updates should be // required, so the NCR queue should be empty. testProcessMessage(DHCPV6_RENEW, "myhost.example.com", "myhost.example.com."); ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Tests that renewals using the same domain name but differing values for // the directional update flags result in NCRs or not, accordingly. // If the new leases's flags are the same as the previous lease's flags, // then no requests should be generated. If at lease one of the new lease's // flags differ from the previous lease, then: // A: A removal NCR should be created based on the previous leases's flags // if at least one of those flags are true // B: An add NCR should be created based on the new lease's flags, if at // least one of those flags are true TEST_F(FqdnDhcpv6SrvTest, processRequestRenewFqdnFlags) { // Create a Request message with FQDN option but with N flag = 1, which // means no updates should be done. This should result in no NCRs. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com.", Option6ClientFqdn::FLAG_N); // Queue should be empty. ASSERT_EQ(0, d2_mgr_.getQueueSize()); // Now renew with Both N and S = 0. This means the server should only // do reverse updates and should result in a reverse-only NCR. testProcessMessage(DHCPV6_RENEW, "myhost.example.com", "myhost.example.com.", 0); // We should a only have reverse-only ADD, no remove. ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, false, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4000); // Renew again with the same flags, this should not generate any NCRs. testProcessMessage(DHCPV6_RENEW, "myhost.example.com", "myhost.example.com.", 0); // Queue should be empty. ASSERT_EQ(0, d2_mgr_.getQueueSize()); // Renew with both N and S flags = 0. This tells the server to update // both directions, which should change forward flag to true. This should // generate a reverse only remove and a dual add. testProcessMessage(DHCPV6_RENEW, "myhost.example.com", "myhost.example.com.", Option6ClientFqdn::FLAG_S); // We need the lease for the expiration value. lease_ = LeaseMgrFactory:: instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef")); ASSERT_TRUE(lease_); // We should have two NCRs, one remove and one add. ASSERT_EQ(2, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, false, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", lease_->cltt_, lease_->valid_lft_); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, lease_->valid_lft_); // Lastly, we renew with the N flag = 1 (which means no updates) so we // should have a dual direction remove NCR but NO add NCR. testProcessMessage(DHCPV6_RENEW, "myhost.example.com", "myhost.example.com.", Option6ClientFqdn::FLAG_N); // We should only have the removal NCR. ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", lease_->cltt_, lease_->valid_lft_); } TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) { CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setFlushReclaimedTimerWaitTime(0); CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setHoldReclaimedTime(0); // Create a Request message with FQDN option and generate server's // response using processRequest function. This will result in the // creation of a new lease and the appropriate NameChangeRequest // to add both reverse and forward mapping to DNS. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); // The lease should have been recorded in the database. lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef")); ASSERT_TRUE(lease_); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, lease_->valid_lft_); // Client may send Release message. In this case the lease should be // removed and all existing DNS entries for this lease should also // be removed. Therefore, we expect that single NameChangeRequest to // remove DNS entries is generated. testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", "otherhost.example.com."); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", lease_->cltt_, lease_->valid_lft_); } TEST_F(FqdnDhcpv6SrvTest, processRequestReleaseNoDelete) { // Create a Request message with FQDN option and generate server's // response using processRequest function. This will result in the // creation of a new lease and the appropriate NameChangeRequest // to add both reverse and forward mapping to DNS. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); // The lease should have been recorded in the database. lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef")); ASSERT_TRUE(lease_); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, lease_->valid_lft_); // Client may send Release message. In this case the lease should be // expired and no NameChangeRequest to remove DNS entries is generated. testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", "otherhost.example.com."); ASSERT_EQ(0, d2_mgr_.getQueueSize()); } // Checks that the server include DHCPv6 Client FQDN option in its // response even when client doesn't request this option using ORO. TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) { // The last parameter disables use of the ORO to request FQDN option // In this case, we expect that the FQDN option will not be included // in the server's response. The testProcessMessage will check that. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com.", Option6ClientFqdn::FLAG_S, IOAddress("2001:db8:1:1::dead:beef"), false); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4000); } // Checks that FQDN is generated from an ip address, when client sends an empty // FQDN. TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) { testProcessMessage(DHCPV6_REQUEST, "", "myhost-2001-db8-1-1--dead-beef.example.com.", Option6ClientFqdn::FLAG_S, IOAddress("2001:db8:1:1::dead:beef"), false); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201C905E54BE12DE6AF92ADE72752B9F362" "13B5A8BC9D217548CD739B4CF31AFB1B", 0, 4000); } // Checks that when the server reuses expired lease, the NameChangeRequest // is generated to remove the DNS mapping for the expired lease and second // NameChangeRequest to add a DNS mapping for a new lease. TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) { // This address will be used throughout the test. IOAddress addr("2001:db8:1:1::dead:beef"); // We are going to configure a subnet with a pool that consists of // exactly one address. This address will be handed out to the // client, will get expired and then be reused. CfgMgr::instance().clear(); subnet_ = Subnet6::create(IOAddress("2001:db8:1:1::"), 56, 1, 2, 3, 4, SubnetID(10)); subnet_->setIface("eth0"); subnet_->setDdnsSendUpdates(true); pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); subnet_->addPool(pool_); CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet_); CfgMgr::instance().commit(); // Enable D2. enableD2(); // Allocate a lease. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com."); // Test that the appropriate NameChangeRequest has been generated. ASSERT_EQ(1, d2_mgr_.getQueueSize()); // Get the lease acquired. Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr); ASSERT_TRUE(lease); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, lease_->valid_lft_); // One of the following: IAID, DUID or subnet identifier has to be changed // because otherwise the allocation engine will treat the lease as // being renewed by the same client. If we at least change subnet identifier // the lease will be treated as expired lease to be reused. ++lease->subnet_id_; // Move the cllt back in time and make sure that the lease got expired. lease->cltt_ = time(NULL) - 10; lease->valid_lft_ = 5; ASSERT_TRUE(lease->expired()); // Change the hostname so as the name change request for removing existing // DNS mapping is generated. lease->hostname_ = "otherhost.example.com."; // Update the lease in the lease database. LeaseMgrFactory::instance().updateLease6(lease); // Simulate another lease acquisition. Since, our pool consists of // exactly one address and this address is used by the lease in the // lease database, it is guaranteed that the allocation engine will // reuse this lease. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com.", "myhost.example.com."); ASSERT_EQ(2, d2_mgr_.getQueueSize()); // The first name change request generated, should remove a DNS // mapping for an expired lease. verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true, "2001:db8:1:1::dead:beef", "000201D422AA463306223D269B6CB7AFE7AAD2" "65FCEA97F93623019B2E0D14E5323D5A", lease->cltt_, lease->valid_lft_); // The second name change request should add a DNS mapping for // a new lease. verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4); } TEST_F(FqdnDhcpv6SrvTest, processClientDelegation) { testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "myhost.example.com.", 0); ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, false, "2001:db8:1:1::dead:beef", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4000); } // Verify that the host reservation is found and used. Lease host name and // FQDN should be the reservation hostname suffixed by the qualifying suffix. TEST_F(FqdnDhcpv6SrvTest, hostnameReservationSuffix) { isc::dhcp::test::IfaceMgrTestConfig test_config(true); string config_str = "{ " "\"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"valid-lifetime\": 4000, " "\"preferred-lifetime\": 3000," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"subnet6\": [ " " { " " \"id\": 1, \n" " \"subnet\": \"2001:db8:1::/48\", " " \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ]," " \"interface\" : \"eth0\" , " " \"reservations\": [" " {" " \"duid\": \"" + duid_->toText() + "\"," " \"ip-addresses\": [ \"2001:db8:1:1::babe\" ]," " \"hostname\": \"alice\"" " }" " ]" " } ]," " \"dhcp-ddns\" : {" " \"enable-updates\" : true, " " \"qualifying-suffix\" : \"example.com\" }" "}"; configure(config_str); // Update subnet_ and pool_ members after config setSubnetAndPool(0, 0, Lease::TYPE_NA); ASSERT_NO_THROW(srv_->startD2()); ASSERT_TRUE(CfgMgr::instance().ddnsEnabled()); // Verify that the host reservation is found and lease name/FQDN are // formed properly from the host name and qualifying suffix. testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "alice.example.com.", 1, IOAddress("2001:db8:1:1::babe")); // Verify that NameChangeRequest is correct. verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::babe", "000201E2EB74FB53A5778E74AFD43870ECA5" "4150B1F52B0CFED434802DA1259D6D3CA4", 0, 4000, "alice.example.com."); } // Verify that the host reservation is found and used, rather than dynamic // Address. Lease host name and FQDN should be the reservation hostname // without a qualifying suffix. TEST_F(FqdnDhcpv6SrvTest, hostnameReservationNoSuffix) { isc::dhcp::test::IfaceMgrTestConfig test_config(true); string config_str = "{ " "\"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"valid-lifetime\": 4000, " "\"preferred-lifetime\": 3000," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"subnet6\": [ " " { " " \"id\": 1, \n" " \"subnet\": \"2001:db8:1::/48\", " " \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ]," " \"interface\" : \"eth0\" , " " \"reservations\": [" " {" " \"duid\": \"" + duid_->toText() + "\"," " \"ip-addresses\": [ \"2001:db8:1:1::babe\" ]," " \"hostname\": \"alice.example.com\"" " }" " ]" " } ]," " \"dhcp-ddns\" : {" " \"enable-updates\" : true, " " \"qualifying-suffix\" : \"\" }" "}"; configure(config_str); // Update subnet_ and pool_ members after config setSubnetAndPool(0, 0, Lease::TYPE_NA); ASSERT_NO_THROW(srv_->startD2()); ASSERT_TRUE(CfgMgr::instance().ddnsEnabled()); testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "alice.example.com.", 1, IOAddress("2001:db8:1:1::babe")); // Verify that NameChangeRequest is correct. verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1:1::babe", "000201E2EB74FB53A5778E74AFD43870ECA5" "4150B1F52B0CFED434802DA1259D6D3CA4", 0, 4000, "alice.example.com."); } TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) { isc::dhcp::test::IfaceMgrTestConfig test_config(true); string config_str = "{ " "\"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"valid-lifetime\": 4000, " "\"preferred-lifetime\": 3000," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"subnet6\": [ " " { " " \"id\": 1, \n" " \"subnet\": \"2001:db8:1::/48\", " " \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ]," " \"interface\" : \"eth0\" , " " \"reservations\": [" " {" " \"duid\": \"" + duid_->toText() + "\"," " \"ip-addresses\": [ \"2001:db8:1:1::babe\" ]," " \"hostname\": \"alice\"" " }" " ]" " } ]," " \"dhcp-ddns\" : {" " \"enable-updates\" : false, " " \"qualifying-suffix\" : \"disabled.example.com\" }" "}"; configure(config_str); // Update subnet_ and pool_ members after config setSubnetAndPool(0, 0, Lease::TYPE_NA); ASSERT_FALSE(CfgMgr::instance().ddnsEnabled()); testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", "alice.disabled.example.com.", 0, IOAddress("2001:db8:1:1::babe")); } // Verifies that the replace-client-name behavior is correct for each of // the supported modes. TEST_F(FqdnDhcpv6SrvTest, replaceClientNameModeTest) { isc::dhcp::test::IfaceMgrTestConfig test_config(true); testReplaceClientNameMode("never", CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED); testReplaceClientNameMode("never", CLIENT_NAME_PRESENT, NAME_NOT_REPLACED); testReplaceClientNameMode("always", CLIENT_NAME_NOT_PRESENT, NAME_REPLACED); testReplaceClientNameMode("always", CLIENT_NAME_PRESENT, NAME_REPLACED); testReplaceClientNameMode("when-present", CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED); testReplaceClientNameMode("when-present", CLIENT_NAME_PRESENT, NAME_REPLACED); testReplaceClientNameMode("when-not-present", CLIENT_NAME_NOT_PRESENT, NAME_REPLACED); testReplaceClientNameMode("when-not-present", CLIENT_NAME_PRESENT, NAME_NOT_REPLACED); } // Verifies that setting hostname-char-set sanitizes FQDN option // values received from clients. TEST_F(FqdnDhcpv6SrvTest, sanitizeFqdn) { // Verify a full FQDN with no invalid chars is left alone testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "myhost.example.com", Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S, "myhost.example.com.", false); // Verify that a partial FQDN with no invalid chars is left alone testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "myhost", Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S, "myhost.example.com.", false); // Verify that a full FQDN with invalid chars is cleaned. testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "m%y*host.example.com", Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S, "mxyxhost.example.com.", false); // Verify that a partial FQDN with invalid chars is cleaned. testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "m%y*host", Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S, "mxyxhost.example.com.", false); // Verify that a full FQDN with nul chars is cleaned. testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, std::string("m\000yhost.exa\000mple.com", 20), Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S, "mxyhost.exaxmple.com.", false); } // Verifies that scoped ddns-parameter handling. // Specifically that D2 can be enabled with sending updates // disabled globally, and enabled at the subnet level. TEST_F(FqdnDhcpv6SrvTest, ddnsScopeTest) { std::string config_str = "{ \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" "},\n" "\"preferred-lifetime\": 3000,\n" "\"rebind-timer\": 2000,\n" "\"renew-timer\": 1000,\n" "\"ddns-send-updates\": false,\n" "\"subnet6\": [ {\n" " \"id\": 1, \n" " \"subnet\": \"2001:db8:1::/48\",\n" " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],\n" " \"interface\": \"eth0\"\n" " },\n" " {\n" " \"id\": 2, \n" " \"subnet\": \"2001:db8:2::/48\",\n" " \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ],\n" " \"interface\": \"eth1\",\n" " \"ddns-send-updates\": true\n" " } ],\n" "\"valid-lifetime\": 4000,\n" " \"dhcp-ddns\" : {\n" " \"enable-updates\" : true\n" " }\n" "}"; Dhcp6Client client1; client1.setInterface("eth0"); // Load a configuration with D2 enabled ASSERT_NO_FATAL_FAILURE(configure(config_str, *client1.getServer())); ASSERT_TRUE(CfgMgr::instance().ddnsEnabled()); ASSERT_NO_THROW(client1.getServer()->startD2()); // Include the Client FQDN option. ASSERT_NO_THROW(client1.useFQDN(Option6ClientFqdn::FLAG_S, "one.example.org.", Option6ClientFqdn::FULL)); // Now send the DHCPREQUEST with including the FQDN option. ASSERT_NO_THROW(client1.doSARR()); Pkt6Ptr resp = client1.getContext().response_; ASSERT_TRUE(resp); ASSERT_EQ(DHCPV6_REPLY, static_cast(resp->getType())); // Check that the response FQDN is as expected. Option6ClientFqdnPtr fqdn; fqdn = boost::dynamic_pointer_cast(resp->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(fqdn); EXPECT_EQ("one.example.org.", fqdn->getDomainName()); // ddns-send-updates for subnet 1 should be off, so we should NOT have an NRC. ASSERT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize()); // Now let's try with a client on subnet 2. Dhcp6Client client2; client2.setInterface("eth1"); client2.requestAddress(); // Include the Client FQDN option. ASSERT_NO_THROW(client2.useFQDN(Option6ClientFqdn::FLAG_S, "two.example.org.", Option6ClientFqdn::FULL)); ASSERT_NO_THROW(client2.doSARR()); resp = client2.getContext().response_; ASSERT_TRUE(resp); ASSERT_EQ(DHCPV6_REPLY, static_cast(resp->getType())); // Check that the response FQDN is as expected. fqdn = boost::dynamic_pointer_cast(resp->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(fqdn); EXPECT_EQ("two.example.org.", fqdn->getDomainName()); Subnet6Ptr subnet = (CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getSubnet(2)); ASSERT_TRUE(subnet); DdnsParamsPtr p = (CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet)); ASSERT_TRUE(p->getEnableUpdates()); // ddns-send-updates for subnet 2 are enabled, verify the NCR is correct. ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:2::1", "", 0, 4000); } // Verifies that the DDNS parameters used for a lease in subnet in // a shared-network belong to lease's subnet. This checks that we // get the right results even when the allocation engine changes the // subnet chosen. The configuration is two 1-address pool subnets in // a shared-network. The first will do a SARR, which consumes the first // pool. This should cause the allocation engine to dynamically select // the second subnet for the second client. The subnets define different // values for qualifying suffixes, thus making it simple to verify // the appropriate subnet parameters are used. Both clients then // renew their leases. TEST_F(FqdnDhcpv6SrvTest, ddnsSharedNetworkTest) { std::string config_str = "{ \"interfaces-config\": { \n" " \"interfaces\": [ \"*\" ] \n" "}, \n" "\"preferred-lifetime\": 3000, \n" "\"rebind-timer\": 2000, \n" "\"renew-timer\": 1000, \n" "\"valid-lifetime\": 4000, \n" "\"shared-networks\": [ \n" "{ \n" "\"name\": \"frog\", \n" "\"interface\": \"eth0\", \n" "\"subnet6\": [ { \n" "\"id\": 1, \n" "\"subnet\": \"2001:db8:1::/64\", \n" "\"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::1\" } ], \n" "\"interface\": \"eth0\", \n" "\"ddns-qualifying-suffix\": \"one.example.com.\" \n" " }, \n" " { \n" "\"id\": 2, \n" "\"subnet\": \"2001:db8:2::/64\", \n" "\"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::1\" } ], \n" "\"interface\": \"eth0\", \n" "\"ddns-qualifying-suffix\": \"two.example.com.\" \n" " } ] \n" "} ], \n" "\"ddns-send-updates\": true, \n" "\"dhcp-ddns\" : { \n" " \"enable-updates\" : true \n" " } \n" "}"; Dhcp6Client client1; client1.setInterface("eth0"); client1.requestAddress(); // Load a configuration with D2 enabled ASSERT_NO_FATAL_FAILURE(configure(config_str, *client1.getServer())); ASSERT_TRUE(CfgMgr::instance().ddnsEnabled()); ASSERT_NO_THROW(client1.getServer()->startD2()); // Include the Client FQDN option. ASSERT_NO_THROW(client1.useFQDN(Option6ClientFqdn::FLAG_S, "client1", Option6ClientFqdn::PARTIAL)); // Now do a SARR. ASSERT_NO_THROW(client1.doSARR()); Pkt6Ptr resp = client1.getContext().response_; ASSERT_TRUE(resp); ASSERT_EQ(DHCPV6_REPLY, static_cast(resp->getType())); // Check that the response FQDN is as expected. Option6ClientFqdnPtr fqdn; fqdn = boost::dynamic_pointer_cast(resp->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(fqdn); EXPECT_EQ("client1.one.example.com.", fqdn->getDomainName()); // ddns-send-updates for subnet 1 are enabled, verify the NCR is correct. ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1::1", "", 0, 4000, "client1.one.example.com."); // Make sure the lease hostname and fqdn flags are correct. Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1")); ASSERT_TRUE(lease); EXPECT_EQ("client1.one.example.com.", lease->hostname_); EXPECT_TRUE(lease->fqdn_fwd_); EXPECT_TRUE(lease->fqdn_rev_); // Now let's try with a different client. Subnet 1 is full so we should get an // address from subnet 2. Dhcp6Client client2(client1.getServer()); client2.setInterface("eth0"); client2.requestAddress(); // Include the Client FQDN option. ASSERT_NO_THROW(client2.useFQDN(Option6ClientFqdn::FLAG_S, "client2", Option6ClientFqdn::PARTIAL)); // Do a SARR. ASSERT_NO_THROW(client2.doSARR()); resp = client2.getContext().response_; ASSERT_TRUE(resp); ASSERT_EQ(DHCPV6_REPLY, static_cast(resp->getType())); // Check that the response FQDN is as expected. fqdn = boost::dynamic_pointer_cast(resp->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(fqdn); EXPECT_EQ("client2.two.example.com.", fqdn->getDomainName()); // ddns-send-updates for subnet 2 are enabled, verify the NCR is correct. ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:2::1", "", 0, 4000, "client2.two.example.com."); // Make sure the lease hostname and fqdn flags are correct. lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1")); ASSERT_TRUE(lease); EXPECT_EQ("client2.two.example.com.", lease->hostname_); EXPECT_TRUE(lease->fqdn_fwd_); EXPECT_TRUE(lease->fqdn_rev_); // Now let's check Renewals // First we'll renew a client2. ASSERT_NO_THROW(client2.doRenew()); resp = client2.getContext().response_; ASSERT_TRUE(resp); ASSERT_EQ(DHCPV6_REPLY, static_cast(resp->getType())); // Check that the response FQDN is as expected. fqdn = boost::dynamic_pointer_cast(resp->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(fqdn); EXPECT_EQ("client2.two.example.com.", fqdn->getDomainName()); // ddns-send-updates for subnet 2 are enabled, but currently a renew/rebind does // not update, unless the FQDN or flags change. ASSERT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize()); // Make sure the lease hostname is still correct. lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1")); ASSERT_TRUE(lease); EXPECT_EQ("client2.two.example.com.", lease->hostname_); // Next we'll renew client1 ASSERT_NO_THROW(client1.doRenew()); resp = client1.getContext().response_; ASSERT_TRUE(resp); ASSERT_EQ(DHCPV6_REPLY, static_cast(resp->getType())); // Check that the response FQDN is as expected. fqdn = boost::dynamic_pointer_cast(resp->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(fqdn); EXPECT_EQ("client1.one.example.com.", fqdn->getDomainName()); // ddns-send-updates for subnet 1 are enabled, but currently a renew/rebind does // not update, unless the FQDN or flags change. ASSERT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize()); // Make sure the lease hostname and fqdn flags are correct. lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1")); ASSERT_TRUE(lease); EXPECT_EQ("client1.one.example.com.", lease->hostname_); EXPECT_TRUE(lease->fqdn_fwd_); EXPECT_TRUE(lease->fqdn_rev_); } // Verifies lease and NCR content (or lack of NCRs) are correct when // subnets in a shared-network define different values for send-ddns-updates // This checks that we get the right results even when the allocation engine // changes the subnet chosen. The configuration is two 1-address pool subnets in // a shared-network. The first client will do a SARR, which consumes the first // pool. This should cause the allocation engine to dynamically select // the second subnet for the second client. TEST_F(FqdnDhcpv6SrvTest, ddnsSharedNetworkTest2) { std::string config_str = "{ \"interfaces-config\": { \n" " \"interfaces\": [ \"*\" ] \n" "}, \n" "\"preferred-lifetime\": 3000, \n" "\"rebind-timer\": 2000, \n" "\"renew-timer\": 1000, \n" "\"valid-lifetime\": 4000, \n" "\"shared-networks\": [ \n" "{ \n" "\"name\": \"frog\", \n" "\"interface\": \"eth0\", \n" "\"subnet6\": [ { \n" "\"id\": 1, \n" "\"subnet\": \"2001:db8:1::/64\", \n" "\"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::1\" } ], \n" "\"interface\": \"eth0\", \n" "\"ddns-qualifying-suffix\": \"one.example.com.\", \n" "\"ddns-send-updates\": true \n" " }, \n" " { \n" "\"id\": 2, \n" "\"subnet\": \"2001:db8:2::/64\", \n" "\"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::1\" } ], \n" "\"interface\": \"eth0\", \n" "\"ddns-qualifying-suffix\": \"two.example.com.\", \n" "\"ddns-send-updates\": false \n" " } ] \n" "} ], \n" "\"dhcp-ddns\" : { \n" " \"enable-updates\" : true \n" " } \n" "}"; Dhcp6Client client1; client1.setInterface("eth0"); client1.requestAddress(); // Load a configuration with D2 enabled ASSERT_NO_FATAL_FAILURE(configure(config_str, *client1.getServer())); ASSERT_TRUE(CfgMgr::instance().ddnsEnabled()); ASSERT_NO_THROW(client1.getServer()->startD2()); // Include the Client FQDN option. ASSERT_NO_THROW(client1.useFQDN(Option6ClientFqdn::FLAG_S, "client1", Option6ClientFqdn::PARTIAL)); // Now do a SARR. ASSERT_NO_THROW(client1.doSARR()); Pkt6Ptr resp = client1.getContext().response_; ASSERT_TRUE(resp); ASSERT_EQ(DHCPV6_REPLY, static_cast(resp->getType())); // Check that the response FQDN is as expected. Option6ClientFqdnPtr fqdn; fqdn = boost::dynamic_pointer_cast(resp->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(fqdn); EXPECT_EQ("client1.one.example.com.", fqdn->getDomainName()); // ddns-send-updates for subnet 1 are enabled, verify the NCR is correct. ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1::1", "", 0, 4000, "client1.one.example.com."); // Make sure the lease hostname and fdqn flags are correct. Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1")); ASSERT_TRUE(lease); EXPECT_EQ("client1.one.example.com.", lease->hostname_); EXPECT_TRUE(lease->fqdn_fwd_); EXPECT_TRUE(lease->fqdn_rev_); // Now let's try with a different client. Subnet 1 is full so we should get an // address from subnet 2. Dhcp6Client client2(client1.getServer()); client2.setInterface("eth0"); client2.requestAddress(); // Include the Client FQDN option. ASSERT_NO_THROW(client2.useFQDN(Option6ClientFqdn::FLAG_S, "client2", Option6ClientFqdn::PARTIAL)); // Do a SARR. ASSERT_NO_THROW(client2.doSARR()); resp = client2.getContext().response_; ASSERT_TRUE(resp); ASSERT_EQ(DHCPV6_REPLY, static_cast(resp->getType())); // Check that the response FQDN is as expected. fqdn = boost::dynamic_pointer_cast(resp->getOption(D6O_CLIENT_FQDN)); ASSERT_TRUE(fqdn); EXPECT_EQ("client2.two.example.com.", fqdn->getDomainName()); // ddns-send-updates for subnet 2 are disabled, verify there is no NCR. ASSERT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize()); // Make sure the lease hostname and fdqn flags are correct. lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1")); ASSERT_TRUE(lease); EXPECT_EQ("client2.two.example.com.", lease->hostname_); EXPECT_FALSE(lease->fqdn_fwd_); EXPECT_FALSE(lease->fqdn_rev_); } // Verifies that renews only generate NCRs if the situation dictates // that it should. It checks: // // -# enable-updates true or false // -# update-on-renew true or false // -# Whether or not the FQDN has changed between old and new lease TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) { std::string fqdn1 = "one.example.com."; std::string fqdn2 = "two.example.com."; struct Scenario { std::string description_; bool send_updates_; bool update_on_renew_; std::string old_fqdn_; std::string new_fqdn_; size_t remove_; size_t add_; }; // Mnemonic constants. const bool send_updates = true; const bool update_on_renew = true; const size_t remove = 1; const size_t add = 1; const std::vector scenarios = { { "#1 update-on-renew false, no change in fqdn", send_updates, !update_on_renew, fqdn1, fqdn1, !remove, !add }, { "#2 update-on-renew is false, change in fqdn", send_updates, !update_on_renew, fqdn1, fqdn2, remove, add }, { "#3 update-on-renew is true, no change in fqdn", send_updates, update_on_renew, fqdn1, fqdn1, remove, add }, { "#4 update-on-renew is true, change in fqdn", send_updates, update_on_renew, fqdn1, fqdn2, remove, add }, // All prior scenarios test with send-updates true. We really // only need one with it false. { "#5 send-updates false, update-on-renew is true, change in fqdn", !send_updates, update_on_renew, fqdn1, fqdn2, !remove, !add } }; enableD2(); subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER); // Iterate over test scenarios. for (auto scenario : scenarios) { SCOPED_TRACE(scenario.description_); { // Make sure the lease does not exist. ASSERT_FALSE(LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef"))); // Set and verify DDNS params flags subnet_->setDdnsSendUpdates(scenario.send_updates_); subnet_->setDdnsUpdateOnRenew(scenario.update_on_renew_); ASSERT_EQ(scenario.send_updates_, getDdnsParams()->getEnableUpdates()); ASSERT_EQ(scenario.update_on_renew_, getDdnsParams()->getUpdateOnRenew()); // Create the "old" lease testProcessMessage(DHCPV6_REQUEST, scenario.old_fqdn_, scenario.old_fqdn_); // The lease should have been recorded in the database. Lease6Ptr old_lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef")); ASSERT_TRUE(old_lease); if (!scenario.send_updates_ || scenario.old_fqdn_.empty()) { // We should not have an NCR. ASSERT_EQ(0, d2_mgr_.getQueueSize()); } else { // We should have an NCR add. ASSERT_EQ(1, d2_mgr_.getQueueSize()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, old_lease->addr_.toText(), "", 0, old_lease->valid_lft_, old_lease->hostname_); } // Now let's renew (or create) the lease. testProcessMessage(DHCPV6_RENEW, scenario.new_fqdn_, scenario.new_fqdn_); // The lease should have been recorded in the database. Lease6Ptr new_lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead:beef")); ASSERT_TRUE(new_lease); // Verify queue count is correct. ASSERT_EQ((scenario.remove_ + scenario.add_), d2_mgr_.getQueueSize()); // If we expect a remove, check it. if (scenario.remove_ > 0) { // Verify NCR content verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true, old_lease->addr_.toText(), "", 0, old_lease->valid_lft_, old_lease->hostname_); } // If we expect an add, check it. if (scenario.add_ > 0) { // Verify NCR content verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, new_lease->addr_.toText(), "", 0, new_lease->valid_lft_, new_lease->hostname_); } // Now delete the lease. bool deleted = false; ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(new_lease)); ASSERT_TRUE(deleted); } } } // Verify that when specified ddns-ttl-percent is used to calculate // the lease length in an NCR. TEST_F(FqdnDhcpv6SrvTest, ddnsTtlPercent) { // Create Reply message with Client Id and Server id. Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY); // Create an IA. addIA(1234, IOAddress("2001:db8:1::1"), answer); // Use domain name in upper case. It should be converted to lower-case // before DHCID is calculated. So, we should get the same result as if // we typed domain name in lower-case. Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S, "MYHOST.EXAMPLE.COM", Option6ClientFqdn::FULL); answer->addOption(fqdn); // Create NameChangeRequest for the first allocated address. AllocEngine::ClientContext6 ctx; subnet_->setDdnsUseConflictResolution(false); subnet_->setDdnsTtlPercent(Optional(0.10)); ctx.subnet_ = subnet_; ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true; ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx)); ASSERT_EQ(1, d2_mgr_.getQueueSize()); // Verify that NameChangeRequest is correct. verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1::1", "000201415AA33D1187D148275136FA30300478" "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 500, "", false, subnet_->getDdnsTtlPercent()); } } // end of anonymous namespace