Browse Source

[3604] Allow for configuration of the socket type for DHCPv4.

Marcin Siodelski 10 years ago
parent
commit
543c155050

+ 23 - 9
src/bin/dhcp4/dhcp4.spec

@@ -17,19 +17,33 @@
         }
       },
 
-      { "item_name": "interfaces",
-        "item_type": "list",
+      { "item_name": "interfaces-config",
+        "item_type": "map",
         "item_optional": false,
-        "item_default": [ "*" ],
-        "list_item_spec":
+        "item_default": {},
+        "map_item_spec": [
         {
-          "item_name": "interface_name",
+            "item_name": "interfaces",
+            "item_type": "list",
+            "item_optional": false,
+            "item_default": [ "*" ],
+            "list_item_spec":
+            {
+                "item_name": "interface_name",
+                "item_type": "string",
+                "item_optional": false,
+                "item_default": "*"
+            }
+        },
+
+        { "item_name": "socket-type",
           "item_type": "string",
-          "item_optional": false,
-          "item_default": "*"
+          "item_optional": true,
+          "item_default": ""
         }
-      } ,
-
+        ]
+      },
+      
       { "item_name": "renew-timer",
         "item_type": "integer",
         "item_optional": true,

+ 51 - 4
src/lib/dhcpsrv/cfg_iface.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,7 +26,7 @@ namespace dhcp {
 const char* CfgIface::ALL_IFACES_KEYWORD = "*";
 
 CfgIface::CfgIface()
-    : wildcard_used_(false) {
+    : wildcard_used_(false), socket_type_(SOCKET_DEFAULT) {
 }
 
 void
@@ -38,7 +38,8 @@ bool
 CfgIface::equals(const CfgIface& other) const {
     return (iface_set_ == other.iface_set_ &&
             address_map_ == other.address_map_ &&
-            wildcard_used_ == other.wildcard_used_);
+            wildcard_used_ == other.wildcard_used_ &&
+            socket_type_ == other.socket_type_);
 }
 
 void
@@ -50,8 +51,22 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
     // specified, mark all interfaces active. In all cases, mark loopback
     // inactive.
     setState(family, !wildcard_used_, true);
+    IfaceMgr& iface_mgr = IfaceMgr::instance();
     // Remove selection of unicast addresses from all interfaces.
-    IfaceMgr::instance().clearUnicasts();
+    iface_mgr.clearUnicasts();
+    // 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
+    // IP address. This should effectively turn on the use of raw
+    // sockets. However, this may be unsupported on some operating
+    // systems, so there is no guarantee.
+    if ((family == AF_INET) && (socket_type_ != SOCKET_DEFAULT)) {
+        iface_mgr.setMatchingPacketFilter(socket_type_ == SOCKET_RAW);
+        if ((socket_type_ == SOCKET_RAW) &&
+            !iface_mgr.isDirectResponseSupported()) {
+            LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED);
+        }
+    }
     // If there is no wildcard interface specified, we will have to iterate
     // over the names specified by the caller and enable them.
     if (!wildcard_used_) {
@@ -136,6 +151,7 @@ CfgIface::reset() {
     wildcard_used_ = false;
     iface_set_.clear();
     address_map_.clear();
+    socket_type_ = SOCKET_DEFAULT;
 }
 
 void
@@ -316,5 +332,36 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
     }
 }
 
+void
+CfgIface::useSocketType(const uint16_t family,
+                        const SocketType& socket_type) {
+    if (family != AF_INET) {
+        isc_throw(InvalidSocketType, "socket type must not be specified for"
+                  " the DHCPv6 server");
+    } else if (socket_type == SOCKET_DEFAULT) {
+            isc_throw(InvalidSocketType, "invalid value SOCKET_DEFAULT"
+                      " used to specify the socket type to be used by"
+                      " the DHCPv4 server");
+    }
+    socket_type_ = socket_type;
+}
+
+void
+CfgIface::useSocketType(const uint16_t family,
+                        const std::string& socket_type_name) {
+    SocketType socket_type;
+    if (socket_type_name == "datagram") {
+        socket_type = SOCKET_DGRAM;
+
+    } else if (socket_type_name == "raw") {
+        socket_type = SOCKET_RAW;
+
+    } else {
+        isc_throw(InvalidSocketType, "unsupported socket type '"
+                  << socket_type_name << "'");
+    }
+    useSocketType(family, socket_type);
+}
+
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 65 - 2
src/lib/dhcpsrv/cfg_iface.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -51,6 +51,14 @@ public:
         isc::Exception(file, line, what) { };
 };
 
+/// @brief Exception thrown when invalid socket type has been specified
+/// for the given family.
+class InvalidSocketType : public Exception {
+public:
+    InvalidSocketType(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 /// @brief Represents selection of interfaces for DHCP server.
 ///
 /// This class manages selection of interfaces on which the DHCP server is
@@ -63,7 +71,22 @@ public:
 /// instructs the server to listen on all available interfaces.
 ///
 /// Once interfaces have been specified the sockets (either IPv4 or IPv6)
-/// can be opened by calling @c CfgIface::openSockets function.
+/// can be opened by calling @c CfgIface::openSockets function. Kea
+/// offers configuration parameters to control the types of sockets to be
+/// opened by the DHCPv4 server. In small deployments it is requires that
+/// the server can handle messages from the directly connected clients
+/// which don't have an address yet. Unicasting the response to such
+/// client is possible by the use of raw sockets. In larger deployments
+/// it is often the case that whole traffic is received via relays, and
+/// in such case the use of datagram sockets is preferred. The type of the
+/// sockets to be opened is specified using one of the
+/// @c CfgIface::useSocketType method variants. The @c CfgIface::SocketType
+/// enumeration specifies the possible values. The @c CfgIface::SOCKET_DEFAULT
+/// is a default setting of the @c CfgIface and it indicates that the
+/// @c IfaceMgr should continue using the currently used sockets' type.
+/// This is mostly used for unit testing to avoid modifying fake
+/// configurations of the @c IfaceMgr. In the real case, one of the
+/// remaining values should be used.
 ///
 /// @warning This class makes use of the AF_INET and AF_INET6 family literals,
 /// but it doesn't verify that the address family value passed as @c uint16_t
@@ -71,6 +94,17 @@ public:
 /// guarantee that the address family value is correct.
 class CfgIface {
 public:
+
+    /// @brief Socket type used by the DHCPv4 server.
+    enum SocketType  {
+        /// Default socket type, mainly used for testing.
+        SOCKET_DEFAULT,
+        /// Raw socket, used for direct DHCPv4 traffic.
+        SOCKET_RAW,
+        /// Datagram socket, i.e. IP/UDP socket.
+        SOCKET_DGRAM
+    };
+
     /// @brief Keyword used to enable all interfaces.
     ///
     /// This keyword can be used instead of the interface name to specify
@@ -143,6 +177,32 @@ public:
     /// @c CfgIface::use has been already called for this interface.
     void use(const uint16_t family, const std::string& iface_name);
 
+    /// @brief Sets the specified socket type to be used by the server.
+    ///
+    /// @param family Address family (AF_INET or AF_INET6).
+    /// @param socket_type Socket type.
+    ///
+    /// @throw InvalidSocketType if the unsupported socket type has been
+    /// specified for the address family. Currently, the socket type
+    /// can only be selected for the AF_INET family.
+    void useSocketType(const uint16_t family, const SocketType& socket_type);
+
+    /// @brief Sets the specified socket type specified in textual format.
+    ///
+    /// The following names of the socket types are currently supported, and
+    /// can be passed in the @c socket_type parameter:
+    /// - raw - for raw sockets,
+    /// - datagram - for the datagram sockets,
+    ///
+    /// @param family Address family (AF_INET or AF_INET6)
+    /// @param socket_type_name Socket type in the textual format.
+    ///
+    /// @throw InvalidSocketType if the unsupported socket type has been
+    /// specified for the address family. Currently, the socket type
+    /// can only be selected for the AF_INET family.
+    void useSocketType(const uint16_t family,
+                       const std::string& socket_type_name);
+
     /// @brief Equality operator.
     ///
     /// @param other Object to be compared with this object.
@@ -204,6 +264,9 @@ private:
     /// @brief A booolean value which indicates that the wildcard interface name
     /// has been specified (*).
     bool wildcard_used_;
+
+    /// @brief A type of the sockets used by the DHCP server.
+    SocketType socket_type_;
 };
 
 }

+ 12 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -99,6 +99,18 @@ This is a debug message reporting that the DHCP configuration manager has
 returned the specified IPv6 subnet when given the address hint specified
 because it is the only subnet defined.
 
+% DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED use of raw sockets is unsupported on this OS, datagram sockets will be used
+This warning message is logged when the user specified that the
+DHCPv4 server should use the raw sockets to receive the DHCP
+messages and respond to the clients, but the use of raw sockets
+is not supported on the particular environment. The raw sockets
+are useful when the server must respond to the directly connected
+clients which don't have an address yet. If the raw sockets are
+not supported by Kea on the particular platform, Kea will fall
+back to use of the datagram IP/UDP sockets. The responses to
+the directly connected clients will be broadcast. The responses
+to relayed clients will be unicast as usual.
+
 % DHCPSRV_CFGMGR_SUBNET4 retrieved subnet %1 for address hint %2
 This is a debug message reporting that the DHCP configuration manager has
 returned the specified IPv4 subnet when given the address hint specified

+ 3 - 1
src/lib/dhcpsrv/parsers/ifaces_config_parser.cc

@@ -89,7 +89,9 @@ IfacesConfigParser4::build(isc::data::ConstElementPtr ifaces_config) {
     BOOST_FOREACH(ConfigPair element, ifaces_config->mapValue()) {
         try {
             if (element.first == "socket-type") {
-                /// @todo set socket-type
+                CfgIface cfg = CfgMgr::instance().getStagingCfg()->getCfgIface();
+                cfg.useSocketType(AF_INET, element.second->stringValue());
+                CfgMgr::instance().getStagingCfg()->setCfgIface(cfg);
 
             } else if (!isGenericParameter(element.first)) {
                 isc_throw(DhcpConfigError, "usupported parameter '"

+ 49 - 2
src/lib/dhcpsrv/tests/cfg_iface_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -209,7 +209,6 @@ TEST_F(CfgIfaceTest, explicitNamesV6) {
     EXPECT_FALSE(socketOpen("eth0", AF_INET6));
     EXPECT_TRUE(socketOpen("eth1", AF_INET6));
     EXPECT_FALSE(socketOpen("lo", AF_INET6));
-
 }
 
 // This test checks that the wildcard interface name can be specified to
@@ -338,6 +337,54 @@ TEST_F(CfgIfaceTest, equality) {
     cfg2.use(AF_INET, "*");
     EXPECT_TRUE(cfg1 == cfg2);
     EXPECT_FALSE(cfg1 != cfg2);
+
+    // Differ by socket type.
+    cfg1.useSocketType(AF_INET, "raw");
+    EXPECT_FALSE(cfg1 == cfg2);
+    EXPECT_TRUE(cfg1 != cfg2);
+
+    // Now, both should use the same socket type.
+    cfg2.useSocketType(AF_INET, "raw");
+    EXPECT_TRUE(cfg1 == cfg2);
+    EXPECT_FALSE(cfg1 != cfg2);
 }
 
+// This test verifies that it is possible to specify the socket
+// type to be used by the DHCPv4 server.
+// This test is enabled on LINUX and BSD only, because the
+// direct traffic is only supported on those systems.
+#if defined OS_LINUX || defined OS_BSD
+TEST(CfgIfaceNoStubTest, useSocketType) {
+    CfgIface cfg;
+    // Select datagram sockets.
+    ASSERT_NO_THROW(cfg.useSocketType(AF_INET, "datagram"));
+    ASSERT_NO_THROW(cfg.openSockets(AF_INET, 10067, true));
+    // For datagram sockets, the direct traffic is not supported.
+    ASSERT_TRUE(!IfaceMgr::instance().isDirectResponseSupported());
+
+    // Select raw sockets.
+    ASSERT_NO_THROW(cfg.useSocketType(AF_INET, "raw"));
+    ASSERT_NO_THROW(cfg.openSockets(AF_INET, 10067, true));
+    // For raw sockets, the direct traffic is supported.
+    ASSERT_TRUE(IfaceMgr::instance().isDirectResponseSupported());
+
+    ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_DGRAM));
+    ASSERT_NO_THROW(cfg.openSockets(AF_INET, 10067, true));
+    ASSERT_TRUE(!IfaceMgr::instance().isDirectResponseSupported());
+
+    ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_RAW));
+    ASSERT_NO_THROW(cfg.openSockets(AF_INET, 10067, true));
+    ASSERT_TRUE(IfaceMgr::instance().isDirectResponseSupported());
+
+    // Test invalid values.
+    EXPECT_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_DEFAULT),
+        InvalidSocketType);
+    EXPECT_THROW(cfg.useSocketType(AF_INET, "default"),
+        InvalidSocketType);
+    EXPECT_THROW(cfg.useSocketType(AF_INET6, "datagram"),
+        InvalidSocketType);
+}
+#endif
+
+
 } // end of anonymous namespace

+ 68 - 7
src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc

@@ -59,9 +59,7 @@ TEST_F(IfacesConfigParserTest, interfaces) {
     IfaceMgrTestConfig test_config(true);
 
     // Configuration with one interface.
-    std::string config = "{"
-        "\"interfaces\": [ \"eth0\" ],"
-        "\"socket-type\": \"raw\" }";
+    std::string config = "{ ""\"interfaces\": [ \"eth0\" ] }";
 
     ElementPtr config_element = Element::fromJSON(config);
 
@@ -84,10 +82,7 @@ TEST_F(IfacesConfigParserTest, interfaces) {
 
     // Try similar configuration but this time add a wildcard interface
     // to see if sockets will open on all interfaces.
-    config = "{"
-        "\"interfaces\": [ \"eth0\", \"*\" ],"
-        "\"socket-type\": \"raw\" }";
-
+    config = "{ \"interfaces\": [ \"eth0\", \"*\" ] }";
     config_element = Element::fromJSON(config);
 
     ASSERT_NO_THROW(parser.build(config_element));
@@ -99,5 +94,71 @@ TEST_F(IfacesConfigParserTest, interfaces) {
     EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));
 }
 
+// This test verifies that it is possible to select the raw socket
+// use in the configuration for interfaces.
+TEST_F(IfacesConfigParserTest, socketTypeRaw) {
+    // Create the reference configuration, which we will compare
+    // the parsed configuration to.
+    CfgIface cfg_ref;
+
+    // Configuration with a raw socket selected.
+    std::string config = "{ ""\"interfaces\": [ ],"
+        " \"socket-type\": \"raw\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    // Parse the configuration.
+    IfacesConfigParser4 parser;
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    // Compare the resulting configuration with a reference
+    // configuration using the raw socket.
+    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+    ASSERT_TRUE(cfg);
+    cfg_ref.useSocketType(AF_INET, CfgIface::SOCKET_RAW);
+    EXPECT_TRUE(cfg->getCfgIface() == cfg_ref);
+}
+
+// This test verifies that it is possible to select the datagram socket
+// use in the configuration for interfaces.
+TEST_F(IfacesConfigParserTest, socketTypeDatagram) {
+    // Create the reference configuration, which we will compare
+    // the parsed configuration to.
+    CfgIface cfg_ref;
+
+    // Configuration with a datagram socket selected.
+    std::string config = "{ ""\"interfaces\": [ ],"
+        " \"socket-type\": \"datagram\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    // Parse the configuration.
+    IfacesConfigParser4 parser;
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    // Compare the resulting configuration with a reference
+    // configuration using the raw socket.
+    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+    ASSERT_TRUE(cfg);
+    cfg_ref.useSocketType(AF_INET, CfgIface::SOCKET_DGRAM);
+    EXPECT_TRUE(cfg->getCfgIface() == cfg_ref);
+}
+
+// Test that the configuration rejects the invalid socket type.
+TEST_F(IfacesConfigParserTest, socketTypeInvalid) {
+    // For DHCPv4 we only accept the raw socket or datagram socket.
+    IfacesConfigParser4 parser4;
+    std::string config = "{ \"interfaces\": [ ],"
+        "\"socket-type\": \"default\" }";
+    ElementPtr config_element = Element::fromJSON(config);
+    ASSERT_THROW(parser4.build(config_element), DhcpConfigError);
+
+    // For DHCPv6 we don't accept any socket type.
+    IfacesConfigParser6 parser6;
+    config = "{ \"interfaces\": [ ],"
+        " \"socket-type\": \"datagram\" }";
+    config_element = Element::fromJSON(config);
+    ASSERT_THROW(parser6.build(config_element), DhcpConfigError);
+}
 
 } // end of anonymous namespace