Commit 5204b8af authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[master] Merge branch 'trac2596' (DHCPv6 subnet tied to interface)

Conflicts:
	ChangeLog
	src/bin/dhcp6/config_parser.cc
parents 79b57c7b a70f6172
248. [func] vorner
549. [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 a70f6172194a976b514cd7d67ce097bbca3c2798)
548. [func] vorner
The message queue daemon now appears on the bus. This has two
effects, one is it obeys logging configuration and logs to the
correct place like the rest of the modules. The other is it
......
......@@ -18,6 +18,7 @@
#include <dhcp/libdhcp++.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp/iface_mgr.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/pool.h>
......@@ -1400,6 +1401,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::const_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
......@@ -1416,6 +1426,17 @@ private:
subnet_->addPool(*it);
}
// Configure interface, if defined
if (!iface.empty()) {
if (!IfaceMgr::instance().getIface(iface)) {
isc_throw(DhcpConfigError, "Specified interface name " << iface
<< " for subnet " << subnet_->toText() << " is not present"
<< " in the system.");
}
subnet_->setIface(iface);
}
// 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
......@@ -1489,6 +1510,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()) {
......
......@@ -149,6 +149,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);
}
......
......@@ -17,6 +17,7 @@
#include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
#include <dhcp/iface_mgr.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcpsrv/cfgmgr.h>
......@@ -46,6 +47,23 @@ public:
// srv_(0) means to not open any sockets. We don't want to
// deal with sockets here, just check if configuration handling
// is sane.
const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
// There must be some interface detected
if (ifaces.empty()) {
// We can't use ASSERT in constructor
ADD_FAILURE() << "No interfaces detected.";
}
valid_iface_ = ifaces.begin()->getName();
bogus_iface_ = "nonexisting0";
if (IfaceMgr::instance().getIface(bogus_iface_)) {
ADD_FAILURE() << "The '" << bogus_iface_ << "' exists on this system"
<< " while the test assumes that it doesn't, to execute"
<< " some negative scenarios. Can't continue this test.";
}
}
~Dhcp6ParserTest() {
......@@ -261,6 +279,9 @@ public:
int rcode_;
ConstElementPtr comment_;
string valid_iface_;
string bogus_iface_;
};
// Goal of this test is a verification if a very simple config update
......@@ -294,9 +315,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;
......@@ -387,6 +407,99 @@ 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;
// There should be at least one interface
string config = "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
" \"interface\": \"" + valid_iface_ + "\","
" \"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(valid_iface_, subnet->getIface());
}
// This test checks if invalid interface name will be rejected in
// Subnet6 definition.
TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
ConstElementPtr status;
// There should be at least one interface
string config = "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
" \"interface\": \"" + bogus_iface_ + "\","
" \"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 (configuration error)
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(1, rcode_);
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
EXPECT_FALSE(subnet);
}
// 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\": \"" + valid_iface_ + "\"," // 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) {
......@@ -411,7 +524,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_);
}
......
......@@ -1296,6 +1296,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.
......
......@@ -122,6 +122,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) {
......@@ -143,6 +163,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)
......
......@@ -155,9 +155,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
......
......@@ -184,5 +184,15 @@ Subnet6::validateOption(const OptionPtr& option) const {
}
}
void Subnet6::setIface(const std::string& iface_name) {
iface_ = iface_name;
}
std::string Subnet6::getIface() const {
return (iface_);
}
} // end of isc::dhcp namespace
} // end of isc namespace
......@@ -416,6 +416,9 @@ protected:
/// fully trusted.
isc::asiolink::IOAddress last_allocated_;
/// @brief Name of the network interface (if connected directly)
std::string iface_;
private:
/// A collection of option spaces grouping option descriptors.
......@@ -496,6 +499,18 @@ 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.
/// @param iface_name name of the interface
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() const;
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_TRUE(subnet.getIface().empty());
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