diff options
author | Marcin Siodelski <marcin@isc.org> | 2015-02-09 21:09:22 +0100 |
---|---|---|
committer | Marcin Siodelski <marcin@isc.org> | 2015-02-09 21:09:22 +0100 |
commit | 543c155050c88e0607e96b6a52867bd71b2dd66a (patch) | |
tree | d7f438f49b1c959e1366b1097d18a23cea1a8dab | |
parent | [3604] Use new interface configuration format where applicable. (diff) | |
download | kea-543c155050c88e0607e96b6a52867bd71b2dd66a.tar.xz kea-543c155050c88e0607e96b6a52867bd71b2dd66a.zip |
[3604] Allow for configuration of the socket type for DHCPv4.
-rw-r--r-- | src/bin/dhcp4/dhcp4.spec | 32 | ||||
-rw-r--r-- | src/lib/dhcpsrv/cfg_iface.cc | 55 | ||||
-rw-r--r-- | src/lib/dhcpsrv/cfg_iface.h | 67 | ||||
-rw-r--r-- | src/lib/dhcpsrv/dhcpsrv_messages.mes | 12 | ||||
-rw-r--r-- | src/lib/dhcpsrv/parsers/ifaces_config_parser.cc | 4 | ||||
-rw-r--r-- | src/lib/dhcpsrv/tests/cfg_iface_unittest.cc | 51 | ||||
-rw-r--r-- | src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc | 75 |
7 files changed, 271 insertions, 25 deletions
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec index 8c99576a87..dc60e82322 100644 --- a/src/bin/dhcp4/dhcp4.spec +++ b/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, diff --git a/src/lib/dhcpsrv/cfg_iface.cc b/src/lib/dhcpsrv/cfg_iface.cc index 3c29ee4437..b90289d0da 100644 --- a/src/lib/dhcpsrv/cfg_iface.cc +++ b/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 diff --git a/src/lib/dhcpsrv/cfg_iface.h b/src/lib/dhcpsrv/cfg_iface.h index bf3113b008..72b450aff5 100644 --- a/src/lib/dhcpsrv/cfg_iface.h +++ b/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_; }; } diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes index 8eac7c5941..9bf0222099 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.mes +++ b/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 diff --git a/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc index 5c9fa34b9a..5166164fe5 100644 --- a/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc +++ b/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 '" diff --git a/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc b/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc index a0f62833fa..99518d73a4 100644 --- a/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc +++ b/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 diff --git a/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc index d3ad960364..8ef72c3375 100644 --- a/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc +++ b/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 |