From: Tomek Mrugalski Date: Thu, 5 Oct 2017 14:16:52 +0000 (+0200) Subject: [5377] outbound-interface fixed, unit-tests added. X-Git-Tag: trac5297_base~15^2~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0f15197536e8f869e8e4f454a074590c76d17bac;p=thirdparty%2Fkea.git [5377] outbound-interface fixed, unit-tests added. --- diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml index a4c450c604..0d1a91dceb 100644 --- a/doc/guide/dhcp4-srv.xml +++ b/doc/guide/dhcp4-srv.xml @@ -720,6 +720,30 @@ temporarily override a list of interface names and listen on all interfaces. fall back to use IP/UDP sockets. + In typical environment the DHCP server is expected to send back its + responses on the same network interface as the query packets come in. This is + the default behavior. However, in some deployments it is expected the outbound + (response) packets will be sent as regular traffic and the outbound interface + will be determined by the routing tables. This kind of asymetric traffic + is uncommon, but valid. Kea now supports a parameter called + outbound-interface that control this behavior. It supports + two values. The first one, same-as-inbound, tells Kea + to send back the response on the same inteface the query packet came in. This + is the default behavior. The second one, use-routing + tells Kea to send regular UDP packets and let the kernel's routing table to + determine most appropriate interface. This only works when dhcp-socket-type is + set to udp. An example configuration looks as follows: + +"Dhcp4": { + "interfaces-config": { + "interfaces": [ "eth1", "eth3" ], + "dhcp-socket-type": "udp", + "outbound-interface": "use-routing" + }, + ... +} + + Interfaces are re-detected at each reconfiguration. This behavior can be disabled by setting re-detect value to false, for instance: diff --git a/src/lib/dhcpsrv/cfg_iface.cc b/src/lib/dhcpsrv/cfg_iface.cc index e180c4f4ce..419e884904 100644 --- a/src/lib/dhcpsrv/cfg_iface.cc +++ b/src/lib/dhcpsrv/cfg_iface.cc @@ -227,6 +227,11 @@ CfgIface::textToSocketType(const std::string& socket_type_name) const { } } +CfgIface::OutboundIface +CfgIface::getOutboundIface() const { + return (outbound_iface_); +} + std::string CfgIface::outboundTypeToText() const { switch (outbound_iface_) { @@ -246,7 +251,7 @@ CfgIface::textToOutboundIface(const std::string& txt) { return (USE_ROUTING); } else { - isc_throw(InvalidSocketType, "unsupported socket type '" + isc_throw(BadValue, "unsupported outbound interface type '" << txt << "'"); } } @@ -455,6 +460,10 @@ CfgIface::toElement() const { result->set("dhcp-socket-type", Element::create(std::string("udp"))); } + if (outbound_iface_ != SAME_AS_INBOUND) { + result->set("outbound-interface", Element::create(outboundTypeToText())); + } + // Set re-detect result->set("re-detect", Element::create(re_detect_)); diff --git a/src/lib/dhcpsrv/cfg_iface.h b/src/lib/dhcpsrv/cfg_iface.h index 8293d512e1..0a09b36292 100644 --- a/src/lib/dhcpsrv/cfg_iface.h +++ b/src/lib/dhcpsrv/cfg_iface.h @@ -233,12 +233,24 @@ public: /// @brief Returns the socket type in the textual format. std::string socketTypeToText() const; + /// @brief Sets outbound interface type + /// + /// @param traffic_type sets the type of traffic void setOutboundIface(const OutboundIface& traffic_type); + /// @brief Returns outbound interface traffic type + /// + /// @return type of traffic (use-routing or same-as-inbound) OutboundIface getOutboundIface() const; + /// @brief Returns outbound type as string + /// + /// @return text representation of the outbound type std::string outboundTypeToText() const; + /// @brief Converts text to outbound interface + /// @param txt either 'same-as-inbound' or 'use-routing' + /// @return converted value static OutboundIface textToOutboundIface(const std::string& txt); /// @brief Converts the socket type in the textual format to the type diff --git a/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc index 8cc6c4779e..dff3a84c52 100644 --- a/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc @@ -98,6 +98,33 @@ TEST_F(IfacesConfigParserTest, interfaces) { EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET)); } + +// 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 = + "{ \"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); + 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(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) { @@ -177,4 +204,44 @@ TEST_F(IfacesConfigParserTest, socketTypeInvalid) { 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); + + // For DHCPv6 we don't accept this at all. + IfacesConfigParser parser6(AF_INET6); + + 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); +} + } // end of anonymous namespace