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

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

Conflicts:
	src/lib/dhcp/tests/pkt6_unittest.cc
This commit is contained in:
Tomek Mrugalski
2013-05-09 17:12:18 +02:00
19 changed files with 871 additions and 85 deletions

View File

@@ -1,3 +1,6 @@
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.

View File

@@ -30,6 +30,7 @@
* - @subpage dhcpv6ConfigInherit
* - @subpage libdhcp
* - @subpage libdhcpIntro
* - @subpage libdhcpRelay
* - @subpage libdhcpIfaceMgr
* - @subpage libdhcpsrv
* - @subpage leasemgr

View File

@@ -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>

View File

@@ -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()) {

View File

@@ -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,

View File

@@ -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,17 +528,38 @@ 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 {
// 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);
}
}
}
// If no subnet was found, try to find it based on remote address
subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
return (subnet);
}

View File

@@ -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) {

View File

@@ -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);
selected = srv.selectSubnet(pkt);
EXPECT_EQ(selected, subnet2);
// CASE 3: We have three subnets defined and we received relayed traffic
// that came out of subnet 2. We should select subnet2 then
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1);
CfgMgr::instance().addSubnet6(subnet2);
CfgMgr::instance().addSubnet6(subnet3);
// Source of the packet should have no meaning. Selection is based
// on linkaddr field in the relay
pkt->setRemoteAddr(IOAddress("2001:db8:1::baca"));
selected = srv.selectSubnet(pkt);
EXPECT_EQ(selected, subnet2);
// CASE 4: We have three subnets defined and we received relayed traffic
// that came out of undefined subnet. We should select nothing
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1);
CfgMgr::instance().addSubnet6(subnet2);
CfgMgr::instance().addSubnet6(subnet3);
pkt->relay_info_.clear();
relay.linkaddr_ = IOAddress("2001:db8:4::1234");
pkt->relay_info_.push_back(relay);
selected = srv.selectSubnet(pkt);
EXPECT_FALSE(selected);
}
// This test verifies if selectSubnet() selects proper subnet for a given
// interface-id option
TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
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));
subnet1->setInterfaceId(generateInterfaceId("relay1"));
subnet2->setInterfaceId(generateInterfaceId("relay2"));
// CASE 1: We have only one subnet defined and it is for interface-id "relay1"
// Packet came with interface-id "relay2". We should not select subnet1
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
Pkt6::RelayInfo relay;
relay.linkaddr_ = IOAddress("2001:db8:2::1234");
relay.peeraddr_ = IOAddress("fe80::1");
OptionPtr opt = generateInterfaceId("relay2");
relay.options_.insert(make_pair(opt->getType(), opt));
pkt->relay_info_.push_back(relay);
// There is only one subnet configured and we are outside of that subnet
Subnet6Ptr selected = srv.selectSubnet(pkt);
EXPECT_FALSE(selected);
// CASE 2: We have only one subnet defined and it is for interface-id "relay2"
// Packet came with interface-id "relay2". We should select it
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet2); // just a single subnet
selected = srv.selectSubnet(pkt);
EXPECT_EQ(selected, subnet2);
// CASE 3: We have only 3 subnets defined: one remote for interface-id "relay1",
// one remote for interface-id "relay2" and third local
// packet comes with interface-id "relay2". We should select subnet2
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1);
CfgMgr::instance().addSubnet6(subnet2);
CfgMgr::instance().addSubnet6(subnet3);
EXPECT_EQ(subnet2, srv.selectSubnet(pkt));
}
// This test verifies if the server-id disk operations (read, write) are
// working properly.
TEST_F(Dhcpv6SrvTest, ServerID) {

View File

@@ -57,6 +57,53 @@ DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
for that purpose.
@section libdhcpRelay Relay v6 support in Pkt6
DHCPv6 clients that are not connected to the same link as DHCPv6
servers need relays to reach the server. Each relay receives a message
on a client facing interface, encapsulates it into RELAY_MSG option
and sends as RELAY_FORW message towards the server (or the next relay,
which is closer to the server). This procedure can be repeated up to
32 times. Kea is able to support up to 32 relays. Each traversed relay
may add certain options. The most obvious example is interface-id
option, but there may be other options as well. Each relay may add such
an option, regardless of whether other relays added it before. Thanks
to encapsulation, those options are separated and it is possible to
differentiate which relay inserted specific instance of an option.
Interface-id is used to identify a subnet (or interface) the original message
came from and is used for that purpose on two occasions. First, the server
uses the interface-id included by the first relay (the one closest to
the client) to select appropriate subnet for a given request. Server includes
that interface-id in its copy, when sending data back to the client.
This will be used by the relay to choose proper interface when forwarding
response towards the client.
Pkt6 class has a public Pkt6::relay_info_ field, which is of type Pkt6::RelayInfo.
This is a simple structure that represents the information in each RELAY_FORW
or RELAY_REPL message. It is important to understand the order in which
the data appear here. Consider the following network:
\verbatim
client-------relay1-----relay2-----relay3----server
\endverbatim
Client will transmit SOLICIT message. Relay1 will forward it as
RELAY_FORW with SOLICIT in it. Relay2 forward it as RELAY_FORW with
RELAY_FORW with SOLICIT in it. Finally the third relay will add yet
another RELAY_FORW around it. The server will parse the packet and
create Pkt6 object for it. Its relay_info_ will have 3
elements. Packet parsing is done in reverse order, compare to the
order the packet traversed in the network. The first element
(relay_info_[0]) will represent relay3 information (the "last" relay or
in other words the one closest to the server). The second element
will represent relay2. The third element (relay_info_[2]) will represent
the first relay (relay1) or in other words the one closest to the client.
Packets sent by the server must maintain the same encapsulation order.
This is easy to do - just copy data from client's message object into
server's response object. See Pkt6::coyRelayInfo for details.
@section libdhcpIfaceMgr Interface Manager
Interface Manager (or IfaceMgr) is an abstraction layer about low-level

View File

@@ -72,6 +72,62 @@ uint16_t Pkt6::len() {
}
}
OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
if (relay_info_.empty()) {
// There's no relay info, this is a direct message
return (OptionPtr());
}
int start = 0; // First relay to check
int end = 0; // Last relay to check
int direction = 0; // How we going to iterate: forward or backward?
switch (order) {
case RELAY_SEARCH_FROM_CLIENT:
// Search backwards
start = relay_info_.size() - 1;
end = 0;
direction = -1;
break;
case RELAY_SEARCH_FROM_SERVER:
// Search forward
start = 0;
end = relay_info_.size() - 1;
direction = 1;
break;
case RELAY_GET_FIRST:
// Look at the innermost relay only
start = relay_info_.size() - 1;
end = start;
direction = 1;
break;
case RELAY_GET_LAST:
// Look at the outermost relay only
start = 0;
end = 0;
direction = 1;
}
// This is a tricky loop. It must go from start to end, but it must work in
// both directions (start > end; or start < end). We can't use regular
// exit condition, because we don't know whether to use i <= end or i >= end.
// That's why we check if in the next iteration we would go past the
// list (end + direction). It is similar to STL concept of end pointing
// to a place after the last element
for (int i = start; i != end + direction; i += direction) {
OptionPtr opt = getRelayOption(opt_type, i);
if (opt) {
return (opt);
}
}
// We iterated over specified relays and haven't found what we were
// looking for
return (OptionPtr());
}
OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
if (relay_level >= relay_info_.size()) {
isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
@@ -483,5 +539,33 @@ const char* Pkt6::getName() const {
return (getName(getType()));
}
void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
// We use index rather than iterator, because we need that as a parameter
// passed to getRelayOption()
for (int i = 0; i < question->relay_info_.size(); ++i) {
RelayInfo info;
info.msg_type_ = DHCPV6_RELAY_REPL;
info.hop_count_ = question->relay_info_[i].hop_count_;
info.linkaddr_ = question->relay_info_[i].linkaddr_;
info.peeraddr_ = question->relay_info_[i].peeraddr_;
// Is there an interface-id option in this nesting level?
// If there is, we need to echo it back
OptionPtr opt = question->getRelayOption(D6O_INTERFACE_ID, i);
// taken from question->RelayInfo_[i].options_
if (opt) {
info.options_.insert(make_pair(opt->getType(), opt));
}
/// @todo: Implement support for ERO (Echo Request Option, RFC4994)
// Add this relay-forw info (client's message) to our relay-repl
// message (server's response)
relay_info_.push_back(info);
}
}
} // end of isc::dhcp namespace
} // end of isc namespace

View File

@@ -30,6 +30,9 @@ namespace isc {
namespace dhcp {
class Pkt6;
typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
class Pkt6 {
public:
/// specifies non-relayed DHCPv6 packet header length (over UDP)
@@ -44,6 +47,28 @@ public:
TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover)
};
/// @brief defines relay search pattern
///
/// Defines order in which options are searched in a message that
/// passed through mulitple relays. RELAY_SEACH_FROM_CLIENT will
/// start search from the relay that was the closest to the client
/// (i.e. innermost in the encapsulated message, which also means
/// this was the first relay that forwarded packet received by the
/// server and this will be the last relay that will handle the
/// response that server sent towards the client.).
/// RELAY_SEARCH_FROM_SERVER is the opposite. This will be the
/// relay closest to the server (i.e. outermost in the encapsulated
/// message, which also means it was the last relay that relayed
/// the received message and will be the first one to process
/// server's response). RELAY_GET_FIRST will try to get option from
/// the first relay only (closest to the client), RELAY_GET_LAST will
/// try to get option form the the last relay (closest to the server).
enum RelaySearchOrder {
RELAY_SEARCH_FROM_CLIENT = 1,
RELAY_SEARCH_FROM_SERVER = 2,
RELAY_GET_FIRST = 3,
RELAY_GET_LAST = 4
};
/// @brief structure that describes a single relay information
///
@@ -201,6 +226,18 @@ public:
/// @return pointer to the option (or NULL if there is no such option)
OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
/// @brief Return first instance of a specified option
///
/// When a client's packet traverses multiple relays, each passing relay may
/// insert extra options. This method allows the specific instance of a given
/// option to be obtained (e.g. closest to the client, closest to the server,
/// etc.) See @ref RelaySearchOrder for a detailed description.
///
/// @param option_code searched option
/// @param order option search order (see @ref RelaySearchOrder)
/// @return option pointer (or NULL if no option matches specified criteria)
OptionPtr getAnyRelayOption(uint16_t option_code, RelaySearchOrder order);
/// @brief Returns all instances of specified type.
///
/// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -356,6 +393,14 @@ public:
/// be freed by the caller.
const char* getName() const;
/// @brief copies relay information from client's packet to server's response
///
/// This information is not simply copied over. Some parameter are
/// removed, msg_type_is updated (RELAY-FORW => RELAY-REPL), etc.
///
/// @param question client's packet
void copyRelayInfo(const Pkt6Ptr& question);
/// relay information
///
/// this is a public field. Otherwise we hit one of the two problems:
@@ -494,8 +539,6 @@ protected:
boost::posix_time::ptime timestamp_;
}; // Pkt6 class
typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
} // isc::dhcp namespace
} // isc namespace

View File

@@ -264,11 +264,11 @@ TEST_F(LibDhcpTest, packOptions6) {
OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt1));
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt2));
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt3));
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt4));
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt5));
opts.insert(make_pair(opt1->getType(), opt1));
opts.insert(make_pair(opt1->getType(), opt2));
opts.insert(make_pair(opt1->getType(), opt3));
opts.insert(make_pair(opt1->getType(), opt4));
opts.insert(make_pair(opt1->getType(), opt5));
OutputBuffer assembled(512);

View File

@@ -22,6 +22,7 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
#include <util/range_utilities.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/scoped_ptr.hpp>
@@ -45,6 +46,18 @@ class Pkt6Test : public ::testing::Test {
public:
Pkt6Test() {
}
/// @brief generates an option with given code (and length) and random content
///
/// @param code option code
/// @param len data length (data will be randomized)
///
/// @return pointer to the new option
OptionPtr generateRandomOption(uint16_t code, size_t len = 10) {
OptionBuffer data(len);
util::fillRandom(data.begin(), data.end());
return OptionPtr(new Option(Option::V6, code, data));
}
};
TEST_F(Pkt6Test, constructor) {
@@ -487,8 +500,7 @@ TEST_F(Pkt6Test, relayPack) {
OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));
relay1.options_.insert(pair<int, boost::shared_ptr<Option> >(
optRelay1->getType(), optRelay1));
relay1.options_.insert(make_pair(optRelay1->getType(), optRelay1));
OptionPtr opt1(new Option(Option::V6, 100));
OptionPtr opt2(new Option(Option::V6, 101));
@@ -546,4 +558,112 @@ TEST_F(Pkt6Test, relayPack) {
EXPECT_EQ(0, memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
}
// This test verified that options added by relays to the message can be
// accessed and retrieved properly
TEST_F(Pkt6Test, getAnyRelayOption) {
boost::scoped_ptr<Pkt6> msg(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
msg->addOption(generateRandomOption(300));
// generate options for relay1
Pkt6::RelayInfo relay1;
// generate 3 options with code 200,201,202 and random content
OptionPtr relay1_opt1(generateRandomOption(200));
OptionPtr relay1_opt2(generateRandomOption(201));
OptionPtr relay1_opt3(generateRandomOption(202));
relay1.options_.insert(make_pair(200, relay1_opt1));
relay1.options_.insert(make_pair(201, relay1_opt2));
relay1.options_.insert(make_pair(202, relay1_opt3));
msg->addRelayInfo(relay1);
// generate options for relay2
Pkt6::RelayInfo relay2;
OptionPtr relay2_opt1(new Option(Option::V6, 100));
OptionPtr relay2_opt2(new Option(Option::V6, 101));
OptionPtr relay2_opt3(new Option(Option::V6, 102));
OptionPtr relay2_opt4(new Option(Option::V6, 200)); // the same code as relay1_opt3
relay2.options_.insert(make_pair(100, relay2_opt1));
relay2.options_.insert(make_pair(101, relay2_opt2));
relay2.options_.insert(make_pair(102, relay2_opt3));
relay2.options_.insert(make_pair(200, relay2_opt4));
msg->addRelayInfo(relay2);
// generate options for relay3
Pkt6::RelayInfo relay3;
OptionPtr relay3_opt1(generateRandomOption(200, 7));
relay3.options_.insert(make_pair(200, relay3_opt1));
msg->addRelayInfo(relay3);
// Ok, so we now have a packet that traversed the following network:
// client---relay3---relay2---relay1---server
// First check that the getAnyRelayOption does not confuse client options
// and relay options
// 300 is a client option, present in the message itself.
OptionPtr opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
EXPECT_FALSE(opt);
opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER);
EXPECT_FALSE(opt);
opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_FIRST);
EXPECT_FALSE(opt);
opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_LAST);
EXPECT_FALSE(opt);
// Option 200 is added in every relay.
// We want to get that one inserted by relay3 (first match, starting from
// closest to the client.
opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
ASSERT_TRUE(opt);
EXPECT_TRUE(opt->equal(relay3_opt1));
// We want to ge that one inserted by relay1 (first match, starting from
// closest to the server.
opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
ASSERT_TRUE(opt);
EXPECT_TRUE(opt->equal(relay1_opt1));
// We just want option from the first relay (closest to the client)
opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
ASSERT_TRUE(opt);
EXPECT_TRUE(opt->equal(relay3_opt1));
// We just want option from the last relay (closest to the server)
opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
ASSERT_TRUE(opt);
EXPECT_TRUE(opt->equal(relay1_opt1));
// Let's try to ask for something that is inserted by the middle relay
// only.
opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
ASSERT_TRUE(opt);
EXPECT_TRUE(opt->equal(relay2_opt1));
opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
ASSERT_TRUE(opt);
EXPECT_TRUE(opt->equal(relay2_opt1));
opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST);
EXPECT_FALSE(opt);
opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_LAST);
EXPECT_FALSE(opt);
// Finally, try to get an option that does not exist
opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_FIRST);
EXPECT_FALSE(opt);
opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_LAST);
EXPECT_FALSE(opt);
opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_SERVER);
EXPECT_FALSE(opt);
opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_CLIENT);
EXPECT_FALSE(opt);
}
}

View File

@@ -40,8 +40,7 @@ CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
isc_throw(InvalidOptionSpace, "option space " << space->getName()
<< " already added.");
}
spaces4_.insert(std::pair<std::string,
OptionSpacePtr>(space->getName(), space));
spaces4_.insert(make_pair(space->getName(), space));
}
void
@@ -55,8 +54,7 @@ CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
isc_throw(InvalidOptionSpace, "option space " << space->getName()
<< " already added.");
}
spaces6_.insert(std::pair<std::string,
OptionSpacePtr>(space->getName(), space));
spaces6_.insert(make_pair(space->getName(), space));
}
void
@@ -147,7 +145,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// If there's only one subnet configured, let's just use it
// The idea is to keep small deployments easy. In a small network - one
// router that also runs DHCPv6 server. Users specifies a single pool and
// router that also runs DHCPv6 server. User specifies a single pool and
// expects it to just work. Without this, the server would complain that it
// doesn't have IP address on its interfaces that matches that
// configuration. Such requirement makes sense in IPv4, but not in IPv6.
@@ -178,14 +176,30 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
return (Subnet6Ptr());
}
Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
/// @todo: Implement get subnet6 by interface-id (for relayed traffic)
isc_throw(NotImplemented, "Relayed DHCPv6 traffic is not supported yet.");
Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
if (!iface_id_option) {
return (Subnet6Ptr());
}
// Let's iterate over all subnets and for those that have interface-id
// defined, check if the interface-id is equal to what we are looking for
for (Subnet6Collection::iterator subnet = subnets6_.begin();
subnet != subnets6_.end(); ++subnet) {
if ( (*subnet)->getInterfaceId() &&
((*subnet)->getInterfaceId()->equal(iface_id_option))) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
.arg((*subnet)->toText());
return (*subnet);
}
}
return (Subnet6Ptr());
}
void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
/// @todo: Check that there is no subnet with the same interface-id
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
.arg(subnet->toText());
subnets6_.push_back(subnet);

View File

@@ -174,7 +174,6 @@ public:
/// @param interface_id content of interface-id option returned by a relay
///
/// @return a subnet object
/// @todo This method is not currently supported.
Subnet6Ptr getSubnet6(OptionPtr interface_id);
/// @brief adds an IPv6 subnet

View File

@@ -105,7 +105,14 @@ This is a debug message reporting that the DHCP configuration manager
has returned the specified IPv6 subnet for a packet received over
given interface. This particular subnet was selected, because it
was specified as being directly reachable over given interface. (see
'interface' parameter in subnet6 definition).
'interface' parameter in the subnet6 definition).
% DHCPSRV_CFGMGR_SUBNET6_IFACE_ID selected subnet %1 (interface-id match) for incoming packet
This is a debug message reporting that the DHCP configuration manager
has returned the specified IPv6 subnet for a received packet. This particular
subnet was selected, because value of interface-id option matched what was
configured in server's interface-id option for that selected subnet6.
(see 'interface-id' parameter in the subnet6 definition).
% DHCPSRV_CLOSE_DB closing currently open %1 database
This is a debug message, issued when the DHCP server closes the currently

View File

@@ -463,6 +463,19 @@ public:
/// @return network interface name for directly attached subnets or ""
std::string getIface() const;
/// @brief sets interface-id option (if defined)
///
/// @param ifaceid pointer to interface-id option
void setInterfaceId(const OptionPtr& ifaceid) {
interface_id_ = ifaceid;
}
/// @brief returns interface-id value (if specified)
/// @return interface-id option (if defined)
OptionPtr getInterfaceId() const {
return interface_id_;
}
protected:
/// @brief Check if option is valid and can be added to a subnet.
@@ -478,6 +491,9 @@ protected:
return (isc::asiolink::IOAddress("::"));
}
/// @brief specifies optional interface-id
OptionPtr interface_id_;
/// @brief collection of pools in that list
Pool6Collection pools_;

View File

@@ -17,6 +17,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <exceptions/exceptions.h>
#include <dhcp/dhcp6.h>
#include <gtest/gtest.h>
@@ -37,7 +38,7 @@ using boost::scoped_ptr;
namespace {
// This test verifies that BooleanStorage functions properly.
// This test verifies that BooleanStorage functions properly.
TEST(ValueStorageTest, BooleanTesting) {
BooleanStorage testStore;
@@ -48,7 +49,7 @@ TEST(ValueStorageTest, BooleanTesting) {
EXPECT_FALSE(testStore.getParam("firstBool"));
EXPECT_TRUE(testStore.getParam("secondBool"));
// Verify that we can update paramaters.
// Verify that we can update parameters.
testStore.setParam("firstBool", true);
testStore.setParam("secondBool", false);
@@ -65,7 +66,7 @@ TEST(ValueStorageTest, BooleanTesting) {
// Verify that looking for a parameter that never existed throws.
ASSERT_THROW(testStore.getParam("bogusBool"), isc::dhcp::DhcpConfigError);
// Verify that attempting to delete a parameter that never existed does not throw.
// Verify that attempting to delete a parameter that never existed does not throw.
EXPECT_NO_THROW(testStore.delParam("bogusBool"));
// Verify that we can empty the list.
@@ -74,21 +75,21 @@ TEST(ValueStorageTest, BooleanTesting) {
}
// This test verifies that Uint32Storage functions properly.
// This test verifies that Uint32Storage functions properly.
TEST(ValueStorageTest, Uint32Testing) {
Uint32Storage testStore;
uint32_t intOne = 77;
uint32_t intTwo = 33;
// Verify that we can add and retrieve parameters.
// Verify that we can add and retrieve parameters.
testStore.setParam("firstInt", intOne);
testStore.setParam("secondInt", intTwo);
EXPECT_EQ(testStore.getParam("firstInt"), intOne);
EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
// Verify that we can update parameters.
// Verify that we can update parameters.
testStore.setParam("firstInt", --intOne);
testStore.setParam("secondInt", ++intTwo);
@@ -105,7 +106,7 @@ TEST(ValueStorageTest, Uint32Testing) {
// Verify that looking for a parameter that never existed throws.
ASSERT_THROW(testStore.getParam("bogusInt"), isc::dhcp::DhcpConfigError);
// Verify that attempting to delete a parameter that never existed does not throw.
// Verify that attempting to delete a parameter that never existed does not throw.
EXPECT_NO_THROW(testStore.delParam("bogusInt"));
// Verify that we can empty the list.
@@ -113,7 +114,7 @@ TEST(ValueStorageTest, Uint32Testing) {
EXPECT_THROW(testStore.getParam("secondInt"), isc::dhcp::DhcpConfigError);
}
// This test verifies that StringStorage functions properly.
// This test verifies that StringStorage functions properly.
TEST(ValueStorageTest, StringTesting) {
StringStorage testStore;
@@ -127,7 +128,7 @@ TEST(ValueStorageTest, StringTesting) {
EXPECT_EQ(testStore.getParam("firstString"), stringOne);
EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
// Verify that we can update parameters.
// Verify that we can update parameters.
stringOne.append("-boo");
stringTwo.append("-boo");
@@ -147,7 +148,7 @@ TEST(ValueStorageTest, StringTesting) {
// Verify that looking for a parameter that never existed throws.
ASSERT_THROW(testStore.getParam("bogusString"), isc::dhcp::DhcpConfigError);
// Verify that attempting to delete a parameter that never existed does not throw.
// Verify that attempting to delete a parameter that never existed does not throw.
EXPECT_NO_THROW(testStore.delParam("bogusString"));
// Verify that we can empty the list.
@@ -165,6 +166,16 @@ public:
CfgMgr::instance().deleteSubnets6();
}
/// @brief generates interface-id option based on provided text
///
/// @param text content of the option to be created
///
/// @return pointer to the option object created
OptionPtr generateInterfaceId(const string& text) {
OptionBuffer buffer(text.begin(), text.end());
return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
}
~CfgMgrTest() {
// clean up after the test
CfgMgr::instance().deleteSubnets4();
@@ -406,6 +417,95 @@ TEST_F(CfgMgrTest, subnet6) {
EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
}
// This test verifies if the configuration manager is able to hold, select
// and return valid subnets, based on interface names.
TEST_F(CfgMgrTest, subnet6Interface) {
CfgMgr& cfg_mgr = CfgMgr::instance();
Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
subnet1->setIface("foo");
subnet2->setIface("bar");
subnet3->setIface("foobar");
// There shouldn't be any subnet configured at this stage
EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
cfg_mgr.addSubnet6(subnet1);
// Now we have only one subnet, any request will be served from it
EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo"));
// Check that the interface name is checked even when there is
// only one subnet defined.
EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
// If we have only a single subnet and the request came from a local
// address, let's use that subnet
EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
cfg_mgr.addSubnet6(subnet2);
cfg_mgr.addSubnet6(subnet3);
EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar"));
EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar"));
EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy")); // no such interface
// Check that deletion of the subnets works.
cfg_mgr.deleteSubnets6();
EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
EXPECT_FALSE(cfg_mgr.getSubnet6("foobar"));
}
// This test verifies if the configuration manager is able to hold, select
// and return valid leases, based on interface-id option values
TEST_F(CfgMgrTest, subnet6InterfaceId) {
CfgMgr& cfg_mgr = CfgMgr::instance();
Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
// interface-id options used in subnets 1,2, and 3
OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
OptionPtr ifaceid2 = generateInterfaceId("VL32");
// That's a strange interface-id, but this is a real life example
OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
// bogus interface-id
OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
subnet1->setInterfaceId(ifaceid1);
subnet2->setInterfaceId(ifaceid2);
subnet3->setInterfaceId(ifaceid3);
// There shouldn't be any subnet configured at this stage
EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
cfg_mgr.addSubnet6(subnet1);
// If we have only a single subnet and the request came from a local
// address, let's use that subnet
EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1));
EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
cfg_mgr.addSubnet6(subnet2);
cfg_mgr.addSubnet6(subnet3);
EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3));
EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2));
EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus));
// Check that deletion of the subnets works.
cfg_mgr.deleteSubnets6();
EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3));
}
// This test verifies that new DHCPv4 option spaces can be added to
// the configuration manager and that duplicated option space is
// rejected.

View File

@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
#include <dhcp/dhcp6.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
@@ -516,4 +517,19 @@ TEST(Subnet6Test, iface) {
EXPECT_EQ("en1", subnet.getIface());
}
// This trivial test checks if the interface-id option can be set and
// later retrieved for a subnet6 object.
TEST(Subnet6Test, interfaceId) {
// Create as subnet to add options to it.
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
EXPECT_FALSE(subnet->getInterfaceId());
OptionPtr option(new Option(Option::V6, D6O_INTERFACE_ID, OptionBuffer(10, 0xFF)));
subnet->setInterfaceId(option);
EXPECT_EQ(option, subnet->getInterfaceId());
}
};