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