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