From: Thomas Markwalder Date: Mon, 4 Apr 2016 11:21:43 +0000 (-0400) Subject: [4259] kea-dhcp6 now supports replace-client-name modes X-Git-Tag: trac4106_update_base~48^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b3f483e537423313edc04f9a47a6dd1143022d49;p=thirdparty%2Fkea.git [4259] kea-dhcp6 now supports replace-client-name modes src/bin/dhcp6/dhcp6_messages.mes - Added new log message, DHCP6_DDNS_SUPPLY_FQDN src/bin/dhcp6/dhcp6_srv.cc - Dhcpv6Srv::processClientFqdn() - modified to support the name replacement modes src/bin/dhcp6/tests/fqdn_unittest.cc - FqdnDhcpv6SrvTest::testReplaceClientNameMode() new method which tests a server's handling of a single client packet for a given replace-client-name mode. - TEST_F(FqdnDhcpv6SrvTest, replaceClientNameModeTest) - new test which exercises the permutations of client packets and replace-client-name modes. --- diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index 3319100e2b..4d9f2b019c 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -183,6 +183,11 @@ the lease is acquired for the client. This debug message is logged when server includes an DHCPv6 Client FQDN Option in its response to the client. +% DHCP6_DDNS_SUPPLY_FQDN %1: client did not send a FQDN option, will supply one for them +This debug message is issued when the server did not receive a FQDN option +from the client and client name replacement is enabled. This provides a means +to create DNS entries for unsophisticated clients. + % DHCP6_DEACTIVATE_INTERFACE deactivate interface %1 This message is printed when DHCPv6 server disables an interface from being used to receive DHCPv6 traffic. Sockets on this interface will not be opened diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 74090ca1af..3fb6550a05 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -1063,17 +1063,34 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer, void Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer, AllocEngine::ClientContext6& ctx) { + D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr(); + // Get Client FQDN Option from the client's message. If this option hasn't // been included, do nothing. Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast< Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN)); if (!fqdn) { - // No FQDN so lease hostname comes from host reservation if one - if (ctx.host_) { - ctx.hostname_ = ctx.host_->getHostname(); - } + D2ClientConfig::ReplaceClientNameMode replace_name_mode = + d2_mgr.getD2ClientConfig()->getReplaceClientNameMode(); + if (d2_mgr.ddnsEnabled() && + (replace_name_mode == D2ClientConfig::RCM_ALWAYS || + replace_name_mode == D2ClientConfig::RCM_WHEN_NOT_PRESENT)) { + // Fabricate an empty "client" FQDN with flags requesting + // the server do all the updates. The flags will get modified + // below according the configuration options, the name will + // be supplied later on. + fqdn.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "", + Option6ClientFqdn::PARTIAL)); + LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_DDNS_SUPPLY_FQDN) + .arg(question->getLabel()); + } else { + // No FQDN so lease hostname comes from host reservation if one + if (ctx.host_) { + ctx.hostname_ = ctx.host_->getHostname(); + } - return; + return; + } } LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_DDNS_RECEIVE_FQDN) @@ -1086,12 +1103,10 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer, // Set the server S, N, and O flags based on client's flags and // current configuration. - D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr(); d2_mgr.adjustFqdnFlags(*fqdn, *fqdn_resp); // If there's a reservation and it has a hostname specified, use it! if (ctx.host_ && !ctx.host_->getHostname().empty()) { - D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr(); // Add the qualifying suffix. // After #3765, this will only occur if the suffix is not empty. fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.host_->getHostname(), diff --git a/src/bin/dhcp6/tests/fqdn_unittest.cc b/src/bin/dhcp6/tests/fqdn_unittest.cc index b3fe7863f7..e425497e35 100644 --- a/src/bin/dhcp6/tests/fqdn_unittest.cc +++ b/src/bin/dhcp6/tests/fqdn_unittest.cc @@ -53,6 +53,20 @@ public: static const uint16_t OVERRIDE_CLIENT_UPDATE = 4; static const uint16_t REPLACE_CLIENT_NAME = 8; + // 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){}; @@ -112,7 +126,8 @@ public: (mask & ALWAYS_INCLUDE_FQDN), (mask & OVERRIDE_NO_UPDATE), (mask & OVERRIDE_CLIENT_UPDATE), - ((mask & REPLACE_CLIENT_NAME) ? D2ClientConfig::RCM_WHEN_PRESENT + ((mask & REPLACE_CLIENT_NAME) ? + D2ClientConfig::RCM_WHEN_PRESENT : D2ClientConfig::RCM_NEVER), "myhost", "example.com"))); ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg)); @@ -368,6 +383,84 @@ public: } } + // 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 client-name-replacment parameter - for + // mode labels such as NEVER and ALWAYS must incluce enclosing quotes: + // "\"NEVER\"". This allows us to also pass in boolean literals which + // are unquoted. + // @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" + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n" + " \"subnet\": \"2001:db8:1::/48\", \n" + " \"interface-id\": \"\", \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]; + sprintf(config_buf, 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); + } + + Pkt6Ptr answer = generateMessageWithIds(DHCPV6_ADVERTISE); + + AllocEngine::ClientContext6 ctx; + 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. /// @@ -1234,4 +1327,44 @@ TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) { 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); + + // We pass mode labels in with enclosing quotes so we can also test + // unquoted boolean literals true/false + 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); + + // Verify that boolean false produces the same result as RCM_NEVER + testReplaceClientNameMode("false", + CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED); + testReplaceClientNameMode("false", + CLIENT_NAME_PRESENT, NAME_NOT_REPLACED); + + // Verify that boolean true produces the same result as RCM_WHEN_PRESENT + testReplaceClientNameMode("true", + CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED); + testReplaceClientNameMode("true", + CLIENT_NAME_PRESENT, NAME_REPLACED); +} + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/d2_client_mgr.h b/src/lib/dhcpsrv/d2_client_mgr.h index 15631203b5..511b910c7b 100644 --- a/src/lib/dhcpsrv/d2_client_mgr.h +++ b/src/lib/dhcpsrv/d2_client_mgr.h @@ -457,7 +457,8 @@ void D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) { // If we're configured to replace it or the supplied name is blank // set the response name to blank. - if ((d2_client_config_->getReplaceClientNameMode() != D2ClientConfig::RCM_NEVER) || + if ((d2_client_config_->getReplaceClientNameMode() == D2ClientConfig::RCM_ALWAYS || + d2_client_config_->getReplaceClientNameMode() == D2ClientConfig::RCM_WHEN_PRESENT) || fqdn.getDomainName().empty()) { fqdn_resp.setDomainName("", T::PARTIAL); } else {