// Copyright (C) 2012-2019,2021 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 // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include "command_options_helper.h" #include "../avalanche_scen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace boost::posix_time; using namespace isc; using namespace isc::dhcp; using namespace isc::perfdhcp; /// \brief FakeAvalancheScenPerfSocket class that mocks PerfSocket. /// /// It stubs send and receive operations and collects statistics. /// Beside that it simulates DHCP server responses for received /// packets. class FakeAvalancheScenPerfSocket: public BasePerfSocket { public: /// \brief Default constructor for FakeAvalancheScenPerfSocket. FakeAvalancheScenPerfSocket(CommandOptions &opt) : opt_(opt), iface_(boost::make_shared("fake", 0)), sent_cnt_(0), recv_cnt_(0), initial_drops_cnt_(0) {}; CommandOptions &opt_; IfacePtr iface_; ///< Local fake interface. int sent_cnt_; ///< Counter of sent packets. int recv_cnt_; ///< Counter of received packets. int initial_drops_cnt_; /// List of pairs containing responses /// planned to send to perfdhcp. std::list> planned_responses_; /// \brief Simulate receiving DHCPv4 packet. virtual dhcp::Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec) override { (void)timeout_sec; // silence compile 'unused parameter' warning; (void)timeout_usec; // silence compile 'unused parameter' warning; recv_cnt_++; if (planned_responses_.empty()) { return Pkt4Ptr(); } // simulate initial drops if (initial_drops_cnt_ > 0) { planned_responses_.pop_front(); initial_drops_cnt_--; return(Pkt4Ptr()); } // prepare received packet auto msg = planned_responses_.front(); planned_responses_.pop_front(); auto msg_type = std::get<0>(msg); Pkt4Ptr pkt(new Pkt4(msg_type, std::get<1>(msg))); 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 Simulate receiving DHCPv6 packet. virtual dhcp::Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec) override { (void)timeout_sec; // silence compile 'unused parameter' warning; (void)timeout_usec; // silence compile 'unused parameter' warning; recv_cnt_++; if (planned_responses_.empty()) { return Pkt6Ptr(); } // simulate initial drops if (initial_drops_cnt_ > 0) { planned_responses_.pop_front(); initial_drops_cnt_--; return(Pkt6Ptr()); } // prepare received packet auto msg = planned_responses_.front(); planned_responses_.pop_front(); auto msg_type = std::get<0>(msg); Pkt6Ptr pkt(new Pkt6(msg_type, std::get<1>(msg))); // Add IA_NA if requested by the client. if (opt_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA); OptionPtr iaaddr(new Option6IAAddr(D6O_IAADDR, isc::asiolink::IOAddress("fe80::abcd"), 300, 500)); opt_ia_na->addOption(iaaddr); pkt->addOption(opt_ia_na); } // Add IA_PD if requested by the client. if (opt_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD); OptionPtr iapref(new Option6IAPrefix(D6O_IAPREFIX, isc::asiolink::IOAddress("fe80::"), 64, 300, 500)); opt_ia_pd->addOption(iapref); pkt->addOption(opt_ia_pd); } OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID)); std::vector duid({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); pkt->addOption(opt_serverid); pkt->addOption(opt_clientid); pkt->updateTimestamp(); return (pkt); }; /// \brief Simulate sending DHCPv4 packet. virtual bool send(const dhcp::Pkt4Ptr& pkt) override { sent_cnt_++; pkt->updateTimestamp(); if (pkt->getType() == DHCPDISCOVER) { planned_responses_.push_back(std::make_tuple(DHCPOFFER, pkt->getTransid())); } else if (pkt->getType() == DHCPREQUEST) { planned_responses_.push_back(std::make_tuple(DHCPACK, pkt->getTransid())); } else { assert(0); } return true; }; /// \brief Simulate sending DHCPv6 packet. virtual bool send(const dhcp::Pkt6Ptr& pkt) override { sent_cnt_++; pkt->updateTimestamp(); if (pkt->getType() == DHCPV6_SOLICIT) { planned_responses_.push_back(std::make_tuple(DHCPV6_ADVERTISE, pkt->getTransid())); } else if (pkt->getType() == DHCPV6_REQUEST) { planned_responses_.push_back(std::make_tuple(DHCPV6_REPLY, pkt->getTransid())); } else { assert(0); } return true; }; /// \brief Override getting interface. virtual IfacePtr getIface() override { return iface_; } void reset() { sent_cnt_ = 0; recv_cnt_ = 0; } }; /// \brief NakedAvalancheScen class. /// /// It exposes AvalancheScen internals for UT. class NakedAvalancheScen: public AvalancheScen { public: using AvalancheScen::tc_; using AvalancheScen::total_resent_; FakeAvalancheScenPerfSocket fake_sock_; NakedAvalancheScen(CommandOptions &opt) : AvalancheScen(opt, fake_sock_), fake_sock_(opt) {}; }; /// \brief Test Fixture Class /// /// This test fixture class is used to perform /// unit tests on perfdhcp AvalancheScenTest class. class AvalancheScenTest : public virtual ::testing::Test { public: AvalancheScenTest() { } /// \brief Parse command line string with CommandOptions. /// /// \param cmdline command line string to be parsed. /// \throw isc::Unexpected if unexpected error occurred. /// \throw isc::InvalidParameter if command line is invalid. void processCmdLine(CommandOptions &opt, const std::string& cmdline) const { CommandOptionsHelper::process(opt, cmdline); } /// \brief Get full path to a file in testdata directory. /// /// \param filename filename being appended to absolute /// path to testdata directory /// /// \return full path to a file in testdata directory. std::string getFullPath(const std::string& filename) const { std::ostringstream stream; stream << TEST_DATA_DIR << "/" << filename; return (stream.str()); } }; TEST_F(AvalancheScenTest, Packet4Exchange) { CommandOptions opt; processCmdLine(opt, "perfdhcp -l fake -4 -R 10 --scenario avalanche -g single 127.0.0.1"); NakedAvalancheScen as(opt); as.run(); // Check if basic exchange of packets happened. No retransmissions expected. EXPECT_EQ(as.total_resent_, 0); EXPECT_EQ(as.fake_sock_.sent_cnt_, 20); // Discovery + Request EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 10); EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 10); EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 10); EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 10); } TEST_F(AvalancheScenTest, Packet4ExchangeOnlyDO) { CommandOptions opt; processCmdLine(opt, "perfdhcp -l fake -4 -R 10 -i --scenario avalanche -g single 127.0.0.1"); NakedAvalancheScen as(opt); as.run(); // Check if DO exchange of packets happened only. No retransmissions expected. EXPECT_EQ(as.total_resent_, 0); EXPECT_EQ(as.fake_sock_.sent_cnt_, 10); // Discovery + Request EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 10); EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 10); EXPECT_THROW(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), isc::BadValue); } TEST_F(AvalancheScenTest, Packet4ExchangeWithRetransmissions) { CommandOptions opt; processCmdLine(opt, "perfdhcp -l fake -4 -R 10 --scenario avalanche -g single 127.0.0.1"); NakedAvalancheScen as(opt); as.fake_sock_.initial_drops_cnt_ = 2; as.run(); // Check if basic exchange of packets happened. No retransmissions expected. EXPECT_EQ(as.total_resent_, 2); EXPECT_EQ(as.fake_sock_.sent_cnt_, 22); // Discovery + Request EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 10); EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 10); EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 10); EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 10); } TEST_F(AvalancheScenTest, Packet6Exchange) { CommandOptions opt; processCmdLine(opt, "perfdhcp -l fake -6 -R 10 --scenario avalanche -g single ::1"); NakedAvalancheScen as(opt); as.run(); // Check if basic exchange of packets happened. No retransmissions expected. EXPECT_EQ(as.total_resent_, 0); EXPECT_GE(as.fake_sock_.sent_cnt_, 20); // Solicit + Request EXPECT_GE(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 10); EXPECT_GE(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 10); EXPECT_GE(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 10); EXPECT_GE(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 10); } TEST_F(AvalancheScenTest, Packet6ExchangeOnlySA) { CommandOptions opt; processCmdLine(opt, "perfdhcp -l fake -6 -R 10 -i --scenario avalanche -g single ::1"); NakedAvalancheScen as(opt); as.run(); // Check if SA exchange of packets happened only. No retransmissions expected. EXPECT_EQ(as.total_resent_, 0); EXPECT_GE(as.fake_sock_.sent_cnt_, 10); // Solicit + Request EXPECT_GE(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 10); EXPECT_GE(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 10); EXPECT_THROW(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), isc::BadValue); } TEST_F(AvalancheScenTest, Packet6ExchangeWithRetransmissions) { CommandOptions opt; processCmdLine(opt, "perfdhcp -l fake -6 -R 10 --scenario avalanche -g single ::1"); NakedAvalancheScen as(opt); as.fake_sock_.initial_drops_cnt_ = 2; as.run(); // Check if basic exchange of packets happened. No retransmissions expected. EXPECT_EQ(as.total_resent_, 2); EXPECT_EQ(as.fake_sock_.sent_cnt_, 22); // Discovery + Request EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 10); EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 10); EXPECT_EQ(as.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 10); EXPECT_EQ(as.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 10); }