Browse Source

[4254] perfdhcp sends DHCPv4 renews when -f option is specified.

Marcin Siodelski 9 years ago
parent
commit
c6a38e4e7c

+ 79 - 5
src/bin/perfdhcp/test_control.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 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
@@ -315,6 +315,14 @@ TestControl::checkExitConditions() const {
     return (false);
 }
 
+Pkt4Ptr
+TestControl::createRequestFromAck(const dhcp::Pkt4Ptr& ack) {
+    Pkt4Ptr msg(new Pkt4(DHCPREQUEST, generateTransid()));
+    msg->setCiaddr(ack->getYiaddr());
+    msg->setHWAddr(ack->getHWAddr());
+    return (msg);
+}
+
 Pkt6Ptr
 TestControl::createMessageFromReply(const uint16_t msg_type,
                                     const dhcp::Pkt6Ptr& reply) {
@@ -663,6 +671,9 @@ TestControl::initializeStatsMgr() {
             stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_RA,
                                           options.getDropTime()[1]);
         }
+        if (options.getRenewRate() != 0) {
+            stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_RN);
+        }
 
     } else if (options.getIpVersion() == 6) {
         stats_mgr6_.reset();
@@ -832,6 +843,17 @@ TestControl::sendPackets(const TestControlSocket& socket,
 }
 
 uint64_t
+TestControl::sendMultipleRequests(const TestControlSocket& socket,
+                                  const uint64_t msg_num) {
+    for (uint64_t i = 0; i < msg_num; ++i) {
+        if (!sendRequestFromAck(socket)) {
+            return (i);
+        }
+    }
+    return (msg_num);
+}
+
+uint64_t
 TestControl::sendMultipleMessages6(const TestControlSocket& socket,
                                    const uint32_t msg_type,
                                    const uint64_t msg_num) {
@@ -1072,7 +1094,28 @@ TestControl::processReceivedPacket4(const TestControlSocket& socket,
             }
         }
     } else if (pkt4->getType() == DHCPACK) {
-        stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RA, pkt4);
+        // If received message is DHCPACK, we have to check if this is a response
+        // to 4-way exchange. We'll match this packet with a DHCPREQUESTs sent
+        // as part of the 4-way exchages.
+        if (stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RA, pkt4)) {
+            // The DHCPACK belongs to DHCPREQUEST-DHCPACK exchange type. So, we
+            // may need to keep this DHCPACK in the storage if renews. Note that,
+            // DHCPACK messages hold the information about leases assigned.
+            // We use this information to renew.
+            if (stats_mgr4_->hasExchangeStats(StatsMgr4::XCHG_RN)) {
+                // Renew messages are sent, because StatsMgr has the
+                // specific exchange type specified. Let's append the DHCPACK.
+                // message to a storage
+                ack_storage_.append(pkt4);
+            }
+        // The DHCPACK message is not a server's response to the DHCPREQUEST
+        // message sent within the 4-way exchange. It may be a response to a
+        // renewal. In this case we first check if StatsMgr has exchange type
+        // for renew specified, and if it has, if there is a corresponding
+        // renew message for the received DHCPACK.
+        } else if (stats_mgr4_->hasExchangeStats(StatsMgr4::XCHG_RN)) {
+            stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RN, pkt4);
+        }
     }
 }
 
@@ -1364,12 +1407,17 @@ TestControl::run() {
 
         // If -f<renew-rate> option was specified we have to check how many
         // Renew packets should be sent to catch up with a desired rate.
-        if ((options.getIpVersion() == 6) && (options.getRenewRate() != 0)) {
+        if (options.getRenewRate() != 0) {
             uint64_t renew_packets_due =
                 renew_rate_control_.getOutboundMessageCount();
             checkLateMessages(renew_rate_control_);
-            // Send Renew messages.
-            sendMultipleMessages6(socket, DHCPV6_RENEW, renew_packets_due);
+
+            // Send multiple renews to satify the desired rate.
+            if (options.getIpVersion() == 4) {
+                sendMultipleRequests(socket, renew_packets_due);
+            } else {
+                sendMultipleMessages6(socket, DHCPV6_RENEW, renew_packets_due);
+            }
         }
 
         // If -F<release-rate> option was specified we have to check how many
@@ -1569,6 +1617,32 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
 }
 
 bool
+TestControl::sendRequestFromAck(const TestControlSocket& socket) {
+    // Update timestamp of last sent renewal.
+    renew_rate_control_.updateSendTime();
+
+    // Get one of the recorded DHCPACK messages.
+    Pkt4Ptr ack = ack_storage_.getRandom();
+    if (!ack) {
+        return (false);
+    }
+
+    // Create message of the specified type.
+    Pkt4Ptr msg = createRequestFromAck(ack);
+    setDefaults4(socket, msg);
+    msg->pack();
+    // And send it.
+    IfaceMgr::instance().send(msg);
+    if (!stats_mgr4_) {
+        isc_throw(Unexpected, "Statistics Manager for DHCPv4 "
+                  "hasn't been initialized");
+    }
+    stats_mgr4_->passSentPacket(StatsMgr4::XCHG_RN, msg);
+    return (true);
+}
+
+
+bool
 TestControl::sendMessageFromReply(const uint16_t msg_type,
                                   const TestControlSocket& socket) {
     // We only permit Release or Renew messages to be sent using this function.

+ 28 - 2
src/bin/perfdhcp/test_control.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 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
@@ -306,13 +306,21 @@ protected:
     /// has been reached.
     void cleanCachedPackets();
 
+    /// \brief Creates DHCPREQUEST from a DHCPACK message.
+    ///
+    /// \param ack An instance of the DHCPACK message to be used to
+    /// create a new message.
+    ///
+    /// \return Pointer to the created message.
+    dhcp::Pkt4Ptr createRequestFromAck(const dhcp::Pkt4Ptr& ack);
+
     /// \brief Creates DHCPv6 message from the Reply packet.
     ///
     /// This function creates DHCPv6 Renew or Release message using the
     /// data from the Reply message by copying options from the Reply
     /// message.
     ///
-    /// \param msg_type A type of the message to be createad.
+    /// \param msg_type A type of the message to be created.
     /// \param reply An instance of the Reply packet which contents should
     /// be used to create an instance of the new message.
     ///
@@ -726,6 +734,15 @@ protected:
                      const uint64_t packets_num,
                      const bool preload = false);
 
+    /// \brief Send number of DHCPREQUEST (renew) messages to a server.
+    ///
+    /// \param socket An object representing socket to be used to send packets.
+    /// \param msg_num A number of messages to be sent.
+    ///
+    /// \return A number of messages actually sent.
+    uint64_t sendMultipleRequests(const TestControlSocket& socket,
+                                  const uint64_t msg_num);
+
     /// \brief Send number of DHCPv6 Renew or Release messages to the server.
     ///
     /// \param socket An object representing socket to be used to send packets.
@@ -738,6 +755,14 @@ protected:
                                    const uint32_t msg_type,
                                    const uint64_t msg_num);
 
+    /// \brief Send DHCPv4 renew (DHCPREQUEST) using specified socket.
+    ///
+    /// \param socket An object encapsulating socket to be used to send
+    /// a packet.
+    ///
+    /// \return true if the message has been sent, false otherwise.
+    bool sendRequestFromAck(const TestControlSocket& socket);
+
     /// \brief Send DHCPv6 Renew or Release message using specified socket.
     ///
     /// This method will select an existing lease from the Reply packet cache
@@ -1077,6 +1102,7 @@ protected:
     StatsMgr4Ptr stats_mgr4_;  ///< Statistics Manager 4.
     StatsMgr6Ptr stats_mgr6_;  ///< Statistics Manager 6.
 
+    PacketStorage<dhcp::Pkt4> ack_storage_; ///< A storage for DHCPACK messages.
     PacketStorage<dhcp::Pkt6> reply_storage_; ///< A storage for reply messages.
 
     NumberGeneratorPtr transid_gen_; ///< Transaction id generator.

+ 179 - 13
src/bin/perfdhcp/tests/test_control_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 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
@@ -95,6 +95,7 @@ public:
 
     using TestControl::checkExitConditions;
     using TestControl::createMessageFromReply;
+    using TestControl::createRequestFromAck;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryGeneric;
     using TestControl::factoryIana6;
@@ -114,6 +115,7 @@ public:
     using TestControl::reset;
     using TestControl::sendDiscover4;
     using TestControl::sendPackets;
+    using TestControl::sendMultipleRequests;
     using TestControl::sendMultipleMessages6;
     using TestControl::sendRequest6;
     using TestControl::sendSolicit6;
@@ -650,7 +652,134 @@ public:
         }
     }
 
-    /// \brief Test that the DHCPv4 Release or Renew message is created
+    /// \brief Test sending DHCPv4 renews.
+    ///
+    /// This function simulates acquiring 10 leases from the server. Returned
+    /// DHCPACK messages are cached and used to send renew messages.
+    /// The maxmimal number of messages which can be sent is equal to the
+    /// number of leases acquired (10). This function also checks that an
+    /// attempt to send more renew messages than the number of leases acquired
+    /// will fail.
+    void testSendRenew4() {
+        std::string loopback_iface(getLocalLoopback());
+        if (loopback_iface.empty()) {
+            std::cout << "Skipping the test because loopback interface could"
+                " not be detected" << std::endl;
+            return;
+        }
+        // Build a command line. Depending on the message type, we will use
+        // -f<renew-rate> or -F<release-rate> parameter.
+        std::ostringstream s;
+        s << "perfdhcp -4 -l " << loopback_iface << " -r 10 -f";
+        s << " 10 -R 10 -L 10067 -n 10 127.0.0.1";
+        ASSERT_NO_THROW(processCmdLine(s.str()));
+        // Create a test controller class.
+        NakedTestControl tc;
+        tc.initializeStatsMgr();
+        // Set the transaction id generator to sequential to control to
+        // guarantee that transaction ids are predictable.
+        boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+        // Socket has to be created so as we can actually send packets.
+        int sock_handle = 0;
+        ASSERT_NO_THROW(sock_handle = tc.openSocket());
+        TestControl::TestControlSocket sock(sock_handle);
+
+        // Send a number of DHCPDISCOVER messages. Each generated message will
+        // be assigned a different transaction id, starting from 1 to 10.
+        tc.sendPackets(sock, 10);
+
+        // Simulate DHCPOFFER responses from the server. Each DHCPOFFER is
+        // assigned a transaction id from the range of 1 to 10, so as they
+        // match the transaction ids from the DHCPDISCOVER messages.
+        for (unsigned i = generator->getNext() - 10;
+             i < generator->getNext(); ++i) {
+            Pkt4Ptr offer(createOfferPkt4(i));
+            // If DHCPOFFER is matched with the DHCPDISCOVER the call below
+            // will trigger a corresponding DHCPREQUEST. They will be assigned
+            // transaction ids from the range from 11 to 20 (the range of
+            // 1 to 10 has been used by DHCPDISCOVER-DHCPOFFER).
+            ASSERT_NO_THROW(tc.processReceivedPacket4(sock, offer));
+    }
+
+        // Requests have been sent, so now let's simulate responses from the
+        // server. Generate corresponding DHCPACK messages with the transaction
+        // ids from the range from 11 to 20.
+        for (unsigned i = generator->getNext() - 10;
+             i < generator->getNext(); ++i) {
+            Pkt4Ptr ack(createAckPkt4(i));
+            // Each DHCPACK packet corresponds to the new lease acquired. Since
+            // -f<renew-rate> option has been specified, received Reply
+            // messages are held so as renew messages can be sent for
+            // existing leases.
+            ASSERT_NO_THROW(tc.processReceivedPacket4(sock, ack));
+        }
+
+        uint64_t msg_num;
+        // Try to send 5 messages. It should be successful because 10
+        // DHCPREQUEST messages has been received. For each of them we
+        // should be able to send renewal.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleRequests(sock, 5)
+        );
+        // Make sure that we have sent 5 messages.
+        EXPECT_EQ(5, msg_num);
+
+        // Try to do it again. We should still have 5 Reply packets for
+        // which renews haven't been sent yet.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleRequests(sock, 5)
+        );
+        EXPECT_EQ(5, msg_num);
+
+        // We used all the DHCPACK packets (we sent renew or release for each of
+        // them already). Therefore, no further renew messages should be sent
+        // before we acquire new leases.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleRequests(sock, 5)
+        );
+        // Make sure that no message has been sent.
+        EXPECT_EQ(0, msg_num);
+    }
+
+    /// \brief Test that the DHCPREQUEST message is created correctly and
+    /// comprises expected values.
+    void testCreateRequest() {
+        // This command line specifies that the Release/Renew messages should
+        // be sent with the same rate as the Solicit messages.
+        std::ostringstream s;
+        s << "perfdhcp -4 -l lo -r 10 -f 10";
+        s << " -R 10 -L 10067 -n 10 127.0.0.1";
+        ASSERT_NO_THROW(processCmdLine(s.str()));
+        // Create a test controller class.
+        NakedTestControl tc;
+        // Set the transaction id generator which will be used by the
+        // createRenew or createRelease function to generate transaction id.
+        boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+
+        Pkt4Ptr ack = createAckPkt4(1);
+
+        // Create DHCPREQUST from DHCPACK.
+        Pkt4Ptr request;
+        ASSERT_NO_THROW(request = tc.createRequestFromAck(ack));
+
+        // Make sure that the DHCPACK has been successfully created and that
+        // it holds expected data.
+        ASSERT_TRUE(request);
+        EXPECT_EQ("127.0.0.1", request->getCiaddr().toText());
+
+        // HW address.
+        HWAddrPtr hwaddr_ack = ack->getHWAddr();
+        ASSERT_TRUE(hwaddr_ack);
+        HWAddrPtr hwaddr_req = request->getHWAddr();
+        ASSERT_TRUE(hwaddr_req);
+        EXPECT_TRUE(hwaddr_ack->hwaddr_ == hwaddr_req->hwaddr_);
+    }
+
+    /// \brief Test that the DHCPv6 Release or Renew message is created
     /// correctly and comprises expected options.
     ///
     /// \param msg_type A type of the message to be tested: DHCPV6_RELEASE
@@ -822,20 +951,41 @@ public:
         CommandOptionsHelper::process(cmdline);
     }
 
+    /// \brief Create DHCPOFFER or DHCPACK packet.
+    ///
+    /// \param pkt_type DHCPOFFER or DHCPACK.
+    /// \param transid Transaction id.
+    ///
+    /// \return Instance of the packet.
+    Pkt4Ptr
+    createResponsePkt4(const uint8_t pkt_type,
+                       const uint32_t transid) const {
+        Pkt4Ptr pkt(new Pkt4(pkt_type, transid));
+        OptionPtr opt_serverid = Option::factory(Option::V4,
+                                                 DHO_DHCP_SERVER_IDENTIFIER,
+                                                 OptionBuffer(4, 1));
+        pkt->setYiaddr(asiolink::IOAddress("127.0.0.1"));
+        pkt->addOption(opt_serverid);
+        pkt->updateTimestamp();
+        return (pkt);
+    }
+
     /// \brief Create DHCPv4 OFFER packet.
     ///
     /// \param transid transaction id.
     /// \return instance of the packet.
-    boost::shared_ptr<Pkt4>
+    Pkt4Ptr
     createOfferPkt4(uint32_t transid) const {
-        boost::shared_ptr<Pkt4> offer(new Pkt4(DHCPOFFER, transid));
-        OptionPtr opt_serverid = Option::factory(Option::V4,
-                                                 DHO_DHCP_SERVER_IDENTIFIER,
-                                                 OptionBuffer(4, 1));
-        offer->setYiaddr(asiolink::IOAddress("127.0.0.1"));
-        offer->addOption(opt_serverid);
-        offer->updateTimestamp();
-        return (offer);
+        return (createResponsePkt4(DHCPOFFER, transid));
+    }
+
+    /// \brief Create DHCPACK packet.
+    ///
+    /// \param transid transaction id.
+    /// \return instance of the packet.
+    Pkt4Ptr
+    createAckPkt4(const uint32_t transid) const {
+        return (createResponsePkt4(DHCPACK, transid));
     }
 
     /// \brief Create DHCPv6 ADVERTISE packet.
@@ -1432,14 +1582,30 @@ TEST_F(TestControlTest, PacketTemplates) {
     EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue);
 }
 
-TEST_F(TestControlTest, processRenew) {
+// This test verifies that DHCPv4 renew (DHCPREQUEST) messages can be
+// sent for acquired leases.
+TEST_F(TestControlTest, processRenew4) {
+    testSendRenew4();
+}
+
+// This test verifies that DHCPv6 Renew messages can be sent for acquired
+// leases.
+TEST_F(TestControlTest, processRenew6) {
     testSendRenewRelease(DHCPV6_RENEW);
 }
 
-TEST_F(TestControlTest, processRelease) {
+// This test verifies that DHCPv6 Release messages can be sent for acquired
+// leases.
+TEST_F(TestControlTest, processRelease6) {
     testSendRenewRelease(DHCPV6_RELEASE);
 }
 
+// This test verifies that DHCPREQUEST is created correctly from the
+// DHCPACK message.
+TEST_F(TestControlTest, createRequest) {
+    testCreateRequest();
+}
+
 // This test verifies that the DHCPV6 Renew message is created correctly
 // and that it comprises all required options.
 TEST_F(TestControlTest, createRenew) {