From 3cb38f1943027460b51f63e7397f1ff0b691b86b Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Tue, 2 Jul 2024 23:36:29 +0200 Subject: [PATCH] [#1387] Checkpoint: server code --- doc/examples/kea6/all-keys.json | 3 + doc/sphinx/arm/dhcp6-srv.rst | 22 ++- src/bin/dhcp6/dhcp6_lexer.ll | 9 ++ src/bin/dhcp6/dhcp6_parser.yy | 13 ++ src/bin/dhcp6/dhcp6_srv.cc | 51 ++++--- src/bin/dhcp6/dhcp6_srv.h | 8 + src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 4 +- src/bin/dhcp6/tests/renew_unittest.cc | 175 +++++++++++++++++++++- src/bin/dhcp6/tests/sarr_unittest.cc | 110 +++++++++++++- 9 files changed, 360 insertions(+), 35 deletions(-) diff --git a/doc/examples/kea6/all-keys.json b/doc/examples/kea6/all-keys.json index 1a7cadc1c8..fc793e118b 100644 --- a/doc/examples/kea6/all-keys.json +++ b/doc/examples/kea6/all-keys.json @@ -1152,6 +1152,9 @@ // List of reserved IPv6 prefixes. "prefixes": [ "2001:db8:2:abcd::/64" ], + // List of excluded IPv6 prefixes. + "excluded-prefixes": [ "2001:db8:2:abcd:1::/80" ], + // Reserved hostname. "hostname": "foo.example.com", diff --git a/doc/sphinx/arm/dhcp6-srv.rst b/doc/sphinx/arm/dhcp6-srv.rst index 2e42d5ddbb..5833516d7d 100644 --- a/doc/sphinx/arm/dhcp6-srv.rst +++ b/doc/sphinx/arm/dhcp6-srv.rst @@ -4285,8 +4285,26 @@ another. An empty ``excluded-prefixes`` list or a list with only empty strings can be omitted (and will be omitted when produced by Kea). - todo: add something about adding the pd-exclude option to returned - prefixes as done for prefix delegation pools. +:: + + "reservations": [ + { + "duid": "01:02:03:04:05:06:07:08:09:0A", + "ip-addresses": [ "2001:db8:1::103" ], + "prefixes": [ "2001:db8::/48 ], + "excluded-prefixes": [ "2001:db8:0:1::/64 ], + "hostname": "foo.example.com" + } + ], + ... + + +.. note:: + + Host reservations have precedence over prefix pools so when a reserved + prefix without an excluded prefix is assigned no pd-exclude option + is added to the prefix option even the prefix is in a configured + prefix pool with an excluded prefix (different from previous behavior). .. _reservation6-conflict: diff --git a/src/bin/dhcp6/dhcp6_lexer.ll b/src/bin/dhcp6/dhcp6_lexer.ll index de968210fc..3646d8e4ae 100644 --- a/src/bin/dhcp6/dhcp6_lexer.ll +++ b/src/bin/dhcp6/dhcp6_lexer.ll @@ -1720,6 +1720,15 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu] } } +\"excluded-prefixes\" { + switch(driver.ctx_) { + case isc::dhcp::Parser6Context::RESERVATIONS: + return isc::dhcp::Dhcp6Parser::make_EXCLUDED_PREFIXES(driver.loc_); + default: + return isc::dhcp::Dhcp6Parser::make_STRING("excluded-prefixes", driver.loc_); + } +} + \"duid\" { switch(driver.ctx_) { case isc::dhcp::Parser6Context::MAC_SOURCES: diff --git a/src/bin/dhcp6/dhcp6_parser.yy b/src/bin/dhcp6/dhcp6_parser.yy index a09f50cb72..0247e2df65 100644 --- a/src/bin/dhcp6/dhcp6_parser.yy +++ b/src/bin/dhcp6/dhcp6_parser.yy @@ -189,6 +189,7 @@ using namespace std; RESERVATIONS "reservations" IP_ADDRESSES "ip-addresses" PREFIXES "prefixes" + EXCLUDED_PREFIXES "excluded-prefixes" DUID "duid" HW_ADDRESS "hw-address" HOSTNAME "hostname" @@ -2377,6 +2378,7 @@ reservation_param: duid | reservation_client_classes | ip_addresses | prefixes + | excluded_prefixes | hw_address | hostname | flex_id_value @@ -2408,6 +2410,17 @@ prefixes: PREFIXES { ctx.leave(); }; +excluded_prefixes: EXCLUDED_PREFIXES { + ctx.unique("excluded-prefixes", ctx.loc2pos(@1)); + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("excluded-prefixes", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.NO_KEYWORD); +} COLON list_strings { + ctx.stack_.pop_back(); + ctx.leave(); +}; + duid: DUID { ctx.unique("duid", ctx.loc2pos(@1)); ctx.enter(ctx.NO_KEYWORD); diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index fcf11f9b79..750eac9d86 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -2462,6 +2462,32 @@ Dhcpv6Srv::getMAC(const Pkt6Ptr& pkt) { return (hwaddr); } +OptionPtr +Dhcpv6Srv::getPDExclude(const AllocEngine::ClientContext6& ctx, + const Lease6Ptr& lease) { + + // Search the reservation the prefix is from. + ConstHostPtr host = ctx.currentHost(); + if (host) { + IPv6ResrvRange resvs = host->getIPv6Reservations(IPv6Resrv::TYPE_PD); + BOOST_FOREACH(auto const& resv, resvs) { + if ((resv.second.getPrefix() == lease->addr_) && + (resv.second.getPrefixLen() == lease->prefixlen_)) { + return (resv.second.getPDExclude()); + } + } + } + + // Search the pool the address is from. + const Subnet6Ptr& subnet = ctx.subnet_; + Pool6Ptr pool = boost::dynamic_pointer_cast( + subnet->getPool(Lease::TYPE_PD, lease->addr_)); + if (pool) { + return (pool->getPrefixExcludeOption()); + } + return (OptionPtr()); +} + OptionPtr Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query, AllocEngine::ClientContext6& ctx, @@ -2718,15 +2744,9 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, ia_rsp->addOption(addr); if (pd_exclude_requested) { - // PD exclude option has been requested via ORO, thus we need to - // include it if the pool configuration specifies this option. - Pool6Ptr pool = boost::dynamic_pointer_cast< - Pool6>(subnet->getPool(Lease::TYPE_PD, l->addr_)); - if (pool) { - Option6PDExcludePtr pd_exclude_option = pool->getPrefixExcludeOption(); - if (pd_exclude_option) { - addr->addOption(pd_exclude_option); - } + OptionPtr pd_exclude_option = getPDExclude(ctx, l); + if (pd_exclude_option) { + addr->addOption(pd_exclude_option); } } } @@ -3049,16 +3069,9 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query, ia_rsp->addOption(prf); if (pd_exclude_requested) { - // PD exclude option has been requested via ORO, thus we need to - // include it if the pool configuration specifies this option. - Pool6Ptr pool = boost::dynamic_pointer_cast< - Pool6>(subnet->getPool(Lease::TYPE_PD, l->addr_)); - - if (pool) { - Option6PDExcludePtr pd_exclude_option = pool->getPrefixExcludeOption(); - if (pd_exclude_option) { - prf->addOption(pd_exclude_option); - } + OptionPtr pd_exclude_option = getPDExclude(ctx, l); + if (pd_exclude_option) { + prf->addOption(pd_exclude_option); } } diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 13dc444ba2..3ef6e9cff4 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -1059,6 +1059,14 @@ protected: void checkDynamicSubnetChange(const Pkt6Ptr& question, Pkt6Ptr& answer, AllocEngine::ClientContext6& ctx, const Subnet6Ptr orig_subnet); + + /// @brief Return the PD exclude option to include. + /// + /// @param ctx client context (contains subnet and hosts). + /// @param lease lease (contains address/prefix and prefix length). + OptionPtr getPDExclude(const AllocEngine::ClientContext6& ctx, + const Lease6Ptr& lease); + public: /// Used for DHCPv4-over-DHCPv6 too. diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index c84a44be60..9904810fc5 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -4050,7 +4050,7 @@ TEST_F(Dhcpv6SrvTest, generateFqdnUpdate) { EXPECT_EQ("myhost-2001-db8-1--1.example.com.", l->hostname_); // Should see follow log message ids in the log file. - EXPECT_EQ(1, countFile("DHCP6_DDNS_FQDN_GENERATED")); + EXPECT_EQ(1, countFile("DHCP6_DDNS_FQDN_GENERATED")); EXPECT_EQ(0, countFile("DHCP6_DDNS_GENERATED_FQDN_UPDATE_FAIL")); } @@ -4104,7 +4104,7 @@ TEST_F(Dhcpv6SrvTest, generateFqdnNoUpdate) { // Check that an IA_NA with an iaaddr was returned for the requested // address with lifetimes of 0. - boost::shared_ptr iaaddr = checkIA_NA(reply, 234, 0, 0); + boost::shared_ptr iaaddr = checkIA_NA(reply, 234, 0, 0); ASSERT_TRUE(iaaddr); EXPECT_EQ(addr, iaaddr->getAddress()); EXPECT_EQ(0, iaaddr->getPreferred()); diff --git a/src/bin/dhcp6/tests/renew_unittest.cc b/src/bin/dhcp6/tests/renew_unittest.cc index e20867b183..01bb3fae8a 100644 --- a/src/bin/dhcp6/tests/renew_unittest.cc +++ b/src/bin/dhcp6/tests/renew_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -61,6 +61,22 @@ namespace { /// - prefix pool: 3000::/72 /// - excluded prefix 3000::1000/120 in a prefix pool. /// +/// - Configuration 6: +/// - addresses and prefixes +/// - 1 subnet with one address pool and one prefix pool +/// - address pool: 2001:db8:1::/64 +/// - prefix pool: 3000::/72 +/// - excluded prefix 3000::1000/120 in a prefix pool. +/// - reservation for 3000::/80 (has precedence over the prefix pool). +/// +/// - Configuration 7: +/// - addresses and prefixes +/// - 1 subnet with one address pool and one prefix pool +/// - address pool: 2001:db8:1::/64 +/// - prefix pool: 3000::/72 +/// - excluded prefix 3000::1000/120 in a prefix pool. +/// - reservation for 3000::/80 with 3000::2000/120 excluded prefix. +/// const char* RENEW_CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -224,7 +240,67 @@ const char* RENEW_CONFIGS[] = { " \"interface-id\": \"\"," " \"interface\": \"eth0\"" " } ]," - "\"valid-lifetime\": 4000 }" + "\"valid-lifetime\": 4000" + "}", + +// Configuration 6 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"id\": 1, " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"pd-pools\": [" + " { \"prefix\": \"3000::\", " + " \"prefix-len\": 72, " + " \"delegated-len\": 80," + " \"excluded-prefix\": \"3000::1000\"," + " \"excluded-prefix-len\": 120" + " } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"reservations\": [" + " {" + " \"duid\": \"01:02:03:05\"," + " \"prefixes\": [ \"3000::/80\" ]" + " } ]," + " \"interface-id\": \"\"," + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000" + "}", + +// Configuration 7 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"id\": 1, " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"pd-pools\": [" + " { \"prefix\": \"3000::\", " + " \"prefix-len\": 72, " + " \"delegated-len\": 80," + " \"excluded-prefix\": \"3000::1000\"," + " \"excluded-prefix-len\": 120" + " } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"reservations\": [" + " {" + " \"duid\": \"01:02:03:05\"," + " \"prefixes\": [ \"3000::/80\" ]," + " \"excluded-prefixes\": [ \"3000::2000/120\" ]" + " } ]," + " \"interface-id\": \"\"," + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000" + "}" }; /// @brief Test fixture class for testing Renew. @@ -314,8 +390,8 @@ TEST_F(RenewTest, requestPrefixInRenew) { } // Test that it is possible to renew a prefix lease with a Prefix Exclude -// option being included during renew. -TEST_F(RenewTest, renewWithExcludedPrefix) { +// option from PD pool being included during renew. +TEST_F(RenewTest, renewWithExcludedPrefixPool) { Dhcp6Client client; // Configure client to request IA_NA and IA_PD. @@ -325,7 +401,7 @@ TEST_F(RenewTest, renewWithExcludedPrefix) { // Request Prefix Exclude option. client.requestOption(D6O_PD_EXCLUDE); - // Configure the server with NA pools only. + // Configure the server with NA and PD pools but without excluded prefix. ASSERT_NO_THROW(configure(RENEW_CONFIGS[2], *client.getServer())); // Perform 4-way exchange. @@ -399,6 +475,95 @@ TEST_F(RenewTest, renewWithExcludedPrefix) { EXPECT_EQ(120, static_cast(pd_exclude->getExcludedPrefixLength())); } +// Test that it is possible to renew a prefix lease with a Prefix Exclude +// option from host reservation being included during renew. +TEST_F(RenewTest, renewWithExcludedPrefixHost) { + Dhcp6Client client; + + // Set DUID matching the one used to create host reservations. + client.setDUID("01:02:03:05"); + + // Configure client to request IA_NA and IA_PD. + client.requestAddress(na_iaid_); + client.requestPrefix(pd_iaid_); + + // Request Prefix Exclude option. + client.requestOption(D6O_PD_EXCLUDE); + + // Configure the server with a reservation but without excluded prefix. + ASSERT_NO_THROW(configure(RENEW_CONFIGS[6], *client.getServer())); + + // Perform 4-way exchange. + ASSERT_NO_THROW(client.doSARR()); + + // Simulate aging of leases. + client.fastFwdTime(1000); + + // Make sure that the client has acquired NA lease. + std::vector leases_client_na = client.getLeasesByType(Lease::TYPE_NA); + ASSERT_EQ(1, leases_client_na.size()); + EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_)); + + // The client should also acquire a PD lease. + std::vector leases_client_pd = client.getLeasesByType(Lease::TYPE_PD); + ASSERT_EQ(1, leases_client_pd.size()); + ASSERT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_)); + + // Send Renew message to the server, including IA_NA and IA_PD. + ASSERT_NO_THROW(client.doRenew()); + + std::vector leases_client_na_renewed = + client.getLeasesByType(Lease::TYPE_NA); + ASSERT_EQ(1, leases_client_na_renewed.size()); + EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_)); + + std::vector leases_client_pd_renewed = + client.getLeasesByType(Lease::TYPE_PD); + ASSERT_EQ(1, leases_client_pd_renewed.size()); + EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_)); + + // Make sure that Prefix Exclude option hasn't been included. + OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD); + ASSERT_TRUE(option); + option = option->getOption(D6O_IAPREFIX); + ASSERT_TRUE(option); + option = option->getOption(D6O_PD_EXCLUDE); + ASSERT_FALSE(option); + + // Reconfigure the server to use the reservation with excluded prefix. + configure(RENEW_CONFIGS[7], *client.getServer()); + + // Send Renew message to the server, including IA_NA and IA_PD. + ASSERT_NO_THROW(client.doRenew()); + + // Make sure that the client has acquired NA lease. + leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA); + ASSERT_EQ(1, leases_client_na_renewed.size()); + EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_)); + + // Make sure that the client has acquired PD lease. + leases_client_pd_renewed = client.getLeasesByType(Lease::TYPE_PD); + ASSERT_EQ(1, leases_client_pd_renewed.size()); + EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_)); + + // The leases should have been renewed. + EXPECT_GE(leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_, 1000); + EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000); + + // This time, the Prefix Exclude option should be included. + option = client.getContext().response_->getOption(D6O_IA_PD); + ASSERT_TRUE(option); + option = option->getOption(D6O_IAPREFIX); + ASSERT_TRUE(option); + option = option->getOption(D6O_PD_EXCLUDE); + ASSERT_TRUE(option); + Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast(option); + ASSERT_TRUE(pd_exclude); + EXPECT_EQ("3000::2000", pd_exclude->getExcludedPrefix(IOAddress("3000::"), + 80).toText()); + EXPECT_EQ(120, static_cast(pd_exclude->getExcludedPrefixLength())); +} + // This test verifies that the client can request a prefix delegation // with a hint, while it is renewing an address lease. TEST_F(RenewTest, requestPrefixInRenewUseHint) { diff --git a/src/bin/dhcp6/tests/sarr_unittest.cc b/src/bin/dhcp6/tests/sarr_unittest.cc index 7c41472892..074b54857d 100644 --- a/src/bin/dhcp6/tests/sarr_unittest.cc +++ b/src/bin/dhcp6/tests/sarr_unittest.cc @@ -74,6 +74,16 @@ namespace { /// - One subnet with three distinct pools. /// - Random allocator enabled globally for delegated prefixes. /// - Iterative allocator for address allocation. +/// +/// - Configation 7: +/// - Cache max age and threshold. +/// +/// - Configuration 8 (derived from 3): +/// - one subnet 3000::/32 used on eth0 interface +/// - prefixes of length 64, delegated from the pool: 2001:db8:3::/48 +/// - Excluded Prefix specified (RFC 6603). +/// - Reservation (which has precedence over the pool) with excluded prefix. +/// const char* CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -328,6 +338,35 @@ const char* CONFIGS[] = { ], "valid-lifetime": 600 })", + + // Configuration 8 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ { " + " \"id\": 1, " + " \"pd-pools\": [" + " { \"prefix\": \"2001:db8:3::\", " + " \"prefix-len\": 48, " + " \"delegated-len\": 64," + " \"excluded-prefix\": \"2001:db8:3::1000\"," + " \"excluded-prefix-len\": 120" + " } ]," + " \"subnet\": \"3000::/32\", " + " \"reservations\": [" + " {" + " \"duid\": \"01:02:03:05\"," + " \"prefixes\": [ \"2001:db8:3::/64\" ]," + " \"excluded-prefixes\": [ \"2001:db8:3::2000/120\" ]" + " } ]," + " \"interface-id\": \"\"," + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000" + "}", }; /// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise, @@ -379,8 +418,13 @@ public: /// @brief This test verifies that it is possible to specify an excluded /// prefix (RFC 6603) and send it back to the client requesting prefix - /// delegation. - void directClientExcludedPrefix(); + /// delegation using a pool. + void directClientExcludedPrefixPool(); + + /// @brief This test verifies that it is possible to specify an excluded + /// prefix (RFC 6603) and send it back to the client requesting prefix + /// delegation using a reservation. + void directClientExcludedPrefixHost(); /// @brief Check that when the client includes the Rapid Commit option in /// its Solicit, the server responds with Reply and commits the lease. @@ -638,7 +682,7 @@ TEST_F(SARRTest, optionsInheritanceMultiThreading) { } void -SARRTest::directClientExcludedPrefix() { +SARRTest::directClientExcludedPrefixPool() { Dhcp6Client client; // Configure client to request IA_PD. client.requestPrefix(); @@ -677,14 +721,66 @@ SARRTest::directClientExcludedPrefix() { EXPECT_EQ(120, static_cast(pd_exclude->getExcludedPrefixLength())); } -TEST_F(SARRTest, directClientExcludedPrefix) { +TEST_F(SARRTest, directClientExcludedPrefixPool) { Dhcpv6SrvMTTestGuard guard(*this, false); - directClientExcludedPrefix(); + directClientExcludedPrefixPool(); } -TEST_F(SARRTest, directClientExcludedPrefixMultiThreading) { +TEST_F(SARRTest, directClientExcludedPrefixPoolMultiThreading) { Dhcpv6SrvMTTestGuard guard(*this, true); - directClientExcludedPrefix(); + directClientExcludedPrefixPool(); +} + +void +SARRTest::directClientExcludedPrefixHost() { + Dhcp6Client client; + // Set DUID matching the one used to create host reservations. + client.setDUID("01:02:03:05"); + // Configure client to request IA_PD. + client.requestPrefix(); + client.requestOption(D6O_PD_EXCLUDE); + configure(CONFIGS[8], *client.getServer()); + // Make sure we ended-up having expected number of subnets configured. + const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()-> + getCfgSubnets6()->getAll(); + ASSERT_EQ(1, subnets->size()); + // Perform 4-way exchange. + ASSERT_NO_THROW(client.doSARR()); + // Server should have assigned a prefix. + ASSERT_EQ(1, client.getLeaseNum()); + Lease6 lease_client = client.getLease(0); + EXPECT_EQ(64, lease_client.prefixlen_); + EXPECT_EQ(3000, lease_client.preferred_lft_); + EXPECT_EQ(4000, lease_client.valid_lft_); + Lease6Ptr lease_server = checkLease(lease_client); + // Check that the server recorded the lease. + ASSERT_TRUE(lease_server); + + OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD); + ASSERT_TRUE(option); + Option6IAPtr ia = boost::dynamic_pointer_cast(option); + ASSERT_TRUE(ia); + option = ia->getOption(D6O_IAPREFIX); + ASSERT_TRUE(option); + Option6IAPrefixPtr pd_option = boost::dynamic_pointer_cast(option); + ASSERT_TRUE(pd_option); + option = pd_option->getOption(D6O_PD_EXCLUDE); + ASSERT_TRUE(option); + Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast(option); + ASSERT_TRUE(pd_exclude); + EXPECT_EQ("2001:db8:3::2000", pd_exclude->getExcludedPrefix(IOAddress("2001:db8:3::"), + 64).toText()); + EXPECT_EQ(120, static_cast(pd_exclude->getExcludedPrefixLength())); +} + +TEST_F(SARRTest, directClientExcludedPrefixHost) { + Dhcpv6SrvMTTestGuard guard(*this, false); + directClientExcludedPrefixHost(); +} + +TEST_F(SARRTest, directClientExcludedPrefixHostMultiThreading) { + Dhcpv6SrvMTTestGuard guard(*this, true); + directClientExcludedPrefixHost(); } void