Commit d47e9e00 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[2596] "Interface" parameter in Subnet6 configuration added.

parent 13674dda
5XX. [func] tomek
b10-dhcp6: It is now possible to specify that a configured subnet
is reachable locally over specified interface (see "interface"
parameter in Subnet6 configuration).
(Trac #2596, git TBD)
545. [func] jinmei
libdns++: the SOA Rdata class now uses the generic lexer in
constructors from text. This means that the MNAME and RNAME of an
......
......@@ -3839,6 +3839,32 @@ Dhcp6/subnet6 [] list (default)</screen>
and problems in the DHCPv6 server. See <xref linkend="dhcp6-limit"/>.
</para>
</note>
<section id="dhcp6-config-subnets">
<title>Subnet selection</title>
<para>
DHCPv6 server may receive requests from local (connected to the same
subnet as the server) and remote (connecting via relays)
clients. Server may have many subnet configurations defined, so it
must select appropriate subnet for a given request. Server first
checks if there is only one subnet defined and source of the packet is
link-local. If this is the case 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, server can not assume
which of those (if any) subnets are local. Therefore an optional
"interface" parameter has been defined to designate that a given subnet
is local, i.e. reachable directly over specified interface. For example
the server that is intended to serve 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>
<section id="dhcp6-std">
......
......@@ -1135,6 +1135,15 @@ private:
Triplet<uint32_t> pref = getParam("preferred-lifetime");
Triplet<uint32_t> valid = getParam("valid-lifetime");
// Get interface name. If it is defined, then the subnet is available
// directly over specified network interface.
string iface;
StringStorage::iterator iface_iter = string_values_.find("interface");
if (iface_iter != string_values_.end()) {
iface = iface_iter->second;
}
/// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
tmp << addr.toText() << "/" << (int)len
......@@ -1151,6 +1160,11 @@ private:
subnet_->addPool(*it);
}
// Configure interface, if defined
if (iface.length()) {
subnet_->setIface(iface);
}
Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
......@@ -1205,6 +1219,7 @@ private:
factories["subnet"] = StringParser::factory;
factories["pool"] = PoolParser::factory;
factories["option-data"] = OptionDataListParser::factory;
factories["interface"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
......
......@@ -94,6 +94,12 @@
"item_default": ""
},
{ "item_name": "interface",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "renew-timer",
"item_type": "integer",
"item_optional": false,
......
......@@ -428,7 +428,17 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
}
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
/// @todo: pass interface information only if received direct (non-relayed) message
// 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 no subnet was found, try to find it based on remote address
subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
return (subnet);
}
......
......@@ -272,9 +272,8 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
EXPECT_EQ(1, rcode_);
}
/// The goal of this test is to verify if wrongly defined subnet will
/// be rejected. Properly defined subnet must include at least one
/// pool definition.
/// The goal of this test is to verify if configuration without any
/// subnets defined can be accepted.
TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status;
......@@ -365,6 +364,64 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
EXPECT_EQ(4, subnet->getValid());
}
// This test checks if it is possible to define a subnet with an
// interface defined.
TEST_F(Dhcp6ParserTest, subnetInterface) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
" \"interface\": \"eth0\","
" \"subnet\": \"2001:db8:1::/64\" } ],"
"\"valid-lifetime\": 4000 }";
cout << config << endl;
ElementPtr json = Element::fromJSON(config);
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_);
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
EXPECT_EQ("eth0", subnet->getIface());
}
// This test checks if it is not allowed to define global interface
// parameter.
TEST_F(Dhcp6ParserTest, interfaceGlobal) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"interface\": \"eth0\"," // 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 }";
cout << config << endl;
ElementPtr json = Element::fromJSON(config);
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_);
}
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
......@@ -389,7 +446,6 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
// as the pool does not belong to that subnet
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(1, rcode_);
}
......
......@@ -1294,6 +1294,120 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
RFCViolation);
}
// This test verifies if selectSubnet() selects proper subnet for a given
// source address.
TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
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));
// CASE 1: We have only one subnet defined and we received local traffic.
// The only available subnet should be selected
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
Subnet6Ptr selected = srv.selectSubnet(pkt);
EXPECT_EQ(selected, subnet1);
// CASE 2: We have only one subnet defined and we received relayed traffic.
// We should NOT select it.
// Identical steps as in case 1, but repeated for clarity
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
selected = srv.selectSubnet(pkt);
EXPECT_FALSE(selected);
// CASE 3: We have three subnets defined and we received local traffic.
// Nothing should be selected.
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1);
CfgMgr::instance().addSubnet6(subnet2);
CfgMgr::instance().addSubnet6(subnet3);
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
selected = srv.selectSubnet(pkt);
EXPECT_FALSE(selected);
// CASE 4: 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);
pkt->setRemoteAddr(IOAddress("2001:db8:2::baca"));
selected = srv.selectSubnet(pkt);
EXPECT_EQ(selected, subnet2);
// CASE 5: 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->setRemoteAddr(IOAddress("2001:db8:4::baca"));
selected = srv.selectSubnet(pkt);
EXPECT_FALSE(selected);
}
// This test verifies if selectSubnet() selects proper subnet for a given
// network interface name.
TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
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->setIface("eth0");
subnet3->setIface("wifi1");
// CASE 1: We have only one subnet defined and it is available via eth0.
// Packet came from eth0. The only available subnet should be selected
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
pkt->setIface("eth0");
Subnet6Ptr selected = srv.selectSubnet(pkt);
EXPECT_EQ(selected, subnet1);
// CASE 2: We have only one subnet defined and it is available via eth0.
// Packet came from eth1. We should not select it
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
pkt->setIface("eth1");
selected = srv.selectSubnet(pkt);
EXPECT_FALSE(selected);
// CASE 3: We have only 3 subnets defined, one over eth0, one remote and
// one over wifi1.
// Packet came from eth1. We should not select it
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet1);
CfgMgr::instance().addSubnet6(subnet2);
CfgMgr::instance().addSubnet6(subnet3);
pkt->setIface("eth0");
EXPECT_EQ(subnet1, srv.selectSubnet(pkt));
pkt->setIface("eth3"); // no such interface
EXPECT_EQ(Subnet6Ptr(), srv.selectSubnet(pkt)); // nothing selected
pkt->setIface("wifi1");
EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
}
/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
/// to call processX() methods.
......
......@@ -137,6 +137,26 @@ CfgMgr::getOptionDef(const std::string& option_space,
return (*range.first);
}
Subnet6Ptr
CfgMgr::getSubnet6(const std::string& iface) {
if (!iface.length()) {
return (Subnet6Ptr());
}
// If there is more than one, we need to choose the proper one
for (Subnet6Collection::iterator subnet = subnets6_.begin();
subnet != subnets6_.end(); ++subnet) {
if (iface == (*subnet)->getIface()) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
DHCPSRV_CFGMGR_SUBNET6_IFACE)
.arg((*subnet)->toText()).arg(iface);
return (*subnet);
}
}
return (Subnet6Ptr());
}
Subnet6Ptr
CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
......@@ -158,6 +178,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// If there is more than one, we need to choose the proper one
for (Subnet6Collection::iterator subnet = subnets6_.begin();
subnet != subnets6_.end(); ++subnet) {
if ((*subnet)->inRange(hint)) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
DHCPSRV_CFGMGR_SUBNET6)
......
......@@ -153,9 +153,18 @@ public:
///
/// @param hint an address that belongs to a searched subnet
///
/// @return a subnet object
/// @return a subnet object (or NULL if no suitable match was fount)
Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
/// @brief get IPv6 subnet by interface name
///
/// Finds a matching local subnet, based on interface name. This
/// is used for selecting subnets that were explicitly marked by the
/// user as reachable over specified network interface.
/// @param iface_name interface name
/// @return a subnet object (or NULL if no suitable match was fount)
Subnet6Ptr getSubnet6(const std::string& iface_name);
/// @brief get IPv6 subnet by interface-id
///
/// Another possibility to find a subnet is based on interface-id.
......
......@@ -60,6 +60,12 @@ This is a debug message reporting that the DHCP configuration manager has
returned the specified IPv6 subnet when given the address hint specified
as the address is within the subnet.
% DHCPSRV_CFGMGR_SUBNET6_IFACE selected subnet %1 for packet received over interface %2
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).
% DHCPSRV_INVALID_ACCESS invalid database access string: %1
This is logged when an attempt has been made to parse a database access string
and the attempt ended in error. The access string in question - which
......
......@@ -202,5 +202,15 @@ Subnet6::validateOption(const OptionPtr& option) const {
}
}
void Subnet6::setIface(const std::string& iface_name) {
iface_ = iface_name;
}
std::string Subnet6::getIface() {
return (iface_);
}
} // end of isc::dhcp namespace
} // end of isc namespace
......@@ -415,6 +415,9 @@ protected:
/// fully trusted.
isc::asiolink::IOAddress last_allocated_;
/// @brief Name of the network interface (if connected directly)
std::string iface_;
private:
/// Container holding options grouped by option space names.
......@@ -496,6 +499,17 @@ public:
return (preferred_);
}
/// @brief sets name of the network interface for directly attached networks
///
/// A subnet may be reachable directly (not via relays). In DHCPv6 it is not
/// possible to decide that based on addresses assigned to network interfaces,
/// as DHCPv6 operates on link-local (and site local) addresses.
void setIface(const std::string& iface_name);
/// @brief network interface name used to reach subnet (or "" for remote subnets)
/// @return network interface name for directly attached subnets or ""
std::string getIface();
protected:
/// @brief Check if option is valid and can be added to a subnet.
......
......@@ -351,4 +351,8 @@ TEST_F(CfgMgrTest, optionSpace6) {
// @todo decide if a duplicate vendor space is allowed.
}
// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
} // end of anonymous namespace
......@@ -505,4 +505,15 @@ TEST(Subnet6Test, get) {
EXPECT_EQ(32, subnet.get().second);
}
// This trivial test checks if interface name is stored properly
// in Subnet6 objects.
TEST(Subnet6Test, iface) {
Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
EXPECT_EQ("", subnet.getIface());
subnet.setIface("en1");
EXPECT_EQ("en1", subnet.getIface());
}
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment