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:
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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_) {
|
||||
|
@@ -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).
|
||||
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user