Commit 04c159af authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[master] Merge branch 'trac2898' (DHCPv6 relayed traffic)

Conflicts:
	src/lib/dhcp/tests/pkt6_unittest.cc
parents 89e00874 c3f6b67f
612. [func] tomek
b10-dhcp6: Support for relayed DHCPv6 traffic has been added.
611. [func] naokikambe
Added Xfrin statistics items such as the number of successful
transfers. These are per-zone type counters. Their values can be
......@@ -51,8 +54,8 @@ bind10-1.0.0beta2 released on May 3, 2013
(Trac #2879, git 4c45f29f28ae766a9f7dc3142859f1d0000284e1)
605. [bug] tmark
Modified perfdhcp to calculate the times displayed for packet sent
and received as time elapsed since perfdhcp process start time.
Modified perfdhcp to calculate the times displayed for packet sent
and received as time elapsed since perfdhcp process start time.
Previously these were times since the start of the epoch.
However the large numbers involved caused loss of precision
in the calculation of the test statistics.
......
......@@ -30,6 +30,7 @@
* - @subpage dhcpv6ConfigInherit
* - @subpage libdhcp
* - @subpage libdhcpIntro
* - @subpage libdhcpRelay
* - @subpage libdhcpIfaceMgr
* - @subpage libdhcpsrv
* - @subpage leasemgr
......
......@@ -4842,35 +4842,88 @@ should include options from the isc option space:
<section id="dhcp6-config-subnets">
<title>Subnet Selection</title>
<para>
The DHCPv6 server may receive requests from local (connected
to the same subnet as the server) and remote (connecting via
relays) clients.
<note>
<para>
Currently relayed DHCPv6 traffic is not supported. The server will
only respond to local DHCPv6 requests - see <xref linkend="dhcp6-limit"/>
</para>
</note>
As it may have many subnet configurations defined, it
must select appropriate subnet for a given request. To do this, the server first
The DHCPv6 server may receive requests from local (connected to the
same subnet as the server) and remote (connecting via relays) clients.
As server may have many subnet configurations defined, it must select
appropriate subnet for a given request. To do this, the server first
checks if there is only one subnet defined and source of the packet is
link-local. If this is the case, the server assumes that the only subnet
defined is local and client is indeed connected to it. This check
simplifies small deployments.
link-local. If this is the case, the server assumes that the only
subnet defined is local and client is indeed connected to it. This
check simplifies small deployments.
</para>
<para>
If there are two or more subnets defined, the server can not assume
which of those (if any) subnets are local. Therefore an optional
"interface" parameter is available within a subnet definition to designate that a given subnet
is local, i.e. reachable directly over specified interface. For example
the server that is intended to serve a local subnet over eth0 may be configured
as follows:
"interface" parameter is available within a subnet definition to
designate that a given subnet is local, i.e. reachable directly over
specified interface. For example the server that is intended to serve
a local subnet over eth0 may be configured as follows:
<screen>
&gt; <userinput>config add Dhcp6/subnet6</userinput>
&gt; <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
&gt; <userinput>config set Dhcp6/subnet6[1]/pool [ "2001:db8:beef::/48" ]</userinput>
&gt; <userinput>config set Dhcp6/subnet6[1]/interface "eth0"</userinput>
&gt; <userinput>config commit</userinput>
</screen>
</para>
</section>
<section id="dhcp6-relays">
<title>DHCPv6 Relays</title>
<para>
A DHCPv6 server with multiple subnets defined must select the
appropriate subnet when it receives a request from client. For clients
connected via relays, two mechanisms are used:
</para>
<para>
The first uses the linkaddr field in the RELAY_FORW message. The name
of this field is somewhat misleading in that it does not contain a link-layer
address: instead, it holds an address (typically a global address) that is
used to identify a link. The DHCPv6 server checks if the address belongs
to a defined subnet and, if it does, that subnet is selected for the client's
request.
</para>
<para>
The second mechanism is based on interface-id options. While forwarding a client's
message, relays may insert an interface-id option into the message that
identifies the interface on the relay that received the message. (Some
relays allow configuration of that parameter, but it is sometimes
hardcoded and may range from the very simple (e.g. "vlan100") to the very cryptic:
one example seen on real hardware was "ISAM144|299|ipv6|nt:vp:1:110"). The
server can use this information to select the appropriate subnet.
The information is also returned to the relay which then knows the
interface to use to transmit the response to the client. In order for
this to work successfully, the relay interface IDs must be unique within
the network and the server configuration must match those values.
</para>
<para>
When configuring the DHCPv6 server, it should be noted that two
similarly-named parameters can be configured for a subnet:
<itemizedlist>
<listitem><simpara>
"interface" defines which local network interface can be used
to access a given subnet.
</simpara></listitem>
<listitem><simpara>
"interface-id" specifies the content of the interface-id option
used by relays to identify the interface on the relay to which
the response packet is sent.
</simpara></listitem>
</itemizedlist>
The two are mutually exclusive: a subnet cannot be both reachable locally
(direct traffic) and via relays (remote traffic). Specifying both is a
configuration error and the DHCPv6 server will refuse such a configuration.
</para>
<para>
To specify interface-id with value "vlan123", the following commands can
be used:
<screen>
&gt; <userinput>config add Dhcp6/subnet6</userinput>
&gt; <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:beef::/48"</userinput>
&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:beef::/48" ]</userinput>
&gt; <userinput>config set Dhcp6/subnet6[0]/interface-id "vland123"</userinput>
&gt; <userinput>config commit</userinput>
</screen>
</para>
</section>
......@@ -4940,9 +4993,6 @@ Dhcp6/renew-timer 1000 integer (default)
&gt; <userinput>config commit</userinput></screen>
</para>
</listitem>
<listitem>
<simpara>Relayed traffic is not supported.</simpara>
</listitem>
<listitem>
<simpara>Temporary addresses are not supported.</simpara>
</listitem>
......
......@@ -1481,13 +1481,29 @@ private:
std::string iface;
try {
iface = string_values_.getParam("interface");
} catch (DhcpConfigError) {
} catch (const DhcpConfigError&) {
// iface not mandatory so swallow the exception
}
/// @todo: Convert this to logger once the parser is working reliably
// Get interface-id option content. For now we support string
// represenation only
std::string ifaceid;
try {
ifaceid = string_values_.getParam("interface-id");
} catch (const DhcpConfigError&) {
// interface-id is not mandatory
}
if (!iface.empty() && !ifaceid.empty()) {
isc_throw(isc::dhcp::DhcpConfigError,
"parser error: interface (defined for locally reachable "
"subnets) and interface-id (defined for subnets reachable"
" via relays) cannot be defined at the same time for "
"subnet " << addr.toText() << "/" << (int)len);
}
stringstream tmp;
tmp << addr.toText() << "/" << (int)len
tmp << addr.toText() << "/" << static_cast<int>(len)
<< " with params t1=" << t1 << ", t2=" << t2 << ", pref="
<< pref << ", valid=" << valid;
......@@ -1512,6 +1528,13 @@ private:
subnet_->setIface(iface);
}
// Configure interface-id for remote interfaces, if defined
if (!ifaceid.empty()) {
OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
subnet_->setInterfaceId(opt);
}
// We are going to move configured options to the Subnet object.
// Configured options reside in the container where options
// are grouped by space names. Thus we need to get all space names
......@@ -1591,6 +1614,7 @@ private:
factories["pool"] = PoolParser::factory;
factories["option-data"] = OptionDataListParser::factory;
factories["interface"] = StringParser::factory;
factories["interface-id"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
......
......@@ -199,6 +199,12 @@
"item_default": ""
},
{ "item_name": "interface-id",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "renew-timer",
"item_type": "integer",
"item_optional": false,
......
......@@ -403,8 +403,13 @@ Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
if (clientid) {
answer->addOption(clientid);
}
/// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)
// If this is a relayed message, we need to copy relay information
if (!question->relay_info_.empty()) {
answer->copyRelayInfo(question);
}
// TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
}
void
......@@ -523,16 +528,37 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
/// @todo: pass interface information only if received direct (non-relayed) message
Subnet6Ptr subnet;
// Try to find a subnet if received packet from a directly connected client
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
if (subnet) {
return (subnet);
}
if (question->relay_info_.empty()) {
// This is a direct (non-relayed) message
// Try to find a subnet if received packet from a directly connected client
subnet = CfgMgr::instance().getSubnet6(question->getIface());
if (!subnet) {
// If no subnet was found, try to find it based on remote address
subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
}
} else {
// If no subnet was found, try to find it based on remote address
subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
// This is a relayed message
OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
Pkt6::RELAY_GET_FIRST);
if (interface_id) {
subnet = CfgMgr::instance().getSubnet6(interface_id);
}
if (!subnet) {
// If no interface-id was specified (or not configured on server), let's
// try address matching
IOAddress link_addr = question->relay_info_.back().linkaddr_;
// if relay filled in link_addr field, then let's use it
if (link_addr != IOAddress("::")) {
subnet = CfgMgr::instance().getSubnet6(link_addr);
}
}
}
return (subnet);
}
......
......@@ -277,13 +277,13 @@ public:
expected_data_len));
}
int rcode_;
Dhcpv6Srv srv_;
int rcode_; ///< return core (see @ref isc::config::parseAnswer)
Dhcpv6Srv srv_; ///< instance of the Dhcp6Srv used during tests
ConstElementPtr comment_;
ConstElementPtr comment_; ///< comment (see @ref isc::config::parseAnswer)
string valid_iface_;
string bogus_iface_;
string valid_iface_; ///< name of a valid network interface (present in system)
string bogus_iface_; ///< name of a invalid network interface (not present in system)
};
// Goal of this test is a verification if a very simple config update
......@@ -500,6 +500,104 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
EXPECT_EQ(1, rcode_);
}
// This test checks if it is possible to define a subnet with an
// interface-id option defined.
TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
const string valid_interface_id = "foobar";
const string bogus_interface_id = "blah";
// There should be at least one interface
const string config = "{ "
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
" \"interface-id\": \"" + valid_interface_id + "\","
" \"subnet\": \"2001:db8:1::/64\" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// Returned value should be 0 (configuration success)
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(0, rcode_);
// Try to get a subnet based on bogus interface-id option
OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid);
EXPECT_FALSE(subnet);
// Now try to get subnet for valid interface-id value
tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
subnet = CfgMgr::instance().getSubnet6(ifaceid);
ASSERT_TRUE(subnet);
EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
}
// This test checks if it is not allowed to define global interface
// parameter.
TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
const string config = "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"interface-id\": \"foobar\"," // Not valid. Can be defined in subnet only
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
" \"subnet\": \"2001:db8:1::/64\" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// Returned value should be 1 (parse error)
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(1, rcode_);
}
// This test checks if it is not possible to define a subnet with an
// interface (i.e. local subnet) and interface-id (remote subnet) defined.
TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {
const string config = "{ \"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
" \"interface\": \"" + valid_iface_ + "\","
" \"interface-id\": \"foobar\","
" \"subnet\": \"2001:db8:1::/64\" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// Returned value should be 1 (configuration error)
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(1, rcode_);
}
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
......
......@@ -80,7 +80,7 @@ public:
static const char* DUID_FILE = "server-id-test.txt";
// test fixture for any tests requiring blank/empty configuration
// serves as base class for additional tests
// serves as base class for additional tests
class NakedDhcpv6SrvTest : public ::testing::Test {
public:
......@@ -98,6 +98,16 @@ public:
return (ia);
}
/// @brief generates interface-id option, based on text
///
/// @param iface_id textual representation of the interface-id content
///
/// @return pointer to the option object
OptionPtr generateInterfaceId(const string& iface_id) {
OptionBuffer tmp(iface_id.begin(), iface_id.end());
return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
}
// Generate client-id option
OptionPtr generateClientId(size_t duid_size = 32) {
......@@ -136,12 +146,12 @@ public:
// Checks if server response is a NAK
void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
uint32_t expected_transid,
uint32_t expected_transid,
uint16_t expected_status_code) {
// Check if we get response at all
checkResponse(rsp, expected_message_type, expected_transid);
// Check that IA_NA was returned
// Check that IA_NA was returned
OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
ASSERT_TRUE(option_ia_na);
......@@ -227,7 +237,7 @@ public:
ConstElementPtr comment_;
};
// Provides suport for tests against a preconfigured subnet6
// Provides suport for tests against a preconfigured subnet6
// extends upon NakedDhcp6SrvTest
class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
public:
......@@ -254,7 +264,7 @@ public:
ADD_FAILURE() << "IA_NA option not present in response";
return (boost::shared_ptr<Option6IAAddr>());
}
boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
if (!ia) {
ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
......@@ -264,7 +274,7 @@ public:
EXPECT_EQ(expected_iaid, ia->getIAID());
EXPECT_EQ(expected_t1, ia->getT1());
EXPECT_EQ(expected_t2, ia->getT2());
tmp = ia->getOption(D6O_IAADDR);
boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
return (addr);
......@@ -320,10 +330,10 @@ public:
};
// This test verifies that incoming SOLICIT can be handled properly when
// there are no subnets configured.
// there are no subnets configured.
//
// This test sends a SOLICIT and the expected response
// is an ADVERTISE with STATUS_NoAddrsAvail and no address provided in the
// This test sends a SOLICIT and the expected response
// is an ADVERTISE with STATUS_NoAddrsAvail and no address provided in the
// response
TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
NakedDhcpv6Srv srv(0);
......@@ -342,10 +352,10 @@ TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
}
// This test verifies that incoming REQUEST can be handled properly when
// there are no subnets configured.
// there are no subnets configured.
//
// This test sends a REQUEST and the expected response
// is an REPLY with STATUS_NoAddrsAvail and no address provided in the
// This test sends a REQUEST and the expected response
// is an REPLY with STATUS_NoAddrsAvail and no address provided in the
// response
TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
NakedDhcpv6Srv srv(0);
......@@ -376,8 +386,8 @@ TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
// This test verifies that incoming RENEW can be handled properly, even when
// no subnets are configured.
//
// This test sends a RENEW and the expected response
// is an REPLY with STATUS_NoBinding and no address provided in the
// This test sends a RENEW and the expected response
// is an REPLY with STATUS_NoBinding and no address provided in the
// response
TEST_F(NakedDhcpv6SrvTest, RenewNoSubnet) {
NakedDhcpv6Srv srv(0);
......@@ -411,8 +421,8 @@ TEST_F(NakedDhcpv6SrvTest, RenewNoSubnet) {
// This test verifies that incoming RELEASE can be handled properly, even when
// no subnets are configured.
//
// This test sends a RELEASE and the expected response
// is an REPLY with STATUS_NoBinding and no address provided in the
// This test sends a RELEASE and the expected response
// is an REPLY with STATUS_NoBinding and no address provided in the
// response
TEST_F(NakedDhcpv6SrvTest, ReleaseNoSubnet) {
NakedDhcpv6Srv srv(0);
......@@ -678,6 +688,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
ASSERT_TRUE(addr);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
......@@ -731,6 +742,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
ASSERT_TRUE(addr);
// check that we've got the address we requested
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
......@@ -779,6 +791,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
ASSERT_TRUE(addr);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
......@@ -840,6 +853,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
subnet_->getT2());
boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
subnet_->getT2());
ASSERT_TRUE(addr1);
ASSERT_TRUE(addr2);
ASSERT_TRUE(addr3);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
......@@ -910,6 +926,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
ASSERT_TRUE(addr);
// check that we've got the address we requested
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
......@@ -934,6 +951,8 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
TEST_F(Dhcpv6SrvTest, ManyRequests) {
NakedDhcpv6Srv srv(0);
ASSERT_TRUE(subnet_);
Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
Pkt6Ptr req3 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 3456));
......@@ -978,6 +997,10 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
subnet_->getT2());
ASSERT_TRUE(addr1);
ASSERT_TRUE(addr2);
ASSERT_TRUE(addr3);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
checkIAAddr(addr2, addr2->getAddress(), subnet_->getPreferred(), subnet_->getValid());
......@@ -1066,6 +1089,8 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
ASSERT_TRUE(addr_opt);
// Check that we've got the address we requested
checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
......@@ -1592,6 +1617,113 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
}
// This test verifies if selectSubnet() selects proper subnet for a given
// linkaddr in RELAY-FORW message
TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
NakedDhcpv6Srv srv(0);
Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
Pkt6::RelayInfo relay;
relay.linkaddr_ = IOAddress("2001:db8:2::1234");
relay.peeraddr_ = IOAddress("fe80::1");
// CASE 1: We have only one subnet defined and we received relayed traffic.
// The only available subnet should NOT be selected.
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
pkt->relay_info_.push_back(relay);
Subnet6Ptr selected = srv.selectSubnet(pkt);
EXPECT_FALSE(selected);
// CASE 2: We have three subnets defined and we received relayed traffic.
// Nothing should be selected.
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1);
CfgMgr::instance().addSubnet6(subnet2);
CfgMgr::instance().addSubnet6(subnet3);