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_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_type": "string",
-          "item_optional": false,
-          "item_default": "*"
+          "item_optional": true,
+          "item_default": ""
         }
         }
-      } ,
-
+        ]
+      },
+      
       { "item_name": "renew-timer",
       { "item_name": "renew-timer",
         "item_type": "integer",
         "item_type": "integer",
         "item_optional": true,
         "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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,7 +26,7 @@ namespace dhcp {
 const char* CfgIface::ALL_IFACES_KEYWORD = "*";
 const char* CfgIface::ALL_IFACES_KEYWORD = "*";
 
 
 CfgIface::CfgIface()
 CfgIface::CfgIface()
-    : wildcard_used_(false) {
+    : wildcard_used_(false), socket_type_(SOCKET_DEFAULT) {
 }
 }
 
 
 void
 void
@@ -38,7 +38,8 @@ bool
 CfgIface::equals(const CfgIface& other) const {
 CfgIface::equals(const CfgIface& other) const {
     return (iface_set_ == other.iface_set_ &&
     return (iface_set_ == other.iface_set_ &&
             address_map_ == other.address_map_ &&
             address_map_ == other.address_map_ &&
-            wildcard_used_ == other.wildcard_used_);
+            wildcard_used_ == other.wildcard_used_ &&
+            socket_type_ == other.socket_type_);
 }
 }
 
 
 void
 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
     // specified, mark all interfaces active. In all cases, mark loopback
     // inactive.
     // inactive.
     setState(family, !wildcard_used_, true);
     setState(family, !wildcard_used_, true);
+    IfaceMgr& iface_mgr = IfaceMgr::instance();
     // Remove selection of unicast addresses from all interfaces.
     // 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
     // If there is no wildcard interface specified, we will have to iterate
     // over the names specified by the caller and enable them.
     // over the names specified by the caller and enable them.
     if (!wildcard_used_) {
     if (!wildcard_used_) {
@@ -136,6 +151,7 @@ CfgIface::reset() {
     wildcard_used_ = false;
     wildcard_used_ = false;
     iface_set_.clear();
     iface_set_.clear();
     address_map_.clear();
     address_map_.clear();
+    socket_type_ = SOCKET_DEFAULT;
 }
 }
 
 
 void
 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::dhcp namespace
 } // end of isc 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -51,6 +51,14 @@ public:
         isc::Exception(file, line, what) { };
         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.
 /// @brief Represents selection of interfaces for DHCP server.
 ///
 ///
 /// This class manages selection of interfaces on which the DHCP server is
 /// 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.
 /// instructs the server to listen on all available interfaces.
 ///
 ///
 /// Once interfaces have been specified the sockets (either IPv4 or IPv6)
 /// 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,
 /// @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
 /// 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.
 /// guarantee that the address family value is correct.
 class CfgIface {
 class CfgIface {
 public:
 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.
     /// @brief Keyword used to enable all interfaces.
     ///
     ///
     /// This keyword can be used instead of the interface name to specify
     /// 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.
     /// @c CfgIface::use has been already called for this interface.
     void use(const uint16_t family, const std::string& iface_name);
     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.
     /// @brief Equality operator.
     ///
     ///
     /// @param other Object to be compared with this object.
     /// @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
     /// @brief A booolean value which indicates that the wildcard interface name
     /// has been specified (*).
     /// has been specified (*).
     bool wildcard_used_;
     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
 returned the specified IPv6 subnet when given the address hint specified
 because it is the only subnet defined.
 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
 % DHCPSRV_CFGMGR_SUBNET4 retrieved subnet %1 for address hint %2
 This is a debug message reporting that the DHCP configuration manager has
 This is a debug message reporting that the DHCP configuration manager has
 returned the specified IPv4 subnet when given the address hint specified
 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()) {
     BOOST_FOREACH(ConfigPair element, ifaces_config->mapValue()) {
         try {
         try {
             if (element.first == "socket-type") {
             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)) {
             } else if (!isGenericParameter(element.first)) {
                 isc_throw(DhcpConfigError, "usupported parameter '"
                 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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_FALSE(socketOpen("eth0", AF_INET6));
     EXPECT_TRUE(socketOpen("eth1", AF_INET6));
     EXPECT_TRUE(socketOpen("eth1", AF_INET6));
     EXPECT_FALSE(socketOpen("lo", AF_INET6));
     EXPECT_FALSE(socketOpen("lo", AF_INET6));
-
 }
 }
 
 
 // This test checks that the wildcard interface name can be specified to
 // This test checks that the wildcard interface name can be specified to
@@ -338,6 +337,54 @@ TEST_F(CfgIfaceTest, equality) {
     cfg2.use(AF_INET, "*");
     cfg2.use(AF_INET, "*");
     EXPECT_TRUE(cfg1 == cfg2);
     EXPECT_TRUE(cfg1 == cfg2);
     EXPECT_FALSE(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
 } // 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);
     IfaceMgrTestConfig test_config(true);
 
 
     // Configuration with one interface.
     // Configuration with one interface.
-    std::string config = "{"
-        "\"interfaces\": [ \"eth0\" ],"
-        "\"socket-type\": \"raw\" }";
+    std::string config = "{ ""\"interfaces\": [ \"eth0\" ] }";
 
 
     ElementPtr config_element = Element::fromJSON(config);
     ElementPtr config_element = Element::fromJSON(config);
 
 
@@ -84,10 +82,7 @@ TEST_F(IfacesConfigParserTest, interfaces) {
 
 
     // Try similar configuration but this time add a wildcard interface
     // Try similar configuration but this time add a wildcard interface
     // to see if sockets will open on all interfaces.
     // to see if sockets will open on all interfaces.
-    config = "{"
-        "\"interfaces\": [ \"eth0\", \"*\" ],"
-        "\"socket-type\": \"raw\" }";
-
+    config = "{ \"interfaces\": [ \"eth0\", \"*\" ] }";
     config_element = Element::fromJSON(config);
     config_element = Element::fromJSON(config);
 
 
     ASSERT_NO_THROW(parser.build(config_element));
     ASSERT_NO_THROW(parser.build(config_element));
@@ -99,5 +94,71 @@ TEST_F(IfacesConfigParserTest, interfaces) {
     EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));
     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
 } // end of anonymous namespace