Browse Source

[5377] outbound-interface fixed, unit-tests added.

Tomek Mrugalski 7 years ago
parent
commit
0f15197536

+ 24 - 0
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.</para>
   </note>
 
+  <para>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
+  <command>outbound-interface</command> that control this behavior. It supports
+  two values. The first one, <userinput>same-as-inbound</userinput>, tells Kea
+  to send back the response on the same inteface the query packet came in. This
+  is the default behavior. The second one, <userinput>use-routing</userinput>
+  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:
+  <screen>
+"Dhcp4": {
+    "interfaces-config": {
+        "interfaces": [ "eth1", "eth3" ],
+        "dhcp-socket-type": "udp",
+        <userinput>"outbound-interface": "use-routing"</userinput>
+    },
+    ...
+}</screen>
+  </para>
+
   <para>Interfaces are re-detected at each reconfiguration. This behavior
   can be disabled by setting <command>re-detect</command> value to
   <userinput>false</userinput>, for instance:

+ 10 - 1
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_));
 

+ 12 - 0
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

+ 67 - 0
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<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) {
@@ -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