2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 05:55:28 +00:00

[3220] Moved DHCPv6 Client FQDN specific tests to a separate file.

This commit is contained in:
Marcin Siodelski
2013-11-21 18:02:11 +01:00
parent 48b7ddcc06
commit 138fa31d02
3 changed files with 779 additions and 643 deletions

View File

@@ -69,6 +69,7 @@ libco2_la_LDFLAGS = -avoid-version -export-dynamic -module
TESTS += dhcp6_unittests
dhcp6_unittests_SOURCES = dhcp6_unittests.cc
dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
dhcp6_unittests_SOURCES += fqdn_unittest.cc
dhcp6_unittests_SOURCES += hooks_unittest.cc
dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h
dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc

View File

@@ -15,19 +15,16 @@
#include <config.h>
#include <asiolink/io_address.h>
#include <dhcp_ddns/ncr_msg.h>
#include <dhcp/dhcp6.h>
#include <dhcp/duid.h>
#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.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_string.h>
#include <dhcp/option_vendor.h>
#include <dhcp/iface_mgr.h>
#include <dhcp6/config_parser.h>
#include <dhcp/dhcp6.h>
@@ -53,267 +50,12 @@ using namespace isc;
using namespace isc::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 has to be named, because friends are defined in Dhcpv6Srv class
// Maybe it should be isc::test?
namespace {
// This is a test fixture class for testing the processing of the DHCPv6 Client
// FQDN Option.
class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest {
public:
// Constructor
FqdnDhcpv6SrvTest()
: Dhcpv6SrvTest() {
// generateClientId assigns DUID to duid_.
generateClientId();
lease_.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
duid_, 1234, 501, 502, 503,
504, 1, 0));
}
// Destructor
virtual ~FqdnDhcpv6SrvTest() {
}
// Construct the DHCPv6 Client FQDN Option using flags and domain-name.
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)));
}
// Create a message which optionally holds DHCPv6 Client FQDN Option.
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,
const bool include_fqdn = true,
OptionPtr srvid = OptionPtr()) {
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
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);
}
if (include_fqdn) {
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);
}
// Creates instance of the DHCPv6 message with client id and server id.
Pkt6Ptr generateMessageWithIds(const uint8_t msg_type,
NakedDhcpv6Srv& srv) {
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
// 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);
}
// Returns an instance of the option carrying FQDN.
Option6ClientFqdnPtr getClientFqdnOption(const Pkt6Ptr& pkt) {
return (boost::dynamic_pointer_cast<Option6ClientFqdn>
(pkt->getOption(D6O_CLIENT_FQDN)));
}
// Adds IA option to the message. Option holds an address.
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);
}
// Adds IA option to the message. Option holds status code.
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);
}
// Creates status code with the specified code and message.
OptionCustomPtr createStatusCode(const uint16_t code,
const std::string& msg) {
OptionDefinition def("status-code", D6O_STATUS_CODE, "record");
def.addRecordField("uint16");
def.addRecordField("string");
OptionCustomPtr opt_status(new OptionCustom(def, Option::V6));
opt_status->writeInteger(code);
if (!msg.empty()) {
opt_status->writeString(msg, 1);
}
return (opt_status);
}
// Adds Status Code option to the IA.
void addStatusCode(const uint16_t code, const std::string& msg,
Option6IAPtr& opt_ia) {
opt_ia->addOption(createStatusCode(code, msg));
}
// Test processing of the DHCPv6 Client FQDN Option.
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 = generateMessage(msg_type,
in_flags,
in_domain_name,
in_domain_type,
use_oro);
ASSERT_TRUE(getClientFqdnOption(question));
Option6ClientFqdnPtr answ_fqdn;
ASSERT_NO_THROW(answ_fqdn = srv.processClientFqdn(question));
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());
EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
}
// Tests that the client message holding an FQDN is processed and the
// lease is acquired.
void testProcessMessage(const uint8_t msg_type,
const std::string& hostname,
NakedDhcpv6Srv& srv,
const bool test_fqdn = true) {
// Create a message of a specified type, add server id and
// FQDN option.
OptionPtr srvid = srv.getServerID();
Pkt6Ptr req = generateMessage(msg_type, Option6ClientFqdn::FLAG_S,
hostname,
Option6ClientFqdn::FULL,
true, test_fqdn, srvid);
// For different client's message types we have to invoke different
// functions to generate response.
Pkt6Ptr reply;
if (msg_type == DHCPV6_SOLICIT) {
ASSERT_NO_THROW(reply = srv.processSolicit(req));
} else if (msg_type == DHCPV6_REQUEST) {
ASSERT_NO_THROW(reply = srv.processRequest(req));
} else if (msg_type == DHCPV6_RENEW) {
ASSERT_NO_THROW(reply = srv.processRequest(req));
} 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(req));
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, IOAddress("2001:db8:1:1::dead:beef"),
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);
}
if (test_fqdn) {
ASSERT_TRUE(reply->getOption(D6O_CLIENT_FQDN));
} else {
ASSERT_FALSE(reply->getOption(D6O_CLIENT_FQDN));
}
}
// Verify that NameChangeRequest holds valid values.
void verifyNameChangeRequest(NakedDhcpv6Srv& srv,
const isc::dhcp_ddns::NameChangeType type,
const bool reverse, const bool forward,
const std::string& addr,
const std::string& dhcid,
const uint16_t expires,
const uint16_t len) {
NameChangeRequest ncr = srv.name_change_reqs_.front();
EXPECT_EQ(type, ncr.getChangeType());
EXPECT_EQ(forward, ncr.isForwardChange());
EXPECT_EQ(reverse, ncr.isReverseChange());
EXPECT_EQ(addr, ncr.getIpAddress());
EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
EXPECT_EQ(expires, ncr.getLeaseExpiresOn());
EXPECT_EQ(len, ncr.getLeaseLength());
EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
srv.name_change_reqs_.pop();
}
// Holds a lease used by a test.
Lease6Ptr lease_;
};
// This test verifies that incoming SOLICIT can be handled properly when
// there are no subnets configured.
//
@@ -1584,390 +1326,6 @@ 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, Option6ClientFqdn::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, Option6ClientFqdn::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, Option6ClientFqdn::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, Option6ClientFqdn::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) {
testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
Option6ClientFqdn::FULL,
Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
"myhost.example.com.");
}
// Test that exception is thrown if supplied NULL answer packet when
// creating NameChangeRequests.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
NakedDhcpv6Srv srv(0);
Pkt6Ptr answer;
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL);
EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
isc::Unexpected);
}
// Test that exception is thrown if supplied answer from the server
// contains no DUID when creating NameChangeRequests.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
NakedDhcpv6Srv srv(0);
Pkt6Ptr answer = Pkt6Ptr(new Pkt6(DHCPV6_REPLY, 1234));
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL);
EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
isc::Unexpected);
}
// Test no NameChangeRequests are added if FQDN option is NULL.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
NakedDhcpv6Srv srv(0);
// Create Reply message with Client Id and Server id.
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
// Pass NULL FQDN option. No NameChangeRequests should be created.
Option6ClientFqdnPtr fqdn;
ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
// There should be no new NameChangeRequests.
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// Test that NameChangeRequests are not generated if an answer message
// contains no addresses.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
NakedDhcpv6Srv srv(0);
// Create Reply message with Client Id and Server id.
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL);
ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
// We didn't add any IAs, so there should be no NameChangeRequests in th
// queue.
ASSERT_TRUE(srv.name_change_reqs_.empty());
}
// Test that a number of NameChangeRequests is created as a result of
// processing the answer message which holds 3 IAs and when FQDN is
// specified.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
NakedDhcpv6Srv srv(0);
// Create Reply message with Client Id and Server id.
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
// 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);
// Create NameChangeRequests. Since we have added 3 IAs, it should
// result in generation of 3 distinct NameChangeRequests.
ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
ASSERT_EQ(3, srv.name_change_reqs_.size());
// Verify that NameChangeRequests are correct. Each call to the
// verifyNameChangeRequest will pop verified request from the queue.
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1::1",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 500);
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1::2",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 500);
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1::3",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 500);
}
// Test creation of the NameChangeRequest to remove both forward and reverse
// mapping for the given lease.
TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
NakedDhcpv6Srv srv(0);
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(srv.createRemovalNameChangeRequest(lease_));
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1::1",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 502);
}
// Test creation of the NameChangeRequest to remove reverse mapping for the
// given lease.
TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
NakedDhcpv6Srv srv(0);
lease_->fqdn_fwd_ = false;
lease_->fqdn_rev_ = true;
lease_->hostname_ = "myhost.example.com.";
ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, false,
"2001:db8:1::1",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 502);
}
// 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) {
NakedDhcpv6Srv srv(0);
lease_->fqdn_fwd_ = false;
lease_->fqdn_rev_ = false;
ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// 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) {
NakedDhcpv6Srv srv(0);
lease_->fqdn_fwd_ = true;
lease_->fqdn_rev_ = true;
lease_->hostname_ = "";
ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// 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) {
NakedDhcpv6Srv srv(0);
lease_->fqdn_fwd_ = true;
lease_->fqdn_rev_ = true;
lease_->hostname_ = "myhost..example.com.";
ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// Test that Advertise message generated in a response to the Solicit will
// not result in generation if the NameChangeRequests.
TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
NakedDhcpv6Srv srv(0);
// Create a Solicit message with FQDN option and generate server's
// response using processSolicit function.
testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// 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, processTwoRequests) {
NakedDhcpv6Srv srv(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", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, 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", srv);
ASSERT_EQ(2, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 4000);
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
"000201D422AA463306223D269B6CB7AFE7AAD265FC"
"EA97F93623019B2E0D14E5323D5A",
0, 4000);
}
// 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, processRequestRenew) {
NakedDhcpv6Srv srv(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", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, 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", srv);
ASSERT_EQ(2, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 4000);
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
"000201D422AA463306223D269B6CB7AFE7AAD265FC"
"EA97F93623019B2E0D14E5323D5A",
0, 4000);
}
TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
NakedDhcpv6Srv srv(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", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 4000);
// Client may send Release message. In this case the lease should be
// removed and all existing DNS entries for this lease should be
// also removed. Therefore, we expect that single NameChangeRequest to
// remove DNS entries is generated.
testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 4000);
}
// Checks that the server does not include DHCPv6 Client FQDN option in its
// response when client doesn't include this option in a Request.
TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
NakedDhcpv6Srv srv(0);
// The last parameter disables the use of DHCPv6 Client FQDN option
// in the client's Request. 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", srv, false);
ASSERT_TRUE(srv.name_change_reqs_.empty());
}
// Checks if server responses are sent to the proper port.
TEST_F(Dhcpv6SrvTest, portsDirectTraffic) {

View File

@@ -0,0 +1,777 @@
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <asiolink/io_address.h>
#include <dhcp_ddns/ncr_msg.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_int_array.h>
#include <dhcpsrv/lease.h>
#include <dhcp6/tests/dhcp6_test_utils.h>
#include <boost/pointer_cast.hpp>
#include <gtest/gtest.h>
using namespace isc;
using namespace isc::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:
/// @brief Constructor
FqdnDhcpv6SrvTest()
: Dhcpv6SrvTest() {
// generateClientId assigns DUID to duid_.
generateClientId();
lease_.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
duid_, 1234, 501, 502, 503,
504, 1, 0));
}
/// @brief Destructor
virtual ~FqdnDhcpv6SrvTest() {
}
/// @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,
const bool include_fqdn = true,
OptionPtr srvid = OptionPtr()) {
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
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);
}
if (include_fqdn) {
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.
/// @param srv An object representing the DHCPv6 server, which
/// is used to generate the client identifier.
///
/// @return An object representing the created message.
Pkt6Ptr generateMessageWithIds(const uint8_t msg_type,
NakedDhcpv6Srv& srv) {
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
// 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<Option6ClientFqdn>
(pkt->getOption(D6O_CLIENT_FQDN)));
}
/// @brief Adds IA option to the message.
///
/// Addded 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.
OptionCustomPtr createStatusCode(const uint16_t code,
const std::string& msg) {
OptionDefinition def("status-code", D6O_STATUS_CODE, "record");
def.addRecordField("uint16");
def.addRecordField("string");
OptionCustomPtr opt_status(new OptionCustom(def, Option::V6));
opt_status->writeInteger(code);
if (!msg.empty()) {
opt_status->writeString(msg, 1);
}
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.
///
/// 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.
///
/// @param msg_type A type of the client's message.
/// @param use_oro A boolean value which indicates whether the DHCPv6 ORO
/// option (requesting return of the FQDN option by the server) should be
/// included in the client's message (if true), or not included (if false).
/// @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.
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 = generateMessage(msg_type,
in_flags,
in_domain_name,
in_domain_type,
use_oro);
ASSERT_TRUE(getClientFqdnOption(question));
Option6ClientFqdnPtr answ_fqdn;
ASSERT_NO_THROW(answ_fqdn = srv.processClientFqdn(question));
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());
EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
}
/// @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 srv A server object, used to process the message.
/// @param test_fqdn A boolean value which indicates whether the FQDN
/// 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,
NakedDhcpv6Srv& srv,
const bool test_fqdn = true) {
// Create a message of a specified type, add server id and
// FQDN option.
OptionPtr srvid = srv.getServerID();
Pkt6Ptr req = generateMessage(msg_type, Option6ClientFqdn::FLAG_S,
hostname,
Option6ClientFqdn::FULL,
true, test_fqdn, srvid);
// For different client's message types we have to invoke different
// functions to generate response.
Pkt6Ptr reply;
if (msg_type == DHCPV6_SOLICIT) {
ASSERT_NO_THROW(reply = srv.processSolicit(req));
} else if (msg_type == DHCPV6_REQUEST) {
ASSERT_NO_THROW(reply = srv.processRequest(req));
} else if (msg_type == DHCPV6_RENEW) {
ASSERT_NO_THROW(reply = srv.processRequest(req));
} 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(req));
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, IOAddress("2001:db8:1:1::dead:beef"),
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);
}
if (test_fqdn) {
ASSERT_TRUE(reply->getOption(D6O_CLIENT_FQDN));
} else {
ASSERT_FALSE(reply->getOption(D6O_CLIENT_FQDN));
}
}
/// @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 srv A server object holding a queue of NameChangeRequests.
/// @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 udpate flag.
/// @param addr A string representation of the IPv6 address held in the
/// NameChangeRequest.
/// @param dhcid An expected DHCID value.
/// @param expires A timestamp when the lease associated with the
/// NameChangeRequest expires.
/// @param len A valid lifetime of the lease associated with the
/// NameChangeRequest.
void verifyNameChangeRequest(NakedDhcpv6Srv& srv,
const isc::dhcp_ddns::NameChangeType type,
const bool reverse, const bool forward,
const std::string& addr,
const std::string& dhcid,
const uint16_t expires,
const uint16_t len) {
NameChangeRequest ncr = srv.name_change_reqs_.front();
EXPECT_EQ(type, ncr.getChangeType());
EXPECT_EQ(forward, ncr.isForwardChange());
EXPECT_EQ(reverse, ncr.isReverseChange());
EXPECT_EQ(addr, ncr.getIpAddress());
EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
EXPECT_EQ(expires, ncr.getLeaseExpiresOn());
EXPECT_EQ(len, ncr.getLeaseLength());
EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
srv.name_change_reqs_.pop();
}
// Holds a lease used by a test.
Lease6Ptr lease_;
};
// 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, Option6ClientFqdn::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, Option6ClientFqdn::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, Option6ClientFqdn::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, Option6ClientFqdn::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) {
testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
Option6ClientFqdn::FULL,
Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
"myhost.example.com.");
}
// Test that exception is thrown if supplied NULL answer packet when
// creating NameChangeRequests.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
NakedDhcpv6Srv srv(0);
Pkt6Ptr answer;
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL);
EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
isc::Unexpected);
}
// Test that exception is thrown if supplied answer from the server
// contains no DUID when creating NameChangeRequests.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
NakedDhcpv6Srv srv(0);
Pkt6Ptr answer = Pkt6Ptr(new Pkt6(DHCPV6_REPLY, 1234));
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL);
EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
isc::Unexpected);
}
// Test no NameChangeRequests are added if FQDN option is NULL.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
NakedDhcpv6Srv srv(0);
// Create Reply message with Client Id and Server id.
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
// Pass NULL FQDN option. No NameChangeRequests should be created.
Option6ClientFqdnPtr fqdn;
ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
// There should be no new NameChangeRequests.
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// Test that NameChangeRequests are not generated if an answer message
// contains no addresses.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
NakedDhcpv6Srv srv(0);
// Create Reply message with Client Id and Server id.
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL);
ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
// We didn't add any IAs, so there should be no NameChangeRequests in th
// queue.
ASSERT_TRUE(srv.name_change_reqs_.empty());
}
// Test that a number of NameChangeRequests is created as a result of
// processing the answer message which holds 3 IAs and when FQDN is
// specified.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
NakedDhcpv6Srv srv(0);
// Create Reply message with Client Id and Server id.
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
// 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);
// Create NameChangeRequests. Since we have added 3 IAs, it should
// result in generation of 3 distinct NameChangeRequests.
ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
ASSERT_EQ(3, srv.name_change_reqs_.size());
// Verify that NameChangeRequests are correct. Each call to the
// verifyNameChangeRequest will pop verified request from the queue.
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1::1",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 500);
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1::2",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 500);
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1::3",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 500);
}
// Test creation of the NameChangeRequest to remove both forward and reverse
// mapping for the given lease.
TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
NakedDhcpv6Srv srv(0);
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(srv.createRemovalNameChangeRequest(lease_));
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1::1",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 502);
}
// Test creation of the NameChangeRequest to remove reverse mapping for the
// given lease.
TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
NakedDhcpv6Srv srv(0);
lease_->fqdn_fwd_ = false;
lease_->fqdn_rev_ = true;
lease_->hostname_ = "myhost.example.com.";
ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, false,
"2001:db8:1::1",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 502);
}
// 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) {
NakedDhcpv6Srv srv(0);
lease_->fqdn_fwd_ = false;
lease_->fqdn_rev_ = false;
ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// 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) {
NakedDhcpv6Srv srv(0);
lease_->fqdn_fwd_ = true;
lease_->fqdn_rev_ = true;
lease_->hostname_ = "";
ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// 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) {
NakedDhcpv6Srv srv(0);
lease_->fqdn_fwd_ = true;
lease_->fqdn_rev_ = true;
lease_->hostname_ = "myhost..example.com.";
ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// Test that Advertise message generated in a response to the Solicit will
// not result in generation if the NameChangeRequests.
TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
NakedDhcpv6Srv srv(0);
// Create a Solicit message with FQDN option and generate server's
// response using processSolicit function.
testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
// 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, processTwoRequests) {
NakedDhcpv6Srv srv(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", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, 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", srv);
ASSERT_EQ(2, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 4000);
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
"000201D422AA463306223D269B6CB7AFE7AAD265FC"
"EA97F93623019B2E0D14E5323D5A",
0, 4000);
}
// 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, processRequestRenew) {
NakedDhcpv6Srv srv(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", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, 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", srv);
ASSERT_EQ(2, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 4000);
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
"000201D422AA463306223D269B6CB7AFE7AAD265FC"
"EA97F93623019B2E0D14E5323D5A",
0, 4000);
}
TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
NakedDhcpv6Srv srv(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", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 4000);
// Client may send Release message. In this case the lease should be
// removed and all existing DNS entries for this lease should be
// also removed. Therefore, we expect that single NameChangeRequest to
// remove DNS entries is generated.
testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 4000);
}
// Checks that the server does not include DHCPv6 Client FQDN option in its
// response when client doesn't include this option in a Request.
TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
NakedDhcpv6Srv srv(0);
// The last parameter disables the use of DHCPv6 Client FQDN option
// in the client's Request. 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", srv, false);
ASSERT_TRUE(srv.name_change_reqs_.empty());
}
} // end of anonymous namespace