2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 21:45:37 +00:00

[3036] Added implementation for the FQDN option processing.

This commit is contained in:
Marcin Siodelski
2013-07-22 13:13:22 +02:00
parent d803eeaf3f
commit 8c1e879aeb
6 changed files with 331 additions and 1 deletions

View File

@@ -20,6 +20,7 @@
#include <dhcp/iface_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaaddr.h>
@@ -56,6 +57,34 @@ using namespace std;
namespace isc {
namespace dhcp {
namespace {
// The following constants describe server's behavior with respect to the
// DHCPv6 Client FQDN Option sent by a client. They will be removed
// when DDNS parameters for DHCPv6 are implemented with the ticket #3034.
// Should server always include the FQDN option in its response, regardless
// if it has been requested in ORO (Disabled).
const bool FQDN_ALWAYS_INCLUDE = false;
// Enable AAAA RR update delegation to the client (Disabled).
const bool FQDN_ALLOW_CLIENT_UPDATE = false;
// Globally enable updates (Enabled).
const bool FQDN_ENABLE_UPDATE = true;
// The partial name generated for the client if empty name has been
// supplied.
const char* FQDN_GENERATED_PARTIAL_NAME = "myhost";
// Do update, even if client requested no updates with N flag (Disabled).
const bool FQDN_OVERRIDE_NO_UPDATE = false;
// Server performs an update when client requested delegation (Enabled).
const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
// The fully qualified domain-name suffix if partial name provided by
// a client.
const char* FQDN_PARTIAL_SUFFIX = "example.com";
// Should server replace the domain-name supplied by the client (Disabled).
const bool FQDN_REPLACE_CLIENT_NAME = false;
}
/// @brief file name of a server-id file
///
/// Server must store its duid in persistent storage that must not change
@@ -631,6 +660,99 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
}
}
void
Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// 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) {
return;
}
// Prepare the FQDN option which will be included in the response to
// the client.
Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
// RFC 4704, section 6. - all flags set to 0.
fqdn_resp->resetFlags();
// Conditions when N flag has to be set to indicate that server will not
// perform DNS updates:
// 1. Updates are globally disabled,
// 2. Client requested no update and server respects it,
// 3. Client requested that the AAAA update is delegated to the client but
// server neither respects delegation of updates nor it is configured
// to send update on its own when client requested delegation.
if (!FQDN_ENABLE_UPDATE ||
(fqdn->getFlag(Option6ClientFqdn::FLAG_N) && !FQDN_OVERRIDE_NO_UPDATE) ||
(!fqdn->getFlag(Option6ClientFqdn::FLAG_S) && !FQDN_ALLOW_CLIENT_UPDATE &&
!FQDN_OVERRIDE_CLIENT_UPDATE)) {
fqdn_resp->setFlag(Option6ClientFqdn::FLAG_N, true);
// Conditions when S flag is set to indicate that server will perform
// DNS update on its own:
// 1. Client requested that server performs DNS update and DNS updates are
// globally enabled
// 2. Client requested that server delegates AAAA update to the client but
// server doesn't respect delegation and it is configured to perform
// an update on its own when client requested delegation.
} else if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) ||
(!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
!FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
fqdn_resp->setFlag(Option6ClientFqdn::FLAG_S, true);
}
// Server MUST set the O flag if it has overridden the client's setting
// of S flag.
if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) !=
fqdn_resp->getFlag(Option6ClientFqdn::FLAG_S)) {
fqdn_resp->setFlag(Option6ClientFqdn::FLAG_O, true);
}
// If client supplied partial or empty domain-name, server should
// generate one.
if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) {
std::ostringstream name;
if (fqdn->getDomainName().empty()) {
name << FQDN_GENERATED_PARTIAL_NAME;
} else {
name << fqdn->getDomainName();
}
name << "." << FQDN_PARTIAL_SUFFIX;
fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
// Server may be configured to replace a name supplied by a client,
// even if client supplied fully qualified domain-name.
} else if (FQDN_REPLACE_CLIENT_NAME) {
std::ostringstream name;
name << FQDN_GENERATED_PARTIAL_NAME << "." << FQDN_PARTIAL_SUFFIX;
fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
}
// Server sends back the FQDN option to the client if client has requested
// it using Option Request Option. However, server may be configured to
// send the FQDN option in its response, regardless whether client requested
// it or not.
bool include_fqdn = FQDN_ALWAYS_INCLUDE;
if (!include_fqdn) {
OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<
OptionUint16Array>(question->getOption(D6O_ORO));
if (oro) {
const std::vector<uint16_t>& values = oro->getValues();
for (int i = 0; i < values.size(); ++i) {
if (values[i] == D6O_CLIENT_FQDN) {
include_fqdn = true;
}
}
}
}
if (include_fqdn) {
answer->addOption(fqdn_resp);
}
}
OptionPtr
Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
@@ -1025,6 +1147,8 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
assignLeases(solicit, advertise);
processClientFqdn(solicit, advertise);
return (advertise);
}
@@ -1041,6 +1165,8 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
assignLeases(request, reply);
processClientFqdn(request, reply);
return (reply);
}
@@ -1055,6 +1181,8 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
appendDefaultOptions(renew, reply);
appendRequestedOptions(renew, reply);
processClientFqdn(renew, reply);
renewLeases(renew, reply);
return reply;
@@ -1086,6 +1214,9 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
releaseLeases(release, reply);
// @todo If client sent a release and we should remove outstanding
// DNS records.
return reply;
}

View File

@@ -264,6 +264,27 @@ protected:
/// @param answer server's message (IA_NA options will be added here)
void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
/// @brief Processes Client FQDN Option.
///
/// This function retrieves DHCPv6 Client FQDN %Option (if any) from the
/// packet sent by a client and takes necessary actions upon this option.
/// Received option comprises flags field which controls what DNS updates
/// server should do. Server may override client's preference based on
/// the current configuration. Server indicates that it has overridden
/// the preference by storing DHCPv6 Client Fqdn %Option with the
/// appropriate flags in the response to a client. This option is also
/// used to communicate the client's domain-name which should be sent
/// to the DNS in the update. Again, server may act upon the received
/// domain-name, i.e. if the provided domain-name is partial it should
/// generate the fully qualified domain-name.
///
/// All the logic required to form appropriate answer to the client is
/// held in this function.
///
/// @param question Client's message.
/// @param answer Server's response to the client.
void processClientFqdn(const Pkt6Ptr& question, Pkt6Ptr& answer);
/// @brief Attempts to renew received addresses
///
/// It iterates through received IA_NA options and attempts to renew

View File

@@ -21,6 +21,7 @@
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_int_array.h>
@@ -33,6 +34,7 @@
#include <util/buffer.h>
#include <util/range_utilities.h>
#include <boost/pointer_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <unistd.h>
@@ -52,6 +54,10 @@ using namespace std;
// Maybe it should be isc::test?
namespace {
const uint8_t FQDN_FLAG_S = 0x1;
const uint8_t FQDN_FLAG_O = 0x2;
const uint8_t FQDN_FLAG_N = 0x4;
class NakedDhcpv6Srv: public Dhcpv6Srv {
// "naked" Interface Manager, exposes internal members
public:
@@ -70,6 +76,7 @@ public:
using Dhcpv6Srv::processRequest;
using Dhcpv6Srv::processRenew;
using Dhcpv6Srv::processRelease;
using Dhcpv6Srv::processClientFqdn;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
using Dhcpv6Srv::sanityCheck;
@@ -225,6 +232,8 @@ public:
EXPECT_EQ(expected_transid, rsp->getTransid());
}
// Generates client's packet holding an FQDN option.
virtual ~NakedDhcpv6SrvTest() {
// Let's clean up if there is such a file.
unlink(DUID_FILE);
@@ -329,6 +338,95 @@ public:
Pool6Ptr pool_;
};
class FqdnDhcpv6SrvTest : public NakedDhcpv6SrvTest {
public:
FqdnDhcpv6SrvTest() {
}
virtual ~FqdnDhcpv6SrvTest() {
}
Pkt6Ptr generatePktWithFqdn(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->setRemoteAddr(IOAddress("fe80::abcd"));
pkt->addOption(generateIA(234, 1500, 3000));
OptionPtr clientid = generateClientId();
pkt->addOption(clientid);
if (srvid && (msg_type != DHCPV6_SOLICIT)) {
pkt->addOption(srvid);
}
pkt->addOption(OptionPtr(new Option6ClientFqdn(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);
}
// Returns an instance of the option carrying FQDN.
Option6ClientFqdnPtr getClientFqdnOption(const Pkt6Ptr& pkt) {
return (boost::dynamic_pointer_cast<Option6ClientFqdn>
(pkt->getOption(D6O_CLIENT_FQDN)));
}
void testFqdn(const uint16_t msg_type,
const bool use_oro,
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) {
NakedDhcpv6Srv srv(0);
Pkt6Ptr question = generatePktWithFqdn(msg_type,
in_flags,
in_domain_name,
in_domain_type,
use_oro);
ASSERT_TRUE(getClientFqdnOption(question));
Pkt6Ptr answer;
if (msg_type == DHCPV6_SOLICIT) {
answer.reset(new Pkt6(DHCPV6_ADVERTISE, 1234));
} else {
answer.reset(new Pkt6(DHCPV6_REPLY, 1234));
}
ASSERT_NO_THROW(srv.processClientFqdn(question, answer));
Option6ClientFqdnPtr answ_fqdn = getClientFqdnOption(answer);
ASSERT_TRUE(answ_fqdn);
const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0 ?
true : false;
const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0 ?
true : false;
const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0 ?
true : false;
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());
EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
}
};
// This test verifies that incoming SOLICIT can be handled properly when
// there are no subnets configured.
//
@@ -1756,6 +1854,50 @@ TEST_F(Dhcpv6SrvTest, ServerID) {
EXPECT_EQ(duid1_text, text);
}
// A set of tests verifying server's behaviour when it receives the DHCPv6
// Client Fqdn Option.
// @todo: Extend these tests once appropriate configuration parameters are
// implemented (ticket #3034).
// Test server's response when client requests that server performs AAAA update.
TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "myhost.example.com",
Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
"myhost.example.com.");
}
// Test server's response when client provides partial domain-name and requests
// that server performs AAAA update.
TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "myhost",
Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
"myhost.example.com.");
}
// Test server's response when client provides empty domain-name and requests
// that server performs AAAA update.
TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) {
testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "",
Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
"myhost.example.com.");
}
// Test server's response when client requests no DNS update.
TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_N, "myhost.example.com",
Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
"myhost.example.com.");
}
// 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) {
SCOPED_TRACE("Client AAAA Update is not allowed");
testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
Option6ClientFqdn::FULL, FQDN_FLAG_S | FQDN_FLAG_O,
"myhost.example.com.");
}
/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
/// to call processX() methods.

View File

@@ -75,8 +75,11 @@ Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
Option6ClientFqdnImpl::
Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
: flags_(source.flags_),
domain_name_(new isc::dns::Name(*source.domain_name_)),
domain_name_(),
domain_name_type_(source.domain_name_type_) {
if (source.domain_name_) {
domain_name_.reset(new isc::dns::Name(*source.domain_name_));
}
}
Option6ClientFqdnImpl&
@@ -260,6 +263,11 @@ Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
impl_->flags_ = new_flag;
}
void
Option6ClientFqdn::resetFlags() {
impl_->flags_ = 0;
}
std::string
Option6ClientFqdn::getDomainName() const {
if (impl_->domain_name_) {

View File

@@ -170,6 +170,9 @@ public:
/// set (true), or cleared (false).
void setFlag(const Flag flag, const bool set);
/// @brief Sets the flag field value to 0.
void resetFlags();
/// @brief Returns the domain-name in the text format.
///
/// If domain-name is partial, it lacks the dot at the end (e.g. myhost).

View File

@@ -392,6 +392,31 @@ TEST(Option6ClientFqdnTest, setFlag) {
InvalidFqdnOptionFlags);
}
// This test verifies that flags field of the option is set to 0 when resetFlags
// function is called.
TEST(Option6ClientFqdnTest, resetFlags) {
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S |
Option6ClientFqdn::FLAG_O,
"myhost.example.com",
Option6ClientFqdn::FULL))
);
ASSERT_TRUE(option);
// Check that flags we set in the constructor are set.
ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
option->resetFlags();
// After reset, all flags should be 0.
EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
}
// This test verifies that current domain-name can be replaced with a new
// domain-name.
TEST(Option6ClientFqdnTest, setDomainName) {