Browse Source

[master] Merge trac4109a (DHCP4o6 v6 part

Francis Dupont 9 years ago
parent
commit
f392806092

+ 37 - 0
doc/guide/dhcp6-srv.xml

@@ -3282,6 +3282,33 @@ should include options from the isc option space:
             </row>
 
             <row>
+              <entry>pkt6-dhcpv4-query-received</entry>
+              <entry>integer</entry>
+              <entry>
+                Number of DHCPv4-QUERY packets received. This
+                statistic is expected to grow if there are devices
+                that are using DHCPv4-over-DHCPv6. DHCPv4-QUERY
+                messages are used by DHCPv4 clients on an IPv6 only
+                line so that encapsulate requests over DHCPv6.
+              </entry>
+            </row>
+
+            <row>
+              <entry>pkt6-dhcpv4-response-received</entry>
+              <entry>integer</entry>
+              <entry>
+                Number of DHCPv4-RESPONSE packets received. This
+                statistic is expected to remain zero at all times, as
+                DHCPv4-RESPONSE packets are sent by the server and the
+                server is never expected to receive them. A non-zero
+                value indicates an error.  One likely cause would be a
+                misbehaving relay agent that incorrectly forwards
+                DHCPv4-RESPONSE message towards the server, rather
+                back to the clients.
+              </entry>
+            </row>
+
+            <row>
               <entry>pkt6-unknown-received</entry>
               <entry>integer</entry>
               <entry>Number of packets received of an unknown type. Non-zero
@@ -3322,6 +3349,16 @@ should include options from the isc option space:
             </row>
 
             <row>
+              <entry>pkt6-dhcpv4-response-sent</entry>
+              <entry>integer</entry>
+              <entry>Number of DHCPv4-RESPONSE packets sent. This
+              statistic is expected to grow in most cases after a
+              DHCPv4-QUERY is processed. There are certain cases where
+              there is no response.
+              </entry>
+            </row>
+
+            <row>
             <entry>subnet[id].total-nas</entry>
             <entry>integer</entry>
             <entry>

+ 18 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -237,6 +237,24 @@ received in Decline message. It's expected that the option will contain an
 address that is being declined. Specific information will be printed in a
 separate message.
 
+% DHCP6_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv4 server (type %1) for %2 on interface %3
+This debug message is printed when the server is receiving a DHCPv4o6
+from the DHCPv4 server over inter-process communication.
+
+% DHCP6_DHCP4O6_RECEIVE_FAIL failed to receive DHCPv4o6: %1
+This debug message indicates the inter-process communication with the
+DHCPv4 server failed. The reason for the error is included in
+the message.
+
+% DHCP6_DHCP4O6_RECEIVING receiving DHCPv4o6 packet from DHCPv4 server
+This debug message is printed when the server is receiving a DHCPv4o6
+from the DHCPv4 server over inter-process communication socket.
+
+% DHCP6_DHCP4O6_SEND_FAIL failed to send DHCPv4o6 packet: %1
+This error is output if the IPv6 DHCP server fails to send an assembled
+DHCPv4o6 message to a client. The reason for the error is included in the
+message.
+
 % DHCP6_DYNAMIC_RECONFIGURATION initiate server reconfiguration using file: %1, after receiving SIGHUP signal
 This is the info message logged when the DHCPv6 server starts reconfiguration
 as a result of receiving SIGHUP signal.

+ 38 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -634,6 +634,10 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
             rsp = processInfRequest(query);
             break;
 
+        case DHCPV6_DHCPV4_QUERY:
+            processDhcp4Query(query);
+            break;
+
         default:
             // We received a packet type that we do not recognize.
             LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED)
@@ -2787,6 +2791,26 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) {
     return (reply);
 }
 
+void
+Dhcpv6Srv::processDhcp4Query(const Pkt6Ptr& dhcp4_query) {
+
+    sanityCheck(dhcp4_query, OPTIONAL, OPTIONAL);
+
+    // flags are in transid
+    // uint32_t flags = dhcp4_query->getTransid();
+    // do nothing with DHCPV4_QUERY_FLAGS_UNICAST
+
+    // Get the DHCPv4 message option
+    OptionPtr dhcp4_msg = dhcp4_query->getOption(D6O_DHCPV4_MSG);
+    if (dhcp4_msg) {
+        // Forward the whole message to the DHCPv4 server via IPC
+        Dhcp6to4Ipc::instance().send(dhcp4_query);
+    }
+
+    // This method does not return anything as we always sent back
+    // the response via Dhcp6To4Ipc.
+}
+
 void Dhcpv6Srv::classifyByVendor(const Pkt6Ptr& pkt, std::string& classes) {
     OptionVendorClassPtr vclass = boost::dynamic_pointer_cast<
         OptionVendorClass>(pkt->getOption(D6O_VENDOR_CLASS));
@@ -3072,6 +3096,13 @@ void Dhcpv6Srv::processStatsReceived(const Pkt6Ptr& query) {
     case DHCPV6_INFORMATION_REQUEST:
         stat_name = "pkt6-infrequest-received";
         break;
+    case DHCPV6_DHCPV4_QUERY:
+        stat_name = "pkt6-dhcpv4-query-received";
+        break;
+    case DHCPV6_DHCPV4_RESPONSE:
+        // Should not happen, but let's keep a counter for it
+        stat_name = "pkt6-dhcpv4-response-received";
+        break;
     default:
             ; // do nothing
     }
@@ -3092,6 +3123,9 @@ void Dhcpv6Srv::processStatsSent(const Pkt6Ptr& response) {
     case DHCPV6_REPLY:
         stat_name = "pkt6-reply-sent";
         break;
+    case DHCPV6_DHCPV4_RESPONSE:
+        stat_name = "pkt6-dhcpv4-response-sent";
+        break;
     default:
         // That should never happen
         return;
@@ -3100,5 +3134,9 @@ void Dhcpv6Srv::processStatsSent(const Pkt6Ptr& response) {
     StatsMgr::instance().addValue(stat_name, static_cast<int64_t>(1));
 }
 
+int Dhcpv6Srv::getHookIndexBuffer6Send() {
+    return (Hooks.hook_index_buffer6_send_);
+}
+
 };
 };

+ 20 - 3
src/bin/dhcp6/dhcp6_srv.h

@@ -289,6 +289,16 @@ protected:
     /// @return Reply message to be sent to the client.
     Pkt6Ptr processInfRequest(const Pkt6Ptr& inf_request);
 
+    /// @brief Processes incoming DHCPv4-query message.
+    ///
+    /// It always returns NULL, as there is nothing to be sent back to the
+    /// client at this time. The message was sent to DHCPv4 server using
+    /// @ref isc::dhcp::Dhcp6to4Ipc::handler()). We will send back a response
+    /// to the client once we get back DHCP4-REPLY from the DHCPv4 server.
+    ///
+    /// @param dhcp4_query message received from client
+    void processDhcp4Query(const Pkt6Ptr& dhcp4_query);
+
     /// @brief Selects a subnet for a given client's packet.
     ///
     /// @param question client's message
@@ -781,12 +791,19 @@ private:
     /// @param query packet received
     static void processStatsReceived(const Pkt6Ptr& query);
 
+    /// UDP port number on which server listens.
+    uint16_t port_;
+
+public:
+    /// @note used by DHCPv4-over-DHCPv6 so must be public and static
+
     /// @brief Updates statistics for transmitted packets
-    /// @param query packet transmitted
+    /// @param response packet transmitted
     static void processStatsSent(const Pkt6Ptr& response);
 
-    /// UDP port number on which server listens.
-    uint16_t port_;
+    /// @brief Returns the index of the buffer6_send hook
+    /// @return the index of the buffer6_send hook
+    static int getHookIndexBuffer6Send();
 
 protected:
 

+ 79 - 6
src/bin/dhcp6/dhcp6to4_ipc.cc

@@ -8,10 +8,20 @@
 
 #include <util/buffer.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/callout_handle_store.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/dhcp6to4_ipc.h>
+#include <dhcp6/dhcp6_log.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <exceptions/exceptions.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_log.h>
+#include <hooks/hooks_manager.h>
+#include <stats/stats_mgr.h>
 
 using namespace std;
+using namespace isc::hooks;
 
 namespace isc {
 namespace dhcp {
@@ -44,13 +54,30 @@ void Dhcp6to4Ipc::open() {
 
 void Dhcp6to4Ipc::handler() {
     Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance();
+    Pkt6Ptr pkt;
+
+    try {
+        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL, DHCP6_DHCP4O6_RECEIVING);
+        // Receive message from IPC.
+        pkt = ipc.receive();
+
+        if (pkt) {
+            LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC, DHCP6_DHCP4O6_PACKET_RECEIVED)
+                .arg(static_cast<int>(pkt->getType()))
+                .arg(pkt->getRemoteAddr().toText())
+                .arg(pkt->getIface());
+        }
+    } catch (const std::exception& e) {
+        LOG_DEBUG(packet6_logger,DBG_DHCP6_DETAIL, DHCP6_DHCP4O6_RECEIVE_FAIL)
+            .arg(e.what());
+    }
 
-    // Receive message from IPC.
-    Pkt6Ptr pkt = ipc.receive();
     if (!pkt) {
         return;
     }
 
+    // Should we check it is a DHCPV6_DHCPV4_RESPONSE?
+
     // The received message has been unpacked by the receive() function. This
     // method could have modified the message so it's better to pack() it
     // again because we'll be forwarding it to a client.
@@ -58,16 +85,62 @@ void Dhcp6to4Ipc::handler() {
     buf.clear();
     pkt->pack();
 
-    uint8_t msg_type = pkt->getType();
+    // Don't use getType(): get the message type from the buffer as we
+    // want to know if it is a relayed message (vs. internal message type).
+    // getType() always returns the type of internal message.
+    uint8_t msg_type = buf[0];
     if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) {
         pkt->setRemotePort(DHCP6_SERVER_PORT);
     } else {
         pkt->setRemotePort(DHCP6_CLIENT_PORT);
     }
 
-    // Forward packet to the client.
-    IfaceMgr::instance().send(pkt);
-    // processStatsSent(pkt);
+    // Can't call the pkt6_send callout because we don't have the query
+
+    // Copied from Dhcpv6Srv::run_one() sending part
+
+    try {
+        // Let's execute all callouts registered for buffer6_send
+        if (HooksManager::calloutsPresent(Dhcpv6Srv::getHookIndexBuffer6Send())) {
+            CalloutHandlePtr callout_handle = getCalloutHandle(pkt);
+
+            // Delete previously set arguments
+            callout_handle->deleteAllArguments();
+
+            // Pass incoming packet as argument
+            callout_handle->setArgument("response6", pkt);
+
+            // Call callouts
+            HooksManager::callCallouts(Dhcpv6Srv::getHookIndexBuffer6Send(),
+                                       *callout_handle);
+
+            // Callouts decided to skip the next processing step. The next
+            // processing step would to parse the packet, so skip at this
+            // stage means drop.
+            if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+                LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP)
+                    .arg(pkt->getLabel());
+                return;
+            }
+
+            /// @todo: Add support for DROP status
+
+            callout_handle->getArgument("response6", pkt);
+        }
+
+        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_RESPONSE_DATA)
+            .arg(static_cast<int>(pkt->getType())).arg(pkt->toText());
+
+
+        // Forward packet to the client.
+        IfaceMgr::instance().send(pkt);
+
+        // Update statistics accordingly for sent packet.
+        Dhcpv6Srv::processStatsSent(pkt);
+
+    } catch (const std::exception& e) {
+        LOG_ERROR(packet6_logger, DHCP6_DHCP4O6_SEND_FAIL).arg(e.what());
+    }
 }
 
 };  // namespace dhcp

+ 12 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -2585,6 +2585,13 @@ TEST_F(Dhcpv6SrvTest, receiveReplyStat) {
     testReceiveStats(DHCPV6_REPLY, "pkt6-reply-received");
 }
 
+// Test checks if pkt6-dhcpv4-response-received is bumped up correctly.
+// Note that in properly configured network the server never receives
+// Dhcpv4-Response messages.
+TEST_F(Dhcpv6SrvTest, receiveDhcpv4ResponseStat) {
+    testReceiveStats(DHCPV6_DHCPV4_RESPONSE, "pkt6-dhcpv4-response-received");
+}
+
 // Test checks if pkt6-unknown-received is bumped up correctly.
 TEST_F(Dhcpv6SrvTest, receiveUnknownStat) {
     testReceiveStats(123, "pkt6-unknown-received");
@@ -2610,6 +2617,11 @@ TEST_F(Dhcpv6SrvTest, receiveDeclineStat) {
     testReceiveStats(DHCPV6_DECLINE, "pkt6-decline-received");
 }
 
+// Test checks if pkt6-dhcpv4-query-received is bumped up correctly.
+TEST_F(Dhcpv6SrvTest, receiveDhcpv4QueryStat) {
+    testReceiveStats(DHCPV6_DHCPV4_QUERY, "pkt6-dhcpv4-query-received");
+}
+
 // Test checks if reception of a malformed packet increases pkt-parse-failed
 // and pkt6-receive-drop
 TEST_F(Dhcpv6SrvTest, receiveParseFailedStat) {

+ 110 - 8
src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc

@@ -5,6 +5,7 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+
 #include <asiolink/io_address.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/iface_mgr.h>
@@ -13,6 +14,11 @@
 #include <dhcp6/dhcp6to4_ipc.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
+#include <stats/stats_mgr.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/library_handle.h>
+
 #include <gtest/gtest.h>
 #include <stdint.h>
 
@@ -20,6 +26,8 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
+using namespace isc::stats;
+using namespace isc::hooks;
 using namespace isc::util;
 
 namespace {
@@ -42,6 +50,11 @@ public:
         : iface_mgr_test_config_(true) {
         IfaceMgr::instance().openSockets6();
         configurePort(TEST_PORT);
+        // Install buffer6_send_callout
+        EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
+                        registerCallout("buffer6_send", buffer6_send_callout));
+        // Let's wipe all existing statistics.
+        StatsMgr::instance().removeAll();
     }
 
     /// @brief Configure DHCP4o6 port.
@@ -57,12 +70,27 @@ public:
     /// @return Pointer to the instance of the DHCPv4-query Message option.
     OptionPtr createDHCPv4MsgOption() const;
 
+    /// @brief Handler for the buffer6_send hook
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    buffer6_send_callout(CalloutHandle& callout_handle) {
+        callout_handle.getArgument("response6", callback_pkt_);
+        return (0);
+    }
+
+    /// @brief Response Pkt6 shared pointer returned in the callout
+    static Pkt6Ptr callback_pkt_;
+
 private:
 
     /// @brief Provides fake configuration of interfaces.
     IfaceMgrTestConfig iface_mgr_test_config_;
 };
 
+Pkt6Ptr Dhcp6to4IpcTest::callback_pkt_;
+
 void
 Dhcp6to4IpcTest::configurePort(const uint16_t port) {
     CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(port);
@@ -104,23 +132,29 @@ TEST_F(Dhcp6to4IpcTest, receive) {
     ASSERT_NO_THROW(ipc.open());
     ASSERT_NO_THROW(src_ipc.open());
 
+    // Get statistics
+    StatsMgr& mgr = StatsMgr::instance();
+    ObservationPtr pkt6_snd = mgr.getObservation("pkt6-sent");
+    ObservationPtr d4_resp = mgr.getObservation("pkt6-dhcpv4-response-sent");
+    EXPECT_FALSE(pkt6_snd);
+    EXPECT_FALSE(d4_resp);
+
     // Create message to be sent over IPC.
-    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
+    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_RESPONSE, 1234));
     pkt->addOption(createDHCPv4MsgOption());
     pkt->setIface("eth0");
     pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
     ASSERT_NO_THROW(pkt->pack());
 
+    // Reset the callout cached packet
+    Dhcp6to4IpcTest::callback_pkt_.reset();
+
     // Send and wait up to 1 second to receive it.
     ASSERT_NO_THROW(src_ipc.send(pkt));
     ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
 
-#if 0
-    // The stub packet filter exposes static function to retrieve messages
-    // sent over the fake sockets/interfaces. This is the message that the
-    // IPC endpoint should forward to the client after receiving it
-    // from the DHCPv4 server.
-    Pkt6Ptr forwarded = PktFilter6TestStub::popSent();
+    // Get the forwarded packet from the callout
+    Pkt6Ptr forwarded = Dhcp6to4IpcTest::callback_pkt_;
     ASSERT_TRUE(forwarded);
 
     // Verify the packet received.
@@ -129,7 +163,75 @@ TEST_F(Dhcp6to4IpcTest, receive) {
     EXPECT_TRUE(forwarded->getOption(D6O_DHCPV4_MSG));
     EXPECT_EQ("eth0", forwarded->getIface());
     EXPECT_EQ("2001:db8:1::123", forwarded->getRemoteAddr().toText());
-#endif
+
+    // Verify statistics
+    pkt6_snd = mgr.getObservation("pkt6-sent");
+    d4_resp = mgr.getObservation("pkt6-dhcpv4-response-sent");
+    ASSERT_TRUE(pkt6_snd);
+    ASSERT_TRUE(d4_resp);
+    EXPECT_EQ(1, pkt6_snd->getInteger().first);
+    EXPECT_EQ(1, d4_resp->getInteger().first);
+}
+
+// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
+// receive relayed messages.
+// This is currently not supported: it is a known defect addressed by #4296.
+TEST_F(Dhcp6to4IpcTest, DISABLED_receiveRelayed) {
+    // Create instance of the IPC endpoint under test.
+    Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance();
+    // Create instance of the IPC endpoint being used as a source of messages.
+    TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+
+    // Open both endpoints.
+    ASSERT_NO_THROW(ipc.open());
+    ASSERT_NO_THROW(src_ipc.open());
+
+    // Get statistics
+    StatsMgr& mgr = StatsMgr::instance();
+    ObservationPtr pkt6_snd = mgr.getObservation("pkt6-sent");
+    ObservationPtr d4_resp = mgr.getObservation("pkt6-dhcpv4-response-sent");
+    EXPECT_FALSE(pkt6_snd);
+    EXPECT_FALSE(d4_resp);
+
+    // Create relayed message to be sent over IPC.
+    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_RESPONSE, 1234));
+    pkt->addOption(createDHCPv4MsgOption());
+    Pkt6::RelayInfo relay;
+    relay.linkaddr_ = IOAddress("3000:1::1");
+    relay.peeraddr_ = IOAddress("fe80::1");
+    relay.msg_type_ = DHCPV6_RELAY_FORW;
+    relay.hop_count_ = 1;
+    pkt->relay_info_.push_back(relay);
+    pkt->setIface("eth0");
+    pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
+    ASSERT_NO_THROW(pkt->pack());
+
+    // Reset the callout cached packet
+    Dhcp6to4IpcTest::callback_pkt_.reset();
+
+    // Send and wait up to 1 second to receive it.
+    ASSERT_NO_THROW(src_ipc.send(pkt));
+    ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+    // Get the forwarded packet from the callout
+    Pkt6Ptr forwarded = Dhcp6to4IpcTest::callback_pkt_;
+    ASSERT_TRUE(forwarded);
+
+    // Verify the packet received.
+    EXPECT_EQ(DHCP6_CLIENT_PORT, forwarded->getRemotePort());
+    EXPECT_EQ(forwarded->getType(), pkt->getType());
+    EXPECT_TRUE(forwarded->getOption(D6O_DHCPV4_MSG));
+    EXPECT_EQ("eth0", forwarded->getIface());
+    EXPECT_EQ("2001:db8:1::123", forwarded->getRemoteAddr().toText());
+    EXPECT_EQ(DHCP6_CLIENT_PORT, forwarded->getRemotePort());
+
+    // Verify statistics
+    pkt6_snd = mgr.getObservation("pkt6-sent");
+    d4_resp = mgr.getObservation("pkt6-dhcpv4-response-sent");
+    ASSERT_TRUE(pkt6_snd);
+    ASSERT_TRUE(d4_resp);
+    EXPECT_EQ(1, pkt6_snd->getInteger().first);
+    EXPECT_EQ(1, d4_resp->getInteger().first);
 }
 
 } // end of anonymous namespace