Commit 620c1351 authored by Francis Dupont's avatar Francis Dupont
Browse files

[5390] Made loopback usable for limited DHCP service

parent 67ff2b50
......@@ -772,6 +772,24 @@ temporarily override a list of interface names and listen on all interfaces.
Note interfaces are not re-detected during <command>config-test</command>.
</para>
<para>Usually loopback interfaces (e.g. the "lo" or "lo0" interface)
may not configured but if only one interface is configured and
IP/UDP sockets are specified a loopback interface is accepted.
</para>
<para>It can be used for instance to run Kea in a FreeBSD jail having
only a loopback interface, servicing relayed DHCP request:
<screen>
"Dhcp4": {
"interfaces-config": {
"interfaces": [ <userinput>"lo0"</userinput> ],
"dhcp-socket-type": "udp"
},
...
}</screen>
</para>
</section>
<section id="dhcpinform-unicast-issues">
......
......@@ -654,6 +654,21 @@ temporarily override a list of interface names and listen on all interfaces.
}
</screen>
<para>Usually loopback interfaces (e.g. the "lo" or "lo0" interface)
may not configured but if only one interface is configured a loopback
interface is accepted. Note Kea requires link-local address which does
not exist on all systems, or a specified unicast address as in:
</para>
<screen>
"Dhcp6": {
"interfaces-config": {
"interfaces": [ <userinput>"lo/::1"</userinput> ]
},
...
}
</screen>
</section>
<section id="ipv6-subnet-id">
......
......@@ -172,7 +172,8 @@ IfaceMgr::IfaceMgr()
control_buf_(new char[control_buf_len_]),
packet_filter_(new PktFilterInet()),
packet_filter6_(new PktFilterInet6()),
test_mode_(false)
test_mode_(false),
allow_loopback_(false)
{
try {
......@@ -465,7 +466,9 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
// that the interface configuration is valid and that the interface
// is not a loopback interface. In both cases, we want to report
// that the socket will not be opened.
if (iface->flag_loopback_) {
// Relax the check when the loopback interface was explicitely
// allowed
if (iface->flag_loopback_ && !allow_loopback_) {
IFACEMGR_ERROR(SocketConfigError, error_handler,
"must not open socket on the loopback"
" interface " << iface->getName());
......@@ -570,7 +573,9 @@ IfaceMgr::openSockets6(const uint16_t port,
// that the interface configuration is valid and that the interface
// is not a loopback interface. In both cases, we want to report
// that the socket will not be opened.
if (iface->flag_loopback_) {
// Relax the check when the loopback interface was explicitely
// allowed
if (iface->flag_loopback_ && !allow_loopback_) {
IFACEMGR_ERROR(SocketConfigError, error_handler,
"must not open socket on the loopback"
" interface " << iface->getName());
......
// Copyright (C) 2011-2015,2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -595,6 +595,15 @@ public:
return (test_mode_);
}
/// @brief Allows or disallows the loopback interface
///
/// By default the loopback interface is not considered when opening
/// sockets. This flag provides a way to relax this constraint.
///
void setAllowLoopBack(const bool allow_loopback) {
allow_loopback_ = allow_loopback;
}
/// @brief Check if packet be sent directly to the client having no address.
///
/// Checks if IfaceMgr can send DHCPv4 packet to the client
......@@ -838,8 +847,8 @@ public:
///
/// This method opens sockets only on interfaces which have the
/// @c inactive6_ field set to false (are active). If the interface is active
/// but it is not running, it is down, or is a loopback interface,
/// an error is reported.
/// but it is not running, it is down, or is a loopback interface when
/// loopback is not allowed, an error is reported.
///
/// On the systems with multiple interfaces, it is often desired that the
/// failure to open a socket on a particular interface doesn't cause a
......@@ -883,8 +892,8 @@ public:
///
/// This method opens sockets only on interfaces which have the
/// @c inactive4_ field set to false (are active). If the interface is active
/// but it is not running, it is down, or is a loopback interface,
/// an error is reported.
/// but it is not running, it is down, or is a loopback interface when
/// oopback is not allowed, an error is reported.
///
/// The type of the socket being open depends on the selected Packet Filter
/// represented by a class derived from @c isc::dhcp::PktFilter abstract
......@@ -1217,6 +1226,9 @@ private:
/// @brief Indicates if the IfaceMgr is in the test mode.
bool test_mode_;
/// @brief Allows to use loopback
bool allow_loopback_;
};
}; // namespace isc::dhcp
......
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -1480,6 +1480,35 @@ TEST_F(IfaceMgrTest, openSockets4) {
EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
}
// This test verifies that IPv4 sockets are open on the loopback interface
// when the loopback is active and allowed.
TEST_F(IfaceMgrTest, openSockets4Loopback) {
NakedIfaceMgr ifacemgr;
// Remove all real interfaces and create a set of dummy interfaces.
ifacemgr.createIfaces();
// Allow the loopback interface.
ifacemgr.setAllowLoopBack(true);
// Make the loopback interface active.
ifacemgr.getIface("lo")->inactive4_ = false;
// Use the custom packet filter object. This object mimics the socket
// opening operation - the real socket is not open.
boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
ASSERT_TRUE(custom_packet_filter);
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
// Simulate opening sockets using the dummy packet filter.
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0));
// Expect that the sockets are open on all interfaces.
EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
EXPECT_EQ(1, ifacemgr.getIface("lo")->getSockets().size());
}
// This test verifies that the socket is not open on the interface which is
// down, but sockets are open on all other non-loopback interfaces.
TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
......@@ -1688,6 +1717,40 @@ TEST_F(IfaceMgrTest, openSockets6LinkLocal) {
#endif
}
// This test checks that the sockets are open on the loopback interface
// when the loopback is active and allowed.
TEST_F(IfaceMgrTest, openSockets6Loopback) {
NakedIfaceMgr ifacemgr;
// Remove all real interfaces and create a set of dummy interfaces.
ifacemgr.createIfaces();
// Allow the loopback interface.
ifacemgr.setAllowLoopBack(true);
// Make the loopback interface active.
ifacemgr.getIface("lo")->inactive6_ = false;
// The loopback interface has no link-local (as for Linux but not BSD)
// so add one.
ifacemgr.getIface("lo")->addUnicast(IOAddress("::1"));
boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
ASSERT_TRUE(filter);
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
// Simulate opening sockets using the dummy packet filter.
bool success = false;
ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
EXPECT_TRUE(success);
// Check that the loopback interface has at least an open socket.
EXPECT_EQ(1, ifacemgr.getIface("lo")->getSockets().size());
// This socket should be bound to ::1
EXPECT_TRUE(ifacemgr.isBound("lo", "::1"));
}
// This test checks that socket is not open on the interface which doesn't
// have a link-local address.
TEST_F(IfaceMgrTest, openSockets6NoLinkLocal) {
......
// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -56,15 +56,55 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
// Close any open sockets because we're going to modify some properties
// of the IfaceMgr. Those modifications require that sockets are closed.
closeSockets();
// The loopback interface can be used only when:
// - wildcard is not used
// - UDP socket will be used, i.e. not IPv4 and RAW socket
// - there is one interface name only in the interface set
// and this interface is a loopback interface.
// - or the interface set is empty and all interfaces in the address
// map are the same and a loopback interface.
bool loopback_used_ = false;
if (!wildcard_used_ &&
((family == AF_INET6) || (socket_type_ == SOCKET_UDP)) &&
(iface_set_.size() == 1) &&
(address_map_.empty())) {
// Get the first and only interface.
IfacePtr iface = IfaceMgr::instance().getIface(*iface_set_.begin());
if (iface && iface->flag_loopback_) {
loopback_used_ = true;
}
} else if (!wildcard_used_ &&
((family == AF_INET6) || (socket_type_ == SOCKET_UDP)) &&
iface_set_.empty() &&
!address_map_.empty()) {
// Get the first interface
const std::string& name = address_map_.begin()->first;
bool same = true;
for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
unicast != address_map_.end(); ++unicast) {
if (unicast->first != name) {
same = false;
break;
}
}
if (same) {
IfacePtr iface = IfaceMgr::instance().getIface(name);
if (iface && iface->flag_loopback_) {
loopback_used_ = true;
}
}
}
// If wildcard interface '*' was not specified, set all interfaces to
// inactive state. We will later enable them selectively using the
// interface names specified by the user. If wildcard interface was
// specified, mark all interfaces active. In all cases, mark loopback
// inactive.
setState(family, !wildcard_used_, true);
// specified, mark all interfaces active. Mark loopback inactive when
// not explicitely allowed.
setState(family, !wildcard_used_, loopback_used_);
IfaceMgr& iface_mgr = IfaceMgr::instance();
// Remove selection of unicast addresses from all interfaces.
iface_mgr.clearUnicasts();
// Allow the loopback interface when required.
iface_mgr.setAllowLoopBack(loopback_used_);
// For the DHCPv4 server, if the user has selected that raw sockets
// should be used, we will try to configure the Interface Manager to
// support the direct responses to the clients that don't have the
......
// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -184,6 +184,60 @@ TEST_F(CfgIfaceTest, multipleAddressesSameInterfaceV4) {
EXPECT_TRUE(socketOpen("eth1", "192.0.2.5"));
}
// This test checks that it is possible to specify the loopback interface.
TEST_F(CfgIfaceTest, explicitLoopbackV4) {
CfgIface cfg;
ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
// Use UDP sockets
ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
// Open sockets on specified interfaces and addresses.
cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
EXPECT_TRUE(socketOpen("lo", "127.0.0.1"));
// Close all sockets and make sure they are really closed.
cfg.closeSockets();
ASSERT_FALSE(socketOpen("lo", "127.0.0.1"));
// Reset configuration.
cfg.reset();
// Retry with wirdcard
ASSERT_NO_THROW(cfg.use(AF_INET, "*"));
ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
// No loopback socket
EXPECT_FALSE(socketOpen("lo", "127.0.0.1"));
// Retry without UDP sockets
cfg.reset();
ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
// No loopback socket
EXPECT_FALSE(socketOpen("lo", "127.0.0.1"));
// Retry with a second interface
cfg.reset();
ASSERT_NO_THROW(cfg.use(AF_INET, "eth0"));
ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
// No loopback socket
EXPECT_FALSE(socketOpen("lo", "127.0.0.1"));
// Finally with a second interface and address
cfg.reset();
ASSERT_NO_THROW(cfg.use(AF_INET, "eth0/10.0.0.1"));
ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
// No loopback socket
EXPECT_FALSE(socketOpen("lo", "127.0.0.1"));
}
// This test checks that the interface names can be explicitly selected
// by their names and IPv6 sockets are opened on these interfaces.
TEST_F(CfgIfaceTest, explicitNamesV6) {
......@@ -299,6 +353,48 @@ TEST_F(CfgIfaceTest, invalidValues) {
ASSERT_THROW(cfg.use(AF_INET6, "*"), DuplicateIfaceName);
}
// This test checks that it is possible to specify the loopback interface.
// Note that without a link-local address an unicast address is required.
TEST_F(CfgIfaceTest, explicitLoopbackV6) {
CfgIface cfg;
ASSERT_NO_THROW(cfg.use(AF_INET6, "lo/::1"));
// Open sockets on specified interfaces and addresses.
cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
EXPECT_TRUE(socketOpen("lo", AF_INET6));
// Close all sockets and make sure they are really closed.
cfg.closeSockets();
ASSERT_FALSE(socketOpen("lo", AF_INET6));
// Reset configuration.
cfg.reset();
// Retry with wirdcard
ASSERT_NO_THROW(cfg.use(AF_INET6, "*"));
ASSERT_NO_THROW(cfg.use(AF_INET6, "lo/::1"));
cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
// No loopback socket
EXPECT_FALSE(socketOpen("lo", AF_INET6));
// Retry with a second interface
cfg.reset();
ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0"));
ASSERT_NO_THROW(cfg.use(AF_INET6, "lo/::1"));
cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
// No loopback socket
EXPECT_FALSE(socketOpen("lo", AF_INET6));
// Finally with a second interface and address
cfg.reset();
ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0/2001:db8:1::1"));
ASSERT_NO_THROW(cfg.use(AF_INET6, "lo/::1"));
cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
// No loopback socket
EXPECT_FALSE(socketOpen("lo", AF_INET6));
}
// Test that the equality and inequality operators work fine for CfgIface.
TEST_F(CfgIfaceTest, equality) {
CfgIface cfg1;
......
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