summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc')
-rw-r--r--src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc392
1 files changed, 392 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
new file mode 100644
index 0000000..9e65462
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
@@ -0,0 +1,392 @@
+// Copyright (C) 2015-2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/ifaces_config_parser.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Test fixture class for @c IfacesConfigParser
+class IfacesConfigParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Setup for each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void SetUp();
+
+ /// @brief Cleans up after each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void TearDown();
+
+};
+
+void
+IfacesConfigParserTest::SetUp() {
+ CfgMgr::instance().clear();
+ IfaceMgr::instance().setTestMode(true);
+}
+
+void
+IfacesConfigParserTest::TearDown() {
+ CfgMgr::instance().clear();
+ IfaceMgr::instance().setTestMode(false);
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().closeSockets();
+ IfaceMgr::instance().detectIfaces();
+}
+
+// This test checks that the parser correctly parses the list of interfaces
+// on which the server should listen.
+TEST_F(IfacesConfigParserTest, interfaces) {
+ // Creates fake interfaces with fake addresses.
+ IfaceMgrTestConfig test_config(true);
+
+ // Configuration with one interface.
+ std::string config =
+ "{ \"interfaces\": [ \"eth0\" ], \"re-detect\": false }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+
+ // Open sockets according to the parsed configuration.
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ ASSERT_NO_THROW(cfg->getCfgIface()->openSockets(AF_INET, 10000));
+
+ // Only eth0 should have an open socket.
+ EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
+ EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET));
+
+ // Reset configuration.
+ cfg->getCfgIface()->closeSockets();
+ CfgMgr::instance().clear();
+
+ // Try similar configuration but this time add a wildcard interface
+ // to see if sockets will open on all interfaces.
+ config = "{ \"interfaces\": [ \"eth0\", \"*\" ], \"re-detect\": false }";
+ config_element = Element::fromJSON(config);
+
+ cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ runToElementTest<CfgIface>(config, *cfg_iface);
+
+ cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_NO_THROW(cfg->getCfgIface()->openSockets(AF_INET, 10000));
+
+ EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
+ EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));
+}
+
+// This test checks that the parser does not re-detect interfaces in test mode.
+TEST_F(IfacesConfigParserTest, testMode) {
+ // Creates fake interfaces with fake addresses.
+ IfaceMgrTestConfig test_config(true);
+
+ // Configuration with wildcard..
+ std::string config =
+ "{ \"interfaces\": [ \"*\" ], \"re-detect\": true }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration in test mode.
+ IfacesConfigParser parser(AF_INET, true);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Verify we still have the eth1961 interface.
+ EXPECT_TRUE(IfaceMgr::instance().getIface("eth1961"));
+
+ // Reparse in not test mode.
+ IfacesConfigParser parser2(AF_INET, false);
+ CfgMgr::instance().clear();
+ cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser2.parse(cfg_iface, config_element));
+
+ // The eth1961 interface no longer exists.
+ EXPECT_FALSE(IfaceMgr::instance().getIface("eth1961"));
+}
+
+// This test checks that the parsed structure can be converted back to Element
+// tree.
+TEST_F(IfacesConfigParserTest, toElement) {
+ // Creates fake interfaces with fake addresses.
+ IfaceMgrTestConfig test_config(true);
+
+ // Configuration with one interface.
+ std::string config =
+ "{ \"user-context\": { \"foo\": \"bar\" }, "
+ " \"interfaces\": [ \"eth0\" ], "
+ " \"dhcp-socket-type\": \"udp\","
+ " \"outbound-interface\": \"use-routing\", "
+ " \"re-detect\": false }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+}
+
+
+// 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\": [ ],"
+ " \"dhcp-socket-type\": \"raw\","
+ " \"re-detect\": false }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_NO_THROW(parser.parse(cfg_iface, 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\": [ ],"
+ " \"dhcp-socket-type\": \"udp\","
+ " \"re-detect\": false }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+
+ // 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_UDP);
+ ASSERT_TRUE(cfg->getCfgIface());
+ 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.
+ IfacesConfigParser parser4(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ std::string config = "{ \"interfaces\": [ ],"
+ "\"dhcp-socket-type\": \"default\","
+ " \"re-detect\": false }";
+ ElementPtr config_element = Element::fromJSON(config);
+ ASSERT_THROW(parser4.parse(cfg_iface, config_element), DhcpConfigError);
+
+ // For DHCPv6 we don't accept any socket type.
+ IfacesConfigParser parser6(AF_INET6, false);
+ config = "{ \"interfaces\": [ ],"
+ " \"dhcp-socket-type\": \"udp\","
+ " \"re-detect\": false }";
+ config_element = Element::fromJSON(config);
+ ASSERT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
+}
+
+// Tests that outbound-interface is parsed properly.
+TEST_F(IfacesConfigParserTest, outboundInterface) {
+ // For DHCPv4 we accept 'use-routing' or 'same-as-inbound'.
+ IfacesConfigParser parser4(AF_INET, false);
+
+ // For DHCPv6 we don't accept this at all.
+ IfacesConfigParser parser6(AF_INET6, false);
+
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+
+ // The default should be to use the same as client's query packet.
+ EXPECT_EQ(CfgIface::SAME_AS_INBOUND, cfg_iface->getOutboundIface());
+
+ // Value 1: use-routing
+ std::string config = "{ \"interfaces\": [ ],"
+ "\"outbound-interface\": \"use-routing\","
+ " \"re-detect\": false }";
+ ElementPtr config_element = Element::fromJSON(config);
+ ASSERT_NO_THROW(parser4.parse(cfg_iface, config_element));
+ EXPECT_EQ(CfgIface::USE_ROUTING, cfg_iface->getOutboundIface());
+ EXPECT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
+
+ // Value 2: same-as-inbound
+ config = "{ \"interfaces\": [ ],"
+ "\"outbound-interface\": \"same-as-inbound\","
+ " \"re-detect\": false }";
+ config_element = Element::fromJSON(config);
+ ASSERT_NO_THROW(parser4.parse(cfg_iface, config_element));
+ EXPECT_EQ(CfgIface::SAME_AS_INBOUND, cfg_iface->getOutboundIface());
+ EXPECT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
+
+ // Other values are not supported.
+ config = "{ \"interfaces\": [ ],"
+ "\"outbound-interface\": \"default\","
+ " \"re-detect\": false }";
+ config_element = Element::fromJSON(config);
+ EXPECT_THROW(parser4.parse(cfg_iface, config_element), DhcpConfigError);
+ EXPECT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
+}
+
+// Tests that service-sockets-require-all is parsed properly.
+TEST_F(IfacesConfigParserTest, serviceSocketRequireAll) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with a require all sockets to open selected.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-require-all\": true }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+ EXPECT_TRUE(cfg_iface->getServiceSocketsRequireAll());
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+}
+
+// Tests that service-sockets-max-retries is parsed properly.
+TEST_F(IfacesConfigParserTest, serviceSocketMaxRetries) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with a non-zero retries selected.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-max-retries\": 42 }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+ EXPECT_FALSE(cfg_iface->getServiceSocketsRequireAll());
+
+ // Configuration should contain a number of retries and a wait time.
+ std::string expected_config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-retry-wait-time\": 5000,"
+ " \"service-sockets-max-retries\": 42 }";
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(expected_config, *cfg_iface);
+}
+
+// Tests that service-sockets-retry-wait-time is parsed properly if
+// service-sockets-max-retries is provided.
+TEST_F(IfacesConfigParserTest, serviceSocketRetryWaitTime) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with a non-zero number of retries and a non-default wait time.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-retry-wait-time\": 4224,"
+ " \"service-sockets-max-retries\": 42 }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+ EXPECT_FALSE(cfg_iface->getServiceSocketsRequireAll());
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+}
+
+// Tests that service-sockets-retry-wait-time is ignored if
+// service-sockets-max-retries is not provided.
+TEST_F(IfacesConfigParserTest, serviceSocketRetryWaitTimeWithoutMaxRetries) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with zero (default) retries and a non-default wait time.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-retry-wait-time\": 4224 }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+ EXPECT_FALSE(cfg_iface->getServiceSocketsRequireAll());
+
+ // Retry wait time is not applicable; it is skipped.
+ std::string expected_config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false }";
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(expected_config, *cfg_iface);
+}
+
+} // end of anonymous namespace