1 // Copyright (C) 2012-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include "command_options_helper.h"
10 #include "../basic_scen.h"
11 
12 #include <asiolink/io_address.h>
13 #include <exceptions/exceptions.h>
14 #include <dhcp/dhcp4.h>
15 #include <dhcp/pkt4.h>
16 #include <dhcp/iface_mgr.h>
17 #include <dhcp/option6_iaaddr.h>
18 #include <dhcp/option6_iaprefix.h>
19 #include <boost/date_time/posix_time/posix_time.hpp>
20 #include <boost/foreach.hpp>
21 
22 #include <algorithm>
23 #include <cstddef>
24 #include <stdint.h>
25 #include <string>
26 #include <fstream>
27 #include <mutex>
28 #include <gtest/gtest.h>
29 
30 using namespace std;
31 using namespace boost::posix_time;
32 using namespace isc;
33 using namespace isc::dhcp;
34 using namespace isc::perfdhcp;
35 
36 /// \brief FakeScenPerfSocket class that mocks PerfSocket.
37 ///
38 /// It stubs send and receive operations and collects statistics.
39 /// Beside that it simulates DHCP server responses for received
40 /// packets.
41 class FakeScenPerfSocket: public BasePerfSocket {
42 public:
43     /// \brief Default constructor for FakeScenPerfSocket.
FakeScenPerfSocket(CommandOptions & opt)44     FakeScenPerfSocket(CommandOptions &opt) :
45         opt_(opt),
46         iface_(boost::make_shared<Iface>("fake", 0)),
47         sent_cnt_(0),
48         recv_cnt_(0),
49         start_dropping_after_cnt_(100000) {};
50 
51     CommandOptions &opt_;
52 
53     IfacePtr iface_;  ///< Local fake interface.
54 
55     int sent_cnt_;  ///< Counter of sent packets.
56     int recv_cnt_;  ///< Counter of received packets.
57 
58     /// List of pairs <msg_type, trans_id> containing responses
59     /// planned to send to perfdhcp.
60     std::list<std::tuple<uint8_t, uint32_t>> planned_responses_;
61 
62     /// Mutex to protect internal state.
63     std::mutex mutex_;
64 
65     /// Limit for sent packets. After this limit not more packets
66     /// are sent. This simulate dropping responses.
67     int start_dropping_after_cnt_;
68 
69     /// \brief Simulate receiving DHCPv4 packet.
receive4(uint32_t,uint32_t)70     virtual dhcp::Pkt4Ptr receive4(uint32_t /*timeout_sec*/, uint32_t /*timeout_usec*/) override {
71         std::lock_guard<std::mutex> lock(mutex_);
72         recv_cnt_++;
73 
74         if (planned_responses_.empty() || sent_cnt_ >= start_dropping_after_cnt_) {
75             return Pkt4Ptr();
76         }
77         auto msg = planned_responses_.front();
78         planned_responses_.pop_front();
79         auto msg_type = std::get<0>(msg);
80         Pkt4Ptr pkt(new Pkt4(msg_type, std::get<1>(msg)));
81         OptionPtr opt_serverid = Option::factory(Option::V4,
82                                                  DHO_DHCP_SERVER_IDENTIFIER,
83                                                  OptionBuffer(4, 1));
84         pkt->setYiaddr(asiolink::IOAddress("127.0.0.1"));
85         pkt->addOption(opt_serverid);
86         pkt->updateTimestamp();
87         return (pkt);
88     };
89 
90     /// \brief Simulate receiving DHCPv6 packet.
receive6(uint32_t,uint32_t)91     virtual dhcp::Pkt6Ptr receive6(uint32_t /*timeout_sec*/, uint32_t /*timeout_usec*/) override {
92         std::lock_guard<std::mutex> lock(mutex_);
93         recv_cnt_++;
94 
95         if (planned_responses_.empty() || sent_cnt_ >= start_dropping_after_cnt_) {
96             return Pkt6Ptr();
97         }
98         auto msg = planned_responses_.front();
99         planned_responses_.pop_front();
100         auto msg_type = std::get<0>(msg);
101         Pkt6Ptr pkt(new Pkt6(msg_type, std::get<1>(msg)));
102         // Add IA_NA if requested by the client.
103         if (opt_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) {
104             OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA);
105             OptionPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
106                              isc::asiolink::IOAddress("fe80::abcd"), 300, 500));
107             opt_ia_na->addOption(iaaddr);
108             pkt->addOption(opt_ia_na);
109         }
110         // Add IA_PD if requested by the client.
111         if (opt_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) {
112             OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD);
113             OptionPtr iapref(new Option6IAPrefix(D6O_IAPREFIX,
114                              isc::asiolink::IOAddress("fe80::"), 64, 300, 500));
115             opt_ia_pd->addOption(iapref);
116             pkt->addOption(opt_ia_pd);
117         }
118         OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID));
119         std::vector<uint8_t> duid({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
120         OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid));
121         pkt->addOption(opt_serverid);
122         pkt->addOption(opt_clientid);
123         pkt->updateTimestamp();
124         return (pkt);
125     };
126 
127     /// \brief Simulate sending DHCPv4 packet.
send(const dhcp::Pkt4Ptr & pkt)128     virtual bool send(const dhcp::Pkt4Ptr& pkt) override {
129         std::lock_guard<std::mutex> lock(mutex_);
130         sent_cnt_++;
131         pkt->updateTimestamp();
132         if (sent_cnt_ >= start_dropping_after_cnt_) {
133             return true;
134         }
135         if (pkt->getType() == DHCPDISCOVER) {
136             planned_responses_.push_back(std::make_tuple(DHCPOFFER, pkt->getTransid()));
137         } else if (pkt->getType() == DHCPREQUEST) {
138             planned_responses_.push_back(std::make_tuple(DHCPACK, pkt->getTransid()));
139         } else {
140             assert(0);
141         }
142         return true;
143     };
144 
145     /// \brief Simulate sending DHCPv6 packet.
send(const dhcp::Pkt6Ptr & pkt)146     virtual bool send(const dhcp::Pkt6Ptr& pkt) override {
147         std::lock_guard<std::mutex> lock(mutex_);
148         sent_cnt_++;
149         pkt->updateTimestamp();
150         if (sent_cnt_ >= start_dropping_after_cnt_) {
151             return true;
152         }
153         if (pkt->getType() == DHCPV6_SOLICIT) {
154             planned_responses_.push_back(std::make_tuple(DHCPV6_ADVERTISE, pkt->getTransid()));
155         } else if (pkt->getType() == DHCPV6_REQUEST) {
156             planned_responses_.push_back(std::make_tuple(DHCPV6_REPLY, pkt->getTransid()));
157         } else {
158             assert(0);
159         }
160         return true;
161     };
162 
163     /// \brief Override getting interface.
getIface()164     virtual IfacePtr getIface() override { return iface_; }
165 
reset()166     void reset() {
167         std::lock_guard<std::mutex> lock(mutex_);
168         sent_cnt_ = 0;
169         recv_cnt_ = 0;
170     }
171 };
172 
173 
174 /// \brief NakedBasicScen class.
175 ///
176 /// It exposes BasicScen internals for UT.
177 class NakedBasicScen: public BasicScen {
178 public:
179     using BasicScen::basic_rate_control_;
180     using BasicScen::renew_rate_control_;
181     using BasicScen::release_rate_control_;
182     using BasicScen::tc_;
183 
184     FakeScenPerfSocket fake_sock_;
185 
NakedBasicScen(CommandOptions & opt)186     NakedBasicScen(CommandOptions &opt) : BasicScen(opt, fake_sock_), fake_sock_(opt) {};
187 
188 };
189 
190 
191 /// \brief Test Fixture Class
192 ///
193 /// This test fixture class is used to perform
194 /// unit tests on perfdhcp BasicScenTest class.
195 class BasicScenTest : public virtual ::testing::Test
196 {
197 public:
BasicScenTest()198     BasicScenTest() { }
199 
200     /// \brief Parse command line string with CommandOptions.
201     ///
202     /// \param cmdline command line string to be parsed.
203     /// \throw isc::Unexpected if unexpected error occurred.
204     /// \throw isc::InvalidParameter if command line is invalid.
processCmdLine(CommandOptions & opt,const std::string & cmdline) const205     void processCmdLine(CommandOptions &opt, const std::string& cmdline) const {
206         CommandOptionsHelper::process(opt, cmdline);
207     }
208 
209     /// \brief Get full path to a file in testdata directory.
210     ///
211     /// \param filename filename being appended to absolute
212     /// path to testdata directory
213     ///
214     /// \return full path to a file in testdata directory.
getFullPath(const std::string & filename) const215     std::string getFullPath(const std::string& filename) const {
216         std::ostringstream stream;
217         stream << TEST_DATA_DIR << "/" << filename;
218         return (stream.str());
219     }
220 };
221 
222 
223 // This test verifies that the class members are reset to expected values.
TEST_F(BasicScenTest,initial_settings)224 TEST_F(BasicScenTest, initial_settings) {
225     CommandOptions opt;
226     processCmdLine(opt, "perfdhcp -6 -l ethx -r 50 -f 30 -F 10 all");
227     NakedBasicScen bs(opt);
228 
229     EXPECT_EQ(50, bs.basic_rate_control_.getRate());
230     EXPECT_EQ(30, bs.renew_rate_control_.getRate());
231     EXPECT_EQ(10, bs.release_rate_control_.getRate());
232 }
233 
234 
TEST_F(BasicScenTest,Packet4Exchange)235 TEST_F(BasicScenTest, Packet4Exchange) {
236     CommandOptions opt;
237     processCmdLine(opt, "perfdhcp -l fake -r 100 -n 10 -g single 127.0.0.1");
238     NakedBasicScen bs(opt);
239     bs.run();
240     // The command line restricts the number of iterations to 10
241     // with -n 10 parameter.
242     EXPECT_GE(bs.fake_sock_.sent_cnt_, 20); // Discovery + Request
243     EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 10);
244     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 10);
245     EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 10);
246     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 10);
247 }
248 
TEST_F(BasicScenTest,Address4Unique)249 TEST_F(BasicScenTest, Address4Unique) {
250     // send more than 1 discover+request but with the same address
251     // counter of a unique addresses should be 1
252     CommandOptions opt;
253     processCmdLine(opt, "perfdhcp -u -l fake -r 10 -n 10 -g single 127.0.0.1");
254     NakedBasicScen bs(opt);
255     bs.run();
256     EXPECT_GE(bs.fake_sock_.sent_cnt_, 5); // Discovery + Request
257     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 1);
258     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 1);
259     EXPECT_GE(bs.tc_.getAllUniqueAddrReply().size(), 1);
260     EXPECT_GE(bs.tc_.getAllUniqueAddrAdvert().size(), 1);
261     EXPECT_EQ(bs.tc_.getStatsMgr().getNonUniqueAddrNum(ExchangeType::DO), 9);
262     EXPECT_EQ(bs.tc_.getStatsMgr().getNonUniqueAddrNum(ExchangeType::RA), 9);
263 }
264 
TEST_F(BasicScenTest,Address6Unique)265 TEST_F(BasicScenTest, Address6Unique) {
266     // send more than 1 solicit+request but with the same address
267     // counter of a unique addresses should be 1
268     CommandOptions opt;
269     processCmdLine(opt, "perfdhcp -6 -u -l fake -r 10 -n 10 -g single ::1");
270     NakedBasicScen bs(opt);
271     bs.run();
272     EXPECT_GE(bs.fake_sock_.sent_cnt_, 5); // Solicit + Request
273     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 1);
274     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 1);
275     EXPECT_GE(bs.tc_.getAllUniqueAddrReply().size(), 1);
276     EXPECT_GE(bs.tc_.getAllUniqueAddrAdvert().size(), 1);
277     EXPECT_EQ(bs.tc_.getStatsMgr().getNonUniqueAddrNum(ExchangeType::SA), 9);
278     EXPECT_EQ(bs.tc_.getStatsMgr().getNonUniqueAddrNum(ExchangeType::RR), 9);
279 }
280 
TEST_F(BasicScenTest,Packet4ExchangeMaxDrop10Proc)281 TEST_F(BasicScenTest, Packet4ExchangeMaxDrop10Proc) {
282     CommandOptions opt;
283 
284     // With the following command line we restrict the maximum
285     // number of dropped packets to 10% of all.
286     // Use templates for this test.
287     processCmdLine(opt, "perfdhcp -l fake -r 100 -R 20 -n 100"
288                    " -D 10% -L 10547  -g single"
289                    // \todo seems to be broken as it crashes building pkt
290                    // " -T " + getFullPath("discover-example.hex")
291                    // + " -T " + getFullPath("request4-example.hex")
292                    " 127.0.0.1");
293     // The number iterations is restricted by the percentage of
294     // dropped packets (-D 10%).
295     NakedBasicScen bs(opt);
296     bs.fake_sock_.start_dropping_after_cnt_ = 10;
297     bs.run();
298     EXPECT_GE(bs.fake_sock_.sent_cnt_, 15); // Discovery + Request
299     EXPECT_LE(bs.fake_sock_.sent_cnt_, 20); // Discovery + Request
300 
301     EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 1);
302     EXPECT_LE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO), 15);
303 
304     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 1);
305     EXPECT_LE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO), 15);
306 
307     EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 1);
308     EXPECT_LE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA), 15);
309 
310     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 1);
311     EXPECT_LE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA), 15);
312 }
313 
314 
TEST_F(BasicScenTest,Packet6Exchange)315 TEST_F(BasicScenTest, Packet6Exchange) {
316     // Set number of iterations to 10.
317     CommandOptions opt;
318     processCmdLine(opt, "perfdhcp -l fake -6 -r 100 -n 10 -g single -R 20 -L 10547 ::1");
319     // Set number of received packets equal to number of iterations.
320     // This simulates no packet drops.
321     NakedBasicScen bs(opt);
322     bs.run();
323     EXPECT_GE(bs.fake_sock_.sent_cnt_, 20); // Solicit + Request
324     EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 10);
325     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 10);
326     EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 10);
327     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 10);
328 }
329 
TEST_F(BasicScenTest,Packet6ExchangeMaxDrop3Pkt)330 TEST_F(BasicScenTest, Packet6ExchangeMaxDrop3Pkt) {
331     CommandOptions opt;
332     // The maximum number of dropped packets is 3 (because of -D 3).
333     processCmdLine(opt, "perfdhcp -l fake"
334                    " -6 -r 100 -n 100 -R 20 -D 3 -L 10547"
335                    // \todo seems to be broken as it crashes building pkt
336                    // " -T " + getFullPath("solicit-example.hex")
337                    // + " -T " + getFullPath("request6-example.hex")
338                    " ::1");
339 
340     // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges.
341     // The test function generates server's responses and passes it to the
342     // TestControl class methods for processing. The number of exchanges
343     // actually performed is controller by 'start_dropping_after_cnt_'.
344     // All exchanged packets carry the IA_NA option
345     // to simulate the IPv6 address acquisition and to verify that the
346     // IA_NA options returned by the server are processed correctly.
347     NakedBasicScen bs(opt);
348     bs.fake_sock_.start_dropping_after_cnt_ = 10;
349     bs.run();
350     EXPECT_GE(bs.fake_sock_.sent_cnt_, 10);
351     EXPECT_LE(bs.fake_sock_.sent_cnt_, 20);
352 
353     EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 1);
354     EXPECT_LE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA), 15);
355 
356     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 1);
357     EXPECT_LE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA), 15);
358 
359     EXPECT_GE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 1);
360     EXPECT_LE(bs.tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR), 15);
361 
362     EXPECT_GE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 1);
363     EXPECT_LE(bs.tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR), 15);
364 }
365