1 // Copyright (C) 2013-2021 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 <asiolink/io_address.h>
10 #include <cc/data.h>
11 #include <cc/command_interpreter.h>
12 #include <dhcp4/json_config_parser.h>
13 #include <dhcp4/tests/dhcp4_test_utils.h>
14 #include <dhcp/libdhcp++.h>
15 #include <dhcp/option4_addrlst.h>
16 #include <dhcp/option_int.h>
17 #include <dhcp/option_int_array.h>
18 #include <dhcp/option_custom.h>
19 #include <dhcp/iface_mgr.h>
20 #include <dhcp/tests/iface_mgr_test_config.h>
21 #include <dhcp/tests/pkt_captures.h>
22 #include <dhcpsrv/cfg_db_access.h>
23 #include <dhcpsrv/cfg_multi_threading.h>
24 #include <dhcpsrv/cfgmgr.h>
25 #include <dhcpsrv/lease.h>
26 #include <dhcpsrv/lease_mgr.h>
27 #include <dhcpsrv/lease_mgr_factory.h>
28 #include <log/logger_support.h>
29 #include <stats/stats_mgr.h>
30 #include <sstream>
31
32 using namespace std;
33 using namespace isc::asiolink;
34 using namespace isc::data;
35 using namespace isc::util;
36
37 namespace isc {
38 namespace dhcp {
39 namespace test {
40
BaseServerTest()41 BaseServerTest::BaseServerTest()
42 : original_datadir_(CfgMgr::instance().getDataDir()) {
43 CfgMgr::instance().setDataDir(TEST_DATA_BUILDDIR);
44 }
45
~BaseServerTest()46 BaseServerTest::~BaseServerTest() {
47 // Remove default lease file.
48 std::ostringstream s2;
49 s2 << CfgMgr::instance().getDataDir() << "/kea-leases4.csv";
50 static_cast<void>(::remove(s2.str().c_str()));
51
52 // Revert to original data directory.
53 CfgMgr::instance().setDataDir(original_datadir_);
54
55 // Revert to unit test logging, in case the test reconfigured it.
56 isc::log::initLogger();
57 }
58
Dhcpv4SrvTest()59 Dhcpv4SrvTest::Dhcpv4SrvTest()
60 : rcode_(-1), srv_(0), multi_threading_(false) {
61
62 // Wipe any existing statistics
63 isc::stats::StatsMgr::instance().removeAll();
64
65 subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
66 2000, 3000));
67 pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
68 subnet_->addPool(pool_);
69
70 // Add Router option.
71 Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
72 opt_routers->setAddress(IOAddress("192.0.2.2"));
73 subnet_->getCfgOption()->add(opt_routers, false, DHCP4_OPTION_SPACE);
74
75 CfgMgr::instance().clear();
76 CfgMgr::instance().setFamily(AF_INET);
77 CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
78 CfgMgr::instance().commit();
79
80 LibDHCP::clearRuntimeOptionDefs();
81
82 // Let's wipe all existing statistics.
83 isc::stats::StatsMgr::instance().removeAll();
84
85 // Reset the thread pool.
86 MultiThreadingMgr::instance().apply(false, 0, 0);
87 }
88
~Dhcpv4SrvTest()89 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
90
91 // Make sure that we revert to default value
92 CfgMgr::instance().clear();
93
94 LibDHCP::clearRuntimeOptionDefs();
95
96 // Let's wipe all existing statistics.
97 isc::stats::StatsMgr::instance().removeAll();
98
99 // Reset the thread pool.
100 MultiThreadingMgr::instance().apply(false, 0, 0);
101 }
102
addPrlOption(Pkt4Ptr & pkt)103 void Dhcpv4SrvTest::addPrlOption(Pkt4Ptr& pkt) {
104
105 OptionUint8ArrayPtr option_prl =
106 OptionUint8ArrayPtr(new OptionUint8Array(Option::V4,
107 DHO_DHCP_PARAMETER_REQUEST_LIST));
108
109 // Let's request options that have been configured for the subnet.
110 option_prl->addValue(DHO_DOMAIN_NAME_SERVERS);
111 option_prl->addValue(DHO_DOMAIN_NAME);
112 option_prl->addValue(DHO_LOG_SERVERS);
113 option_prl->addValue(DHO_COOKIE_SERVERS);
114 // Let's also request the option that hasn't been configured. In such
115 // case server should ignore request for this particular option.
116 option_prl->addValue(DHO_LPR_SERVERS);
117 // And add 'Parameter Request List' option into the DISCOVER packet.
118 pkt->addOption(option_prl);
119 }
120
121 void
configureRequestedOptions()122 Dhcpv4SrvTest::configureRequestedOptions() {
123 // dns-servers
124 Option4AddrLstPtr
125 option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS));
126 option_dns_servers->addAddress(IOAddress("192.0.2.1"));
127 option_dns_servers->addAddress(IOAddress("192.0.2.100"));
128 ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_dns_servers, false, DHCP4_OPTION_SPACE));
129
130 // domain-name
131 OptionDefinition def("domain-name", DHO_DOMAIN_NAME, DHCP4_OPTION_SPACE,
132 OPT_FQDN_TYPE);
133 OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
134 option_domain_name->writeFqdn("example.com");
135 subnet_->getCfgOption()->add(option_domain_name, false, DHCP4_OPTION_SPACE);
136
137 // log-servers
138 Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS));
139 option_log_servers->addAddress(IOAddress("192.0.2.2"));
140 option_log_servers->addAddress(IOAddress("192.0.2.10"));
141 ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_log_servers, false, DHCP4_OPTION_SPACE));
142
143 // cookie-servers
144 Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS));
145 option_cookie_servers->addAddress(IOAddress("192.0.2.1"));
146 ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_cookie_servers, false, DHCP4_OPTION_SPACE));
147 }
148
149 void
configureServerIdentifier()150 Dhcpv4SrvTest::configureServerIdentifier() {
151 CfgMgr& cfg_mgr = CfgMgr::instance();
152 CfgSubnets4Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets4();
153
154 // Build and add subnet2.
155 Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 1200, 2400, 3600, 2));
156 Pool4Ptr pool(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.200")));
157 // Add server identifier to the pool.
158 OptionCustomPtr server_id = makeServerIdOption(IOAddress("192.0.2.254"));
159 CfgOptionPtr cfg_option = pool->getCfgOption();
160 cfg_option->add(server_id, false, DHCP4_OPTION_SPACE);
161 subnet2->addPool(pool);
162
163 // Add a second pool.
164 pool.reset(new Pool4(IOAddress("192.0.2.201"), IOAddress("192.0.2.220")));
165 subnet2->addPool(pool);
166
167 subnets->add(subnet2);
168
169 // Build and add subnet3.
170 Triplet<uint32_t> unspec;
171 Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"), 24, unspec, unspec, 3600, 3));
172 pool.reset(new Pool4(IOAddress("192.0.3.100"), IOAddress("192.0.3.200")));
173 subnet3->addPool(pool);
174 subnet3->setT1Percent(0.5);
175 subnet3->setT2Percent(0.75);
176 subnet3->setCalculateTeeTimes(true);
177
178 // Add server identifier.
179 server_id = makeServerIdOption(IOAddress("192.0.3.254"));
180 cfg_option = subnet3->getCfgOption();
181 cfg_option->add(server_id, false, DHCP4_OPTION_SPACE);
182
183 subnets->add(subnet3);
184
185 // Build and add subnet4.
186 Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.4.0"), 24, unspec, unspec, 3600, 4));
187 pool.reset(new Pool4(IOAddress("192.0.4.100"), IOAddress("192.0.4.200")));
188 subnet4->addPool(pool);
189 subnet4->setCalculateTeeTimes(false);
190
191 subnets->add(subnet4);
192
193 // Build and add subnet5.
194 Subnet4Ptr subnet5(new Subnet4(IOAddress("192.0.5.0"), 24, unspec, unspec, 3600, 5));
195 pool.reset(new Pool4(IOAddress("192.0.5.100"), IOAddress("192.0.5.200")));
196 subnet5->addPool(pool);
197 subnet5->setCalculateTeeTimes(false);
198
199 subnets->add(subnet5);
200
201 CfgOptionPtr options(new CfgOption());
202 OptionDescriptor desc(false);
203 desc.option_ = makeServerIdOption(IOAddress("192.0.5.254"));
204 options->add(desc, DHCP4_OPTION_SPACE);
205 CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("foo", ExpressionPtr(), "", true, false, options);
206 subnet5->requireClientClass("foo");
207
208 // Build and add subnet6.
209 Subnet4Ptr subnet6(new Subnet4(IOAddress("192.0.6.0"), 24, unspec, unspec, 3600, 6));
210 pool.reset(new Pool4(IOAddress("192.0.6.100"), IOAddress("192.0.6.200")));
211 subnet6->addPool(pool);
212 subnet6->setCalculateTeeTimes(false);
213
214 subnets->add(subnet6);
215
216 options.reset(new CfgOption());
217 OptionDescriptor desc_other(false);
218 desc_other.option_ = makeFqdnListOption();
219 options->add(desc_other, DHCP4_OPTION_SPACE);
220 CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("bar", ExpressionPtr(), "", true, false, options);
221 subnet6->requireClientClass("bar");
222
223 // Build and add subnet7.
224 Subnet4Ptr subnet7(new Subnet4(IOAddress("192.0.7.0"), 24, unspec, unspec, 3600, 7));
225 pool.reset(new Pool4(IOAddress("192.0.7.100"), IOAddress("192.0.7.200")));
226 subnet7->addPool(pool);
227 subnet7->setCalculateTeeTimes(false);
228
229 subnets->add(subnet7);
230
231 options.reset();
232 CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("xyz", ExpressionPtr(), "", true, false, options);
233 subnet7->requireClientClass("xyz");
234
235 // Build and add a shared-network.
236 CfgSharedNetworks4Ptr networks = cfg_mgr.getStagingCfg()->getCfgSharedNetworks4();
237 SharedNetwork4Ptr network1(new SharedNetwork4("one"));
238 network1->add(subnet4);
239
240 // Add server identifier.
241 server_id = makeServerIdOption(IOAddress("192.0.4.254"));
242 cfg_option = network1->getCfgOption();
243 cfg_option->add(server_id, false, DHCP4_OPTION_SPACE);
244
245 networks->add(network1);
246
247 // Add a global server identifier.
248 cfg_option = cfg_mgr.getStagingCfg()->getCfgOption();
249 server_id = makeServerIdOption(IOAddress("10.0.0.254"));
250 cfg_option->add(server_id, false, DHCP4_OPTION_SPACE);
251
252 // Commit the config.
253 cfg_mgr.commit();
254 }
255
256 void
messageCheck(const Pkt4Ptr & q,const Pkt4Ptr & a)257 Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
258 ASSERT_TRUE(q);
259 ASSERT_TRUE(a);
260
261 EXPECT_EQ(q->getHops(), a->getHops());
262 EXPECT_EQ(q->getIface(), a->getIface());
263 EXPECT_EQ(q->getIndex(), a->getIndex());
264 EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
265 // When processing an incoming packet the remote address
266 // is copied as a src address, and the source address is
267 // copied as a remote address to the response.
268 EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr());
269 EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr());
270
271 // Check that the server identifier is present in the response.
272 // Presence (or absence) of other options is checked elsewhere.
273 EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
274
275 // Check that something is offered
276 EXPECT_NE("0.0.0.0", a->getYiaddr().toText());
277 }
278
279 ::testing::AssertionResult
basicOptionsPresent(const Pkt4Ptr & pkt)280 Dhcpv4SrvTest::basicOptionsPresent(const Pkt4Ptr& pkt) {
281 std::ostringstream errmsg;
282 errmsg << "option missing in the response";
283 if (!pkt->getOption(DHO_DOMAIN_NAME)) {
284 return (::testing::AssertionFailure(::testing::Message()
285 << "domain-name " << errmsg.str()));
286
287 } else if (!pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) {
288 return (::testing::AssertionFailure(::testing::Message()
289 << "dns-servers " << errmsg.str()));
290
291 } else if (!pkt->getOption(DHO_SUBNET_MASK)) {
292 return (::testing::AssertionFailure(::testing::Message()
293 << "subnet-mask " << errmsg.str()));
294
295 } else if (!pkt->getOption(DHO_ROUTERS)) {
296 return (::testing::AssertionFailure(::testing::Message() << "routers "
297 << errmsg.str()));
298
299 } else if (!pkt->getOption(DHO_DHCP_LEASE_TIME)) {
300 return (::testing::AssertionFailure(::testing::Message() <<
301 "dhcp-lease-time " << errmsg.str()));
302
303 }
304 return (::testing::AssertionSuccess());
305
306 }
307
308 ::testing::AssertionResult
noBasicOptions(const Pkt4Ptr & pkt)309 Dhcpv4SrvTest::noBasicOptions(const Pkt4Ptr& pkt) {
310 std::ostringstream errmsg;
311 errmsg << "option present in the response";
312 if (pkt->getOption(DHO_DOMAIN_NAME)) {
313 return (::testing::AssertionFailure(::testing::Message()
314 << "domain-name " << errmsg.str()));
315
316 } else if (pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) {
317 return (::testing::AssertionFailure(::testing::Message()
318 << "dns-servers " << errmsg.str()));
319
320 } else if (pkt->getOption(DHO_SUBNET_MASK)) {
321 return (::testing::AssertionFailure(::testing::Message()
322 << "subnet-mask " << errmsg.str()));
323
324 } else if (pkt->getOption(DHO_ROUTERS)) {
325 return (::testing::AssertionFailure(::testing::Message() << "routers "
326 << errmsg.str()));
327
328 } else if (pkt->getOption(DHO_DHCP_LEASE_TIME)) {
329 return (::testing::AssertionFailure(::testing::Message()
330 << "dhcp-lease-time " << errmsg.str()));
331
332 }
333 return (::testing::AssertionSuccess());
334 }
335
336 ::testing::AssertionResult
requestedOptionsPresent(const Pkt4Ptr & pkt)337 Dhcpv4SrvTest::requestedOptionsPresent(const Pkt4Ptr& pkt) {
338 std::ostringstream errmsg;
339 errmsg << "option missing in the response";
340 if (!pkt->getOption(DHO_LOG_SERVERS)) {
341 return (::testing::AssertionFailure(::testing::Message()
342 << "log-servers " << errmsg.str()));
343
344 } else if (!pkt->getOption(DHO_COOKIE_SERVERS)) {
345 return (::testing::AssertionFailure(::testing::Message()
346 << "cookie-servers " << errmsg.str()));
347
348 }
349 return (::testing::AssertionSuccess());
350 }
351
352 ::testing::AssertionResult
noRequestedOptions(const Pkt4Ptr & pkt)353 Dhcpv4SrvTest::noRequestedOptions(const Pkt4Ptr& pkt) {
354 std::ostringstream errmsg;
355 errmsg << "option present in the response";
356 if (pkt->getOption(DHO_LOG_SERVERS)) {
357 return (::testing::AssertionFailure(::testing::Message()
358 << "log-servers " << errmsg.str()));
359
360 } else if (pkt->getOption(DHO_COOKIE_SERVERS)) {
361 return (::testing::AssertionFailure(::testing::Message()
362 << "cookie-servers " << errmsg.str()));
363
364 }
365 return (::testing::AssertionSuccess());
366 }
367
368 OptionPtr
generateClientId(size_t size)369 Dhcpv4SrvTest::generateClientId(size_t size /*= 4*/) {
370
371 OptionBuffer clnt_id(size);
372 for (size_t i = 0; i < size; i++) {
373 clnt_id[i] = 100 + i;
374 }
375
376 client_id_ = ClientIdPtr(new ClientId(clnt_id));
377
378 return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
379 clnt_id.begin(),
380 clnt_id.begin() + size)));
381 }
382
383 HWAddrPtr
generateHWAddr(size_t size)384 Dhcpv4SrvTest::generateHWAddr(size_t size /*= 6*/) {
385 const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h)
386 OptionBuffer mac(size);
387 for (size_t i = 0; i < size; ++i) {
388 mac[i] = 50 + i;
389 }
390 return (HWAddrPtr(new HWAddr(mac, hw_type)));
391 }
392
393 OptionCustomPtr
makeServerIdOption(const IOAddress & address)394 Dhcpv4SrvTest::makeServerIdOption(const IOAddress& address) {
395 OptionDefinitionPtr option_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
396 DHO_DHCP_SERVER_IDENTIFIER);
397 OptionCustomPtr server_id(new OptionCustom(*option_def, Option::V4));
398 server_id->writeAddress(address);
399 return (server_id);
400 }
401
402 OptionPtr
makeFqdnListOption()403 Dhcpv4SrvTest::makeFqdnListOption() {
404 OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
405 DHO_DOMAIN_SEARCH);
406
407 // Prepare buffer holding an array of FQDNs.
408 const uint8_t fqdn[] = {
409 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
410 7, 101, 120, 97, 109, 112, 108, 101, // "example"
411 3, 99, 111, 109, // "com"
412 0
413 };
414
415 // Initialize a vector with the FQDN data.
416 std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn));
417
418 OptionPtr option = def->optionFactory(Option::V4, DHO_DOMAIN_SEARCH,
419 fqdn_buf.begin(), fqdn_buf.end());
420
421 return (option);
422 }
423
424 void
checkAddressParams(const Pkt4Ptr & rsp,const Subnet4Ptr subnet,bool t1_present,bool t2_present,uint32_t expected_valid)425 Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp,
426 const Subnet4Ptr subnet,
427 bool t1_present,
428 bool t2_present,
429 uint32_t expected_valid) {
430
431 // Technically inPool implies inRange, but let's be on the safe
432 // side and check both.
433 EXPECT_TRUE(subnet->inRange(rsp->getYiaddr()));
434 EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, rsp->getYiaddr()));
435
436 // Check lease time
437 OptionUint32Ptr opt = boost::dynamic_pointer_cast<
438 OptionUint32>(rsp->getOption(DHO_DHCP_LEASE_TIME));
439 if (!opt) {
440 ADD_FAILURE() << "Lease time option missing in response or the"
441 " option has unexpected type";
442 } else if (subnet->getValid().getMin() != subnet->getValid().getMax()) {
443 EXPECT_GE(opt->getValue(), subnet->getValid().getMin());
444 EXPECT_LE(opt->getValue(), subnet->getValid().getMax());
445 } else {
446 EXPECT_EQ(opt->getValue(), subnet->getValid());
447 }
448
449 // Check expected value when wanted.
450 if (opt && expected_valid) {
451 EXPECT_EQ(opt->getValue(), expected_valid);
452 }
453
454 // Check T1 timer
455 opt = boost::dynamic_pointer_cast<
456 OptionUint32>(rsp->getOption(DHO_DHCP_RENEWAL_TIME));
457 if (t1_present) {
458 ASSERT_TRUE(opt) << "Required T1 option missing or it has"
459 " an unexpected type";
460 EXPECT_EQ(opt->getValue(), subnet->getT1());
461 } else {
462 EXPECT_FALSE(opt);
463 }
464
465 // Check T2 timer
466 opt = boost::dynamic_pointer_cast<
467 OptionUint32>(rsp->getOption(DHO_DHCP_REBINDING_TIME));
468 if (t2_present) {
469 ASSERT_TRUE(opt) << "Required T2 option missing or it has"
470 " an unexpected type";
471 EXPECT_EQ(opt->getValue(), subnet->getT2());
472 } else {
473 EXPECT_FALSE(opt);
474 }
475 }
476
477 void
checkResponse(const Pkt4Ptr & rsp,int expected_message_type,uint32_t expected_transid)478 Dhcpv4SrvTest::checkResponse(const Pkt4Ptr& rsp, int expected_message_type,
479 uint32_t expected_transid) {
480 ASSERT_TRUE(rsp);
481 EXPECT_EQ(expected_message_type,
482 static_cast<int>(rsp->getType()));
483 EXPECT_EQ(expected_transid, rsp->getTransid());
484 }
485
486 Lease4Ptr
checkLease(const Pkt4Ptr & rsp,const OptionPtr & client_id,const HWAddrPtr &,const IOAddress & expected_addr)487 Dhcpv4SrvTest::checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
488 const HWAddrPtr&, const IOAddress& expected_addr) {
489
490 ClientIdPtr id;
491 if (client_id) {
492 OptionBuffer data = client_id->getData();
493 id.reset(new ClientId(data));
494 }
495
496 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
497 if (!lease) {
498 cout << "Lease for " << expected_addr
499 << " not found in the database backend.";
500 return (Lease4Ptr());
501 }
502
503 EXPECT_EQ(rsp->getYiaddr(), expected_addr);
504
505 EXPECT_EQ(expected_addr, lease->addr_);
506 if (client_id) {
507 EXPECT_TRUE(*lease->client_id_ == *id);
508 }
509 EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
510
511 return (lease);
512 }
513
514 void
checkServerId(const Pkt4Ptr & rsp,const OptionPtr & expected_srvid)515 Dhcpv4SrvTest::checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) {
516 // Check that server included its server-id
517 OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER);
518 ASSERT_TRUE(opt);
519 EXPECT_EQ(opt->getType(), expected_srvid->getType() );
520 EXPECT_EQ(opt->len(), expected_srvid->len() );
521 EXPECT_TRUE(opt->getData() == expected_srvid->getData());
522 }
523
524 void
checkClientId(const Pkt4Ptr & rsp,const OptionPtr & expected_clientid)525 Dhcpv4SrvTest::checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) {
526
527 bool include_clientid =
528 CfgMgr::instance().getCurrentCfg()->getEchoClientId();
529
530 // check that server included our own client-id
531 OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
532 if (include_clientid) {
533 // Normal mode: echo back (see RFC6842)
534 ASSERT_TRUE(opt);
535 EXPECT_EQ(expected_clientid->getType(), opt->getType());
536 EXPECT_EQ(expected_clientid->len(), opt->len());
537 EXPECT_TRUE(expected_clientid->getData() == opt->getData());
538 } else {
539 // Backward compatibility mode for pre-RFC6842 devices
540 ASSERT_FALSE(opt);
541 }
542 }
543
544 void
checkServerIdOption(const Pkt4Ptr & packet,const IOAddress & expected_address)545 Dhcpv4SrvTest::checkServerIdOption(const Pkt4Ptr& packet, const IOAddress& expected_address) {
546 OptionPtr opt = packet->getOption(DHO_DHCP_SERVER_IDENTIFIER);
547 ASSERT_TRUE(opt) << "no server-id option";
548
549 OptionCustomPtr server_id_opt = boost::dynamic_pointer_cast<OptionCustom>(opt);
550 ASSERT_TRUE(server_id_opt) << "server-id option is not an instance of OptionCustom";
551
552 EXPECT_EQ(expected_address, server_id_opt->readAddress());
553 }
554
555 ::testing::AssertionResult
createPacketFromBuffer(const Pkt4Ptr & src_pkt,Pkt4Ptr & dst_pkt)556 Dhcpv4SrvTest::createPacketFromBuffer(const Pkt4Ptr& src_pkt,
557 Pkt4Ptr& dst_pkt) {
558 // Create on-wire format of the packet. If pack() has been called
559 // on this instance of the packet already, the next call to pack()
560 // should remove all contents of the output buffer.
561 try {
562 src_pkt->pack();
563 } catch (const Exception& ex) {
564 return (::testing::AssertionFailure(::testing::Message()
565 << "Failed to parse source packet: "
566 << ex.what()));
567 }
568 // Get the output buffer from the source packet.
569 const util::OutputBuffer& buf = src_pkt->getBuffer();
570 // Create a copy of the packet using the output buffer from the source
571 // packet.
572 try {
573 dst_pkt.reset(new Pkt4(static_cast<const uint8_t*>(buf.getData()),
574 buf.getLength()));
575 } catch (const Exception& ex) {
576 return (::testing::AssertionFailure(::testing::Message()
577 << "Failed to create a"
578 " destination packet from"
579 " the buffer: "
580 << ex.what()));
581 }
582
583 try {
584 // Parse the new packet and return to the caller.
585 dst_pkt->unpack();
586 } catch (const Exception& ex) {
587 return (::testing::AssertionFailure(::testing::Message()
588 << "Failed to parse a"
589 << " destination packet: "
590 << ex.what()));
591 }
592
593 return (::testing::AssertionSuccess());
594 }
595
596 void
597 // cppcheck-suppress unusedFunction
TearDown()598 Dhcpv4SrvTest::TearDown() {
599
600 CfgMgr::instance().clear();
601
602 // Close all open sockets.
603 IfaceMgr::instance().closeSockets();
604
605 // Some unit tests override the default packet filtering class, used
606 // by the IfaceMgr. The dummy class, called PktFilterTest, reports the
607 // capability to directly respond to the clients without IP address
608 // assigned. This capability is not supported by the default packet
609 // filtering class: PktFilterInet. Therefore setting the dummy class
610 // allows to test scenarios, when server responds to the broadcast address
611 // on client's request, despite having support for direct response.
612 // The following call restores the use of original packet filtering class
613 // after the test.
614 try {
615 IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
616
617 } catch (const Exception& ex) {
618 FAIL() << "Failed to restore the default (PktFilterInet) packet filtering"
619 << " class after the test. Exception has been caught: "
620 << ex.what();
621 }
622 }
623
624 void
testDiscoverRequest(const uint8_t msg_type)625 Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
626 IfaceMgrTestConfig test_config(true);
627 IfaceMgr::instance().openSockets4();
628
629 // Create an instance of the tested class.
630 boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
631
632 // Initialize the source HW address.
633 vector<uint8_t> mac(6);
634 for (uint8_t i = 0; i < 6; ++i) {
635 mac[i] = i * 10;
636 }
637 // Initialized the destination HW address.
638 vector<uint8_t> dst_mac(6);
639 for (uint8_t i = 0; i < 6; ++i) {
640 dst_mac[i] = i * 20;
641 }
642 // Create a DHCP message. It will be used to simulate the
643 // incoming message.
644 boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234));
645 // Create a response message. It will hold a response packet.
646 // Initially, set it to NULL.
647 boost::shared_ptr<Pkt4> rsp;
648 // Set the name of the interface on which packet is received.
649 req->setIface("eth0");
650 // Set the interface index.
651 req->setIndex(ETH0_INDEX);
652 // Set the target HW address. This value is normally used to
653 // construct the data link layer header.
654 req->setRemoteHWAddr(1, 6, dst_mac);
655 // Set the HW address. This value is set on DHCP level (in chaddr).
656 req->setHWAddr(1, 6, mac);
657 // Set local HW address. It is used to construct the data link layer
658 // header.
659 req->setLocalHWAddr(1, 6, mac);
660 // Set target IP address.
661 req->setRemoteAddr(IOAddress("192.0.2.55"));
662 // Set relay address and hops.
663 req->setGiaddr(IOAddress("192.0.2.10"));
664 req->setHops(1);
665
666 // We are going to test that certain options are returned
667 // in the response message when requested using 'Parameter
668 // Request List' option. Let's configure those options that
669 // are returned when requested.
670 configureRequestedOptions();
671
672 // Create a copy of the original packet by parsing its wire format.
673 // This simulates the real life scenario when we process the packet
674 // which was parsed from its wire format.
675 Pkt4Ptr received;
676 ASSERT_TRUE(createPacketFromBuffer(req, received));
677 // Set interface. It is required for the server to generate server id.
678 received->setIface("eth0");
679 received->setIndex(ETH0_INDEX);
680 if (msg_type == DHCPDISCOVER) {
681 ASSERT_NO_THROW(rsp = srv->processDiscover(received));
682
683 // Should return OFFER
684 ASSERT_TRUE(rsp);
685 EXPECT_EQ(DHCPOFFER, rsp->getType());
686
687 } else {
688 ASSERT_NO_THROW(rsp = srv->processRequest(received));
689
690 // Should return ACK
691 ASSERT_TRUE(rsp);
692 EXPECT_EQ(DHCPACK, rsp->getType());
693
694 }
695
696 messageCheck(received, rsp);
697
698 // Basic options should be present when we got the lease.
699 EXPECT_TRUE(basicOptionsPresent(rsp));
700 // We did not request any options so these should not be present
701 // in the RSP.
702 EXPECT_TRUE(noRequestedOptions(rsp));
703
704 // Repeat the test but request some options.
705 // Add 'Parameter Request List' option.
706 addPrlOption(req);
707
708 ASSERT_TRUE(createPacketFromBuffer(req, received));
709 ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
710
711 // Set interface. It is required for the server to generate server id.
712 received->setIface("eth0");
713 received->setIndex(ETH0_INDEX);
714
715 if (msg_type == DHCPDISCOVER) {
716 ASSERT_NO_THROW(rsp = srv->processDiscover(received));
717
718 // Should return non-NULL packet.
719 ASSERT_TRUE(rsp);
720 EXPECT_EQ(DHCPOFFER, rsp->getType());
721
722 } else {
723 ASSERT_NO_THROW(rsp = srv->processRequest(received));
724
725 // Should return non-NULL packet.
726 ASSERT_TRUE(rsp);
727 EXPECT_EQ(DHCPACK, rsp->getType());
728 }
729
730 // Check that the requested options are returned.
731 EXPECT_TRUE(basicOptionsPresent(rsp));
732 EXPECT_TRUE(requestedOptionsPresent(rsp));
733
734 // The following part of the test will test that the NAK is sent when
735 // there is no address pool configured. In the same time, we expect
736 // that the requested options are not included in NAK message, but that
737 // they are only included when yiaddr is set to non-zero value.
738 ASSERT_NO_THROW(subnet_->delPools(Lease::TYPE_V4));
739
740 // There has been a lease allocated for the particular client. So,
741 // even though we deleted the subnet, the client would get the
742 // existing lease (not a NAK). Therefore, we have to change the chaddr
743 // in the packet so as the existing lease is not returned.
744 req->setHWAddr(1, 6, std::vector<uint8_t>(2, 6));
745 ASSERT_TRUE(createPacketFromBuffer(req, received));
746 ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
747
748 // Set interface. It is required for the server to generate server id.
749 received->setIface("eth0");
750 received->setIndex(ETH0_INDEX);
751
752 if (msg_type == DHCPDISCOVER) {
753 ASSERT_NO_THROW(rsp = srv->processDiscover(received));
754 // Should return NULL packet.
755 ASSERT_FALSE(rsp);
756
757 } else {
758 ASSERT_NO_THROW(rsp = srv->processRequest(received));
759 // Should return non-NULL packet.
760 ASSERT_TRUE(rsp);
761 // We should get the NAK packet with yiaddr set to 0.
762 EXPECT_EQ(DHCPNAK, rsp->getType());
763 ASSERT_EQ("0.0.0.0", rsp->getYiaddr().toText());
764
765 // Make sure that none of the requested options is returned in NAK.
766 // Also options such as Routers or Subnet Mask should not be there,
767 // because lease hasn't been acquired.
768 EXPECT_TRUE(noRequestedOptions(rsp));
769 EXPECT_TRUE(noBasicOptions(rsp));
770 }
771 }
772
773 void
buildCfgOptionTest(IOAddress expected_server_id,Pkt4Ptr & query,IOAddress requested,IOAddress server_id)774 Dhcpv4SrvTest::buildCfgOptionTest(IOAddress expected_server_id,
775 Pkt4Ptr& query,
776 IOAddress requested,
777 IOAddress server_id) {
778 OptionDefinitionPtr req_addr_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
779 DHO_DHCP_REQUESTED_ADDRESS);
780 ASSERT_TRUE(req_addr_def);
781
782 OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
783 DHO_SUBNET_SELECTION);
784 ASSERT_TRUE(sbnsel_def);
785
786 OptionCustomPtr req_addr(new OptionCustom(*req_addr_def, Option::V4));
787 req_addr->writeAddress(requested);
788
789 OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
790 sbnsel->writeAddress(requested);
791
792 query->addOption(req_addr);
793 query->addOption(sbnsel);
794 query->addOption(makeServerIdOption(server_id));
795
796 Pkt4Ptr response;
797 ASSERT_NO_THROW(response = srv_.processRequest(query));
798
799 checkServerIdOption(response, expected_server_id);
800
801 ASSERT_NO_THROW(query->delOption(DHO_DHCP_REQUESTED_ADDRESS));
802 ASSERT_NO_THROW(query->delOption(DHO_SUBNET_SELECTION));
803 ASSERT_NO_THROW(query->delOption(DHO_DHCP_SERVER_IDENTIFIER));
804 }
805
806 void
configure(const std::string & config,const bool commit,const bool open_sockets)807 Dhcpv4SrvTest::configure(const std::string& config,
808 const bool commit,
809 const bool open_sockets) {
810 configure(config, srv_, commit, open_sockets);
811 }
812
813 void
configure(const std::string & config,NakedDhcpv4Srv & srv,const bool commit,const bool open_sockets)814 Dhcpv4SrvTest::configure(const std::string& config,
815 NakedDhcpv4Srv& srv,
816 const bool commit,
817 const bool open_sockets) {
818 setenv("KEA_LFC_EXECUTABLE", KEA_LFC_EXECUTABLE, 1);
819 MultiThreadingCriticalSection cs;
820 ConstElementPtr json;
821 try {
822 json = parseJSON(config);
823 } catch (const std::exception& ex){
824 // Fatal failure on parsing error
825 FAIL() << "parsing failure:"
826 << "config:" << config << std::endl
827 << "error: " << ex.what();
828 }
829
830 ConstElementPtr status;
831
832 // Disable the re-detect flag
833 disableIfacesReDetect(json);
834
835 // Set up multi-threading
836 configureMultiThreading(multi_threading_, json);
837
838 // Configure the server and make sure the config is accepted
839 EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
840 ASSERT_TRUE(status);
841 int rcode;
842 ConstElementPtr comment = config::parseAnswer(rcode, status);
843 ASSERT_EQ(0, rcode) << comment->stringValue();
844
845 // Use specified lease database backend.
846 ASSERT_NO_THROW( {
847 CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
848 cfg_db->setAppendedParameters("universe=4");
849 cfg_db->createManagers();
850 } );
851
852 try {
853 CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading());
854 } catch (const std::exception& ex) {
855 ADD_FAILURE() << "Error applying multi threading settings: "
856 << ex.what();
857 }
858
859 if (commit) {
860 CfgMgr::instance().commit();
861 }
862
863 // Opening sockets.
864 if (open_sockets) {
865 IfaceMgr::instance().openSockets4();
866 }
867 }
868
869 std::pair<int, std::string>
configureWithStatus(const std::string & config,NakedDhcpv4Srv & srv,const bool commit,const int exp_rcode)870 Dhcpv4SrvTest::configureWithStatus(const std::string& config, NakedDhcpv4Srv& srv,
871 const bool commit, const int exp_rcode) {
872 ConstElementPtr json;
873 try {
874 json = parseJSON(config);
875 } catch (const std::exception& ex){
876 // Fatal failure on parsing error
877
878 std::stringstream tmp;
879 tmp << "parsing failure:"
880 << "config:" << config << std::endl
881 << "error: " << ex.what();
882 ADD_FAILURE() << tmp.str();
883 return (std::make_pair(-1, tmp.str()));
884 }
885
886 ConstElementPtr status;
887
888 // Disable the re-detect flag
889 disableIfacesReDetect(json);
890
891 // Configure the server and make sure the config is accepted
892 EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
893 EXPECT_TRUE(status);
894 if (!status) {
895 return (make_pair(-1, "configureDhcp4Server didn't return anything"));
896 }
897
898 int rcode;
899 ConstElementPtr comment = config::parseAnswer(rcode, status);
900 EXPECT_EQ(exp_rcode, rcode) << comment->stringValue();
901
902 // Use specified lease database backend.
903 if (rcode == 0) {
904 EXPECT_NO_THROW( {
905 CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
906 cfg_db->setAppendedParameters("universe=4");
907 cfg_db->createManagers();
908 } );
909 if (commit) {
910 CfgMgr::instance().commit();
911 }
912 }
913
914 return (std::make_pair(rcode, comment->stringValue()));
915 }
916
917 Dhcpv4Exchange
createExchange(const Pkt4Ptr & query)918 Dhcpv4SrvTest::createExchange(const Pkt4Ptr& query) {
919 bool drop = false;
920 Subnet4Ptr subnet = srv_.selectSubnet(query, drop);
921 EXPECT_FALSE(drop);
922 Dhcpv4Exchange ex(srv_.alloc_engine_, query, subnet, drop);
923 EXPECT_FALSE(drop);
924 return (ex);
925 }
926
927 void
pretendReceivingPkt(NakedDhcpv4Srv & srv,const std::string & config,uint8_t pkt_type,const std::string & stat_name)928 Dhcpv4SrvTest::pretendReceivingPkt(NakedDhcpv4Srv& srv, const std::string& config,
929 uint8_t pkt_type, const std::string& stat_name) {
930
931 IfaceMgrTestConfig test_config(true);
932 IfaceMgr::instance().openSockets4();
933
934 // Apply the configuration we just received.
935 configure(config);
936
937 // Let's just use one of the actual captured packets that we have.
938 Pkt4Ptr pkt = PktCaptures::captureRelayedDiscover();
939
940 // We just need to tweak it a it, to pretend that its type is as desired.
941 // Note that when receiving a packet, its on-wire form is stored in data_
942 // field. Most methods (including setType()) operates on option objects
943 // (objects stored in options_ after unpack() is called). Finally, outgoing
944 // packets are stored in out_buffer_. So we need to go through the full
945 // unpack/tweak/pack cycle and do repack, i.e. move the output buffer back
946 // to incoming buffer.
947 pkt->unpack();
948 pkt->setType(pkt_type); // Set message type.
949 pkt->pack();
950 pkt->data_.resize(pkt->getBuffer().getLength());
951 // Copy out_buffer_ to data_ to pretend that it's what was just received.
952 memcpy(&pkt->data_[0], pkt->getBuffer().getData(), pkt->getBuffer().getLength());
953
954 // Simulate that we have received that traffic
955 srv.fakeReceive(pkt);
956 srv.run();
957
958 using namespace isc::stats;
959 StatsMgr& mgr = StatsMgr::instance();
960 ObservationPtr pkt4_rcvd = mgr.getObservation("pkt4-received");
961 ObservationPtr tested_stat = mgr.getObservation(stat_name);
962
963 // All expected statistics must be present.
964 ASSERT_TRUE(pkt4_rcvd);
965 ASSERT_TRUE(tested_stat);
966
967 // They also must have expected values.
968 EXPECT_EQ(1, pkt4_rcvd->getInteger().first);
969 EXPECT_EQ(1, tested_stat->getInteger().first);
970 }
971
972 } // end of isc::dhcp::test namespace
973 } // end of isc::dhcp namespace
974 } // end of isc namespace
975