From c6a38e4e7c94bca2bb09b7fa07d9cb211a11da0c Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Wed, 13 Jan 2016 14:41:00 +0100 Subject: [PATCH] [4254] perfdhcp sends DHCPv4 renews when -f option is specified. --- src/bin/perfdhcp/test_control.cc | 84 +++++++- src/bin/perfdhcp/test_control.h | 30 ++- .../perfdhcp/tests/test_control_unittest.cc | 192 ++++++++++++++++-- 3 files changed, 286 insertions(+), 20 deletions(-) diff --git a/src/bin/perfdhcp/test_control.cc b/src/bin/perfdhcp/test_control.cc index 672038824d..8421d5cd93 100644 --- a/src/bin/perfdhcp/test_control.cc +++ b/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(); @@ -831,6 +842,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, @@ -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 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 option was specified we have to check how many @@ -1568,6 +1616,32 @@ TestControl::sendDiscover4(const TestControlSocket& socket, saveFirstPacket(pkt4); } +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) { diff --git a/src/bin/perfdhcp/test_control.h b/src/bin/perfdhcp/test_control.h index ab0baa7f16..33eb21850c 100644 --- a/src/bin/perfdhcp/test_control.h +++ b/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 ack_storage_; ///< A storage for DHCPACK messages. PacketStorage reply_storage_; ///< A storage for reply messages. NumberGeneratorPtr transid_gen_; ///< Transaction id generator. diff --git a/src/bin/perfdhcp/tests/test_control_unittest.cc b/src/bin/perfdhcp/tests/test_control_unittest.cc index 309c92e6c2..07093a0c62 100644 --- a/src/bin/perfdhcp/tests/test_control_unittest.cc +++ b/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 or -F 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 + 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 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 + 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 + Pkt4Ptr createOfferPkt4(uint32_t transid) const { - boost::shared_ptr 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) {