]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5390] Made loopback usable for limited DHCP service
authorFrancis Dupont <fdupont@isc.org>
Thu, 18 Jan 2018 08:55:37 +0000 (09:55 +0100)
committerFrancis Dupont <fdupont@isc.org>
Thu, 18 Jan 2018 08:55:37 +0000 (09:55 +0100)
doc/guide/dhcp4-srv.xml
doc/guide/dhcp6-srv.xml
src/lib/dhcp/iface_mgr.cc
src/lib/dhcp/iface_mgr.h
src/lib/dhcp/tests/iface_mgr_unittest.cc
src/lib/dhcpsrv/cfg_iface.cc
src/lib/dhcpsrv/tests/cfg_iface_unittest.cc

index 1f7a1eaf371bc32cadc665607418367630ee93e2..9df4dd62a82885bea4601ce5d6190b4fe85c7778 100644 (file)
@@ -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">
index f0b96148c747ffc2205bd0652fcaa283d70df0db..3ff642e1d6e60f9bd82acedaedb685b45e50b313 100644 (file)
@@ -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">
index b0e872cf984e6f27fc1736397de21ccdfb2656c7..7d06326c0cf610952a369e3fa64b76cbb20ed34a 100644 (file)
@@ -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());
index 7aea6e43cd8238a2a285a13f8caecf3674b90b94..42b50376649236e285107efcfaf567e5776abcdc 100644 (file)
@@ -1,4 +1,4 @@
-// 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
index 7a49b403cdb989c6fce2b50a4aa1d7cfdaf5c64f..10f943336770561d4cf9cb814e0f0f1cdc81b270 100644 (file)
@@ -1,4 +1,4 @@
-// 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) {
index ebb64f2dc859a2973803ab8073879e52592fb144..540ccac0bff612fbf908a26652e76eb22c991913 100644 (file)
@@ -1,4 +1,4 @@
-// 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
index e0e921a1b0c401229f91956d707d06ebd2a78dda..2f30565987ff28ceac4fd52557cb9aaa8c1524a5 100644 (file)
@@ -1,4 +1,4 @@
-// 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;