1 // Copyright (C) 2016-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 #include <dhcp/dhcp6.h>
9 #include <dhcp/option.h>
10 #include <dhcp/option_int.h>
11 #include <dhcp/option_int_array.h>
12 #include <dhcp/pkt6.h>
13 #include <dhcp/tests/iface_mgr_test_config.h>
14 #include <dhcp/opaque_data_tuple.h>
15 #include <dhcp/option_string.h>
16 #include <dhcp/option_vendor_class.h>
17 #include <dhcp/option6_addrlst.h>
18 #include <dhcp/tests/pkt_captures.h>
19 #include <dhcpsrv/cfgmgr.h>
20 #include <dhcp6/tests/dhcp6_test_utils.h>
21 #include <dhcp6/tests/dhcp6_client.h>
22 #include <asiolink/io_address.h>
23 #include <stats/stats_mgr.h>
24 #include <boost/pointer_cast.hpp>
25 #include <string>
26
27 using namespace isc;
28 using namespace isc::asiolink;
29 using namespace isc::dhcp;
30 using namespace isc::dhcp::test;
31
32 namespace {
33
34 /// @brief Set of JSON configurations used by the classification unit tests.
35 ///
36 /// - Configuration 0:
37 /// - Specifies 3 classes: 'router', 'reserved-class1' and 'reserved-class2'.
38 /// - 'router' class is assigned when the client sends option 1234 (string)
39 /// equal to 'foo'.
40 /// - The other two classes are reserved for the client having
41 /// DUID '01:02:03:04'
42 /// - Class 'router' includes option 'ipv6-forwarding'.
43 /// - Class 'reserved-class1' includes option DNS servers.
44 /// - Class 'reserved-class2' includes option NIS servers.
45 /// - All three options are sent when client has reservations for the
46 /// 'reserved-class1', 'reserved-class2' and sends option 1234 with
47 /// the 'foo' value.
48 /// - There is one subnet specified 2001:db8:1::/48 with pool of
49 /// IPv6 addresses.
50 ///
51 /// - Configuration 1:
52 /// - Used for complex membership (example taken from HA)
53 /// - 1 subnet: 2001:db8:1::/48
54 /// - 4 pools: 2001:db8:1:1::/64, 2001:db8:1:2::/64,
55 /// 2001:db8:1:3::/64 and 2001:db8:1:4::/64
56 /// - 4 classes to compose:
57 /// server1 and server2 for each HA server
58 /// option 1234 'foo' aka telephones
59 /// option 1234 'bar' aka computers
60 ///
61 /// - Configuration 2:
62 /// - Used for complex membership (example taken from HA) and pd-pools
63 /// - 1 subnet: 2001:db8::/32
64 /// - 4 pd-pools: 2001:db8:1::/48, 2001:db8:2::/48,
65 /// 2001:db8:3::/48 and 2001:db8:4::/48
66 /// - 4 classes to compose:
67 /// server1 and server2 for each HA server
68 /// option 1234 'foo' aka telephones
69 /// option 1234 'bar' aka computers
70 ///
71 /// - Configuration 3:
72 /// - Used for the DROP class
73 /// - 1 subnet: 2001:db8:1::/48
74 /// - 2 pool: 2001:db8:1:1::/64
75 /// - the following class defined: option 1234 'foo', DROP
76 ///
77 /// - Configuration 4:
78 /// - Used for the DROP class and reservation existence
79 /// - 1 subnet: 2001:db8:1::/48
80 /// - 2 pool: 2001:db8:1:1::/64
81 /// - the following class defined: not member('KNOWN'), DROP
82 /// @note the reservation includes a hostname because raw reservations are
83 /// not yet allowed
84 ///
85 /// - Configuration 5:
86 /// - Used for the DROP class and reservation class
87 /// - 1 subnet: 2001:db8:1::/48
88 /// - 2 pool: 2001:db8:1:1::/64
89 /// - the following class defined:
90 /// - allowed
91 /// - member('KNOWN') or member('UNKNOWN'), t
92 /// - not member('allowed') and member('t'), DROP
93 /// The function of the always true 't' class is to move the DROP
94 /// evaluation to the classification point after the host reservation
95 /// lookup, i.e. indirect KNOWN / UNKNOWN dependency
96 ///
97 const char* CONFIGS[] = {
98 // Configuration 0
99 "{ \"interfaces-config\": {"
100 " \"interfaces\": [ \"*\" ]"
101 "},"
102 "\"preferred-lifetime\": 3000,"
103 "\"rebind-timer\": 2000, "
104 "\"renew-timer\": 1000, "
105 "\"option-def\": [ "
106 "{"
107 " \"name\": \"host-name\","
108 " \"code\": 1234,"
109 " \"type\": \"string\""
110 "},"
111 "{"
112 " \"name\": \"ipv6-forwarding\","
113 " \"code\": 2345,"
114 " \"type\": \"boolean\""
115 "} ],"
116 "\"client-classes\": ["
117 "{"
118 " \"name\": \"router\","
119 " \"test\": \"option[host-name].text == 'foo'\","
120 " \"option-data\": ["
121 " {"
122 " \"name\": \"ipv6-forwarding\", "
123 " \"data\": \"true\""
124 " } ]"
125 "},"
126 "{"
127 " \"name\": \"reserved-class1\","
128 " \"option-data\": ["
129 " {"
130 " \"name\": \"dns-servers\","
131 " \"data\": \"2001:db8:1::50\""
132 " }"
133 " ]"
134 "},"
135 "{"
136 " \"name\": \"reserved-class2\","
137 " \"option-data\": ["
138 " {"
139 " \"name\": \"nis-servers\","
140 " \"data\": \"2001:db8:1::100\""
141 " }"
142 " ]"
143 "}"
144 "],"
145 "\"subnet6\": [ "
146 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
147 " \"subnet\": \"2001:db8:1::/48\", "
148 " \"interface\": \"eth1\","
149 " \"reservations\": ["
150 " {"
151 " \"duid\": \"01:02:03:04\","
152 " \"client-classes\": [ \"reserved-class1\", \"reserved-class2\" ]"
153 " } ]"
154 " } ],"
155 "\"valid-lifetime\": 4000 }",
156
157 // Configuration 1
158 "{ \"interfaces-config\": {"
159 " \"interfaces\": [ \"*\" ]"
160 "},"
161 "\"preferred-lifetime\": 3000,"
162 "\"rebind-timer\": 2000, "
163 "\"renew-timer\": 1000, "
164 "\"option-def\": [ "
165 "{"
166 " \"name\": \"host-name\","
167 " \"code\": 1234,"
168 " \"type\": \"string\""
169 "} ],"
170 "\"client-classes\": ["
171 "{"
172 " \"name\": \"server1\""
173 "},"
174 "{"
175 " \"name\": \"server2\""
176 "},"
177 "{"
178 " \"name\": \"telephones\","
179 " \"test\": \"option[host-name].text == 'foo'\""
180 "},"
181 "{"
182 " \"name\": \"computers\","
183 " \"test\": \"option[host-name].text == 'bar'\""
184 "},"
185 "{"
186 " \"name\": \"server1_and_telephones\","
187 " \"test\": \"member('server1') and member('telephones')\""
188 "},"
189 "{"
190 " \"name\": \"server1_and_computers\","
191 " \"test\": \"member('server1') and member('computers')\""
192 "},"
193 "{"
194 " \"name\": \"server2_and_telephones\","
195 " \"test\": \"member('server2') and member('telephones')\""
196 "},"
197 "{"
198 " \"name\": \"server2_and_computers\","
199 " \"test\": \"member('server2') and member('computers')\""
200 "}"
201 "],"
202 "\"subnet6\": [ "
203 "{ \"subnet\": \"2001:db8:1::/48\", "
204 " \"interface\": \"eth1\","
205 " \"pools\": [ "
206 " { \"pool\": \"2001:db8:1:1::/64\","
207 " \"client-class\": \"server1_and_telephones\" },"
208 " { \"pool\": \"2001:db8:1:2::/64\","
209 " \"client-class\": \"server1_and_computers\" },"
210 " { \"pool\": \"2001:db8:1:3::/64\","
211 " \"client-class\": \"server2_and_telephones\" },"
212 " { \"pool\": \"2001:db8:1:4::/64\","
213 " \"client-class\": \"server2_and_computers\" } ]"
214 " } ],"
215 "\"valid-lifetime\": 4000 }",
216
217 // Configuration 2
218 "{ \"interfaces-config\": {"
219 " \"interfaces\": [ \"*\" ]"
220 "},"
221 "\"preferred-lifetime\": 3000,"
222 "\"rebind-timer\": 2000, "
223 "\"renew-timer\": 1000, "
224 "\"option-def\": [ "
225 "{"
226 " \"name\": \"host-name\","
227 " \"code\": 1234,"
228 " \"type\": \"string\""
229 "} ],"
230 "\"client-classes\": ["
231 "{"
232 " \"name\": \"server1\""
233 "},"
234 "{"
235 " \"name\": \"server2\""
236 "},"
237 "{"
238 " \"name\": \"telephones\","
239 " \"test\": \"option[host-name].text == 'foo'\""
240 "},"
241 "{"
242 " \"name\": \"computers\","
243 " \"test\": \"option[host-name].text == 'bar'\""
244 "},"
245 "{"
246 " \"name\": \"server1_and_telephones\","
247 " \"test\": \"member('server1') and member('telephones')\""
248 "},"
249 "{"
250 " \"name\": \"server1_and_computers\","
251 " \"test\": \"member('server1') and member('computers')\""
252 "},"
253 "{"
254 " \"name\": \"server2_and_telephones\","
255 " \"test\": \"member('server2') and member('telephones')\""
256 "},"
257 "{"
258 " \"name\": \"server2_and_computers\","
259 " \"test\": \"member('server2') and member('computers')\""
260 "}"
261 "],"
262 "\"subnet6\": [ "
263 "{ \"subnet\": \"2001:db8::/32\", "
264 " \"interface\": \"eth1\","
265 " \"pd-pools\": [ "
266 " { \"prefix\": \"2001:db8:1::\","
267 " \"prefix-len\": 48, \"delegated-len\": 64,"
268 " \"client-class\": \"server1_and_telephones\" },"
269 " { \"prefix\": \"2001:db8:2::\","
270 " \"prefix-len\": 48, \"delegated-len\": 64,"
271 " \"client-class\": \"server1_and_computers\" },"
272 " { \"prefix\": \"2001:db8:3::\","
273 " \"prefix-len\": 48, \"delegated-len\": 64,"
274 " \"client-class\": \"server2_and_telephones\" },"
275 " { \"prefix\": \"2001:db8:4::\","
276 " \"prefix-len\": 48, \"delegated-len\": 64,"
277 " \"client-class\": \"server2_and_computers\" } ]"
278 " } ],"
279 "\"valid-lifetime\": 4000 }",
280
281 // Configuration 3
282 "{ \"interfaces-config\": {"
283 " \"interfaces\": [ \"*\" ]"
284 "},"
285 "\"preferred-lifetime\": 3000,"
286 "\"rebind-timer\": 2000, "
287 "\"renew-timer\": 1000, "
288 "\"option-def\": [ "
289 "{"
290 " \"name\": \"host-name\","
291 " \"code\": 1234,"
292 " \"type\": \"string\""
293 "},"
294 "{"
295 " \"name\": \"ipv6-forwarding\","
296 " \"code\": 2345,"
297 " \"type\": \"boolean\""
298 "} ],"
299 "\"client-classes\": ["
300 "{"
301 " \"name\": \"DROP\","
302 " \"test\": \"option[host-name].text == 'foo'\""
303 "}"
304 "],"
305 "\"subnet6\": [ "
306 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
307 " \"subnet\": \"2001:db8:1::/48\", "
308 " \"interface\": \"eth1\""
309 " } ],"
310 "\"valid-lifetime\": 4000 }",
311
312 // Configuration 4
313 "{ \"interfaces-config\": {"
314 " \"interfaces\": [ \"*\" ]"
315 "},"
316 "\"preferred-lifetime\": 3000,"
317 "\"rebind-timer\": 2000, "
318 "\"renew-timer\": 1000, "
319 "\"client-classes\": ["
320 "{"
321 " \"name\": \"DROP\","
322 " \"test\": \"not member('KNOWN')\""
323 "}"
324 "],"
325 "\"subnet6\": [ "
326 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
327 " \"subnet\": \"2001:db8:1::/48\", "
328 " \"interface\": \"eth1\","
329 " \"reservations\": ["
330 " {"
331 " \"duid\": \"01:02:03:04\","
332 " \"hostname\": \"allowed\""
333 " } ]"
334 " } ],"
335 "\"valid-lifetime\": 4000 }",
336
337 // Configuration 5
338 "{ \"interfaces-config\": {"
339 " \"interfaces\": [ \"*\" ]"
340 "},"
341 "\"preferred-lifetime\": 3000,"
342 "\"rebind-timer\": 2000, "
343 "\"renew-timer\": 1000, "
344 "\"client-classes\": ["
345 "{"
346 " \"name\": \"allowed\""
347 "},"
348 "{"
349 " \"name\": \"t\","
350 " \"test\": \"member('KNOWN') or member('UNKNOWN')\""
351 "},"
352 "{"
353 " \"name\": \"DROP\","
354 " \"test\": \"not member('allowed') and member('t')\""
355 "}"
356 "],"
357 "\"subnet6\": [ "
358 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
359 " \"subnet\": \"2001:db8:1::/48\", "
360 " \"interface\": \"eth1\","
361 " \"reservations\": ["
362 " {"
363 " \"duid\": \"01:02:03:04\","
364 " \"client-classes\": [ \"allowed\" ]"
365 " } ]"
366 " } ],"
367 "\"valid-lifetime\": 4000 }"
368 };
369
370 /// @brief Test fixture class for testing client classification by the
371 /// DHCPv6 server.
372 ///
373 /// @todo There are numerous tests not using Dhcp6Client class. They should be
374 /// migrated to use it one day.
375 class ClassifyTest : public Dhcpv6SrvTest {
376 public:
377 /// @brief Constructor.
378 ///
379 /// Sets up fake interfaces.
ClassifyTest()380 ClassifyTest()
381 : Dhcpv6SrvTest(),
382 iface_mgr_test_config_(true) {
383 }
384
385 /// @brief Verify values of options returned by the server when the server
386 /// uses configuration with index 0.
387 ///
388 /// @param config Reference to DHCP client's configuration received.
389 /// @param ip_forwarding Expected value of IP forwarding option. This option
390 /// is expected to always be present.
391 /// @param dns_servers String holding an address carried within DNS
392 /// servers option. If this value is empty, the option is expected to not
393 /// be included in the response.
394 /// @param nis_servers String holding an address carried within NIS
395 /// servers option. If this value is empty, the option is expected to not
396 /// be included in the response.
verifyConfig0Options(const Dhcp6Client::Configuration & config,const uint8_t ip_forwarding=1,const std::string & dns_servers="",const std::string & nis_servers="")397 void verifyConfig0Options(const Dhcp6Client::Configuration& config,
398 const uint8_t ip_forwarding = 1,
399 const std::string& dns_servers = "",
400 const std::string& nis_servers = "") {
401 // IP forwarding option should always exist.
402 OptionPtr ip_forwarding_opt = config.findOption(2345);
403 ASSERT_TRUE(ip_forwarding_opt);
404 // The option comprises 2 bytes of option code, 2 bytes of option length,
405 // and a single 1 byte value. This makes it 5 bytes of a total length.
406 ASSERT_EQ(5, ip_forwarding_opt->len());
407 ASSERT_EQ(static_cast<int>(ip_forwarding),
408 static_cast<int>(ip_forwarding_opt->getUint8()));
409
410 // DNS servers.
411 Option6AddrLstPtr dns_servers_opt = boost::dynamic_pointer_cast<
412 Option6AddrLst>(config.findOption(D6O_NAME_SERVERS));
413 if (!dns_servers.empty()) {
414 ASSERT_TRUE(dns_servers_opt);
415 Option6AddrLst::AddressContainer addresses = dns_servers_opt->getAddresses();
416 // For simplicity, we expect only a single address.
417 ASSERT_EQ(1, addresses.size());
418 EXPECT_EQ(dns_servers, addresses[0].toText());
419
420 } else {
421 EXPECT_FALSE(dns_servers_opt);
422 }
423
424 // NIS servers.
425 Option6AddrLstPtr nis_servers_opt = boost::dynamic_pointer_cast<
426 Option6AddrLst>(config.findOption(D6O_NIS_SERVERS));
427 if (!nis_servers.empty()) {
428 ASSERT_TRUE(nis_servers_opt);
429 Option6AddrLst::AddressContainer addresses = nis_servers_opt->getAddresses();
430 // For simplicity, we expect only a single address.
431 ASSERT_EQ(1, addresses.size());
432 EXPECT_EQ(nis_servers, addresses[0].toText());
433
434 } else {
435 EXPECT_FALSE(nis_servers_opt);
436 }
437 }
438
439 /// @brief Create a solicit
createSolicit(std::string remote_addr="fe80::abcd")440 Pkt6Ptr createSolicit(std::string remote_addr = "fe80::abcd") {
441 OptionPtr clientid = generateClientId();
442 Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
443 query->setRemoteAddr(IOAddress(remote_addr));
444 query->addOption(clientid);
445 query->setIface("eth1");
446 query->setIndex(ETH1_INDEX);
447 query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
448 return (query);
449 }
450
451 /// @brief Interface Manager's fake configuration control.
452 IfaceMgrTestConfig iface_mgr_test_config_;
453 };
454
455 // Checks if DOCSIS client packets are classified properly
TEST_F(ClassifyTest,docsisClientClassification)456 TEST_F(ClassifyTest, docsisClientClassification) {
457
458 NakedDhcpv6Srv srv(0);
459
460 // Let's create a relayed SOLICIT. This particular relayed SOLICIT has
461 // vendor-class set to docsis3.0
462 Pkt6Ptr sol1;
463 ASSERT_NO_THROW(sol1 = PktCaptures::captureDocsisRelayedSolicit());
464 ASSERT_NO_THROW(sol1->unpack());
465
466 srv.classifyPacket(sol1);
467
468 // It should belong to docsis3.0 class. It should not belong to eRouter1.0
469 EXPECT_TRUE(sol1->inClass("VENDOR_CLASS_docsis3.0"));
470 EXPECT_FALSE(sol1->inClass("eRouter1.0"));
471
472 // Let's get a relayed SOLICIT. This particular relayed SOLICIT has
473 // vendor-class set to eRouter1.0
474 Pkt6Ptr sol2;
475 ASSERT_NO_THROW(sol2 = PktCaptures::captureeRouterRelayedSolicit());
476 ASSERT_NO_THROW(sol2->unpack());
477
478 srv.classifyPacket(sol2);
479
480 EXPECT_TRUE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
481 EXPECT_FALSE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
482 }
483
484 // Checks if client packets are classified properly using match expressions.
485 // Note option names and definitions are used.
TEST_F(ClassifyTest,matchClassification)486 TEST_F(ClassifyTest, matchClassification) {
487 IfaceMgrTestConfig test_config(true);
488
489 NakedDhcpv6Srv srv(0);
490
491 // The router class matches incoming packets with foo in a host-name
492 // option (code 1234) and sets an ipv6-forwarding option in the response.
493 std::string config = "{ \"interfaces-config\": {"
494 " \"interfaces\": [ \"*\" ] }, "
495 "\"preferred-lifetime\": 3000,"
496 "\"rebind-timer\": 2000, "
497 "\"renew-timer\": 1000, "
498 "\"valid-lifetime\": 4000, "
499 "\"option-def\": [ "
500 "{ \"name\": \"host-name\","
501 " \"code\": 1234,"
502 " \"type\": \"string\" },"
503 "{ \"name\": \"ipv6-forwarding\","
504 " \"code\": 2345,"
505 " \"type\": \"boolean\" }],"
506 "\"subnet6\": [ "
507 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
508 " \"subnet\": \"2001:db8:1::/48\", "
509 " \"interface\": \"eth1\" } ],"
510 "\"client-classes\": [ "
511 "{ \"name\": \"router\", "
512 " \"option-data\": ["
513 " { \"name\": \"ipv6-forwarding\", "
514 " \"data\": \"true\" } ], "
515 " \"test\": \"option[host-name].text == 'foo'\" } ] }";
516 ASSERT_NO_THROW(configure(config));
517
518 // Create packets with enough to select the subnet
519 Pkt6Ptr query1 = createSolicit();
520 Pkt6Ptr query2 = createSolicit();
521 Pkt6Ptr query3 = createSolicit();
522
523 // Create and add an ORO option to the first 2 queries
524 OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
525 ASSERT_TRUE(oro);
526 oro->addValue(2345);
527 query1->addOption(oro);
528 query2->addOption(oro);
529
530 // Create and add a host-name option to the first and last queries
531 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
532 ASSERT_TRUE(hostname);
533 query1->addOption(hostname);
534 query3->addOption(hostname);
535
536 // Classify packets
537 srv.classifyPacket(query1);
538 srv.classifyPacket(query2);
539 srv.classifyPacket(query3);
540
541 // Packets with the exception of the second should be in the router class
542 EXPECT_TRUE(query1->inClass("router"));
543 EXPECT_FALSE(query2->inClass("router"));
544 EXPECT_TRUE(query3->inClass("router"));
545
546 // Process queries
547 AllocEngine::ClientContext6 ctx1;
548 bool drop = false;
549 srv.initContext(query1, ctx1, drop);
550 ASSERT_FALSE(drop);
551 Pkt6Ptr response1 = srv.processSolicit(ctx1);
552 AllocEngine::ClientContext6 ctx2;
553 srv.initContext(query2, ctx2, drop);
554 ASSERT_FALSE(drop);
555 Pkt6Ptr response2 = srv.processSolicit(ctx2);
556 AllocEngine::ClientContext6 ctx3;
557 srv.initContext(query3, ctx3, drop);
558 ASSERT_FALSE(drop);
559 Pkt6Ptr response3 = srv.processSolicit(ctx3);
560
561 // Classification processing should add an ip-forwarding option
562 OptionPtr opt1 = response1->getOption(2345);
563 EXPECT_TRUE(opt1);
564
565 // But only for the first query: second was not classified
566 OptionPtr opt2 = response2->getOption(2345);
567 EXPECT_FALSE(opt2);
568
569 // But only for the first query: third has no ORO
570 OptionPtr opt3 = response3->getOption(2345);
571 EXPECT_FALSE(opt3);
572 }
573
574 // Check that only-if-required classes are not evaluated by classifyPacket
TEST_F(ClassifyTest,required)575 TEST_F(ClassifyTest, required) {
576 IfaceMgrTestConfig test_config(true);
577
578 NakedDhcpv6Srv srv(0);
579
580 // The router class matches incoming packets with foo in a host-name
581 // option (code 1234) and sets an ipv6-forwarding option in the response.
582 std::string config = "{ \"interfaces-config\": {"
583 " \"interfaces\": [ \"*\" ] }, "
584 "\"preferred-lifetime\": 3000,"
585 "\"rebind-timer\": 2000, "
586 "\"renew-timer\": 1000, "
587 "\"valid-lifetime\": 4000, "
588 "\"option-def\": [ "
589 "{ \"name\": \"host-name\","
590 " \"code\": 1234,"
591 " \"type\": \"string\" },"
592 "{ \"name\": \"ipv6-forwarding\","
593 " \"code\": 2345,"
594 " \"type\": \"boolean\" }],"
595 "\"subnet6\": [ "
596 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
597 " \"subnet\": \"2001:db8:1::/48\", "
598 " \"interface\": \"eth1\" } ],"
599 "\"client-classes\": [ "
600 "{ \"name\": \"router\", "
601 " \"only-if-required\": true, "
602 " \"option-data\": ["
603 " { \"name\": \"ipv6-forwarding\", "
604 " \"data\": \"true\" } ], "
605 " \"test\": \"option[host-name].text == 'foo'\" } ] }";
606 ASSERT_NO_THROW(configure(config));
607
608 // Create packets with enough to select the subnet
609 OptionPtr clientid = generateClientId();
610 Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
611 query1->setRemoteAddr(IOAddress("fe80::abcd"));
612 query1->addOption(clientid);
613 query1->setIface("eth1");
614 query1->setIndex(ETH1_INDEX);
615 query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
616 Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
617 query2->setRemoteAddr(IOAddress("fe80::abcd"));
618 query2->addOption(clientid);
619 query2->setIface("eth1");
620 query2->setIndex(ETH1_INDEX);
621 query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
622 Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
623 query3->setRemoteAddr(IOAddress("fe80::abcd"));
624 query3->addOption(clientid);
625 query3->setIface("eth1");
626 query3->setIndex(ETH1_INDEX);
627 query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
628
629 // Create and add an ORO option to the first 2 queries
630 OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
631 ASSERT_TRUE(oro);
632 oro->addValue(2345);
633 query1->addOption(oro);
634 query2->addOption(oro);
635
636 // Create and add a host-name option to the first and last queries
637 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
638 ASSERT_TRUE(hostname);
639 query1->addOption(hostname);
640 query3->addOption(hostname);
641
642 // Classify packets
643 srv.classifyPacket(query1);
644 srv.classifyPacket(query2);
645 srv.classifyPacket(query3);
646
647 // No packet is in the router class
648 EXPECT_FALSE(query1->inClass("router"));
649 EXPECT_FALSE(query2->inClass("router"));
650 EXPECT_FALSE(query3->inClass("router"));
651
652 // Process queries
653 AllocEngine::ClientContext6 ctx1;
654 bool drop = false;
655 srv.initContext(query1, ctx1, drop);
656 ASSERT_FALSE(drop);
657 Pkt6Ptr response1 = srv.processSolicit(ctx1);
658 AllocEngine::ClientContext6 ctx2;
659 srv.initContext(query2, ctx2, drop);
660 ASSERT_FALSE(drop);
661 Pkt6Ptr response2 = srv.processSolicit(ctx2);
662 AllocEngine::ClientContext6 ctx3;
663 srv.initContext(query3, ctx3, drop);
664 ASSERT_FALSE(drop);
665 Pkt6Ptr response3 = srv.processSolicit(ctx3);
666
667 // Classification processing should do nothing
668 OptionPtr opt1 = response1->getOption(2345);
669 EXPECT_FALSE(opt1);
670 OptionPtr opt2 = response2->getOption(2345);
671 EXPECT_FALSE(opt2);
672 OptionPtr opt3 = response3->getOption(2345);
673 EXPECT_FALSE(opt3);
674 }
675
676 // Checks that when only-if-required classes are still evaluated
TEST_F(ClassifyTest,requiredClassification)677 TEST_F(ClassifyTest, requiredClassification) {
678 IfaceMgrTestConfig test_config(true);
679
680 NakedDhcpv6Srv srv(0);
681
682 // The router class matches incoming packets with foo in a host-name
683 // option (code 1234) and sets an ipv6-forwarding option in the response.
684 std::string config = "{ \"interfaces-config\": {"
685 " \"interfaces\": [ \"*\" ] }, "
686 "\"preferred-lifetime\": 3000,"
687 "\"rebind-timer\": 2000, "
688 "\"renew-timer\": 1000, "
689 "\"valid-lifetime\": 4000, "
690 "\"option-def\": [ "
691 "{ \"name\": \"host-name\","
692 " \"code\": 1234,"
693 " \"type\": \"string\" },"
694 "{ \"name\": \"ipv6-forwarding\","
695 " \"code\": 2345,"
696 " \"type\": \"boolean\" }],"
697 "\"subnet6\": [ "
698 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
699 " \"subnet\": \"2001:db8:1::/48\", "
700 " \"require-client-classes\": [ \"router\" ], "
701 " \"interface\": \"eth1\" } ],"
702 "\"client-classes\": [ "
703 "{ \"name\": \"router\", "
704 " \"only-if-required\": true, "
705 " \"option-data\": ["
706 " { \"name\": \"ipv6-forwarding\", "
707 " \"data\": \"true\" } ], "
708 " \"test\": \"option[host-name].text == 'foo'\" } ] }";
709 ASSERT_NO_THROW(configure(config));
710
711 // Create packets with enough to select the subnet
712 OptionPtr clientid = generateClientId();
713 Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
714 query1->setRemoteAddr(IOAddress("fe80::abcd"));
715 query1->addOption(clientid);
716 query1->setIface("eth1");
717 query1->setIndex(ETH1_INDEX);
718 query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
719 Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
720 query2->setRemoteAddr(IOAddress("fe80::abcd"));
721 query2->addOption(clientid);
722 query2->setIface("eth1");
723 query2->setIndex(ETH1_INDEX);
724 query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
725 Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
726 query3->setRemoteAddr(IOAddress("fe80::abcd"));
727 query3->addOption(clientid);
728 query3->setIface("eth1");
729 query3->setIndex(ETH1_INDEX);
730 query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
731
732 // Create and add an ORO option to the first 2 queries
733 OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
734 ASSERT_TRUE(oro);
735 oro->addValue(2345);
736 query1->addOption(oro);
737 query2->addOption(oro);
738
739 // Create and add a host-name option to the first and last queries
740 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
741 ASSERT_TRUE(hostname);
742 query1->addOption(hostname);
743 query3->addOption(hostname);
744
745 // Classify packets
746 srv.classifyPacket(query1);
747 srv.classifyPacket(query2);
748 srv.classifyPacket(query3);
749
750 // No packet is in the router class yet
751 EXPECT_FALSE(query1->inClass("router"));
752 EXPECT_FALSE(query2->inClass("router"));
753 EXPECT_FALSE(query3->inClass("router"));
754
755 // Process queries
756 AllocEngine::ClientContext6 ctx1;
757 bool drop = false;
758 srv.initContext(query1, ctx1, drop);
759 ASSERT_FALSE(drop);
760 Pkt6Ptr response1 = srv.processSolicit(ctx1);
761 AllocEngine::ClientContext6 ctx2;
762 srv.initContext(query2, ctx2, drop);
763 ASSERT_FALSE(drop);
764 Pkt6Ptr response2 = srv.processSolicit(ctx2);
765 AllocEngine::ClientContext6 ctx3;
766 srv.initContext(query3, ctx3, drop);
767 ASSERT_FALSE(drop);
768 Pkt6Ptr response3 = srv.processSolicit(ctx3);
769
770 // Classification processing should add an ip-forwarding option
771 OptionPtr opt1 = response1->getOption(2345);
772 EXPECT_TRUE(opt1);
773
774 // But only for the first query: second was not classified
775 OptionPtr opt2 = response2->getOption(2345);
776 EXPECT_FALSE(opt2);
777
778 // But only for the first query: third has no ORO
779 OptionPtr opt3 = response3->getOption(2345);
780 EXPECT_FALSE(opt3);
781 }
782
783 // Checks subnet options have the priority over class options
TEST_F(ClassifyTest,subnetClassPriority)784 TEST_F(ClassifyTest, subnetClassPriority) {
785 IfaceMgrTestConfig test_config(true);
786
787 NakedDhcpv6Srv srv(0);
788
789 // Subnet sets an ipv6-forwarding option in the response.
790 // The router class matches incoming packets with foo in a host-name
791 // option (code 1234) and sets an ipv6-forwarding option in the response.
792 std::string config = "{ \"interfaces-config\": {"
793 " \"interfaces\": [ \"*\" ] }, "
794 "\"preferred-lifetime\": 3000,"
795 "\"rebind-timer\": 2000, "
796 "\"renew-timer\": 1000, "
797 "\"valid-lifetime\": 4000, "
798 "\"option-def\": [ "
799 "{ \"name\": \"host-name\","
800 " \"code\": 1234,"
801 " \"type\": \"string\" },"
802 "{ \"name\": \"ipv6-forwarding\","
803 " \"code\": 2345,"
804 " \"type\": \"boolean\" }],"
805 "\"subnet6\": [ "
806 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
807 " \"subnet\": \"2001:db8:1::/48\", "
808 " \"interface\": \"eth1\", "
809 " \"option-data\": ["
810 " { \"name\": \"ipv6-forwarding\", "
811 " \"data\": \"false\" } ] } ], "
812 "\"client-classes\": [ "
813 "{ \"name\": \"router\","
814 " \"option-data\": ["
815 " { \"name\": \"ipv6-forwarding\", "
816 " \"data\": \"true\" } ], "
817 " \"test\": \"option[1234].text == 'foo'\" } ] }";
818 ASSERT_NO_THROW(configure(config));
819
820 // Create a packet with enough to select the subnet and go through
821 // the SOLICIT processing
822 Pkt6Ptr query = createSolicit();
823
824 // Create and add an ORO option to the query
825 OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
826 ASSERT_TRUE(oro);
827 oro->addValue(2345);
828 query->addOption(oro);
829
830 // Create and add a host-name option to the query
831 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
832 ASSERT_TRUE(hostname);
833 query->addOption(hostname);
834
835 // Classify the packet
836 srv.classifyPacket(query);
837
838 // The packet should be in the router class
839 EXPECT_TRUE(query->inClass("router"));
840
841 // Process the query
842 AllocEngine::ClientContext6 ctx;
843 bool drop = false;
844 srv.initContext(query, ctx, drop);
845 ASSERT_FALSE(drop);
846 Pkt6Ptr response = srv.processSolicit(ctx);
847
848 // Processing should add an ip-forwarding option
849 OptionPtr opt = response->getOption(2345);
850 ASSERT_TRUE(opt);
851 ASSERT_GT(opt->len(), opt->getHeaderLen());
852 // Classification sets the value to true/1, subnet to false/0
853 // Here subnet has the priority
854 EXPECT_EQ(0, opt->getUint8());
855 }
856
857 // Checks subnet options have the priority over global options
TEST_F(ClassifyTest,subnetGlobalPriority)858 TEST_F(ClassifyTest, subnetGlobalPriority) {
859 IfaceMgrTestConfig test_config(true);
860
861 NakedDhcpv6Srv srv(0);
862
863 // Subnet sets an ipv6-forwarding option in the response.
864 // The router class matches incoming packets with foo in a host-name
865 // option (code 1234) and sets an ipv6-forwarding option in the response.
866 std::string config = "{ \"interfaces-config\": {"
867 " \"interfaces\": [ \"*\" ] }, "
868 "\"preferred-lifetime\": 3000,"
869 "\"rebind-timer\": 2000, "
870 "\"renew-timer\": 1000, "
871 "\"valid-lifetime\": 4000, "
872 "\"option-def\": [ "
873 "{ \"name\": \"host-name\","
874 " \"code\": 1234,"
875 " \"type\": \"string\" },"
876 "{ \"name\": \"ipv6-forwarding\","
877 " \"code\": 2345,"
878 " \"type\": \"boolean\" }],"
879 "\"option-data\": ["
880 " { \"name\": \"ipv6-forwarding\", "
881 " \"data\": \"false\" } ], "
882 "\"subnet6\": [ "
883 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
884 " \"subnet\": \"2001:db8:1::/48\", "
885 " \"interface\": \"eth1\", "
886 " \"option-data\": ["
887 " { \"name\": \"ipv6-forwarding\", "
888 " \"data\": \"false\" } ] } ] }";
889 ASSERT_NO_THROW(configure(config));
890
891 // Create a packet with enough to select the subnet and go through
892 // the SOLICIT processing
893 Pkt6Ptr query = createSolicit();
894
895 // Create and add an ORO option to the query
896 OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
897 ASSERT_TRUE(oro);
898 oro->addValue(2345);
899 query->addOption(oro);
900
901 // Create and add a host-name option to the query
902 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
903 ASSERT_TRUE(hostname);
904 query->addOption(hostname);
905
906 // Process the query
907 AllocEngine::ClientContext6 ctx;
908 bool drop = false;
909 srv.initContext(query, ctx, drop);
910 ASSERT_FALSE(drop);
911 Pkt6Ptr response = srv.processSolicit(ctx);
912
913 // Processing should add an ip-forwarding option
914 OptionPtr opt = response->getOption(2345);
915 ASSERT_TRUE(opt);
916 ASSERT_GT(opt->len(), opt->getHeaderLen());
917 // Global sets the value to true/1, subnet to false/0
918 // Here subnet has the priority
919 EXPECT_EQ(0, opt->getUint8());
920 }
921
922 // Checks class options have the priority over global options
TEST_F(ClassifyTest,classGlobalPriority)923 TEST_F(ClassifyTest, classGlobalPriority) {
924 IfaceMgrTestConfig test_config(true);
925
926 NakedDhcpv6Srv srv(0);
927
928 // A global ipv6-forwarding option is set in the response.
929 // The router class matches incoming packets with foo in a host-name
930 // option (code 1234) and sets an ipv6-forwarding option in the response.
931 std::string config = "{ \"interfaces-config\": {"
932 " \"interfaces\": [ \"*\" ] }, "
933 "\"preferred-lifetime\": 3000,"
934 "\"rebind-timer\": 2000, "
935 "\"renew-timer\": 1000, "
936 "\"valid-lifetime\": 4000, "
937 "\"option-def\": [ "
938 "{ \"name\": \"host-name\","
939 " \"code\": 1234,"
940 " \"type\": \"string\" },"
941 "{ \"name\": \"ipv6-forwarding\","
942 " \"code\": 2345,"
943 " \"type\": \"boolean\" }],"
944 "\"subnet6\": [ "
945 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
946 " \"subnet\": \"2001:db8:1::/48\", "
947 " \"interface\": \"eth1\" } ],"
948 "\"option-data\": ["
949 " { \"name\": \"ipv6-forwarding\", "
950 " \"data\": \"false\" } ], "
951 "\"client-classes\": [ "
952 "{ \"name\": \"router\","
953 " \"option-data\": ["
954 " { \"name\": \"ipv6-forwarding\", "
955 " \"data\": \"true\" } ], "
956 " \"test\": \"option[1234].text == 'foo'\" } ] }";
957 ASSERT_NO_THROW(configure(config));
958
959 // Create a packet with enough to select the subnet and go through
960 // the SOLICIT processing
961 Pkt6Ptr query = createSolicit();
962
963 // Create and add an ORO option to the query
964 OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
965 ASSERT_TRUE(oro);
966 oro->addValue(2345);
967 query->addOption(oro);
968
969 // Create and add a host-name option to the query
970 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
971 ASSERT_TRUE(hostname);
972 query->addOption(hostname);
973
974 // Classify the packet
975 srv.classifyPacket(query);
976
977 // The packet should be in the router class
978 EXPECT_TRUE(query->inClass("router"));
979
980 // Process the query
981 AllocEngine::ClientContext6 ctx;
982 bool drop = false;
983 srv.initContext(query, ctx, drop);
984 ASSERT_FALSE(drop);
985 Pkt6Ptr response = srv.processSolicit(ctx);
986
987 // Processing should add an ip-forwarding option
988 OptionPtr opt = response->getOption(2345);
989 ASSERT_TRUE(opt);
990 ASSERT_GT(opt->len(), opt->getHeaderLen());
991 // Classification sets the value to true/1, global to false/0
992 // Here class has the priority
993 EXPECT_NE(0, opt->getUint8());
994 }
995
996 // Checks class options have the priority over global persistent options
TEST_F(ClassifyTest,classGlobalPersistency)997 TEST_F(ClassifyTest, classGlobalPersistency) {
998 IfaceMgrTestConfig test_config(true);
999
1000 NakedDhcpv6Srv srv(0);
1001
1002 // Subnet sets an ipv6-forwarding option in the response.
1003 // The router class matches incoming packets with foo in a host-name
1004 // option (code 1234) and sets an ipv6-forwarding option in the response.
1005 // Note the persistency flag follows a "OR" semantic so to set
1006 // it to false (or to leave the default) has no effect.
1007 std::string config = "{ \"interfaces-config\": {"
1008 " \"interfaces\": [ \"*\" ] }, "
1009 "\"preferred-lifetime\": 3000,"
1010 "\"rebind-timer\": 2000, "
1011 "\"renew-timer\": 1000, "
1012 "\"valid-lifetime\": 4000, "
1013 "\"option-def\": [ "
1014 "{ \"name\": \"host-name\","
1015 " \"code\": 1234,"
1016 " \"type\": \"string\" },"
1017 "{ \"name\": \"ipv6-forwarding\","
1018 " \"code\": 2345,"
1019 " \"type\": \"boolean\" }],"
1020 "\"option-data\": ["
1021 " { \"name\": \"ipv6-forwarding\", "
1022 " \"data\": \"false\", "
1023 " \"always-send\": true } ], "
1024 "\"subnet6\": [ "
1025 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
1026 " \"subnet\": \"2001:db8:1::/48\", "
1027 " \"interface\": \"eth1\", "
1028 " \"option-data\": ["
1029 " { \"name\": \"ipv6-forwarding\", "
1030 " \"data\": \"false\", "
1031 " \"always-send\": false } ] } ] }";
1032 ASSERT_NO_THROW(configure(config));
1033
1034 // Create a packet with enough to select the subnet and go through
1035 // the SOLICIT processing
1036 Pkt6Ptr query = createSolicit();
1037
1038 // Do not add an ORO.
1039 OptionPtr oro = query->getOption(D6O_ORO);
1040 EXPECT_FALSE(oro);
1041
1042 // Create and add a host-name option to the query
1043 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
1044 ASSERT_TRUE(hostname);
1045 query->addOption(hostname);
1046
1047 // Process the query
1048 AllocEngine::ClientContext6 ctx;
1049 bool drop = false;
1050 srv.initContext(query, ctx, drop);
1051 ASSERT_FALSE(drop);
1052 Pkt6Ptr response = srv.processSolicit(ctx);
1053
1054 // Processing should add an ip-forwarding option
1055 OptionPtr opt = response->getOption(2345);
1056 ASSERT_TRUE(opt);
1057 ASSERT_GT(opt->len(), opt->getHeaderLen());
1058 // Global sets the value to true/1, subnet to false/0
1059 // Here subnet has the priority
1060 EXPECT_EQ(0, opt->getUint8());
1061 }
1062
1063 // Checks if the client-class field is indeed used for subnet selection.
1064 // Note that packet classification is already checked in ClassifyTest
1065 // .*Classification above.
TEST_F(ClassifyTest,clientClassifySubnet)1066 TEST_F(ClassifyTest, clientClassifySubnet) {
1067
1068 // This test configures 2 subnets. We actually only need the
1069 // first one, but since there's still this ugly hack that picks
1070 // the pool if there is only one, we must use more than one
1071 // subnet. That ugly hack will be removed in #3242, currently
1072 // under review.
1073
1074 // The second subnet does not play any role here. The client's
1075 // IP address belongs to the first subnet, so only that first
1076 // subnet is being tested.
1077 std::string config = "{ \"interfaces-config\": {"
1078 " \"interfaces\": [ \"*\" ]"
1079 "},"
1080 "\"preferred-lifetime\": 3000,"
1081 "\"rebind-timer\": 2000, "
1082 "\"renew-timer\": 1000, "
1083 "\"subnet6\": [ "
1084 " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
1085 " \"subnet\": \"2001:db8:1::/48\", "
1086 " \"client-class\": \"foo\" "
1087 " }, "
1088 " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
1089 " \"subnet\": \"2001:db8:2::/48\", "
1090 " \"client-class\": \"xyzzy\" "
1091 " } "
1092 "],"
1093 "\"valid-lifetime\": 4000 }";
1094
1095 ASSERT_NO_THROW(configure(config));
1096
1097 Pkt6Ptr sol = createSolicit("2001:db8:1::3");
1098
1099 // This discover does not belong to foo class, so it will not
1100 // be serviced
1101 bool drop = false;
1102 EXPECT_FALSE(srv_.selectSubnet(sol, drop));
1103 EXPECT_FALSE(drop);
1104
1105 // Let's add the packet to bar class and try again.
1106 sol->addClass("bar");
1107
1108 // Still not supported, because it belongs to wrong class.
1109 EXPECT_FALSE(srv_.selectSubnet(sol, drop));
1110 EXPECT_FALSE(drop);
1111
1112 // Let's add it to matching class.
1113 sol->addClass("foo");
1114
1115 // This time it should work
1116 EXPECT_TRUE(srv_.selectSubnet(sol, drop));
1117 EXPECT_FALSE(drop);
1118 }
1119
1120 // Checks if the client-class field is indeed used for pool selection.
TEST_F(ClassifyTest,clientClassifyPool)1121 TEST_F(ClassifyTest, clientClassifyPool) {
1122 IfaceMgrTestConfig test_config(true);
1123
1124 NakedDhcpv6Srv srv(0);
1125
1126 // This test configures 2 pools.
1127 // The second pool does not play any role here. The client's
1128 // IP address belongs to the first pool, so only that first
1129 // pool is being tested.
1130 std::string config = "{ \"interfaces-config\": {"
1131 " \"interfaces\": [ \"*\" ]"
1132 "},"
1133 "\"preferred-lifetime\": 3000,"
1134 "\"rebind-timer\": 2000, "
1135 "\"renew-timer\": 1000, "
1136 "\"client-classes\": [ "
1137 " { "
1138 " \"name\": \"foo\" "
1139 " }, "
1140 " { "
1141 " \"name\": \"bar\" "
1142 " } "
1143 "], "
1144 "\"subnet6\": [ "
1145 " { \"pools\": [ "
1146 " { "
1147 " \"pool\": \"2001:db8:1::/64\", "
1148 " \"client-class\": \"foo\" "
1149 " }, "
1150 " { "
1151 " \"pool\": \"2001:db8:2::/64\", "
1152 " \"client-class\": \"xyzzy\" "
1153 " } "
1154 " ], "
1155 " \"subnet\": \"2001:db8::/40\" "
1156 " } "
1157 "], "
1158 "\"valid-lifetime\": 4000 }";
1159
1160 ASSERT_NO_THROW(configure(config));
1161
1162 Pkt6Ptr query1 = createSolicit("2001:db8:1::3");
1163 Pkt6Ptr query2 = createSolicit("2001:db8:1::3");
1164 Pkt6Ptr query3 = createSolicit("2001:db8:1::3");
1165
1166 // This discover does not belong to foo class, so it will not
1167 // be serviced
1168 srv.classifyPacket(query1);
1169 AllocEngine::ClientContext6 ctx1;
1170 bool drop = false;
1171 srv.initContext(query1, ctx1, drop);
1172 ASSERT_FALSE(drop);
1173 Pkt6Ptr response1 = srv.processSolicit(ctx1);
1174 ASSERT_TRUE(response1);
1175 OptionPtr ia_na1 = response1->getOption(D6O_IA_NA);
1176 ASSERT_TRUE(ia_na1);
1177 EXPECT_TRUE(ia_na1->getOption(D6O_STATUS_CODE));
1178 EXPECT_FALSE(ia_na1->getOption(D6O_IAADDR));
1179
1180 // Let's add the packet to bar class and try again.
1181 query2->addClass("bar");
1182 // Still not supported, because it belongs to wrong class.
1183 srv.classifyPacket(query2);
1184 AllocEngine::ClientContext6 ctx2;
1185 srv.initContext(query2, ctx2, drop);
1186 ASSERT_FALSE(drop);
1187 Pkt6Ptr response2 = srv.processSolicit(ctx2);
1188 ASSERT_TRUE(response2);
1189 OptionPtr ia_na2 = response2->getOption(D6O_IA_NA);
1190 ASSERT_TRUE(ia_na2);
1191 EXPECT_TRUE(ia_na2->getOption(D6O_STATUS_CODE));
1192 EXPECT_FALSE(ia_na2->getOption(D6O_IAADDR));
1193
1194 // Let's add it to matching class.
1195 query3->addClass("foo");
1196 // This time it should work
1197 srv.classifyPacket(query3);
1198 AllocEngine::ClientContext6 ctx3;
1199 srv.initContext(query3, ctx3, drop);
1200 ASSERT_FALSE(drop);
1201 Pkt6Ptr response3 = srv.processSolicit(ctx3);
1202 ASSERT_TRUE(response3);
1203 OptionPtr ia_na3 = response3->getOption(D6O_IA_NA);
1204 ASSERT_TRUE(ia_na3);
1205 EXPECT_FALSE(ia_na3->getOption(D6O_STATUS_CODE));
1206 EXPECT_TRUE(ia_na3->getOption(D6O_IAADDR));
1207 }
1208
1209 // Checks if the [UN]KNOWN built-in classes is indeed used for pool selection.
TEST_F(ClassifyTest,clientClassifyPoolKnown)1210 TEST_F(ClassifyTest, clientClassifyPoolKnown) {
1211 IfaceMgrTestConfig test_config(true);
1212
1213 NakedDhcpv6Srv srv(0);
1214
1215 // This test configures 2 pools.
1216 // The first one requires reservation, the second does the opposite.
1217 std::string config = "{ \"interfaces-config\": {"
1218 " \"interfaces\": [ \"*\" ]"
1219 "},"
1220 "\"preferred-lifetime\": 3000,"
1221 "\"rebind-timer\": 2000, "
1222 "\"renew-timer\": 1000, "
1223 "\"subnet6\": [ "
1224 " { \"pools\": [ "
1225 " { "
1226 " \"pool\": \"2001:db8:1::/64\", "
1227 " \"client-class\": \"KNOWN\" "
1228 " }, "
1229 " { "
1230 " \"pool\": \"2001:db8:2::/64\", "
1231 " \"client-class\": \"UNKNOWN\" "
1232 " } "
1233 " ], "
1234 " \"subnet\": \"2001:db8::/40\", "
1235 " \"reservations\": [ "
1236 " { \"duid\": \"01:02:03:04\", \"hostname\": \"foo\" } ] "
1237 " } "
1238 "], "
1239 "\"valid-lifetime\": 4000 }";
1240
1241 ASSERT_NO_THROW(configure(config));
1242
1243 OptionPtr clientid1 = generateClientId();
1244 Pkt6Ptr query1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
1245 query1->setRemoteAddr(IOAddress("2001:db8:1::3"));
1246 query1->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
1247 query1->addOption(clientid1);
1248 query1->setIface("eth1");
1249 query1->setIndex(ETH1_INDEX);
1250
1251 // First pool requires reservation so the second will be used
1252 srv.classifyPacket(query1);
1253 AllocEngine::ClientContext6 ctx1;
1254 bool drop = false;
1255 srv.initContext(query1, ctx1, drop);
1256 ASSERT_FALSE(drop);
1257 Pkt6Ptr response1 = srv.processSolicit(ctx1);
1258 ASSERT_TRUE(response1);
1259 OptionPtr ia_na1 = response1->getOption(D6O_IA_NA);
1260 ASSERT_TRUE(ia_na1);
1261 EXPECT_FALSE(ia_na1->getOption(D6O_STATUS_CODE));
1262 OptionPtr iaaddr1 = ia_na1->getOption(D6O_IAADDR);
1263 ASSERT_TRUE(iaaddr1);
1264 boost::shared_ptr<Option6IAAddr> addr1 =
1265 boost::dynamic_pointer_cast<Option6IAAddr>(iaaddr1);
1266 ASSERT_TRUE(addr1);
1267 EXPECT_EQ("2001:db8:2::", addr1->getAddress().toText());
1268
1269 // Try with DUID 01:02:03:04
1270 uint8_t duid[] = { 0x01, 0x02, 0x03, 0x04 };
1271 OptionBuffer buf(duid, duid + sizeof(duid));
1272 OptionPtr clientid2(new Option(Option::V6, D6O_CLIENTID, buf));
1273 Pkt6Ptr query2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
1274 query2->setRemoteAddr(IOAddress("2001:db8:1::3"));
1275 query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
1276 query2->addOption(clientid2);
1277 query2->setIface("eth1");
1278 query2->setIndex(ETH1_INDEX);
1279
1280 // Now the first pool will be used
1281 srv.classifyPacket(query2);
1282 AllocEngine::ClientContext6 ctx2;
1283 srv.initContext(query2, ctx2, drop);
1284 ASSERT_FALSE(drop);
1285 Pkt6Ptr response2 = srv.processSolicit(ctx2);
1286 ASSERT_TRUE(response2);
1287 OptionPtr ia_na2 = response2->getOption(D6O_IA_NA);
1288 ASSERT_TRUE(ia_na2);
1289 EXPECT_FALSE(ia_na2->getOption(D6O_STATUS_CODE));
1290 OptionPtr iaaddr2 = ia_na2->getOption(D6O_IAADDR);
1291 ASSERT_TRUE(iaaddr2);
1292 boost::shared_ptr<Option6IAAddr> addr2 =
1293 boost::dynamic_pointer_cast<Option6IAAddr>(iaaddr2);
1294 ASSERT_TRUE(addr2);
1295 EXPECT_EQ("2001:db8:1::", addr2->getAddress().toText());
1296 }
1297
1298 // Tests whether a packet with custom vendor-class (not erouter or docsis)
1299 // is classified properly.
TEST_F(ClassifyTest,vendorClientClassification2)1300 TEST_F(ClassifyTest, vendorClientClassification2) {
1301 NakedDhcpv6Srv srv(0);
1302
1303 // Let's create a SOLICIT.
1304 Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
1305 sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
1306 sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
1307 OptionPtr clientid = generateClientId();
1308 sol->addOption(clientid);
1309
1310 // Now let's add a vendor-class with id=1234 and content "foo"
1311 OptionVendorClassPtr vendor_class(new OptionVendorClass(Option::V6, 1234));
1312 OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
1313 tuple = "foo";
1314 vendor_class->addTuple(tuple);
1315 sol->addOption(vendor_class);
1316
1317 // Now the server classifies the packet.
1318 srv.classifyPacket(sol);
1319
1320 // The packet should now belong to VENDOR_CLASS_foo.
1321 EXPECT_TRUE(sol->inClass(srv.VENDOR_CLASS_PREFIX + "foo"));
1322
1323 // It should not belong to "foo"
1324 EXPECT_FALSE(sol->inClass("foo"));
1325 }
1326
1327 // Checks if relay IP address specified in the relay-info structure can be
1328 // used together with client-classification.
TEST_F(ClassifyTest,relayOverrideAndClientClass)1329 TEST_F(ClassifyTest, relayOverrideAndClientClass) {
1330
1331 // This test configures 2 subnets. They both are on the same link, so they
1332 // have the same relay-ip address. Furthermore, the first subnet is
1333 // reserved for clients that belong to class "foo".
1334 std::string config = "{ \"interfaces-config\": {"
1335 " \"interfaces\": [ \"*\" ]"
1336 "},"
1337 "\"preferred-lifetime\": 3000,"
1338 "\"rebind-timer\": 2000, "
1339 "\"renew-timer\": 1000, "
1340 "\"subnet6\": [ "
1341 " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
1342 " \"subnet\": \"2001:db8:1::/48\", "
1343 " \"client-class\": \"foo\", "
1344 " \"relay\": { "
1345 " \"ip-address\": \"2001:db8:3::1\""
1346 " }"
1347 " }, "
1348 " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
1349 " \"subnet\": \"2001:db8:2::/48\", "
1350 " \"relay\": { "
1351 " \"ip-address\": \"2001:db8:3::1\""
1352 " }"
1353 " } "
1354 "],"
1355 "\"valid-lifetime\": 4000 }";
1356
1357 // Use this config to set up the server
1358 ASSERT_NO_THROW(configure(config));
1359
1360 // Let's get the subnet configuration objects
1361 const Subnet6Collection* subnets =
1362 CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
1363 ASSERT_EQ(2, subnets->size());
1364
1365 // Let's get them for easy reference
1366 Subnet6Ptr subnet1 = *subnets->begin();
1367 Subnet6Ptr subnet2 = *std::next(subnets->begin());
1368 ASSERT_TRUE(subnet1);
1369 ASSERT_TRUE(subnet2);
1370
1371 Pkt6Ptr sol = createSolicit("2001:db8:1::3");
1372
1373 // Now pretend the packet came via one relay.
1374 Pkt6::RelayInfo relay;
1375 relay.linkaddr_ = IOAddress("2001:db8:3::1");
1376 relay.peeraddr_ = IOAddress("fe80::1");
1377
1378 sol->relay_info_.push_back(relay);
1379
1380 // This packet does not belong to class foo, so it should be rejected in
1381 // subnet[0], even though the relay-ip matches. It should be accepted in
1382 // subnet[1], because the subnet matches and there are no class
1383 // requirements.
1384 bool drop = false;
1385 EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol, drop));
1386 EXPECT_FALSE(drop);
1387
1388 // Now let's add this packet to class foo and recheck. This time it should
1389 // be accepted in the first subnet, because both class and relay-ip match.
1390 sol->addClass("foo");
1391 EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol, drop));
1392 EXPECT_FALSE(drop);
1393 }
1394
1395 // This test checks that it is possible to specify static reservations for
1396 // client classes.
TEST_F(ClassifyTest,clientClassesInHostReservations)1397 TEST_F(ClassifyTest, clientClassesInHostReservations) {
1398 Dhcp6Client client;
1399 // Initially use a DUID for which there are no reservations. As a result,
1400 // the client should be assigned a single class "router".
1401 client.setDUID("01:02:03:05");
1402 client.setInterface("eth1");
1403 client.requestAddress();
1404 // Request all options we may potentially get. Otherwise, the server will
1405 // not return them, even when the client is assigned to the classes for
1406 // which these options should be sent.
1407 client.requestOption(2345);
1408 client.requestOption(D6O_NAME_SERVERS);
1409 client.requestOption(D6O_NIS_SERVERS);
1410
1411 ASSERT_NO_THROW(configure(CONFIGS[0], *client.getServer()));
1412
1413 // Adding this option to the client's message will cause the client to
1414 // belong to the 'router' class.
1415 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
1416 client.addExtraOption(hostname);
1417
1418 // Send a message to the server.
1419 ASSERT_NO_THROW(client.doSolicit(true));
1420
1421 // IP forwarding should be present, but DNS and NIS servers should not.
1422 ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_));
1423
1424 // Modify the DUID of our client to the one for which class reservations
1425 // have been made.
1426 client.setDUID("01:02:03:04");
1427 ASSERT_NO_THROW(client.doSolicit(true));
1428
1429 // This time, the client should obtain options from all three classes.
1430 ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
1431 "2001:db8:1::50",
1432 "2001:db8:1::100"));
1433
1434 // This should also work for Request case.
1435 ASSERT_NO_THROW(client.doSARR());
1436 ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
1437 "2001:db8:1::50",
1438 "2001:db8:1::100"));
1439
1440 // Renew case.
1441 ASSERT_NO_THROW(client.doRenew());
1442 ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
1443 "2001:db8:1::50",
1444 "2001:db8:1::100"));
1445
1446 // Rebind case.
1447 ASSERT_NO_THROW(client.doRebind());
1448 ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
1449 "2001:db8:1::50",
1450 "2001:db8:1::100"));
1451
1452 // Confirm case. This must be before Information-request because the
1453 // client must have an address to confirm from one of the transactions
1454 // involving address assignment, i.e. Request, Renew or Rebind.
1455 ASSERT_NO_THROW(client.doConfirm());
1456 ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
1457 "2001:db8:1::50",
1458 "2001:db8:1::100"));
1459
1460 // Information-request case.
1461 ASSERT_NO_THROW(client.doInfRequest());
1462 ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
1463 "2001:db8:1::50",
1464 "2001:db8:1::100"));
1465 }
1466
1467 // Check classification using membership expressions.
TEST_F(ClassifyTest,member)1468 TEST_F(ClassifyTest, member) {
1469 IfaceMgrTestConfig test_config(true);
1470
1471 NakedDhcpv6Srv srv(0);
1472
1473 // The router class matches incoming packets with foo in a host-name
1474 // option (code 1234) and sets an ipv6-forwarding option in the response.
1475 std::string config = "{ \"interfaces-config\": {"
1476 " \"interfaces\": [ \"*\" ] }, "
1477 "\"preferred-lifetime\": 3000,"
1478 "\"rebind-timer\": 2000, "
1479 "\"renew-timer\": 1000, "
1480 "\"valid-lifetime\": 4000, "
1481 "\"option-def\": [ "
1482 "{ \"name\": \"host-name\","
1483 " \"code\": 1234,"
1484 " \"type\": \"string\" },"
1485 "{ \"name\": \"ipv6-forwarding\","
1486 " \"code\": 2345,"
1487 " \"type\": \"boolean\" }],"
1488 "\"subnet6\": [ "
1489 "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
1490 " \"subnet\": \"2001:db8:1::/48\", "
1491 " \"interface\": \"eth1\" } ],"
1492 "\"client-classes\": [ "
1493 "{ \"name\": \"not-foo\", "
1494 " \"test\": \"not (option[host-name].text == 'foo')\""
1495 "},"
1496 "{ \"name\": \"foo\", "
1497 " \"option-data\": ["
1498 " { \"name\": \"ipv6-forwarding\", "
1499 " \"data\": \"true\" } ], "
1500 " \"test\": \"not member('not-foo')\""
1501 "},"
1502 "{ \"name\": \"bar\", "
1503 " \"test\": \"option[host-name].text == 'bar'\""
1504 "},"
1505 "{ \"name\": \"baz\", "
1506 " \"test\": \"option[host-name].text == 'baz'\""
1507 "},"
1508 "{ \"name\": \"barz\", "
1509 " \"option-data\": ["
1510 " { \"name\": \"ipv6-forwarding\", "
1511 " \"data\": \"false\" } ], "
1512 " \"test\": \"member('bar') or member('baz')\" } ] }";
1513
1514 ASSERT_NO_THROW(configure(config));
1515
1516 // Create packets with enough to select the subnet
1517 OptionPtr clientid = generateClientId();
1518 Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
1519 query1->setRemoteAddr(IOAddress("fe80::abcd"));
1520 query1->addOption(clientid);
1521 query1->setIface("eth1");
1522 query1->setIndex(ETH1_INDEX);
1523 query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
1524 Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
1525 query2->setRemoteAddr(IOAddress("fe80::abcd"));
1526 query2->addOption(clientid);
1527 query2->setIface("eth1");
1528 query2->setIndex(ETH1_INDEX);
1529 query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
1530 Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
1531 query3->setRemoteAddr(IOAddress("fe80::abcd"));
1532 query3->addOption(clientid);
1533 query3->setIface("eth1");
1534 query3->setIndex(ETH1_INDEX);
1535 query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
1536
1537 // Create and add an ORO option to queries
1538 OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
1539 ASSERT_TRUE(oro);
1540 oro->addValue(2345);
1541 query1->addOption(oro);
1542 query2->addOption(oro);
1543 query3->addOption(oro);
1544
1545 // Create and add a host-name option to the first and last queries
1546 OptionStringPtr hostname1(new OptionString(Option::V6, 1234, "foo"));
1547 ASSERT_TRUE(hostname1);
1548 query1->addOption(hostname1);
1549 OptionStringPtr hostname3(new OptionString(Option::V6, 1234, "baz"));
1550 ASSERT_TRUE(hostname3);
1551 query3->addOption(hostname3);
1552
1553 // Classify packets
1554 srv.classifyPacket(query1);
1555 srv.classifyPacket(query2);
1556 srv.classifyPacket(query3);
1557
1558 // Check classes
1559 EXPECT_FALSE(query1->inClass("not-foo"));
1560 EXPECT_TRUE(query1->inClass("foo"));
1561 EXPECT_FALSE(query1->inClass("bar"));
1562 EXPECT_FALSE(query1->inClass("baz"));
1563 EXPECT_FALSE(query1->inClass("barz"));
1564
1565 EXPECT_TRUE(query2->inClass("not-foo"));
1566 EXPECT_FALSE(query2->inClass("foo"));
1567 EXPECT_FALSE(query2->inClass("bar"));
1568 EXPECT_FALSE(query2->inClass("baz"));
1569 EXPECT_FALSE(query2->inClass("barz"));
1570
1571 EXPECT_TRUE(query3->inClass("not-foo"));
1572 EXPECT_FALSE(query3->inClass("foo"));
1573 EXPECT_FALSE(query3->inClass("bar"));
1574 EXPECT_TRUE(query3->inClass("baz"));
1575 EXPECT_TRUE(query3->inClass("barz"));
1576
1577 // Process queries
1578 AllocEngine::ClientContext6 ctx1;
1579 bool drop = false;
1580 srv.initContext(query1, ctx1, drop);
1581 ASSERT_FALSE(drop);
1582 Pkt6Ptr response1 = srv.processSolicit(ctx1);
1583 AllocEngine::ClientContext6 ctx2;
1584 srv.initContext(query2, ctx2, drop);
1585 ASSERT_FALSE(drop);
1586 Pkt6Ptr response2 = srv.processSolicit(ctx2);
1587 AllocEngine::ClientContext6 ctx3;
1588 srv.initContext(query3, ctx3, drop);
1589 ASSERT_FALSE(drop);
1590 Pkt6Ptr response3 = srv.processSolicit(ctx3);
1591
1592 // Classification processing should add an ip-forwarding option
1593 OptionPtr opt1 = response1->getOption(2345);
1594 EXPECT_TRUE(opt1);
1595 OptionCustomPtr ipf1 =
1596 boost::dynamic_pointer_cast<OptionCustom>(opt1);
1597 ASSERT_TRUE(ipf1);
1598 EXPECT_TRUE(ipf1->readBoolean());
1599
1600 // But not the second query which was not classified
1601 OptionPtr opt2 = response2->getOption(2345);
1602 EXPECT_FALSE(opt2);
1603
1604 // The third has the option but with another value
1605 OptionPtr opt3 = response3->getOption(2345);
1606 EXPECT_TRUE(opt3);
1607 OptionCustomPtr ipf3 =
1608 boost::dynamic_pointer_cast<OptionCustom>(opt3);
1609 ASSERT_TRUE(ipf3);
1610 EXPECT_FALSE(ipf3->readBoolean());
1611 }
1612
1613 // This test checks the precedence order in required evaluation.
1614 // This order is: shared-network > subnet > pools
TEST_F(ClassifyTest,precedenceNone)1615 TEST_F(ClassifyTest, precedenceNone) {
1616 std::string config =
1617 "{"
1618 "\"interfaces-config\": {"
1619 " \"interfaces\": [ \"*\" ]"
1620 "},"
1621 "\"preferred-lifetime\": 3000,"
1622 "\"rebind-timer\": 2000,"
1623 "\"renew-timer\": 1000,"
1624 "\"client-classes\": ["
1625 " {"
1626 " \"name\": \"all\","
1627 " \"test\": \"'' == ''\""
1628 " },"
1629 " {"
1630 " \"name\": \"for-pool\","
1631 " \"test\": \"member('all')\","
1632 " \"only-if-required\": true,"
1633 " \"option-data\": [ {"
1634 " \"name\": \"dns-servers\","
1635 " \"data\": \"2001:db8:1::1\""
1636 " } ]"
1637 " },"
1638 " {"
1639 " \"name\": \"for-subnet\","
1640 " \"test\": \"member('all')\","
1641 " \"only-if-required\": true,"
1642 " \"option-data\": [ {"
1643 " \"name\": \"dns-servers\","
1644 " \"data\": \"2001:db8:1::2\""
1645 " } ]"
1646 " },"
1647 " {"
1648 " \"name\": \"for-network\","
1649 " \"test\": \"member('all')\","
1650 " \"only-if-required\": true,"
1651 " \"option-data\": [ {"
1652 " \"name\": \"dns-servers\","
1653 " \"data\": \"2001:db8:1::3\""
1654 " } ]"
1655 " }"
1656 "],"
1657 "\"shared-networks\": [ {"
1658 " \"name\": \"frog\","
1659 " \"interface\": \"eth1\","
1660 " \"subnet6\": [ { "
1661 " \"subnet\": \"2001:db8:1::/64\","
1662 " \"id\": 1,"
1663 " \"pools\": [ { "
1664 " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
1665 " } ]"
1666 " } ]"
1667 "} ],"
1668 "\"valid-lifetime\": 600"
1669 "}";
1670
1671 // Create a client requesting dns-servers option
1672 Dhcp6Client client;
1673 client.setInterface("eth1");
1674 client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
1675 client.requestOption(D6O_NAME_SERVERS);
1676
1677 // Load the config and perform a SARR
1678 configure(config, *client.getServer());
1679 ASSERT_NO_THROW(client.doSARR());
1680
1681 // Check response
1682 EXPECT_EQ(1, client.getLeaseNum());
1683 Pkt6Ptr resp = client.getContext().response_;
1684 ASSERT_TRUE(resp);
1685
1686 // Check dns-servers option
1687 OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
1688 EXPECT_FALSE(opt);
1689 }
1690
1691 // This test checks the precedence order in required evaluation.
1692 // This order is: shared-network > subnet > pools
TEST_F(ClassifyTest,precedencePool)1693 TEST_F(ClassifyTest, precedencePool) {
1694 std::string config =
1695 "{"
1696 "\"interfaces-config\": {"
1697 " \"interfaces\": [ \"*\" ]"
1698 "},"
1699 "\"client-classes\": ["
1700 " {"
1701 " \"name\": \"all\","
1702 " \"test\": \"'' == ''\""
1703 " },"
1704 " {"
1705 " \"name\": \"for-pool\","
1706 " \"test\": \"member('all')\","
1707 " \"only-if-required\": true,"
1708 " \"option-data\": [ {"
1709 " \"name\": \"dns-servers\","
1710 " \"data\": \"2001:db8:1::1\""
1711 " } ]"
1712 " },"
1713 " {"
1714 " \"name\": \"for-subnet\","
1715 " \"test\": \"member('all')\","
1716 " \"only-if-required\": true,"
1717 " \"option-data\": [ {"
1718 " \"name\": \"dns-servers\","
1719 " \"data\": \"2001:db8:1::2\""
1720 " } ]"
1721 " },"
1722 " {"
1723 " \"name\": \"for-network\","
1724 " \"test\": \"member('all')\","
1725 " \"only-if-required\": true,"
1726 " \"option-data\": [ {"
1727 " \"name\": \"dns-servers\","
1728 " \"data\": \"2001:db8:1::3\""
1729 " } ]"
1730 " }"
1731 "],"
1732 "\"shared-networks\": [ {"
1733 " \"name\": \"frog\","
1734 " \"interface\": \"eth1\","
1735 " \"subnet6\": [ { "
1736 " \"subnet\": \"2001:db8:1::/64\","
1737 " \"id\": 1,"
1738 " \"pools\": [ { "
1739 " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
1740 " \"require-client-classes\": [ \"for-pool\" ]"
1741 " } ]"
1742 " } ]"
1743 "} ],"
1744 "\"valid-lifetime\": 600"
1745 "}";
1746
1747 // Create a client requesting dns-servers option
1748 Dhcp6Client client;
1749 client.setInterface("eth1");
1750 client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
1751 client.requestOption(D6O_NAME_SERVERS);
1752
1753 // Load the config and perform a SARR
1754 configure(config, *client.getServer());
1755 ASSERT_NO_THROW(client.doSARR());
1756
1757 // Check response
1758 EXPECT_EQ(1, client.getLeaseNum());
1759 Pkt6Ptr resp = client.getContext().response_;
1760 ASSERT_TRUE(resp);
1761
1762 // Check dns-servers option
1763 OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
1764 ASSERT_TRUE(opt);
1765 Option6AddrLstPtr servers =
1766 boost::dynamic_pointer_cast<Option6AddrLst>(opt);
1767 ASSERT_TRUE(servers);
1768 auto addrs = servers->getAddresses();
1769 ASSERT_EQ(1, addrs.size());
1770 EXPECT_EQ("2001:db8:1::1", addrs[0].toText());
1771 }
1772
1773 // This test checks the precedence order in required evaluation.
1774 // This order is: shared-network > subnet > pools
TEST_F(ClassifyTest,precedenceSubnet)1775 TEST_F(ClassifyTest, precedenceSubnet) {
1776 std::string config =
1777 "{"
1778 "\"interfaces-config\": {"
1779 " \"interfaces\": [ \"*\" ]"
1780 "},"
1781 "\"client-classes\": ["
1782 " {"
1783 " \"name\": \"all\","
1784 " \"test\": \"'' == ''\""
1785 " },"
1786 " {"
1787 " \"name\": \"for-pool\","
1788 " \"test\": \"member('all')\","
1789 " \"only-if-required\": true,"
1790 " \"option-data\": [ {"
1791 " \"name\": \"dns-servers\","
1792 " \"data\": \"2001:db8:1::1\""
1793 " } ]"
1794 " },"
1795 " {"
1796 " \"name\": \"for-subnet\","
1797 " \"test\": \"member('all')\","
1798 " \"only-if-required\": true,"
1799 " \"option-data\": [ {"
1800 " \"name\": \"dns-servers\","
1801 " \"data\": \"2001:db8:1::2\""
1802 " } ]"
1803 " },"
1804 " {"
1805 " \"name\": \"for-network\","
1806 " \"test\": \"member('all')\","
1807 " \"only-if-required\": true,"
1808 " \"option-data\": [ {"
1809 " \"name\": \"dns-servers\","
1810 " \"data\": \"2001:db8:1::3\""
1811 " } ]"
1812 " }"
1813 "],"
1814 "\"shared-networks\": [ {"
1815 " \"name\": \"frog\","
1816 " \"interface\": \"eth1\","
1817 " \"subnet6\": [ { "
1818 " \"subnet\": \"2001:db8:1::/64\","
1819 " \"id\": 1,"
1820 " \"require-client-classes\": [ \"for-subnet\" ],"
1821 " \"pools\": [ { "
1822 " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
1823 " \"require-client-classes\": [ \"for-pool\" ]"
1824 " } ]"
1825 " } ]"
1826 "} ],"
1827 "\"valid-lifetime\": 600"
1828 "}";
1829
1830 // Create a client requesting dns-servers option
1831 Dhcp6Client client;
1832 client.setInterface("eth1");
1833 client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
1834 client.requestOption(D6O_NAME_SERVERS);
1835
1836 // Load the config and perform a SARR
1837 configure(config, *client.getServer());
1838 ASSERT_NO_THROW(client.doSARR());
1839
1840 // Check response
1841 EXPECT_EQ(1, client.getLeaseNum());
1842 Pkt6Ptr resp = client.getContext().response_;
1843 ASSERT_TRUE(resp);
1844
1845 // Check dns-servers option
1846 OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
1847 ASSERT_TRUE(opt);
1848 Option6AddrLstPtr servers =
1849 boost::dynamic_pointer_cast<Option6AddrLst>(opt);
1850 ASSERT_TRUE(servers);
1851 auto addrs = servers->getAddresses();
1852 ASSERT_EQ(1, addrs.size());
1853 EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
1854 }
1855
1856 // This test checks the precedence order in required evaluation.
1857 // This order is: shared-network > subnet > pools
TEST_F(ClassifyTest,precedenceNetwork)1858 TEST_F(ClassifyTest, precedenceNetwork) {
1859 std::string config =
1860 "{"
1861 "\"interfaces-config\": {"
1862 " \"interfaces\": [ \"*\" ]"
1863 "},"
1864 "\"client-classes\": ["
1865 " {"
1866 " \"name\": \"all\","
1867 " \"test\": \"'' == ''\""
1868 " },"
1869 " {"
1870 " \"name\": \"for-pool\","
1871 " \"test\": \"member('all')\","
1872 " \"only-if-required\": true,"
1873 " \"option-data\": [ {"
1874 " \"name\": \"dns-servers\","
1875 " \"data\": \"2001:db8:1::1\""
1876 " } ]"
1877 " },"
1878 " {"
1879 " \"name\": \"for-subnet\","
1880 " \"test\": \"member('all')\","
1881 " \"only-if-required\": true,"
1882 " \"option-data\": [ {"
1883 " \"name\": \"dns-servers\","
1884 " \"data\": \"2001:db8:1::2\""
1885 " } ]"
1886 " },"
1887 " {"
1888 " \"name\": \"for-network\","
1889 " \"test\": \"member('all')\","
1890 " \"only-if-required\": true,"
1891 " \"option-data\": [ {"
1892 " \"name\": \"dns-servers\","
1893 " \"data\": \"2001:db8:1::3\""
1894 " } ]"
1895 " }"
1896 "],"
1897 "\"shared-networks\": [ {"
1898 " \"name\": \"frog\","
1899 " \"interface\": \"eth1\","
1900 " \"require-client-classes\": [ \"for-network\" ],"
1901 " \"subnet6\": [ { "
1902 " \"subnet\": \"2001:db8:1::/64\","
1903 " \"id\": 1,"
1904 " \"require-client-classes\": [ \"for-subnet\" ],"
1905 " \"pools\": [ { "
1906 " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
1907 " \"require-client-classes\": [ \"for-pool\" ]"
1908 " } ]"
1909 " } ]"
1910 "} ],"
1911 "\"valid-lifetime\": 600"
1912 "}";
1913
1914 // Create a client requesting dns-servers option
1915 Dhcp6Client client;
1916 client.setInterface("eth1");
1917 client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
1918 client.requestOption(D6O_NAME_SERVERS);
1919
1920 // Load the config and perform a SARR
1921 configure(config, *client.getServer());
1922 ASSERT_NO_THROW(client.doSARR());
1923
1924 // Check response
1925 EXPECT_EQ(1, client.getLeaseNum());
1926 Pkt6Ptr resp = client.getContext().response_;
1927 ASSERT_TRUE(resp);
1928
1929 // Check dns-servers option
1930 OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
1931 ASSERT_TRUE(opt);
1932 Option6AddrLstPtr servers =
1933 boost::dynamic_pointer_cast<Option6AddrLst>(opt);
1934 ASSERT_TRUE(servers);
1935 auto addrs = servers->getAddresses();
1936 ASSERT_EQ(1, addrs.size());
1937 EXPECT_EQ("2001:db8:1::3", addrs[0].toText());
1938 }
1939
1940 // This test checks the complex membership from HA with server1 telephone.
TEST_F(ClassifyTest,server1Telephone)1941 TEST_F(ClassifyTest, server1Telephone) {
1942 // Create a client.
1943 Dhcp6Client client;
1944 client.setInterface("eth1");
1945 ASSERT_NO_THROW(client.requestAddress(0xabca0));
1946
1947 // Add option.
1948 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
1949 client.addExtraOption(hostname);
1950
1951 // Add server1
1952 client.addClass("server1");
1953
1954 // Load the config and perform a SARR
1955 configure(CONFIGS[1], *client.getServer());
1956 ASSERT_NO_THROW(client.doSARR());
1957
1958 // Check response
1959 Pkt6Ptr resp = client.getContext().response_;
1960 ASSERT_TRUE(resp);
1961
1962 // The address is from the first pool.
1963 ASSERT_EQ(1, client.getLeaseNum());
1964 Lease6 lease_client = client.getLease(0);
1965 EXPECT_EQ("2001:db8:1:1::", lease_client.addr_.toText());
1966 }
1967
1968 // This test checks the complex membership from HA with server1 computer.
TEST_F(ClassifyTest,server1Computer)1969 TEST_F(ClassifyTest, server1Computer) {
1970 // Create a client.
1971 Dhcp6Client client;
1972 client.setInterface("eth1");
1973 ASSERT_NO_THROW(client.requestAddress(0xabca0));
1974
1975 // Add option.
1976 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
1977 client.addExtraOption(hostname);
1978
1979 // Add server1
1980 client.addClass("server1");
1981
1982 // Load the config and perform a SARR
1983 configure(CONFIGS[1], *client.getServer());
1984 ASSERT_NO_THROW(client.doSARR());
1985
1986 // Check response
1987 Pkt6Ptr resp = client.getContext().response_;
1988 ASSERT_TRUE(resp);
1989
1990 // The address is from the second pool.
1991 ASSERT_EQ(1, client.getLeaseNum());
1992 Lease6 lease_client = client.getLease(0);
1993 EXPECT_EQ("2001:db8:1:2::", lease_client.addr_.toText());
1994 }
1995
1996 // This test checks the complex membership from HA with server2 telephone.
TEST_F(ClassifyTest,server2Telephone)1997 TEST_F(ClassifyTest, server2Telephone) {
1998 // Create a client.
1999 Dhcp6Client client;
2000 client.setInterface("eth1");
2001 ASSERT_NO_THROW(client.requestAddress(0xabca0));
2002
2003 // Add option.
2004 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
2005 client.addExtraOption(hostname);
2006
2007 // Add server2
2008 client.addClass("server2");
2009
2010 // Load the config and perform a SARR
2011 configure(CONFIGS[1], *client.getServer());
2012 ASSERT_NO_THROW(client.doSARR());
2013
2014 // Check response
2015 Pkt6Ptr resp = client.getContext().response_;
2016 ASSERT_TRUE(resp);
2017
2018 // The address is from the third pool.
2019 ASSERT_EQ(1, client.getLeaseNum());
2020 Lease6 lease_client = client.getLease(0);
2021 EXPECT_EQ("2001:db8:1:3::", lease_client.addr_.toText());
2022 }
2023
2024 // This test checks the complex membership from HA with server2 computer.
TEST_F(ClassifyTest,server2Computer)2025 TEST_F(ClassifyTest, server2Computer) {
2026 // Create a client.
2027 Dhcp6Client client;
2028 client.setInterface("eth1");
2029 ASSERT_NO_THROW(client.requestAddress(0xabca0));
2030
2031 // Add option.
2032 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
2033 client.addExtraOption(hostname);
2034
2035 // Add server2
2036 client.addClass("server2");
2037
2038 // Load the config and perform a SARR
2039 configure(CONFIGS[1], *client.getServer());
2040 ASSERT_NO_THROW(client.doSARR());
2041
2042 // Check response
2043 Pkt6Ptr resp = client.getContext().response_;
2044 ASSERT_TRUE(resp);
2045
2046 // The address is from the forth pool.
2047 ASSERT_EQ(1, client.getLeaseNum());
2048 Lease6 lease_client = client.getLease(0);
2049 EXPECT_EQ("2001:db8:1:4::", lease_client.addr_.toText());
2050 }
2051
2052 // This test checks the complex membership from HA with server1 telephone
2053 // with prefixes.
TEST_F(ClassifyTest,pDserver1Telephone)2054 TEST_F(ClassifyTest, pDserver1Telephone) {
2055 // Create a client.
2056 Dhcp6Client client;
2057 client.setInterface("eth1");
2058 ASSERT_NO_THROW(client.requestPrefix(0xabca0));
2059
2060 // Add option.
2061 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
2062 client.addExtraOption(hostname);
2063
2064 // Add server1
2065 client.addClass("server1");
2066
2067 // Load the config and perform a SARR
2068 configure(CONFIGS[2], *client.getServer());
2069 ASSERT_NO_THROW(client.doSARR());
2070
2071 // Check response
2072 Pkt6Ptr resp = client.getContext().response_;
2073 ASSERT_TRUE(resp);
2074
2075 // The prefix is from the first pool.
2076 ASSERT_EQ(1, client.getLeaseNum());
2077 Lease6 lease_client = client.getLease(0);
2078 EXPECT_EQ("2001:db8:1::", lease_client.addr_.toText());
2079 }
2080
2081 // This test checks the complex membership from HA with server1 computer
2082 // with prefix.
TEST_F(ClassifyTest,pDserver1Computer)2083 TEST_F(ClassifyTest, pDserver1Computer) {
2084 // Create a client.
2085 Dhcp6Client client;
2086 client.setInterface("eth1");
2087 ASSERT_NO_THROW(client.requestPrefix(0xabca0));
2088
2089 // Add option.
2090 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
2091 client.addExtraOption(hostname);
2092
2093 // Add server1
2094 client.addClass("server1");
2095
2096 // Load the config and perform a SARR
2097 configure(CONFIGS[2], *client.getServer());
2098 ASSERT_NO_THROW(client.doSARR());
2099
2100 // Check response
2101 Pkt6Ptr resp = client.getContext().response_;
2102 ASSERT_TRUE(resp);
2103
2104 // The prefix is from the second pool.
2105 ASSERT_EQ(1, client.getLeaseNum());
2106 Lease6 lease_client = client.getLease(0);
2107 EXPECT_EQ("2001:db8:2::", lease_client.addr_.toText());
2108 }
2109
2110 // This test checks the complex membership from HA with server2 telephone
2111 // with prefixes.
TEST_F(ClassifyTest,pDserver2Telephone)2112 TEST_F(ClassifyTest, pDserver2Telephone) {
2113 // Create a client.
2114 Dhcp6Client client;
2115 client.setInterface("eth1");
2116 ASSERT_NO_THROW(client.requestPrefix(0xabca0));
2117
2118 // Add option.
2119 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
2120 client.addExtraOption(hostname);
2121
2122 // Add server2
2123 client.addClass("server2");
2124
2125 // Load the config and perform a SARR
2126 configure(CONFIGS[2], *client.getServer());
2127 ASSERT_NO_THROW(client.doSARR());
2128
2129 // Check response
2130 Pkt6Ptr resp = client.getContext().response_;
2131 ASSERT_TRUE(resp);
2132
2133 // The prefix is from the third pool.
2134 ASSERT_EQ(1, client.getLeaseNum());
2135 Lease6 lease_client = client.getLease(0);
2136 EXPECT_EQ("2001:db8:3::", lease_client.addr_.toText());
2137 }
2138
2139 // This test checks the complex membership from HA with server2 computer
2140 // with prefix.
TEST_F(ClassifyTest,pDserver2Computer)2141 TEST_F(ClassifyTest, pDserver2Computer) {
2142 // Create a client.
2143 Dhcp6Client client;
2144 client.setInterface("eth1");
2145 ASSERT_NO_THROW(client.requestPrefix(0xabca0));
2146
2147 // Add option.
2148 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
2149 client.addExtraOption(hostname);
2150
2151 // Add server2
2152 client.addClass("server2");
2153
2154 // Load the config and perform a SARR
2155 configure(CONFIGS[2], *client.getServer());
2156 ASSERT_NO_THROW(client.doSARR());
2157
2158 // Check response
2159 Pkt6Ptr resp = client.getContext().response_;
2160 ASSERT_TRUE(resp);
2161
2162 // The prefix is from the forth pool.
2163 ASSERT_EQ(1, client.getLeaseNum());
2164 Lease6 lease_client = client.getLease(0);
2165 EXPECT_EQ("2001:db8:4::", lease_client.addr_.toText());
2166 }
2167
2168 // This test checks the handling for the DROP special class.
TEST_F(ClassifyTest,dropClass)2169 TEST_F(ClassifyTest, dropClass) {
2170 Dhcp6Client client;
2171 client.setDUID("01:02:03:05");
2172 client.setInterface("eth1");
2173 client.requestAddress();
2174
2175 // Configure DHCP server.
2176 ASSERT_NO_THROW(configure(CONFIGS[3], *client.getServer()));
2177
2178 // Send a message to the server.
2179 ASSERT_NO_THROW(client.doSolicit(true));
2180
2181 // No option: no drop.
2182 EXPECT_TRUE(client.getContext().response_);
2183
2184 // Retry with an option matching the DROP class.
2185 Dhcp6Client client2;
2186
2187 // Add the host-name option.
2188 OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
2189 ASSERT_TRUE(hostname);
2190 client2.addExtraOption(hostname);
2191
2192 // Send a message to the server.
2193 ASSERT_NO_THROW(client2.doSolicit(true));
2194
2195 // Option, dropped.
2196 EXPECT_FALSE(client2.getContext().response_);
2197
2198 // There should also be pkt6-receive-drop stat bumped up.
2199 stats::StatsMgr& mgr = stats::StatsMgr::instance();
2200 stats::ObservationPtr drop_stat = mgr.getObservation("pkt6-receive-drop");
2201
2202 // This statistic must be present and must be set to 1.
2203 ASSERT_TRUE(drop_stat);
2204 EXPECT_EQ(1, drop_stat->getInteger().first);
2205 }
2206
2207 // This test checks the handling for the DROP special class at the host
2208 // reservation classification point with KNOWN / UNKNOWN.
TEST_F(ClassifyTest,dropClassUnknown)2209 TEST_F(ClassifyTest, dropClassUnknown) {
2210 Dhcp6Client client;
2211 client.setDUID("01:02:03:04");
2212 client.setInterface("eth1");
2213 client.requestAddress();
2214
2215 // Configure DHCP server.
2216 ASSERT_NO_THROW(configure(CONFIGS[4], *client.getServer()));
2217
2218 // Send a message to the server.
2219 ASSERT_NO_THROW(client.doSolicit(true));
2220
2221 // Reservation match: no drop.
2222 EXPECT_TRUE(client.getContext().response_);
2223
2224 // Retry with an option matching the DROP class.
2225 Dhcp6Client client2;
2226
2227 // Retry with another DUID.
2228 client2.setDUID("01:02:03:05");
2229
2230 // Send a message to the server.
2231 ASSERT_NO_THROW(client2.doSolicit(true));
2232
2233 // No reservation, dropped.
2234 EXPECT_FALSE(client2.getContext().response_);
2235
2236 // There should also be pkt6-receive-drop stat bumped up.
2237 stats::StatsMgr& mgr = stats::StatsMgr::instance();
2238 stats::ObservationPtr drop_stat = mgr.getObservation("pkt6-receive-drop");
2239
2240 // This statistic must be present and must be set to 1.
2241 ASSERT_TRUE(drop_stat);
2242 EXPECT_EQ(1, drop_stat->getInteger().first);
2243 }
2244
2245 // This test checks the handling for the DROP special class at the host
2246 // reservation classification point with a reserved class.
TEST_F(ClassifyTest,dropClassReservedClass)2247 TEST_F(ClassifyTest, dropClassReservedClass) {
2248 Dhcp6Client client;
2249 client.setDUID("01:02:03:04");
2250 client.setInterface("eth1");
2251 client.requestAddress();
2252
2253 // Configure DHCP server.
2254 ASSERT_NO_THROW(configure(CONFIGS[5], *client.getServer()));
2255
2256 // Send a message to the server.
2257 ASSERT_NO_THROW(client.doSolicit(true));
2258
2259 // Reservation match: no drop.
2260 EXPECT_TRUE(client.getContext().response_);
2261
2262 // Retry with an option matching the DROP class.
2263 Dhcp6Client client2;
2264
2265 // Retry with another DUID.
2266 client2.setDUID("01:02:03:05");
2267
2268 // Send a message to the server.
2269 ASSERT_NO_THROW(client2.doSolicit(true));
2270
2271 // No reservation, dropped.
2272 EXPECT_FALSE(client2.getContext().response_);
2273
2274 // There should also be pkt6-receive-drop stat bumped up.
2275 stats::StatsMgr& mgr = stats::StatsMgr::instance();
2276 stats::ObservationPtr drop_stat = mgr.getObservation("pkt6-receive-drop");
2277
2278 // This statistic must be present and must be set to 1.
2279 ASSERT_TRUE(drop_stat);
2280 EXPECT_EQ(1, drop_stat->getInteger().first);
2281 }
2282
2283 } // end of anonymous namespace
2284