1 // Copyright (C) 2017-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 <asiolink/io_address.h>
9 #include <cc/data.h>
10 #include <dhcp/dhcp4.h>
11 #include <dhcp/tests/iface_mgr_test_config.h>
12 #include <dhcp/option.h>
13 #include <dhcp/option_int.h>
14 #include <dhcp/option_string.h>
15 #include <dhcpsrv/cfgmgr.h>
16 #include <dhcpsrv/cfg_subnets4.h>
17 #include <dhcpsrv/lease_mgr_factory.h>
18 #include <dhcp4/json_config_parser.h>
19 #include <dhcp4/tests/dhcp4_client.h>
20 #include <dhcp4/tests/dhcp4_test_utils.h>
21 #include <stats/stats_mgr.h>
22 #include <boost/pointer_cast.hpp>
23 #include <boost/shared_ptr.hpp>
24 #include <functional>
25 
26 using namespace isc;
27 using namespace isc::asiolink;
28 using namespace isc::data;
29 using namespace isc::dhcp;
30 using namespace isc::dhcp::test;
31 using namespace isc::stats;
32 
33 namespace {
34 
35 /// @brief Array of server configurations used throughout the tests.
36 const char* NETWORKS_CONFIG[] = {
37 // Configuration #0
38 // - 1 shared network with 2 subnets (interface specified)
39 // - 1 "plain" subnet (different interface specified)
40     "{"
41     "    \"interfaces-config\": {"
42     "        \"interfaces\": [ \"*\" ]"
43     "    },"
44     "    \"valid-lifetime\": 600,"
45     "    \"shared-networks\": ["
46     "        {"
47     "            \"name\": \"frog\","
48     "            \"interface\": \"eth1\","
49     "            \"comment\": \"example\","
50     "            \"subnet4\": ["
51     "                {"
52     "                    \"subnet\": \"192.0.2.0/26\","
53     "                    \"id\": 10,"
54     "                    \"pools\": ["
55     "                        {"
56     "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
57     "                        }"
58     "                    ]"
59     "                },"
60     "                {"
61     "                    \"subnet\": \"10.0.0.0/24\","
62     "                    \"id\": 100,"
63     "                    \"pools\": ["
64     "                        {"
65     "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
66     "                        }"
67     "                    ]"
68     "                }"
69     "            ]"
70     "        }"
71     "    ],"
72     "    \"subnet4\": ["
73     "        {"
74     "            \"subnet\": \"192.0.2.64/26\","
75     "            \"id\": 1000,"
76     "            \"interface\": \"eth0\","
77     "            \"pools\": ["
78     "                {"
79     "                    \"pool\": \"192.0.2.65 - 192.0.2.65\""
80     "                }"
81     "            ]"
82     "        }"
83     "    ]"
84     "}",
85 
86 // Configuration #1
87 // - 1 shared networks with 1 subnet, relay ip specified
88 // - 1 "plain" subnet, relay ip specified
89     "{"
90     "    \"interfaces-config\": {"
91     "        \"interfaces\": [ \"*\" ]"
92     "    },"
93     "    \"valid-lifetime\": 600,"
94     "    \"shared-networks\": ["
95     "        {"
96     "            \"name\": \"frog\","
97     "            \"relay\": {"
98     "                \"ip-address\": \"192.3.5.6\""
99     "            },"
100     "            \"subnet4\": ["
101     "                {"
102     "                    \"subnet\": \"192.0.2.0/26\","
103     "                    \"id\": 10,"
104     "                    \"pools\": ["
105     "                        {"
106     "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
107     "                        }"
108     "                    ]"
109     "                }"
110     "            ]"
111     "        }"
112     "    ],"
113     "    \"subnet4\": ["
114     "        {"
115     "            \"subnet\": \"192.0.2.64/26\","
116     "            \"id\": 1000,"
117     "            \"relay\": {"
118     "                \"ip-address\": \"192.1.2.3\""
119     "            },"
120     "            \"pools\": ["
121     "                {"
122     "                    \"pool\": \"192.0.2.65 - 192.0.2.65\""
123     "                }"
124     "            ]"
125     "        }"
126     "    ]"
127     "}",
128 
129 // Configuration #2
130 // - 2 classes defined
131 // - 1 shared network with 2 subnets (first has class restriction)
132     "{"
133     "    \"interfaces-config\": {"
134     "        \"interfaces\": [ \"*\" ]"
135     "    },"
136     "    \"valid-lifetime\": 600,"
137     "    \"client-classes\": ["
138     "        {"
139     "            \"name\": \"a-devices\","
140     "            \"test\": \"option[93].hex == 0x0001\""
141     "        },"
142     "        {"
143     "            \"name\": \"b-devices\","
144     "            \"test\": \"option[93].hex == 0x0002\""
145     "        }"
146     "    ],"
147     "    \"shared-networks\": ["
148     "        {"
149     "            \"name\": \"frog\","
150     "            \"relay\": {"
151     "                \"ip-address\": \"192.3.5.6\""
152     "            },"
153     "            \"subnet4\": ["
154     "                {"
155     "                    \"subnet\": \"192.0.2.0/26\","
156     "                    \"id\": 10,"
157     "                    \"pools\": ["
158     "                        {"
159     "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
160     "                        }"
161     "                    ],"
162     "                    \"client-class\": \"a-devices\""
163     "                },"
164     "                {"
165     "                    \"subnet\": \"10.0.0.0/24\","
166     "                    \"id\": 100,"
167     "                    \"pools\": ["
168     "                        {"
169     "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
170     "                        }"
171     "                    ]"
172     "                }"
173     "            ]"
174     "        }"
175     "    ]"
176     "}",
177 
178 // Configuration #3
179 // - 2 classes specified
180 // - 1 shared network with 2 subnets (each with class restriction)
181     "{"
182     "    \"interfaces-config\": {"
183     "        \"interfaces\": [ \"*\" ]"
184     "    },"
185     "    \"valid-lifetime\": 600,"
186     "    \"client-classes\": ["
187     "        {"
188     "            \"name\": \"a-devices\","
189     "            \"test\": \"option[93].hex == 0x0001\""
190     "        },"
191     "        {"
192     "            \"name\": \"b-devices\","
193     "            \"test\": \"option[93].hex == 0x0002\""
194     "        }"
195     "    ],"
196     "    \"shared-networks\": ["
197     "        {"
198     "            \"name\": \"frog\","
199     "            \"relay\": {"
200     "                \"ip-address\": \"192.3.5.6\""
201     "            },"
202     "            \"subnet4\": ["
203     "                {"
204     "                    \"subnet\": \"192.0.2.0/26\","
205     "                    \"id\": 10,"
206     "                    \"pools\": ["
207     "                        {"
208     "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
209     "                        }"
210     "                    ],"
211     "                    \"client-class\": \"a-devices\""
212     "                },"
213     "                {"
214     "                    \"subnet\": \"10.0.0.0/24\","
215     "                    \"id\": 100,"
216     "                    \"pools\": ["
217     "                        {"
218     "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
219     "                        }"
220     "                    ],"
221     "                    \"client-class\": \"b-devices\""
222     "                }"
223     "            ]"
224     "        }"
225     "    ]"
226     "}",
227 
228 // Configuration #4
229 // - 1 shared network with 2 subnets, each has one host reservation
230     "{"
231     "    \"interfaces-config\": {"
232     "        \"interfaces\": [ \"*\" ]"
233     "    },"
234     "    \"valid-lifetime\": 600,"
235     "    \"shared-networks\": ["
236     "        {"
237     "            \"name\": \"frog\","
238     "            \"relay\": {"
239     "                \"ip-address\": \"192.3.5.6\""
240     "            },"
241     "            \"subnet4\": ["
242     "                {"
243     "                    \"subnet\": \"192.0.2.0/26\","
244     "                    \"id\": 10,"
245     "                    \"pools\": ["
246     "                        {"
247     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
248     "                        }"
249     "                    ],"
250     "                    \"reservations\": ["
251     "                        {"
252     "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
253     "                            \"ip-address\": \"192.0.2.28\""
254     "                        }"
255     "                    ]"
256     "                },"
257     "                {"
258     "                    \"subnet\": \"10.0.0.0/24\","
259     "                    \"id\": 100,"
260     "                    \"pools\": ["
261     "                        {"
262     "                            \"pool\": \"10.0.0.1 - 10.0.0.254\""
263     "                        }"
264     "                    ],"
265     "                    \"reservations\": ["
266     "                        {"
267     "                            \"hw-address\": \"11:22:33:44:55:66\","
268     "                            \"ip-address\": \"10.0.0.29\""
269     "                        }"
270     "                    ]"
271     "                }"
272     "            ]"
273     "        }"
274     "    ]"
275     "}",
276 
277 // Configuration #5
278 // - 1 shared network, with 2 subnets. Each has host reservation
279 // - similar to config #4, but with different hw-address reserved
280     "{"
281     "    \"interfaces-config\": {"
282     "        \"interfaces\": [ \"*\" ]"
283     "    },"
284     "    \"valid-lifetime\": 600,"
285     "    \"shared-networks\": ["
286     "        {"
287     "            \"name\": \"frog\","
288     "            \"relay\": {"
289     "                \"ip-address\": \"192.3.5.6\""
290     "            },"
291     "            \"subnet4\": ["
292     "                {"
293     "                    \"subnet\": \"192.0.2.0/26\","
294     "                    \"id\": 10,"
295     "                    \"pools\": ["
296     "                        {"
297     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
298     "                        }"
299     "                    ],"
300     "                    \"reservations\": ["
301     "                        {"
302     "                            \"hw-address\": \"11:22:33:44:55:66\","
303     "                            \"ip-address\": \"192.0.2.28\""
304     "                        }"
305     "                    ]"
306     "                },"
307     "                {"
308     "                    \"subnet\": \"10.0.0.0/24\","
309     "                    \"id\": 100,"
310     "                    \"pools\": ["
311     "                        {"
312     "                            \"pool\": \"10.0.0.1 - 10.0.0.254\""
313     "                        }"
314     "                    ],"
315     "                    \"reservations\": ["
316     "                        {"
317     "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
318     "                            \"ip-address\": \"10.0.0.29\""
319     "                        }"
320     "                    ]"
321     "                }"
322     "            ]"
323     "        }"
324     "    ]"
325     "}",
326 
327 // Configuration #6
328 // - 1 class
329 // - 1 shared network, with 2 subnets. First has class restriction and
330 //     host reservation
331     "{"
332     "    \"interfaces-config\": {"
333     "        \"interfaces\": [ \"*\" ]"
334     "    },"
335     "    \"valid-lifetime\": 600,"
336     "    \"client-classes\": ["
337     "        {"
338     "            \"name\": \"a-devices\","
339     "            \"test\": \"option[93].hex == 0x0001\""
340     "        }"
341     "    ],"
342     "    \"shared-networks\": ["
343     "        {"
344     "            \"name\": \"frog\","
345     "            \"relay\": {"
346     "                \"ip-address\": \"192.3.5.6\""
347     "            },"
348     "            \"subnet4\": ["
349     "                {"
350     "                    \"subnet\": \"192.0.2.0/26\","
351     "                    \"id\": 10,"
352     "                    \"pools\": ["
353     "                        {"
354     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
355     "                        }"
356     "                    ],"
357     "                    \"client-class\": \"a-devices\","
358     "                    \"reservations\": ["
359     "                        {"
360     "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
361     "                            \"ip-address\": \"192.0.2.28\""
362     "                        }"
363     "                    ]"
364     "                },"
365     "                {"
366     "                    \"subnet\": \"10.0.0.0/24\","
367     "                    \"id\": 100,"
368     "                    \"pools\": ["
369     "                        {"
370     "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
371     "                        }"
372     "                    ]"
373     "                }"
374     "            ]"
375     "        }"
376     "    ]"
377     "}",
378 
379 // Configuration #7
380 // - 1 global option
381 // - 1 shared network with some options and 2 subnets (the first one has extra
382 //     options)
383 // - 1 plain subnet (that has an option)
384     "{"
385     "    \"interfaces-config\": {"
386     "        \"interfaces\": [ \"*\" ]"
387     "    },"
388     "    \"valid-lifetime\": 600,"
389     "    \"option-data\": ["
390     "        {"
391     "            \"name\": \"log-servers\","
392     "            \"data\": \"1.2.3.4\""
393     "        }"
394     "    ],"
395     "    \"shared-networks\": ["
396     "        {"
397     "            \"name\": \"frog\","
398     "            \"interface\": \"eth1\","
399     "            \"option-data\": ["
400     "                {"
401     "                    \"name\": \"domain-name-servers\","
402     "                    \"data\": \"10.1.2.3\""
403     "                },"
404     "                {"
405     "                    \"name\": \"cookie-servers\","
406     "                    \"data\": \"10.6.5.4\""
407     "                }"
408     "            ],"
409     "            \"subnet4\": ["
410     "                {"
411     "                    \"subnet\": \"192.0.2.0/26\","
412     "                    \"id\": 10,"
413     "                    \"option-data\": ["
414     "                        {"
415     "                            \"name\": \"routers\","
416     "                            \"data\": \"192.0.2.5\""
417     "                        },"
418     "                        {"
419     "                            \"name\": \"cookie-servers\","
420     "                            \"data\": \"10.5.4.3\""
421     "                        }"
422     "                    ],"
423     "                    \"pools\": ["
424     "                        {"
425     "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
426     "                        }"
427     "                    ]"
428     "                },"
429     "                {"
430     "                    \"subnet\": \"10.0.0.0/24\","
431     "                    \"id\": 100,"
432     "                    \"pools\": ["
433     "                        {"
434     "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
435     "                        }"
436     "                    ]"
437     "                }"
438     "            ]"
439     "        }"
440     "    ],"
441     "    \"subnet4\": ["
442     "        {"
443     "            \"subnet\": \"192.0.2.64/26\","
444     "            \"id\": 1000,"
445     "            \"interface\": \"eth0\","
446     "            \"option-data\": ["
447     "                {"
448     "                    \"name\": \"cookie-servers\","
449     "                    \"data\": \"10.1.1.1\""
450     "                }"
451     "            ],"
452     "            \"pools\": ["
453     "                {"
454     "                    \"pool\": \"192.0.2.65 - 192.0.2.65\""
455     "                }"
456     "            ]"
457     "        }"
458     "    ]"
459     "}",
460 
461 // Configuration #8
462 // - two shared networks, each with two subnets (each with interface specified)
463     "{"
464     "    \"interfaces-config\": {"
465     "        \"interfaces\": [ \"*\" ]"
466     "    },"
467     "    \"valid-lifetime\": 600,"
468     "    \"shared-networks\": ["
469     "        {"
470     "            \"name\": \"frog\","
471     "            \"interface\": \"eth1\","
472     "            \"subnet4\": ["
473     "                {"
474     "                    \"subnet\": \"192.0.2.0/26\","
475     "                    \"id\": 10,"
476     "                    \"pools\": ["
477     "                        {"
478     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
479     "                        }"
480     "                    ]"
481     "                },"
482     "                {"
483     "                    \"subnet\": \"192.0.2.64/26\","
484     "                    \"id\": 100,"
485     "                    \"pools\": ["
486     "                        {"
487     "                            \"pool\": \"192.0.2.65 - 192.0.2.127\""
488     "                        }"
489     "                    ]"
490     "                }"
491     "            ]"
492     "        },"
493     "        {"
494     "            \"name\": \"dog\","
495     "            \"interface\": \"eth0\","
496     "            \"subnet4\": ["
497     "                {"
498     "                    \"subnet\": \"10.0.0.0/26\","
499     "                    \"id\": 1000,"
500     "                    \"pools\": ["
501     "                        {"
502     "                            \"pool\": \"10.0.0.1 - 10.0.0.63\""
503     "                        }"
504     "                    ]"
505     "                },"
506     "                {"
507     "                    \"subnet\": \"10.0.0.64/26\","
508     "                    \"id\": 10000,"
509     "                    \"pools\": ["
510     "                        {"
511     "                            \"pool\": \"10.0.0.65 - 10.0.0.127\""
512     "                        }"
513     "                    ]"
514     "                }"
515     "            ]"
516     "        }"
517     "    ]"
518     "}",
519 
520 // Configuration #9
521 // - 2 shared networks, each with relay ip address and 2 subnets
522     "{"
523     "    \"interfaces-config\": {"
524     "        \"interfaces\": [ \"*\" ]"
525     "    },"
526     "    \"valid-lifetime\": 600,"
527     "    \"shared-networks\": ["
528     "        {"
529     "            \"name\": \"frog\","
530     "            \"relay\": { \"ip-address\": \"10.1.2.3\" },"
531     "            \"subnet4\": ["
532     "                {"
533     "                    \"subnet\": \"192.0.2.0/26\","
534     "                    \"id\": 10,"
535     "                    \"pools\": ["
536     "                        {"
537     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
538     "                        }"
539     "                    ]"
540     "                },"
541     "                {"
542     "                    \"subnet\": \"192.0.2.64/26\","
543     "                    \"id\": 100,"
544     "                    \"pools\": ["
545     "                        {"
546     "                            \"pool\": \"192.0.2.65 - 192.0.2.127\""
547     "                        }"
548     "                    ]"
549     "                }"
550     "            ]"
551     "        },"
552     "        {"
553     "            \"name\": \"dog\","
554     "            \"relay\": { \"ip-address\": \"192.1.2.3\" },"
555     "            \"subnet4\": ["
556     "                {"
557     "                    \"subnet\": \"10.0.0.0/26\","
558     "                    \"id\": 1000,"
559     "                    \"pools\": ["
560     "                        {"
561     "                            \"pool\": \"10.0.0.1 - 10.0.0.63\""
562     "                        }"
563     "                    ]"
564     "                },"
565     "                {"
566     "                    \"subnet\": \"10.0.0.64/26\","
567     "                    \"id\": 10000,"
568     "                    \"pools\": ["
569     "                        {"
570     "                            \"pool\": \"10.0.0.65 - 10.0.0.127\""
571     "                        }"
572     "                    ]"
573     "                }"
574     "            ]"
575     "        }"
576     "    ]"
577     "}",
578 // Configuration #10.
579 // - 1 client class
580 // - 1 shared network with two subnets (second has a host reservation)
581     "{"
582     "    \"interfaces-config\": {"
583     "        \"interfaces\": [ \"*\" ]"
584     "    },"
585     "    \"valid-lifetime\": 600,"
586     "    \"client-classes\": ["
587     "        {"
588     "            \"name\": \"class-with-bootfile\","
589     "            \"boot-file-name\": \"/dev/null\""
590     "        }"
591     "    ],"
592     "    \"shared-networks\": ["
593     "        {"
594     "            \"name\": \"frog\","
595     "            \"relay\": {"
596     "                \"ip-address\": \"192.3.5.6\""
597     "            },"
598     "            \"subnet4\": ["
599     "                {"
600     "                    \"subnet\": \"192.0.2.0/26\","
601     "                    \"id\": 10,"
602     "                    \"pools\": ["
603     "                        {"
604     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
605     "                        }"
606     "                    ]"
607     "                },"
608     "                {"
609     "                    \"subnet\": \"10.0.0.0/24\","
610     "                    \"id\": 100,"
611     "                    \"pools\": ["
612     "                        {"
613     "                            \"pool\": \"10.0.0.1 - 10.0.0.254\""
614     "                        }"
615     "                    ],"
616     "                    \"reservations\": ["
617     "                        {"
618     "                            \"hw-address\": \"11:22:33:44:55:66\","
619     "                            \"ip-address\": \"10.0.0.29\","
620     "                            \"hostname\": \"test.example.org\","
621     "                            \"next-server\": \"10.10.10.10\","
622     "                            \"client-classes\": [ \"class-with-bootfile\" ]"
623     "                        }"
624     "                    ]"
625     "                }"
626     "            ]"
627     "        }"
628     "    ]"
629     "}",
630 
631 // Configuration #11.
632 // - global value of match-client-id set to false
633 // - 1 shared network (match-client-id set to true) with 2 subnets
634 // - the first subnet has match-client-id set to false
635     "{"
636     "    \"interfaces-config\": {"
637     "        \"interfaces\": [ \"*\" ]"
638     "    },"
639     "    \"valid-lifetime\": 600,"
640     "    \"match-client-id\": false,"
641     "    \"shared-networks\": ["
642     "        {"
643     "            \"name\": \"frog\","
644     "            \"interface\": \"eth1\","
645     "            \"match-client-id\": true,"
646     "            \"subnet4\": ["
647     "                {"
648     "                    \"subnet\": \"192.0.2.0/26\","
649     "                    \"id\": 10,"
650     "                    \"match-client-id\": false"
651     "                },"
652     "                {"
653     "                    \"subnet\": \"192.0.2.64/26\","
654     "                    \"id\": 100,"
655     "                    \"pools\": ["
656     "                        {"
657     "                            \"pool\": \"192.0.2.65 - 192.0.2.127\""
658     "                        }"
659     "                    ]"
660     "                }"
661     "            ]"
662     "        }"
663     "    ]"
664     "}",
665 
666 // Configuration #12.
667 // - global value of match-client-id set to false
668 // - 1 shared network (match-client-id set to false) with 2 subnets
669 // - the first subnet has match-client-id set to false
670     "{"
671     "    \"interfaces-config\": {"
672     "        \"interfaces\": [ \"*\" ]"
673     "    },"
674     "    \"valid-lifetime\": 600,"
675     "    \"match-client-id\": false,"
676     "    \"shared-networks\": ["
677     "        {"
678     "            \"name\": \"frog\","
679     "            \"interface\": \"eth1\","
680     "            \"match-client-id\": false,"
681     "            \"subnet4\": ["
682     "                {"
683     "                    \"subnet\": \"192.0.2.0/26\","
684     "                    \"id\": 10,"
685     "                    \"match-client-id\": false"
686     "                },"
687     "                {"
688     "                    \"subnet\": \"192.0.2.64/26\","
689     "                    \"id\": 100,"
690     "                    \"pools\": ["
691     "                        {"
692     "                            \"pool\": \"192.0.2.65 - 192.0.2.127\""
693     "                        }"
694     "                    ]"
695     "                }"
696     "            ]"
697     "        }"
698     "    ]"
699     "}",
700 
701 // Configuration #13.
702 // - 2 classes
703 // - 2 shared networks, each with 1 subnet and client class restriction
704     "{"
705     "    \"interfaces-config\": {"
706     "        \"interfaces\": [ \"*\" ]"
707     "    },"
708     "    \"client-classes\": ["
709     "        {"
710     "            \"name\": \"a-devices\","
711     "            \"test\": \"option[93].hex == 0x0001\""
712     "        },"
713     "        {"
714     "            \"name\": \"b-devices\","
715     "            \"test\": \"option[93].hex == 0x0002\""
716     "        }"
717     "    ],"
718     "    \"valid-lifetime\": 600,"
719     "    \"shared-networks\": ["
720     "        {"
721     "            \"name\": \"frog\","
722     "            \"interface\": \"eth1\","
723     "            \"client-class\": \"a-devices\","
724     "            \"subnet4\": ["
725     "                {"
726     "                    \"subnet\": \"192.0.2.0/26\","
727     "                    \"id\": 10,"
728     "                    \"pools\": ["
729     "                        {"
730     "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
731     "                        }"
732     "                    ]"
733     "                }"
734     "            ]"
735     "        },"
736     "        {"
737     "            \"name\": \"dog\","
738     "            \"interface\": \"eth1\","
739     "            \"client-class\": \"b-devices\","
740     "            \"subnet4\": ["
741     "                {"
742     "                    \"subnet\": \"10.0.0.0/26\","
743     "                    \"id\": 1000,"
744     "                    \"pools\": ["
745     "                        {"
746     "                            \"pool\": \"10.0.0.63 - 10.0.0.63\""
747     "                        }"
748     "                    ]"
749     "                }"
750     "            ]"
751     "        }"
752     "    ]"
753     "}",
754 // Configuration #14
755 // - 1 shared networks with 2 subnets, relay ip specified,
756 //     each relay has its own relay ip specified
757 // - 1 "plain" subnet, relay ip specified
758     "{"
759     "    \"interfaces-config\": {"
760     "        \"interfaces\": [ \"*\" ]"
761     "    },"
762     "    \"valid-lifetime\": 600,"
763     "    \"shared-networks\": ["
764     "        {"
765     "            \"name\": \"frog\","
766     "            \"relay\": {"
767     "                \"ip-address\": \"192.3.5.6\""
768     "            },"
769     "            \"subnet4\": ["
770     "                {"
771     "                    \"subnet\": \"192.0.2.0/26\","
772     "                    \"id\": 10,"
773     "                    \"relay\": {"
774     "                        \"ip-address\": \"192.1.1.1\""
775     "                    },"
776     "                    \"pools\": ["
777     "                        {"
778     "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
779     "                        }"
780     "                    ]"
781     "                },"
782     "                {"
783     "                    \"subnet\": \"10.0.0.0/24\","
784     "                    \"id\": 100,"
785     "                    \"relay\": {"
786     "                        \"ip-address\": \"192.2.2.2\""
787     "                    },"
788     "                    \"pools\": ["
789     "                        {"
790     "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
791     "                        }"
792     "                    ]"
793     "                }"
794     "            ]"
795     "        }"
796     "    ],"
797     "    \"subnet4\": ["
798     "        {"
799     "            \"subnet\": \"192.0.2.64/26\","
800     "            \"id\": 1000,"
801     "            \"relay\": {"
802     "                \"ip-address\": \"192.3.3.3\""
803     "            },"
804     "            \"pools\": ["
805     "                {"
806     "                    \"pool\": \"192.0.2.65 - 192.0.2.65\""
807     "                }"
808     "            ]"
809     "        }"
810     "    ]"
811     "}",
812 
813 // Configuration #15
814 // - two shared networks, each comes with its own server identifier.
815     "{"
816     "    \"interfaces-config\": {"
817     "        \"interfaces\": [ \"*\" ]"
818     "    },"
819     "    \"valid-lifetime\": 600,"
820     "    \"shared-networks\": ["
821     "        {"
822     "            \"name\": \"frog\","
823     "            \"interface\": \"eth1\","
824     "            \"option-data\": ["
825     "                {"
826     "                    \"name\": \"dhcp-server-identifier\","
827     "                    \"data\": \"1.2.3.4\""
828     "                }"
829     "            ],"
830     "            \"subnet4\": ["
831     "                {"
832     "                    \"subnet\": \"192.0.2.0/26\","
833     "                    \"id\": 10,"
834     "                    \"pools\": ["
835     "                        {"
836     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
837     "                        }"
838     "                    ]"
839     "                }"
840     "            ]"
841     "        },"
842     "        {"
843     "            \"name\": \"dog\","
844     "            \"interface\": \"eth0\","
845     "            \"option-data\": ["
846     "                {"
847     "                    \"name\": \"dhcp-server-identifier\","
848     "                    \"data\": \"2.3.4.5\""
849     "                }"
850     "            ],"
851     "            \"subnet4\": ["
852     "                {"
853     "                    \"subnet\": \"10.0.0.0/26\","
854     "                    \"id\": 1000,"
855     "                    \"pools\": ["
856     "                        {"
857     "                            \"pool\": \"10.0.0.1 - 10.0.0.63\""
858     "                        }"
859     "                    ]"
860     "                }"
861     "            ]"
862     "        }"
863     "    ]"
864     "}",
865 
866 // Configuration #16
867 // - 1 shared network with 1 subnet and 2 pools (first pool has class restriction)
868     "{"
869     "    \"interfaces-config\": {"
870     "        \"interfaces\": [ \"*\" ]"
871     "    },"
872     "    \"client-classes\": ["
873     "        {"
874     "            \"name\": \"a-devices\","
875     "            \"test\": \"option[93].hex == 0x0001\""
876     "        },"
877     "        {"
878     "            \"name\": \"b-devices\","
879     "            \"test\": \"option[93].hex == 0x0002\""
880     "        }"
881     "    ],"
882     "    \"valid-lifetime\": 600,"
883     "    \"shared-networks\": ["
884     "        {"
885     "            \"name\": \"frog\","
886     "            \"interface\": \"eth1\","
887     "            \"subnet4\": ["
888     "                {"
889     "                    \"subnet\": \"192.0.2.0/24\","
890     "                    \"id\": 10,"
891     "                    \"pools\": ["
892     "                        {"
893     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\","
894     "                            \"client-class\": \"a-devices\""
895     "                        },"
896     "                        {"
897     "                            \"pool\": \"192.0.2.100 - 192.0.2.100\""
898     "                        }"
899     "                    ]"
900     "                }"
901     "            ]"
902     "        }"
903     "    ]"
904     "}",
905 
906 // Configuration #17
907 // - 1 shared network with 1 subnet and 2 pools (each with class restriction)
908     "{"
909     "    \"interfaces-config\": {"
910     "        \"interfaces\": [ \"*\" ]"
911     "    },"
912     "    \"client-classes\": ["
913     "        {"
914     "            \"name\": \"a-devices\","
915     "            \"test\": \"option[93].hex == 0x0001\""
916     "        },"
917     "        {"
918     "            \"name\": \"b-devices\","
919     "            \"test\": \"option[93].hex == 0x0002\""
920     "        }"
921     "    ],"
922     "    \"valid-lifetime\": 600,"
923     "    \"shared-networks\": ["
924     "        {"
925     "            \"name\": \"frog\","
926     "            \"interface\": \"eth1\","
927     "            \"subnet4\": ["
928     "                {"
929     "                    \"subnet\": \"192.0.2.0/24\","
930     "                    \"id\": 10,"
931     "                    \"pools\": ["
932     "                        {"
933     "                            \"pool\": \"192.0.2.1 - 192.0.2.63\","
934     "                            \"client-class\": \"a-devices\""
935     "                        },"
936     "                        {"
937     "                            \"pool\": \"192.0.2.100 - 192.0.2.100\","
938     "                            \"client-class\": \"b-devices\""
939     "                        }"
940     "                    ]"
941     "                }"
942     "            ]"
943     "        }"
944     "    ]"
945     "}",
946 
947 // Configuration #18
948 // - plain subnet and 2 pools (first pool has class restriction)
949     "{"
950     "    \"interfaces-config\": {"
951     "        \"interfaces\": [ \"*\" ]"
952     "    },"
953     "    \"client-classes\": ["
954     "        {"
955     "            \"name\": \"a-devices\","
956     "            \"test\": \"option[93].hex == 0x0001\""
957     "        },"
958     "        {"
959     "            \"name\": \"b-devices\","
960     "            \"test\": \"option[93].hex == 0x0002\""
961     "        }"
962     "    ],"
963     "    \"valid-lifetime\": 600,"
964     "    \"subnet4\": ["
965     "        {"
966     "            \"subnet\": \"192.0.2.0/24\","
967     "            \"id\": 10,"
968     "            \"interface\": \"eth1\","
969     "            \"pools\": ["
970     "                {"
971     "                    \"pool\": \"192.0.2.1 - 192.0.2.63\","
972     "                    \"client-class\": \"a-devices\""
973     "                },"
974     "                {"
975     "                    \"pool\": \"192.0.2.100 - 192.0.2.100\""
976     "                }"
977     "            ]"
978     "        }"
979     "    ]"
980     "}",
981 
982 // Configuration #19
983 // - plain subnet and 2 pools (each with class restriction)
984     "{"
985     "    \"interfaces-config\": {"
986     "        \"interfaces\": [ \"*\" ]"
987     "    },"
988     "    \"client-classes\": ["
989     "        {"
990     "            \"name\": \"a-devices\","
991     "            \"test\": \"option[93].hex == 0x0001\""
992     "        },"
993     "        {"
994     "            \"name\": \"b-devices\","
995     "            \"test\": \"option[93].hex == 0x0002\""
996     "        }"
997     "    ],"
998     "    \"valid-lifetime\": 600,"
999     "    \"subnet4\": ["
1000     "        {"
1001     "            \"subnet\": \"192.0.2.0/24\","
1002     "            \"id\": 10,"
1003     "            \"interface\": \"eth1\","
1004     "            \"pools\": ["
1005     "                {"
1006     "                    \"pool\": \"192.0.2.1 - 192.0.2.63\","
1007     "                    \"client-class\": \"a-devices\""
1008     "                },"
1009     "                {"
1010     "                    \"pool\": \"192.0.2.100 - 192.0.2.100\","
1011     "                    \"client-class\": \"b-devices\""
1012     "                }"
1013     "            ]"
1014     "        }"
1015     "    ]"
1016     "}"
1017 
1018 };
1019 
1020 /// @Brief Test fixture class for DHCPv4 server using shared networks.
1021 class Dhcpv4SharedNetworkTest : public Dhcpv4SrvTest {
1022 public:
1023 
1024     /// @brief Constructor.
Dhcpv4SharedNetworkTest()1025     Dhcpv4SharedNetworkTest()
1026         : Dhcpv4SrvTest(),
1027           iface_mgr_test_config_(true) {
1028         StatsMgr::instance().removeAll();
1029     }
1030 
1031     /// @brief Specifies authoritative flag value
1032     ///
1033     /// Used to generate authoritative configs
1034     typedef enum AuthoritativeFlag {
1035         AUTH_DEFAULT, // explicit value not specified (use default)
1036         AUTH_YES,     // defined explicitly as yes
1037         AUTH_NO       // defined explicitly as no
1038     } AuthoritativeFlag;
1039 
1040     /// @brief Returns subnet having specified address in range.
1041     ///
1042     /// @param address Address for which subnet is being searched.
1043     /// @return Pointer to the subnet having an address in range or null pointer
1044     /// if no subnet found.
getConfiguredSubnet(const IOAddress & address)1045     Subnet4Ptr getConfiguredSubnet(const IOAddress& address) {
1046         CfgSubnets4Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4();
1047         const Subnet4Collection* subnets = cfg->getAll();
1048         for (auto subnet_it = subnets->cbegin(); subnet_it != subnets->cend();
1049              ++subnet_it) {
1050             if ((*subnet_it)->inRange(address)) {
1051                 return (*subnet_it);
1052             }
1053         }
1054         return (Subnet4Ptr());
1055     }
1056 
1057     /// @brief Perform DORA exchange and checks the result
1058     ///
1059     /// This convenience method conducts DORA exchange with client
1060     /// packets using hint values specified by third parameter.
1061     /// The response is expected to be either ACK (ack = true) or
1062     /// NAK (ack = false). The received address is checked against
1063     /// exp_addr
1064     ///
1065     /// @param client client to perform the DORA exchange
1066     /// @param exp_addr expected address (in yiaddr field)
1067     /// @param hint the address the client is supposed to sent
1068     ///             (empty string means send 0.0.0.0)
1069     /// @param ack expected response (true = ACK, false = NAK)
1070     void
doDORA(Dhcp4Client & client,std::string exp_addr,std::string hint="",bool ack=true)1071     doDORA(Dhcp4Client& client, std::string exp_addr, std::string hint = "",
1072            bool ack = true) {
1073 
1074         if (hint.empty()) {
1075             ASSERT_NO_THROW(client.doDORA());
1076         } else {
1077             boost::shared_ptr<IOAddress> addr(new IOAddress(hint));
1078             ASSERT_NO_THROW(client.doDORA(addr));
1079         }
1080         Pkt4Ptr resp = client.getContext().response_;
1081         ASSERT_TRUE(resp);
1082         EXPECT_EQ((ack ? DHCPACK : DHCPNAK), resp->getType());
1083         if (ack) {
1084             EXPECT_EQ(exp_addr, resp->getYiaddr().toText());
1085             Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(IOAddress(resp->getYiaddr()));
1086             ASSERT_TRUE(lease);
1087             // Make sure that the subnet id in the lease database is not messed up.
1088             Subnet4Ptr subnet = getConfiguredSubnet(resp->getYiaddr());
1089             ASSERT_TRUE(subnet);
1090             ASSERT_EQ(subnet->getID(), lease->subnet_id_);
1091 
1092         } else {
1093             EXPECT_EQ("0.0.0.0", resp->getYiaddr().toText());
1094         }
1095     }
1096 
1097     /// @brief Perform Discover/Offer exchange and checks the result
1098     ///
1099     /// This convenience method conducts Discover/Offer exchange with client
1100     /// packets using hint values specified by third parameter.
1101     /// The response is expected to be either ACK (ack = true) or
1102     /// NAK (ack = false). The received address is checked against
1103     /// exp_addr
1104     ///
1105     /// @param client client to perform the DORA exchange
1106     /// @param exp_addr expected address (in yiaddr field)
1107     /// @param hint the address the client is supposed to sent
1108     ///             (empty string means send 0.0.0.0)
1109     /// @param ack expected response (true = ACK, false = NAK)
1110     void
doDiscover(Dhcp4Client & client,std::string exp_addr,std::string hint,bool ack=true)1111     doDiscover(Dhcp4Client& client, std::string exp_addr, std::string hint,
1112            bool ack = true) {
1113 
1114         if (hint.empty()) {
1115             ASSERT_NO_THROW(client.doDiscover());
1116         } else {
1117             boost::shared_ptr<IOAddress> addr(new IOAddress(hint));
1118             ASSERT_NO_THROW(client.doDiscover(addr));
1119         }
1120         Pkt4Ptr resp = client.getContext().response_;
1121         ASSERT_TRUE(resp);
1122         EXPECT_EQ((ack ? DHCPOFFER : DHCPNAK), resp->getType());
1123         if (ack) {
1124             EXPECT_EQ(exp_addr, resp->getYiaddr().toText());
1125         } else {
1126             EXPECT_EQ("0.0.0.0", resp->getYiaddr().toText());
1127         }
1128     }
1129 
1130     /// @brief Perform Request/Reply exchange and checks the result
1131     ///
1132     /// This convenience method conducts Request/Reply exchange with client
1133     /// packets using hint values specified by a parameter.  The response is
1134     /// expected to be either ACK if exp_addr has a non-empty length,
1135     /// or NAK when exp_addr context is empty.
1136     ///
1137     /// @param client client to perform the DORA exchange
1138     /// @param exp_addr expected address (in yiaddr field)
1139     void
doRequest(Dhcp4Client & client,std::string exp_addr)1140     doRequest(Dhcp4Client& client, std::string exp_addr) {
1141         ASSERT_NO_THROW(client.doRequest());
1142         Pkt4Ptr resp = client.getContext().response_;
1143         ASSERT_TRUE(resp);
1144         if (exp_addr.empty()) {
1145             EXPECT_EQ(DHCPNAK, resp->getType());
1146             EXPECT_EQ("0.0.0.0", resp->getYiaddr().toText());
1147         } else {
1148             EXPECT_EQ(DHCPACK, resp->getType());
1149             EXPECT_EQ(exp_addr, resp->getYiaddr().toText());
1150             Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(IOAddress(resp->getYiaddr()));
1151             ASSERT_TRUE(lease);
1152             // Make sure that the subnet id in the lease database is not messed up.
1153             Subnet4Ptr subnet = getConfiguredSubnet(resp->getYiaddr());
1154             ASSERT_TRUE(subnet);
1155             ASSERT_EQ(subnet->getID(), lease->subnet_id_);
1156         }
1157     }
1158 
1159     /// @brief Verifies lease statistics against values held by StatsMgr.
1160     ///
1161     /// This method retrieves lease statistics from the database and then compares it
1162     /// against values held by the StatsMgr. The compared statistics are number of
1163     /// assigned addresses and prefixes for a subnet.
verifyAssignedStats()1164     void verifyAssignedStats() {
1165         LeaseStatsQueryPtr query = LeaseMgrFactory::instance().startLeaseStatsQuery4();
1166         LeaseStatsRow row;
1167         while (query->getNextRow(row)) {
1168             // Only check valid leases.
1169             if (row.lease_state_ == Lease::STATE_DEFAULT) {
1170                 ASSERT_EQ(row.state_count_, getStatsAssignedAddresses(row.subnet_id_))
1171                     << "test failed for subnet id " << row.subnet_id_;
1172             }
1173         }
1174     }
1175 
1176     /// @brief Retrieves subnet[id].assigned-addresses statistics for a subnet.
1177     ///
1178     /// @param subnet_id Identifier of a subnet for which statistics should be
1179     /// retrieved.
1180     /// @return Number of assigned addresses for a subnet.
getStatsAssignedAddresses(const SubnetID & subnet_id) const1181     int64_t getStatsAssignedAddresses(const SubnetID& subnet_id) const {
1182         // Retrieve statistics name, e.g. subnet[1234].assigned-addresses.
1183         const std::string stats_name = StatsMgr::generateName("subnet", subnet_id, "assigned-addresses");
1184         // Top element is a map with a subnet[id].assigned-addresses parameter.
1185         ConstElementPtr top_element = StatsMgr::instance().get(stats_name);
1186         if (top_element && (top_element->getType() == Element::map)) {
1187             // It contains two lists (nested).
1188             ConstElementPtr first_list = top_element->get(stats_name);
1189             if (first_list && (first_list->getType() == Element::list) &&
1190                 (first_list->size() > 0)) {
1191                 // Get the nested list which should have two elements, of which first
1192                 // is the statistics value we're looking for.
1193                 ConstElementPtr second_list = first_list->get(0);
1194                 if (second_list && (second_list->getType() == Element::list)) {
1195                     ConstElementPtr addresses_element = second_list->get(0);
1196                     if (addresses_element && (addresses_element->getType() == Element::integer)) {
1197                         return (addresses_element->intValue());
1198                     }
1199                 }
1200             }
1201         }
1202 
1203         // Statistics invalid or not found.
1204         return (0);
1205     }
1206 
1207     /// @brief Launches specific operation and verifies lease statistics before and
1208     /// after this operation.
1209     ///
1210     /// @param operation Operation to be launched.
testAssigned(const std::function<void ()> & operation)1211     void testAssigned(const std::function<void()>& operation) {
1212         ASSERT_NO_FATAL_FAILURE(verifyAssignedStats());
1213         operation();
1214         ASSERT_NO_FATAL_FAILURE(verifyAssignedStats());
1215     }
1216 
1217     /// @brief Check precedence.
1218     ///
1219     /// @param config the configuration.
1220     /// @param ns_address expected name server address.
testPrecedence(const std::string & config,const std::string & ns_address)1221     void testPrecedence(const std::string& config, const std::string& ns_address) {
1222         // Create client and set MAC address to the one that has a reservation.
1223         Dhcp4Client client(Dhcp4Client::SELECTING);
1224         client.setIfaceName("eth1");
1225         client.setIfaceIndex(ETH1_INDEX);
1226         client.setHWAddress("aa:bb:cc:dd:ee:ff");
1227         // Request domain-name-servers.
1228         client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
1229 
1230         // Create server configuration.
1231         configure(config, *client.getServer());
1232 
1233         // Perform a DORA.
1234         doDORA(client, "192.0.2.28", "192.0.2.28");
1235 
1236         // Check response.
1237         Pkt4Ptr resp = client.getContext().response_;
1238         ASSERT_TRUE(resp);
1239         EXPECT_EQ(DHCPACK, resp->getType());
1240         EXPECT_EQ("192.0.2.28", resp->getYiaddr().toText());
1241 
1242         // Check domain-name-servers option.
1243         OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
1244         ASSERT_TRUE(opt);
1245         Option4AddrLstPtr servers =
1246             boost::dynamic_pointer_cast<Option4AddrLst>(opt);
1247         ASSERT_TRUE(servers);
1248         auto addrs = servers->getAddresses();
1249         ASSERT_EQ(1, addrs.size());
1250         EXPECT_EQ(ns_address, addrs[0].toText());
1251     }
1252 
1253     /// @brief returns authoritative flag as JSON string
1254     /// @param f value to be specified (default, true or false)
auth(AuthoritativeFlag f)1255     string auth(AuthoritativeFlag f) {
1256         switch (f) {
1257         case AUTH_DEFAULT:
1258             return ("");
1259         case AUTH_YES:
1260             return ("            \"authoritative\": true,");
1261             break;
1262         case AUTH_NO:
1263             return ("            \"authoritative\": false,");
1264         }
1265         return ("");
1266     }
1267 
1268     /// @brief generates Config file with specified authoritative flag values
1269     ///
1270     /// The config file has the same structure:
1271     /// - 1 shared network with 2 subnets (global authoritative flag)
1272     ///   - first subnet: authoritative (subnet1 flag here)
1273     ///   - second subnet: authoritative (subnet2 flag here)
1274     ///
1275     /// @param global governs presence/value of global authoritative flag
1276     /// @param subnet1 governs presence/value of authoritative flag in subnet1
1277     /// @param subnet2 governs presence/value of authoritative flag in subnet2
generateAuthConfig(AuthoritativeFlag global,AuthoritativeFlag subnet1,AuthoritativeFlag subnet2)1278     string generateAuthConfig(AuthoritativeFlag global, AuthoritativeFlag subnet1,
1279                               AuthoritativeFlag subnet2) {
1280         string cfg = "{"
1281             "    \"interfaces-config\": {"
1282             "        \"interfaces\": [ \"*\" ]"
1283             "    },"
1284             "    \"valid-lifetime\": 600,"
1285             "    \"shared-networks\": ["
1286             "        {"
1287             "            \"name\": \"frog\","
1288             "            \"comment\": \"example\",";
1289         cfg += auth(global);
1290         cfg +=
1291             "            \"subnet4\": ["
1292             "                {"
1293             "                    \"subnet\": \"192.0.2.0/26\","
1294             "                    \"id\": 10,";
1295         cfg += auth(subnet1);
1296 
1297         cfg +=
1298             "                    \"pools\": ["
1299             "                        {"
1300             "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
1301             "                        }"
1302             "                    ]"
1303             "                },"
1304             "                {"
1305             "                    \"subnet\": \"10.0.0.0/24\","
1306             "                    \"id\": 100,";
1307         cfg += auth(subnet2);
1308         cfg +=
1309             "                    \"pools\": ["
1310             "                        {"
1311             "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
1312             "                        }"
1313             "                    ]"
1314             "                }"
1315             "            ]"
1316             "        }"
1317             "    ]"
1318             "}";
1319 
1320         return (cfg);
1321     }
1322 
1323     /// @brief Destructor.
~Dhcpv4SharedNetworkTest()1324     virtual ~Dhcpv4SharedNetworkTest() {
1325         StatsMgr::instance().removeAll();
1326     }
1327 
1328     /// @brief Interface Manager's fake configuration control.
1329     IfaceMgrTestConfig iface_mgr_test_config_;
1330 };
1331 
1332 // Check user-context parsing
TEST_F(Dhcpv4SharedNetworkTest,parse)1333 TEST_F(Dhcpv4SharedNetworkTest, parse) {
1334     // Create client
1335     Dhcp4Client client1(Dhcp4Client::SELECTING);
1336 
1337     // Don't use configure from utils
1338     Parser4Context ctx;
1339     ConstElementPtr json;
1340     ASSERT_NO_THROW(json = parseDHCP4(NETWORKS_CONFIG[0], true));
1341     ConstElementPtr status;
1342     disableIfacesReDetect(json);
1343     EXPECT_NO_THROW(status = configureDhcp4Server(*client1.getServer(), json));
1344     ASSERT_TRUE(status);
1345     int rcode;
1346     ConstElementPtr comment = config::parseAnswer(rcode, status);
1347     ASSERT_EQ(0, rcode) << " comment: " << comment->stringValue();
1348     ASSERT_NO_THROW( {
1349         CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
1350         cfg_db->setAppendedParameters("universe=4");
1351         cfg_db->createManagers();
1352     } );
1353     CfgMgr::instance().commit();
1354 
1355     CfgSharedNetworks4Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSharedNetworks4();
1356     SharedNetwork4Ptr network = cfg->getByName("frog");
1357     ConstElementPtr context = network->getContext();
1358     ASSERT_TRUE(context);
1359     ASSERT_EQ(1, context->size());
1360     ASSERT_TRUE(context->get("comment"));
1361     EXPECT_EQ("\"example\"", context->get("comment")->str());
1362 }
1363 
1364 // Running out of addresses within a subnet in a shared network.
TEST_F(Dhcpv4SharedNetworkTest,poolInSharedNetworkShortage)1365 TEST_F(Dhcpv4SharedNetworkTest, poolInSharedNetworkShortage) {
1366     // Create client #1
1367     Dhcp4Client client1(Dhcp4Client::SELECTING);
1368     client1.setIfaceName("eth1");
1369     client1.setIfaceIndex(ETH1_INDEX);
1370 
1371     // Configure the server with one shared network including two subnets and
1372     // one subnet outside of the shared network.
1373     configure(NETWORKS_CONFIG[0], *client1.getServer());
1374 
1375     // Client #1 requests an address in first subnet within a shared network.
1376     // We'll send a hint of 192.0.2.63 and expect to get it.
1377     testAssigned([this, &client1]() {
1378         doDORA(client1, "192.0.2.63", "192.0.2.63");
1379     });
1380 
1381     // Client #2 The second client will request a lease and should be assigned
1382     // an address from the second subnet.
1383     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
1384     client2.setIfaceName("eth1");
1385     client2.setIfaceIndex(ETH1_INDEX);
1386     testAssigned([this, &client2]() {
1387         doDORA(client2, "10.0.0.16");
1388     });
1389 
1390     // Client #3. It sends DHCPDISCOVER which should be dropped by the server because
1391     // the server has no more addresses to assign.
1392     Dhcp4Client client3(client1.getServer(), Dhcp4Client::SELECTING);
1393     client3.setIfaceName("eth1");
1394     client3.setIfaceIndex(ETH1_INDEX);
1395     testAssigned([&client3]() {
1396         ASSERT_NO_THROW(client3.doDiscover());
1397         Pkt4Ptr resp3 = client3.getContext().response_;
1398         ASSERT_FALSE(resp3);
1399     });
1400 
1401     // Client #3 should be assigned an address if subnet 3 is selected for this client.
1402     client3.setIfaceName("eth0");
1403     client3.setIfaceIndex(ETH0_INDEX);
1404     testAssigned([this, &client3]() {
1405         doDORA(client3, "192.0.2.65");
1406     });
1407 
1408     // Client #1 should be able to renew its address.
1409     client1.setState(Dhcp4Client::RENEWING);
1410     testAssigned([this, &client1]() {
1411         doRequest(client1, "192.0.2.63");
1412     });
1413 
1414     // Client #2 should be able to renew its address.
1415     client2.setState(Dhcp4Client::RENEWING);
1416     testAssigned([this, &client2]() {
1417         doRequest(client2, "10.0.0.16");
1418     });
1419 }
1420 
1421 // Returning client sends 4-way exchange.
TEST_F(Dhcpv4SharedNetworkTest,returningClientStartsOver)1422 TEST_F(Dhcpv4SharedNetworkTest, returningClientStartsOver) {
1423     // Create client.
1424     Dhcp4Client client(Dhcp4Client::SELECTING);
1425     client.setIfaceName("eth1");
1426     client.setIfaceIndex(ETH1_INDEX);
1427     client.includeClientId("01:02:03:04");
1428 
1429     // Configure the server with one shared network including two subnets and
1430     // one subnet outside of the shared network.
1431     configure(NETWORKS_CONFIG[0], *client.getServer());
1432 
1433     // Client requests an address in first subnet within a shared network.
1434     // We'll send a hint of 192.0.2.63 and expect to get it.
1435     testAssigned([this, &client]() {
1436         doDORA(client, "192.0.2.63", "192.0.2.63");
1437     });
1438 
1439 
1440     // The client reboots and performs 4-way exchange again without a hint.
1441     // It should be assigned the same (existing) lease.
1442     testAssigned([this, &client]() {
1443         doDORA(client, "192.0.2.63");
1444     });
1445 }
1446 
1447 // Shared network is selected based on giaddr value (relay specified
1448 // on shared network level)
TEST_F(Dhcpv4SharedNetworkTest,sharedNetworkSelectedByRelay1)1449 TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByRelay1) {
1450     // Create client #1. This is a relayed client which is using relay
1451     // address matching configured shared network.
1452     Dhcp4Client client1(Dhcp4Client::SELECTING);
1453     client1.useRelay(true, IOAddress("192.3.5.6"), IOAddress("10.0.0.2"));
1454 
1455     // Configure the server with one shared network and one subnet outside of the
1456     // shared network.
1457     configure(NETWORKS_CONFIG[1], *client1.getServer());
1458 
1459     // Client #1 should be assigned an address from shared network.
1460     testAssigned([this, &client1] {
1461         doDORA(client1, "192.0.2.63", "192.0.2.63");
1462     });
1463 
1464     // Create client #2. This is a relayed client which is using relay
1465     // address matching subnet outside of the shared network.
1466     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
1467     client2.useRelay(true, IOAddress("192.1.2.3"), IOAddress("10.0.0.3"));
1468     testAssigned([this, &client2] {
1469         doDORA(client2, "192.0.2.65", "192.0.2.63");
1470     });
1471 }
1472 
1473 // Shared network is selected based on giaddr value (relay specified
1474 // on subnet in shared network level). Note the relay ip is specified
1475 // on the shared network level, but its value is overridden on subnet
1476 // level.
TEST_F(Dhcpv4SharedNetworkTest,sharedNetworkSelectedByRelay2)1477 TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByRelay2) {
1478     // Create client #1. This is a relayed client which is using relay
1479     // address matching configured subnet 1 in shared network.
1480     Dhcp4Client client1(Dhcp4Client::SELECTING);
1481     client1.useRelay(true, IOAddress("192.1.1.1"), IOAddress("10.0.0.2"));
1482 
1483     // Configure the server with one shared network and one subnet outside of the
1484     // shared network.
1485     configure(NETWORKS_CONFIG[14], *client1.getServer());
1486 
1487     // Client #1 should be assigned an address from shared network.
1488     testAssigned([this, &client1] {
1489         doDORA(client1, "192.0.2.63");
1490     });
1491 
1492     // Create client #2. This is a relayed client which is using relay
1493     // address that is used for subnet 2 in the shared network.
1494     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
1495     client2.useRelay(true, IOAddress("192.2.2.2"), IOAddress("10.0.0.3"));
1496     testAssigned([this, &client2] {
1497         doDORA(client2, "10.0.0.16");
1498     });
1499 
1500     // Create client #3. This is a relayed client which is using relay
1501     // address matching subnet outside of the shared network.
1502     Dhcp4Client client3(client1.getServer(), Dhcp4Client::SELECTING);
1503     client3.useRelay(true, IOAddress("192.3.3.3"), IOAddress("10.0.0.4"));
1504     testAssigned([this, &client3] {
1505         doDORA(client3, "192.0.2.65");
1506     });
1507 }
1508 
1509 // Providing a hint for any address belonging to a shared network.
TEST_F(Dhcpv4SharedNetworkTest,hintWithinSharedNetwork)1510 TEST_F(Dhcpv4SharedNetworkTest, hintWithinSharedNetwork) {
1511     // Create client.
1512     Dhcp4Client client(Dhcp4Client::SELECTING);
1513     client.setIfaceName("eth1");
1514     client.setIfaceIndex(ETH1_INDEX);
1515 
1516     // Configure the server with one shared network including two subnets and
1517     // one subnet outside of the shared network.
1518     configure(NETWORKS_CONFIG[0], *client.getServer());
1519 
1520     // Provide a hint to an existing address within first subnet. This address
1521     // should be offered out of this subnet.
1522     testAssigned([this, &client] {
1523         doDiscover(client, "192.0.2.63", "192.0.2.63");
1524     });
1525 
1526     // Similarly, we should be offered an address from another subnet within
1527     // the same shared network when we ask for it.
1528     testAssigned([this, &client] {
1529         doDiscover(client, "10.0.0.16", "10.0.0.16");
1530     });
1531 
1532     // Asking for an address that is not in address pool should result in getting
1533     // an address from one of the subnets, but generally hard to tell from which one.
1534     testAssigned([&client] {
1535         ASSERT_NO_THROW(client.doDiscover(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.23"))));
1536     });
1537 
1538     Pkt4Ptr resp = client.getContext().response_;
1539     ASSERT_TRUE(resp);
1540 
1541     // We expect one of the two addresses available in this shared network.
1542     EXPECT_EQ(DHCPOFFER, resp->getType());
1543     if ((resp->getYiaddr() != IOAddress("10.0.0.16")) &&
1544         (resp->getYiaddr() != IOAddress("192.0.2.63"))) {
1545         ADD_FAILURE() << "Unexpected address offered by the server " << resp->getYiaddr();
1546     }
1547 }
1548 
1549 // Access to a subnet within shared network is restricted by client
1550 // classification.
TEST_F(Dhcpv4SharedNetworkTest,subnetInSharedNetworkSelectedByClass)1551 TEST_F(Dhcpv4SharedNetworkTest, subnetInSharedNetworkSelectedByClass) {
1552     // Create client #1
1553     Dhcp4Client client1(Dhcp4Client::SELECTING);
1554     client1.useRelay(true, IOAddress("192.3.5.6"));
1555 
1556     // Configure the server with one shared network including two subnets in
1557     // it. The access to one of the subnets is restricted by client classification.
1558     configure(NETWORKS_CONFIG[2], *client1.getServer());
1559 
1560     // Client #1 requests an address in the restricted subnet but can't be assigned
1561     // this address because the client doesn't belong to a certain class.
1562     testAssigned([this, &client1] {
1563         doDORA(client1, "10.0.0.16", "192.0.2.63");
1564     });
1565 
1566     // Release the lease that the client has got, because we'll need this address
1567     // further in the test.
1568     testAssigned([&client1] {
1569         ASSERT_NO_THROW(client1.doRelease());
1570     });
1571 
1572     // Add option93 which would cause the client to be classified as "a-devices".
1573     OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
1574     client1.addExtraOption(option93);
1575 
1576     // This time, the allocation of the address provided as hint should be successful.
1577     testAssigned([this, &client1] {
1578         doDORA(client1, "192.0.2.63", "192.0.2.63");
1579     });
1580 
1581     // Client 2 should be assigned an address from the unrestricted subnet.
1582     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
1583     client2.useRelay(true, IOAddress("192.3.5.6"));
1584     client2.setIfaceName("eth1");
1585     client2.setIfaceIndex(ETH1_INDEX);
1586     testAssigned([this, &client2] {
1587         doDORA(client2, "10.0.0.16");
1588     });
1589 
1590     // Now, let's reconfigure the server to also apply restrictions on the
1591     // subnet to which client2 now belongs.
1592     configure(NETWORKS_CONFIG[3], *client1.getServer());
1593 
1594     // The client should be refused to renew the lease because it doesn't belong
1595     // to "b-devices" class.
1596     client2.setState(Dhcp4Client::RENEWING);
1597     testAssigned([this, &client2] {
1598         doRequest(client2, "");
1599     });
1600 
1601     // If we add option93 with a value matching this class, the lease should
1602     // get renewed.
1603     OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
1604     client2.addExtraOption(option93_bis);
1605 
1606     testAssigned([this, &client2] {
1607         doRequest(client2, "10.0.0.16");
1608     });
1609 }
1610 
1611 // IPv4 address reservation exists in one of the subnets within
1612 // shared network. This test also verifies that conflict resolution for
1613 // reserved addresses is working properly in case of shared networks.
TEST_F(Dhcpv4SharedNetworkTest,reservationInSharedNetwork)1614 TEST_F(Dhcpv4SharedNetworkTest, reservationInSharedNetwork) {
1615     // Create client #1. Explicitly set client's MAC address to the one that
1616     // has a reservation in the first subnet within shared network.
1617     Dhcp4Client client1(Dhcp4Client::SELECTING);
1618     client1.useRelay(true, IOAddress("192.3.5.6"));
1619     client1.setHWAddress("11:22:33:44:55:66");
1620 
1621     // Create server configuration with a shared network including two subnets. There
1622     // is an IP address reservation in each subnet for two respective clients.
1623     configure(NETWORKS_CONFIG[4], *client1.getServer());
1624 
1625     // Client #1 should get his reserved address from the second subnet.
1626     testAssigned([this, &client1] {
1627         doDORA(client1, "10.0.0.29", "192.0.2.28");
1628     });
1629 
1630     // Create client #2
1631     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
1632     client2.useRelay(true, IOAddress("192.3.5.6"));
1633     client2.setHWAddress("aa:bb:cc:dd:ee:ff");
1634 
1635     // Client #2 should get its reserved address from the first subnet.
1636     testAssigned([this, &client2] {
1637         doDORA(client2, "192.0.2.28");
1638     });
1639 
1640     // Reconfigure the server. Now, the first client gets second client's
1641     // reservation and vice versa.
1642     configure(NETWORKS_CONFIG[5], *client1.getServer());
1643 
1644     // The first client is trying to renew the lease and should get a DHCPNAK.
1645     client1.setState(Dhcp4Client::RENEWING);
1646     testAssigned([this, &client1] {
1647         doRequest(client1, "");
1648     });
1649 
1650     // Similarly, the second client is trying to renew the lease and should
1651     // get a DHCPNAK.
1652     client2.setState(Dhcp4Client::RENEWING);
1653     testAssigned([this, &client2] {
1654         doRequest(client2, "");
1655     });
1656 
1657     // But the client should get a lease, if it does 4-way exchange. However, it
1658     // must not get any of the reserved addresses because one of them is reserved
1659     // for another client and for another one there is a valid lease.
1660     client1.setState(Dhcp4Client::SELECTING);
1661     testAssigned([this, &client1] {
1662         ASSERT_NO_THROW(doDORA(client1, "192.0.2.29", "192.0.2.29"));
1663     });
1664     Pkt4Ptr resp1 = client1.getContext().response_;
1665     ASSERT_TRUE(resp1);
1666     EXPECT_EQ(DHCPACK, resp1->getType());
1667     EXPECT_NE("10.0.0.29", resp1->getYiaddr().toText());
1668     EXPECT_NE("192.0.2.28", resp1->getYiaddr().toText());
1669 
1670     // Client #2 is now doing 4-way exchange and should get its newly reserved
1671     // address, released by the 4-way transaction of client 1.
1672     client2.setState(Dhcp4Client::SELECTING);
1673     testAssigned([this, &client2] {
1674         doDORA(client2, "10.0.0.29");
1675     });
1676 
1677     // Same for client #1.
1678     client1.setState(Dhcp4Client::SELECTING);
1679     testAssigned([this, &client1] {
1680         doDORA(client1, "192.0.2.28");
1681     });
1682 }
1683 
1684 // Reserved address can't be assigned as long as access to a subnet is
1685 // restricted by classification.
TEST_F(Dhcpv4SharedNetworkTest,reservationAccessRestrictedByClass)1686 TEST_F(Dhcpv4SharedNetworkTest, reservationAccessRestrictedByClass) {
1687     // Create a client and set explicit MAC address for which there is a reservation
1688     // in first subnet belonging to a shared network.
1689     Dhcp4Client client(Dhcp4Client::SELECTING);
1690     client.useRelay(true, IOAddress("192.3.5.6"));
1691     client.setHWAddress("aa:bb:cc:dd:ee:ff");
1692 
1693     // Create configuration with a shared network including two subnets. Access to
1694     // one of the subnets is restricted by client classification.
1695     configure(NETWORKS_CONFIG[6], *client.getServer());
1696 
1697     // Assigned address should be allocated from the second subnet, because the
1698     // client doesn't belong to the "a-devices" class.
1699     testAssigned([this, &client] {
1700         doDORA(client, "10.0.0.16");
1701     });
1702 
1703     // Add option 93 which would cause the client to be classified as "a-devices".
1704     OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
1705     client.addExtraOption(option93);
1706 
1707     // Client renews its lease and should get DHCPNAK because this client now belongs
1708     // to the "a-devices" class and can be assigned a reserved address instead.
1709     client.setState(Dhcp4Client::RENEWING);
1710     testAssigned([this, &client] {
1711         doRequest(client, "");
1712     });
1713 
1714     // Perform 4-way exchange again. It should be assigned a reserved address this time.
1715     client.setState(Dhcp4Client::SELECTING);
1716     testAssigned([this, &client] {
1717         doDORA(client, "192.0.2.28");
1718     });
1719 }
1720 
1721 // Some options are specified on the shared subnet level, some on the
1722 // subnets level.
TEST_F(Dhcpv4SharedNetworkTest,optionsDerivation)1723 TEST_F(Dhcpv4SharedNetworkTest, optionsDerivation) {
1724     // Client #1.
1725     Dhcp4Client client1(Dhcp4Client::SELECTING);
1726     client1.setIfaceName("eth1");
1727     client1.setIfaceIndex(ETH1_INDEX);
1728     client1.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
1729 
1730     configure(NETWORKS_CONFIG[7], *client1.getServer());
1731 
1732     // Client #1 belongs to shared network. By providing a hint "192.0.2.63" we force
1733     // the server to select first subnet within the shared network for this client.
1734     doDORA(client1, "192.0.2.63", "192.0.2.63");
1735 
1736     // This option is specified at the global level.
1737     ASSERT_EQ(1, client1.config_.log_servers_.size());
1738     EXPECT_EQ("1.2.3.4", client1.config_.log_servers_[0].toText());
1739 
1740     // This option is specified on the subnet level.
1741     ASSERT_EQ(1, client1.config_.routers_.size());
1742     EXPECT_EQ("192.0.2.5", client1.config_.routers_[0].toText());
1743 
1744     // This option is specified on the shared network level and the subnet level.
1745     // The instance on the subnet level should take precedence.
1746     ASSERT_EQ(1, client1.config_.quotes_servers_.size());
1747     EXPECT_EQ("10.5.4.3", client1.config_.quotes_servers_[0].toText());
1748 
1749     // This option is only specified on the shared network level and should be
1750     // inherited by all subnets within this network.
1751     ASSERT_EQ(1, client1.config_.dns_servers_.size());
1752     EXPECT_EQ("10.1.2.3", client1.config_.dns_servers_[0].toText());
1753 
1754     // Client #2.
1755     Dhcp4Client client2(Dhcp4Client::SELECTING);
1756     client2.setIfaceName("eth1");
1757     client2.setIfaceIndex(ETH1_INDEX);
1758     client2.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
1759 
1760     // Request an address from the second subnet within the shared network.
1761     doDORA(client2, "10.0.0.16", "10.0.0.16");
1762 
1763     // This option is specified at the global level.
1764     ASSERT_EQ(1, client2.config_.log_servers_.size());
1765     EXPECT_EQ("1.2.3.4", client2.config_.log_servers_[0].toText());
1766 
1767     // This option is only specified on the shared network level and should be
1768     // inherited by all subnets within this network.
1769     ASSERT_EQ(1, client2.config_.quotes_servers_.size());
1770     EXPECT_EQ("10.6.5.4", client2.config_.quotes_servers_[0].toText());
1771 
1772     // This option is only specified on the shared network level and should be
1773     // inherited by all subnets within this network.
1774     ASSERT_EQ(1, client2.config_.dns_servers_.size());
1775     EXPECT_EQ("10.1.2.3", client2.config_.dns_servers_[0].toText());
1776 
1777     // Client #3.
1778     Dhcp4Client client3(Dhcp4Client::SELECTING);
1779     client3.setIfaceName("eth0");
1780     client3.setIfaceIndex(ETH0_INDEX);
1781     client3.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
1782 
1783     // Client 3 should get an address from the subnet defined outside of the shared network.
1784     doDORA(client3, "192.0.2.65");
1785 
1786     // This option is specified at the global level.
1787     ASSERT_EQ(1, client3.config_.log_servers_.size());
1788     EXPECT_EQ("1.2.3.4", client3.config_.log_servers_[0].toText());
1789 
1790     // This option is specified on the subnet level.
1791     ASSERT_EQ(1, client3.config_.quotes_servers_.size());
1792     EXPECT_EQ("10.1.1.1", client3.config_.quotes_servers_[0].toText());
1793 
1794     // This option is only specified on the shared network level and thus it should
1795     // not be returned to this client, because the client doesn't belong to the
1796     // shared network.
1797     ASSERT_EQ(0, client3.config_.dns_servers_.size());
1798 }
1799 
1800 // Client has a lease in a subnet within shared network.
TEST_F(Dhcpv4SharedNetworkTest,initReboot)1801 TEST_F(Dhcpv4SharedNetworkTest, initReboot) {
1802     // Create client #1.
1803     Dhcp4Client client1(Dhcp4Client::SELECTING);
1804     client1.setIfaceName("eth1");
1805     client1.setIfaceIndex(ETH1_INDEX);
1806 
1807     configure(NETWORKS_CONFIG[0], *client1.getServer());
1808 
1809     // Perform 4-way exchange to obtain a lease. The client should get the lease from
1810     // the second subnet.
1811     testAssigned([this, &client1] {
1812         doDORA(client1, "10.0.0.16", "10.0.0.16");
1813     });
1814 
1815     // The client1 transitions to INIT-REBOOT state in which the client1 remembers the
1816     // lease and sends DHCPREQUEST to all servers (server id) is not specified. If
1817     // the server doesn't know the client1 (doesn't have its lease), it should
1818     // drop the request. We want to make sure that the server responds (resp1) regardless
1819     // of the subnet from which the lease has been allocated.
1820     client1.setState(Dhcp4Client::INIT_REBOOT);
1821     testAssigned([this, &client1] {
1822         doRequest(client1, "10.0.0.16");
1823     });
1824 
1825     // Create client #2.
1826     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
1827     client2.setIfaceName("eth1");
1828     client2.setIfaceIndex(ETH1_INDEX);
1829 
1830     // Let's make sure that the behavior is the same for the other subnet within the
1831     // same shared network.
1832     testAssigned([this, &client2] {
1833         doDORA(client2, "192.0.2.63", "192.0.2.63");
1834     });
1835 
1836     // The client2 transitions to INIT-REBOOT state in which the client2 remembers the
1837     // lease and sends DHCPREQUEST to all servers (server id) is not specified. If
1838     // the server doesn't know the client2 (doesn't have its lease), it should
1839     // drop the request. We want to make sure that the server responds (resp2) regardless
1840     // of the subnet from which the lease has been allocated.
1841     client2.setState(Dhcp4Client::INIT_REBOOT);
1842     testAssigned([this, &client2] {
1843         doRequest(client2, "192.0.2.63");
1844     });
1845 }
1846 
1847 // Host reservations include hostname, next server and client class.
TEST_F(Dhcpv4SharedNetworkTest,variousFieldsInReservation)1848 TEST_F(Dhcpv4SharedNetworkTest, variousFieldsInReservation) {
1849     // Create client.
1850     Dhcp4Client client(Dhcp4Client::SELECTING);
1851     client.setIfaceName("eth1");
1852     client.setIfaceIndex(ETH1_INDEX);
1853     client.setHWAddress("11:22:33:44:55:66");
1854 
1855     // Include hostname to force the server to return hostname to
1856     // the client.
1857     client.includeHostname("my.example.org");
1858 
1859     // Configure the server with a shared network including two subnets.
1860     // The client has address/hostname reservation in the second subnet.
1861     configure(NETWORKS_CONFIG[10], *client.getServer());
1862 
1863     // Perform 4-way exchange.
1864     testAssigned([&client] {
1865         ASSERT_NO_THROW(client.doDORA());
1866     });
1867     Pkt4Ptr resp = client.getContext().response_;
1868     ASSERT_TRUE(resp);
1869     EXPECT_EQ(DHCPACK, resp->getType());
1870     EXPECT_EQ("10.0.0.29", resp->getYiaddr().toText());
1871 
1872     // The client should get a hostname from the reservation, rather than
1873     // the hostname it has sent to the server. If there is a logic error,
1874     // the server would use the first subnet from the shared network to
1875     // assign the hostname. This subnet has no reservation so it would
1876     // return the same hostname that the client has sent. We expect
1877     // that the hostname being sent is the one that is incldued in the
1878     // reservations.
1879     OptionStringPtr hostname;
1880     hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
1881     ASSERT_TRUE(hostname);
1882     EXPECT_EQ("test.example.org", hostname->getValue());
1883 
1884     // The next server value should also be set according to the settings
1885     // in host reservations.
1886     EXPECT_EQ("10.10.10.10", resp->getSiaddr().toText());
1887 
1888     // The boot-file-name value should be derived from the client class
1889     // based on the static class reservations.
1890     const std::string expected_fname = "/dev/null";
1891     const OptionBuffer fname = resp->getFile();
1892     const std::string converted_fname(fname.cbegin(),
1893                                       fname.cbegin() + expected_fname.size());
1894     EXPECT_EQ(expected_fname, converted_fname);
1895 }
1896 
1897 // Different shared network is selected for different local interface.
TEST_F(Dhcpv4SharedNetworkTest,sharedNetworkSelectionByInterface)1898 TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectionByInterface) {
1899     // Create client #1. The server receives requests from this client
1900     // via interface eth1 and should assign shared network "frog" for
1901     // this client.
1902     Dhcp4Client client1(Dhcp4Client::SELECTING);
1903     client1.setIfaceName("eth1");
1904     client1.setIfaceIndex(ETH1_INDEX);
1905 
1906     // Create server configuration with two shared networks selected
1907     // by the local interface: eth1 and eth0.
1908     configure(NETWORKS_CONFIG[8], *client1.getServer());
1909 
1910     // Perform 4-way exchange.
1911     testAssigned([&client1] {
1912         ASSERT_NO_THROW(client1.doDORA());
1913     });
1914     Pkt4Ptr resp1 = client1.getContext().response_;
1915     ASSERT_TRUE(resp1);
1916     EXPECT_EQ(DHCPACK, resp1->getType());
1917     // The client should be assigned an address from the 192.0.2.X
1918     // address range.
1919     EXPECT_EQ("192.0.2", resp1->getYiaddr().toText().substr(0, 7));
1920 
1921     // Create client #2 which requests are received on eth0.
1922     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
1923     client2.setIfaceName("eth0");
1924     client2.setIfaceIndex(ETH0_INDEX);
1925 
1926     // Perform 4-way exchange.
1927     testAssigned([&client2] {
1928         ASSERT_NO_THROW(client2.doDORA());
1929     });
1930     Pkt4Ptr resp2 = client2.getContext().response_;
1931     ASSERT_TRUE(resp2);
1932     EXPECT_EQ(DHCPACK, resp2->getType());
1933     // The client should be assigned an address from the 10.0.0.X
1934     // address range.
1935     EXPECT_EQ("10.0.0", resp2->getYiaddr().toText().substr(0, 6));
1936 }
1937 
1938 // Different shared network is selected for different relay address.
TEST_F(Dhcpv4SharedNetworkTest,sharedNetworkSelectionByRelay)1939 TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectionByRelay) {
1940     // Create relayed client #1.
1941     Dhcp4Client client1(Dhcp4Client::SELECTING);
1942     client1.useRelay(true, IOAddress("10.1.2.3"));
1943 
1944     // Create server configuration with two shared networks selected
1945     // by the relay address.
1946     configure(NETWORKS_CONFIG[9], *client1.getServer());
1947 
1948     // Perform 4-way exchange.
1949     testAssigned([&client1] {
1950         ASSERT_NO_THROW(client1.doDORA());
1951     });
1952     Pkt4Ptr resp1 = client1.getContext().response_;
1953     ASSERT_TRUE(resp1);
1954     EXPECT_EQ(DHCPACK, resp1->getType());
1955     // The client should be assigned an address from the 192.0.2.X
1956     // address range.
1957     EXPECT_EQ("192.0.2", resp1->getYiaddr().toText().substr(0, 7));
1958 
1959     // Create relayed client #2.
1960     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
1961     client2.useRelay(true, IOAddress("192.1.2.3"));
1962 
1963     // Perform 4-way exchange.
1964     testAssigned([&client2] {
1965         ASSERT_NO_THROW(client2.doDORA());
1966     });
1967     Pkt4Ptr resp2 = client2.getContext().response_;
1968     ASSERT_TRUE(resp2);
1969     EXPECT_EQ(DHCPACK, resp2->getType());
1970     // The client should be assigned an address from the 10.0.0.X
1971     // address range.
1972     EXPECT_EQ("10.0.0", resp2->getYiaddr().toText().substr(0, 6));
1973 }
1974 
1975 // Client id matching gets disabled on the shared network level.
TEST_F(Dhcpv4SharedNetworkTest,matchClientId)1976 TEST_F(Dhcpv4SharedNetworkTest, matchClientId) {
1977     // Create client using client identifier besides MAC address.
1978     Dhcp4Client client(Dhcp4Client::SELECTING);
1979     client.includeClientId("01:02:03:04");
1980     client.setIfaceName("eth1");
1981     client.setIfaceIndex(ETH1_INDEX);
1982 
1983     // Create server configuration with match-client-id value initially
1984     // set to true. The client should be allocated a lease and the
1985     // client identifier should be included in this lease.
1986     configure(NETWORKS_CONFIG[11], *client.getServer());
1987 
1988     // Perform 4-way exchange.
1989     testAssigned([&client] {
1990         ASSERT_NO_THROW(client.doDORA());
1991     });
1992     Pkt4Ptr resp1 = client.getContext().response_;
1993     ASSERT_TRUE(resp1);
1994     ASSERT_EQ(DHCPACK, resp1->getType());
1995 
1996     // Reconfigure the server and turn off client identifier matching
1997     // on the shared network level. The subnet from which the client
1998     // is allocated an address should derive the match-client-id value
1999     // and ignore the fact that the client identifier is not matching.
2000     configure(NETWORKS_CONFIG[12], *client.getServer());
2001 
2002     client.includeClientId("01:01:01:01");
2003     client.setState(Dhcp4Client::RENEWING);
2004 
2005     // Try to renew the lease with modified MAC address.
2006     testAssigned([&client] {
2007         ASSERT_NO_THROW(client.doRequest());
2008     });
2009     Pkt4Ptr resp2 = client.getContext().response_;
2010     ASSERT_TRUE(resp2);
2011     ASSERT_EQ(DHCPACK, resp2->getType());
2012 
2013     // The lease should get renewed.
2014     EXPECT_EQ(resp2->getYiaddr().toText(), resp1->getYiaddr().toText());
2015 }
2016 
2017 // Shared network is selected based on the client class specified.
TEST_F(Dhcpv4SharedNetworkTest,sharedNetworkSelectedByClass)2018 TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByClass) {
2019    // Create client #1.
2020     Dhcp4Client client1(Dhcp4Client::SELECTING);
2021     client1.setIfaceName("eth1");
2022     client1.setIfaceIndex(ETH1_INDEX);
2023 
2024     // Add option93 which would cause the client1 to be classified as "b-devices".
2025     OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0002));
2026     client1.addExtraOption(option93);
2027 
2028     // Configure the server with two shared networks which can be accessed
2029     // by clients belonging to "a-devices" and "b-devices" classes
2030     // respectively.
2031     configure(NETWORKS_CONFIG[13], *client1.getServer());
2032 
2033     // Simply send DHCPDISCOVER to avoid allocating a lease.
2034     testAssigned([&client1] {
2035         ASSERT_NO_THROW(client1.doDiscover());
2036     });
2037     Pkt4Ptr resp1 = client1.getContext().response_;
2038     ASSERT_TRUE(resp1);
2039     ASSERT_EQ(DHCPOFFER, resp1->getType());
2040     // The client should be offered a lease from the second shared network.
2041     EXPECT_EQ("10.0.0.63", resp1->getYiaddr().toText());
2042 
2043     // Create another client which will belong to a different class.
2044     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
2045     client2.setIfaceName("eth1");
2046     client2.setIfaceIndex(ETH1_INDEX);
2047 
2048     // Add option93 which would cause the client1 to be classified as "a-devices".
2049     option93.reset(new OptionUint16(Option::V4, 93, 0x0001));
2050     client2.addExtraOption(option93);
2051 
2052     // Send DHCPDISCOVER. There is no lease in the lease database so the
2053     // client should be offered a lease based on the client class selection.
2054     testAssigned([this, &client2] {
2055         doDiscover(client2, "192.0.2.63", "");
2056     });
2057 }
2058 
2059 // This test verifies that custom server identifier can be specified for a
2060 // shared network.
TEST_F(Dhcpv4SharedNetworkTest,customServerIdentifier)2061 TEST_F(Dhcpv4SharedNetworkTest, customServerIdentifier) {
2062     Dhcp4Client client1(Dhcp4Client::SELECTING);
2063     client1.setIfaceName("eth1");
2064     client1.setIfaceIndex(ETH1_INDEX);
2065 
2066     // Configure DHCP server.
2067     ASSERT_NO_THROW(configure(NETWORKS_CONFIG[15], *client1.getServer()));
2068 
2069     testAssigned([&client1] {
2070         ASSERT_NO_THROW(client1.doDORA());
2071     });
2072 
2073     // Make sure that the server responded.
2074     ASSERT_TRUE(client1.getContext().response_);
2075     Pkt4Ptr resp = client1.getContext().response_;
2076     // Make sure that the server has responded with DHCPACK.
2077     ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
2078     // The explicitly configured server identifier should take precedence
2079     // over generated server identifier.
2080     EXPECT_EQ("1.2.3.4", client1.config_.serverid_.toText());
2081 
2082     // Create another client using different interface.
2083     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
2084     client2.setIfaceName("eth0");
2085     client2.setIfaceIndex(ETH0_INDEX);
2086 
2087     testAssigned([&client2] {
2088         ASSERT_NO_THROW(client2.doDORA());
2089     });
2090 
2091     // Make sure that the server responded.
2092     ASSERT_TRUE(client2.getContext().response_);
2093     resp = client2.getContext().response_;
2094     // Make sure that the server has responded with DHCPACK.
2095     ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
2096     // The explicitly configured server identifier should take precedence
2097     // over generated server identifier.
2098     EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
2099 }
2100 
2101 // Access to a pool within shared network is restricted by client
2102 // classification.
TEST_F(Dhcpv4SharedNetworkTest,poolInSharedNetworkSelectedByClass)2103 TEST_F(Dhcpv4SharedNetworkTest, poolInSharedNetworkSelectedByClass) {
2104     // Create client #1
2105     Dhcp4Client client1(Dhcp4Client::SELECTING);
2106     client1.setIfaceName("eth1");
2107     client1.setIfaceIndex(ETH1_INDEX);
2108 
2109     // Configure the server with one shared network including one subnet and
2110     // in 2 pools in it. The access to one of the pools is restricted
2111     // by client classification.
2112     configure(NETWORKS_CONFIG[16], *client1.getServer());
2113 
2114     // Client #1 requests an address in the restricted pool but can't be assigned
2115     // this address because the client doesn't belong to a certain class.
2116     testAssigned([this, &client1] {
2117         doDORA(client1, "192.0.2.100", "192.0.2.63");
2118     });
2119 
2120     // Release the lease that the client has got, because we'll need this address
2121     // further in the test.
2122     testAssigned([&client1] {
2123         ASSERT_NO_THROW(client1.doRelease());
2124     });
2125 
2126     // Add option93 which would cause the client to be classified as "a-devices".
2127     OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
2128     client1.addExtraOption(option93);
2129 
2130     // This time, the allocation of the address provided as hint should be successful.
2131     testAssigned([this, &client1] {
2132         doDORA(client1, "192.0.2.63", "192.0.2.63");
2133     });
2134 
2135     // Client 2 should be assigned an address from the unrestricted pool.
2136     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
2137     client2.setIfaceName("eth1");
2138     client2.setIfaceIndex(ETH1_INDEX);
2139     testAssigned([this, &client2] {
2140         doDORA(client2, "192.0.2.100");
2141     });
2142 
2143     // Now, let's reconfigure the server to also apply restrictions on the
2144     // pool to which client2 now belongs.
2145     configure(NETWORKS_CONFIG[17], *client1.getServer());
2146 
2147     // The client should be refused to renew the lease because it doesn't belong
2148     // to "b-devices" class.
2149     client2.setState(Dhcp4Client::RENEWING);
2150     testAssigned([this, &client2] {
2151         doRequest(client2, "");
2152     });
2153 
2154     // If we add option93 with a value matching this class, the lease should
2155     // get renewed.
2156     OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
2157     client2.addExtraOption(option93_bis);
2158 
2159     testAssigned([this, &client2] {
2160         doRequest(client2, "192.0.2.100");
2161     });
2162 }
2163 
2164 // Access to a pool within plain subnet is restricted by client classification.
TEST_F(Dhcpv4SharedNetworkTest,poolInSubnetSelectedByClass)2165 TEST_F(Dhcpv4SharedNetworkTest, poolInSubnetSelectedByClass) {
2166     // Create client #1
2167     Dhcp4Client client1(Dhcp4Client::SELECTING);
2168     client1.setIfaceName("eth1");
2169     client1.setIfaceIndex(ETH1_INDEX);
2170 
2171     // Configure the server with one plain subnet including two pools.
2172     // The access to one of the pools is restricted by client classification.
2173     configure(NETWORKS_CONFIG[18], *client1.getServer());
2174 
2175     // Client #1 requests an address in the restricted pool but can't be assigned
2176     // this address because the client doesn't belong to a certain class.
2177     testAssigned([this, &client1] {
2178         doDORA(client1, "192.0.2.100", "192.0.2.63");
2179     });
2180 
2181     // Release the lease that the client has got, because we'll need this address
2182     // further in the test.
2183     testAssigned([&client1] {
2184         ASSERT_NO_THROW(client1.doRelease());
2185     });
2186 
2187     // Add option93 which would cause the client to be classified as "a-devices".
2188     OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
2189     client1.addExtraOption(option93);
2190 
2191     // This time, the allocation of the address provided as hint should be successful.
2192     testAssigned([this, &client1] {
2193         doDORA(client1, "192.0.2.63", "192.0.2.63");
2194     });
2195 
2196     // Client 2 should be assigned an address from the unrestricted pool.
2197     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
2198     client2.setIfaceName("eth1");
2199     client2.setIfaceIndex(ETH1_INDEX);
2200     testAssigned([this, &client2] {
2201         doDORA(client2, "192.0.2.100");
2202     });
2203 
2204     // Now, let's reconfigure the server to also apply restrictions on the
2205     // pool to which client2 now belongs.
2206     configure(NETWORKS_CONFIG[19], *client1.getServer());
2207 
2208     // The client should be refused to renew the lease because it doesn't belong
2209     // to "b-devices" class.
2210     client2.setState(Dhcp4Client::RENEWING);
2211     testAssigned([this, &client2] {
2212         doRequest(client2, "");
2213     });
2214 
2215     // If we add option93 with a value matching this class, the lease should
2216     // get renewed.
2217     OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
2218     client2.addExtraOption(option93_bis);
2219 
2220     testAssigned([this, &client2] {
2221         doRequest(client2, "192.0.2.100");
2222     });
2223 }
2224 
2225 // Shared network is selected based on giaddr value (relay specified
2226 // on shared network level, but response is send to source address.
TEST_F(Dhcpv4SharedNetworkTest,sharedNetworkSendToSourceTestingModeEnabled)2227 TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSendToSourceTestingModeEnabled) {
2228     // Create client #1. This is a relayed client which is using relay
2229     // address matching configured shared network.
2230     // Source address is set to unrelated to configuration.
2231 
2232     Dhcp4Client client1(Dhcp4Client::SELECTING);
2233     // Put Kea into testing mode.
2234     client1.getServer()->setSendResponsesToSource(true);
2235     client1.useRelay(true, IOAddress("192.3.5.6"), IOAddress("1.1.1.2"));
2236     // Configure the server with one shared network and one subnet outside of the
2237     // shared network.
2238     configure(NETWORKS_CONFIG[1], *client1.getServer());
2239     // Client #1 should be assigned an address from shared network.
2240     testAssigned([this, &client1] {
2241         doDORA(client1, "192.0.2.63", "192.0.2.63");
2242     });
2243 
2244     // normally Kea would send packet to 192.3.5.6 but we want it get from
2245     // 1.1.1.2 in send to source testing mode but still with correctly
2246     // assigned address.
2247     Pkt4Ptr resp1 = client1.getContext().response_;
2248     EXPECT_EQ("1.1.1.2", resp1->getLocalAddr().toText());
2249 
2250     // Create client #2. This is a relayed client which is using relay
2251     // address matching subnet outside of the shared network.
2252     Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
2253     client2.useRelay(true, IOAddress("192.1.2.3"), IOAddress("2.2.2.3"));
2254     testAssigned([this, &client2] {
2255         doDORA(client2, "192.0.2.65", "192.0.2.63");
2256     });
2257 
2258     Pkt4Ptr resp2 = client2.getContext().response_;
2259     EXPECT_EQ("2.2.2.3", resp2->getLocalAddr().toText());
2260     // reset testing mode.
2261     client1.getServer()->setSendResponsesToSource(false);
2262 }
2263 
2264 // Verify option processing precedence
2265 // Order is global < class < shared-network < subnet < pool < host reservation
TEST_F(Dhcpv4SharedNetworkTest,precedenceGlobal)2266 TEST_F(Dhcpv4SharedNetworkTest, precedenceGlobal) {
2267     const std::string config =
2268         "{"
2269         "    \"interfaces-config\": {"
2270         "        \"interfaces\": [ \"*\" ]"
2271         "    },"
2272         "    \"valid-lifetime\": 600,"
2273         "    \"option-data\": ["
2274         "        {"
2275         "           \"name\": \"domain-name-servers\","
2276         "           \"data\": \"192.0.2.1\""
2277         "        }"
2278         "    ],"
2279         "    \"shared-networks\": ["
2280         "        {"
2281         "            \"name\": \"frog\","
2282         "            \"interface\": \"eth1\","
2283         "            \"subnet4\": ["
2284         "                {"
2285         "                    \"subnet\": \"192.0.2.0/26\","
2286         "                    \"id\": 10,"
2287         "                    \"pools\": ["
2288         "                        {"
2289         "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
2290         "                        }"
2291         "                    ],"
2292         "                    \"reservations\": ["
2293         "                        {"
2294         "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
2295         "                            \"ip-address\": \"192.0.2.28\""
2296         "                        }"
2297         "                    ]"
2298         "                }"
2299         "            ]"
2300         "        }"
2301         "    ]"
2302         "}";
2303 
2304     testPrecedence(config, "192.0.2.1");
2305 }
2306 
2307 // Verify option processing precedence
2308 // Order is global < class < shared-network < subnet < pool < host reservation
TEST_F(Dhcpv4SharedNetworkTest,precedenceClass)2309 TEST_F(Dhcpv4SharedNetworkTest, precedenceClass) {
2310     const std::string config =
2311         "{"
2312         "    \"interfaces-config\": {"
2313         "        \"interfaces\": [ \"*\" ]"
2314         "    },"
2315         "    \"valid-lifetime\": 600,"
2316         "    \"option-data\": ["
2317         "        {"
2318         "           \"name\": \"domain-name-servers\","
2319         "           \"data\": \"192.0.2.1\""
2320         "        }"
2321         "    ],"
2322         "    \"client-classes\": ["
2323         "        {"
2324         "            \"name\": \"alpha\","
2325         "            \"test\": \"'' == ''\","
2326         "            \"option-data\": ["
2327         "                {"
2328         "                   \"name\": \"domain-name-servers\","
2329         "                   \"data\": \"192.0.2.2\""
2330         "                }"
2331         "            ]"
2332         "        }"
2333         "    ],"
2334         "    \"shared-networks\": ["
2335         "        {"
2336         "            \"name\": \"frog\","
2337         "            \"interface\": \"eth1\","
2338         "            \"subnet4\": ["
2339         "                {"
2340         "                    \"subnet\": \"192.0.2.0/26\","
2341         "                    \"id\": 10,"
2342         "                    \"pools\": ["
2343         "                        {"
2344         "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
2345         "                        }"
2346         "                    ],"
2347         "                    \"reservations\": ["
2348         "                        {"
2349         "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
2350         "                            \"ip-address\": \"192.0.2.28\""
2351         "                        }"
2352         "                    ]"
2353         "                }"
2354         "            ]"
2355         "        }"
2356         "    ]"
2357         "}";
2358 
2359     testPrecedence(config, "192.0.2.2");
2360 }
2361 
2362 // Verify option processing precedence
2363 // Order is global < class < shared-network < subnet < pool < host reservation
TEST_F(Dhcpv4SharedNetworkTest,precedenceClasses)2364 TEST_F(Dhcpv4SharedNetworkTest, precedenceClasses) {
2365     const std::string config =
2366         "{"
2367         "    \"interfaces-config\": {"
2368         "        \"interfaces\": [ \"*\" ]"
2369         "    },"
2370         "    \"valid-lifetime\": 600,"
2371         "    \"option-data\": ["
2372         "        {"
2373         "           \"name\": \"domain-name-servers\","
2374         "           \"data\": \"192.0.2.1\""
2375         "        }"
2376         "    ],"
2377         "    \"client-classes\": ["
2378         "        {"
2379         "            \"name\": \"beta\","
2380         "            \"test\": \"'' == ''\","
2381         "            \"option-data\": ["
2382         "                {"
2383         "                   \"name\": \"domain-name-servers\","
2384         "                   \"data\": \"192.0.2.2\""
2385         "                }"
2386         "            ]"
2387         "        },"
2388         "        {"
2389         "            \"name\": \"alpha\","
2390         "            \"test\": \"'' == ''\","
2391         "            \"option-data\": ["
2392         "                {"
2393         "                   \"name\": \"domain-name-servers\","
2394         "                   \"data\": \"192.0.2.3\""
2395         "                }"
2396         "            ]"
2397         "        }"
2398         "    ],"
2399         "    \"shared-networks\": ["
2400         "        {"
2401         "            \"name\": \"frog\","
2402         "            \"interface\": \"eth1\","
2403         "            \"subnet4\": ["
2404         "                {"
2405         "                    \"subnet\": \"192.0.2.0/26\","
2406         "                    \"id\": 10,"
2407         "                    \"pools\": ["
2408         "                        {"
2409         "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
2410         "                        }"
2411         "                    ],"
2412         "                    \"reservations\": ["
2413         "                        {"
2414         "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
2415         "                            \"ip-address\": \"192.0.2.28\""
2416         "                        }"
2417         "                    ]"
2418         "                }"
2419         "            ]"
2420         "        }"
2421         "    ]"
2422         "}";
2423 
2424     // Class order is the insert order
2425     testPrecedence(config, "192.0.2.2");
2426 }
2427 
2428 // Verify option processing precedence
2429 // Order is global < class < shared-network < subnet < pool < host reservation
TEST_F(Dhcpv4SharedNetworkTest,precedenceNetwork)2430 TEST_F(Dhcpv4SharedNetworkTest, precedenceNetwork) {
2431     const std::string config =
2432         "{"
2433         "    \"interfaces-config\": {"
2434         "        \"interfaces\": [ \"*\" ]"
2435         "    },"
2436         "    \"valid-lifetime\": 600,"
2437         "    \"option-data\": ["
2438         "        {"
2439         "           \"name\": \"domain-name-servers\","
2440         "           \"data\": \"192.0.2.1\""
2441         "        }"
2442         "    ],"
2443         "    \"client-classes\": ["
2444         "        {"
2445         "            \"name\": \"alpha\","
2446         "            \"test\": \"'' == ''\","
2447         "            \"option-data\": ["
2448         "                {"
2449         "                   \"name\": \"domain-name-servers\","
2450         "                   \"data\": \"192.0.2.2\""
2451         "                }"
2452         "            ]"
2453         "        }"
2454         "    ],"
2455         "    \"shared-networks\": ["
2456         "        {"
2457         "            \"name\": \"frog\","
2458         "            \"interface\": \"eth1\","
2459         "            \"option-data\": ["
2460         "                {"
2461         "                   \"name\": \"domain-name-servers\","
2462         "                   \"data\": \"192.0.2.3\""
2463         "                }"
2464         "            ],"
2465         "            \"subnet4\": ["
2466         "                {"
2467         "                    \"subnet\": \"192.0.2.0/26\","
2468         "                    \"id\": 10,"
2469         "                    \"pools\": ["
2470         "                        {"
2471         "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
2472         "                        }"
2473         "                    ],"
2474         "                    \"reservations\": ["
2475         "                        {"
2476         "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
2477         "                            \"ip-address\": \"192.0.2.28\""
2478         "                        }"
2479         "                    ]"
2480         "                }"
2481         "            ]"
2482         "        }"
2483         "    ]"
2484         "}";
2485 
2486     testPrecedence(config, "192.0.2.3");
2487 }
2488 
2489 // Verify option processing precedence
2490 // Order is global < class < shared-network < subnet < pool < host reservation
TEST_F(Dhcpv4SharedNetworkTest,precedenceSubnet)2491 TEST_F(Dhcpv4SharedNetworkTest, precedenceSubnet) {
2492     const std::string config =
2493         "{"
2494         "    \"interfaces-config\": {"
2495         "        \"interfaces\": [ \"*\" ]"
2496         "    },"
2497         "    \"valid-lifetime\": 600,"
2498         "    \"option-data\": ["
2499         "        {"
2500         "           \"name\": \"domain-name-servers\","
2501         "           \"data\": \"192.0.2.1\""
2502         "        }"
2503         "    ],"
2504         "    \"client-classes\": ["
2505         "        {"
2506         "            \"name\": \"alpha\","
2507         "            \"test\": \"'' == ''\","
2508         "            \"option-data\": ["
2509         "                {"
2510         "                   \"name\": \"domain-name-servers\","
2511         "                   \"data\": \"192.0.2.2\""
2512         "                }"
2513         "            ]"
2514         "        }"
2515         "    ],"
2516         "    \"shared-networks\": ["
2517         "        {"
2518         "            \"name\": \"frog\","
2519         "            \"interface\": \"eth1\","
2520         "            \"option-data\": ["
2521         "                {"
2522         "                   \"name\": \"domain-name-servers\","
2523         "                   \"data\": \"192.0.2.3\""
2524         "                }"
2525         "            ],"
2526         "            \"subnet4\": ["
2527         "                {"
2528         "                    \"subnet\": \"192.0.2.0/26\","
2529         "                    \"id\": 10,"
2530         "                    \"option-data\": ["
2531         "                        {"
2532         "                           \"name\": \"domain-name-servers\","
2533         "                           \"data\": \"192.0.2.4\""
2534         "                        }"
2535         "                    ],"
2536         "                    \"pools\": ["
2537         "                        {"
2538         "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
2539         "                        }"
2540         "                    ],"
2541         "                    \"reservations\": ["
2542         "                        {"
2543         "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
2544         "                            \"ip-address\": \"192.0.2.28\""
2545         "                        }"
2546         "                    ]"
2547         "                }"
2548         "            ]"
2549         "        }"
2550         "    ]"
2551         "}";
2552 
2553     testPrecedence(config, "192.0.2.4");
2554 }
2555 
2556 // Verify option processing precedence
2557 // Order is global < class < shared-network < subnet < pool < host reservation
TEST_F(Dhcpv4SharedNetworkTest,precedencePool)2558 TEST_F(Dhcpv4SharedNetworkTest, precedencePool) {
2559     const std::string config =
2560         "{"
2561         "    \"interfaces-config\": {"
2562         "        \"interfaces\": [ \"*\" ]"
2563         "    },"
2564         "    \"valid-lifetime\": 600,"
2565         "    \"option-data\": ["
2566         "        {"
2567         "           \"name\": \"domain-name-servers\","
2568         "           \"data\": \"192.0.2.1\""
2569         "        }"
2570         "    ],"
2571         "    \"client-classes\": ["
2572         "        {"
2573         "            \"name\": \"alpha\","
2574         "            \"test\": \"'' == ''\","
2575         "            \"option-data\": ["
2576         "                {"
2577         "                   \"name\": \"domain-name-servers\","
2578         "                   \"data\": \"192.0.2.2\""
2579         "                }"
2580         "            ]"
2581         "        }"
2582         "    ],"
2583         "    \"shared-networks\": ["
2584         "        {"
2585         "            \"name\": \"frog\","
2586         "            \"interface\": \"eth1\","
2587         "            \"option-data\": ["
2588         "                {"
2589         "                   \"name\": \"domain-name-servers\","
2590         "                   \"data\": \"192.0.2.3\""
2591         "                }"
2592         "            ],"
2593         "            \"subnet4\": ["
2594         "                {"
2595         "                    \"subnet\": \"192.0.2.0/26\","
2596         "                    \"id\": 10,"
2597         "                    \"option-data\": ["
2598         "                        {"
2599         "                           \"name\": \"domain-name-servers\","
2600         "                           \"data\": \"192.0.2.4\""
2601         "                        }"
2602         "                    ],"
2603         "                    \"pools\": ["
2604         "                        {"
2605         "                            \"pool\": \"192.0.2.1 - 192.0.2.63\","
2606         "                            \"option-data\": ["
2607         "                                {"
2608         "                                   \"name\": \"domain-name-servers\","
2609         "                                   \"data\": \"192.0.2.5\""
2610         "                                }"
2611         "                            ]"
2612         "                        }"
2613         "                    ],"
2614         "                    \"reservations\": ["
2615         "                        {"
2616         "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
2617         "                            \"ip-address\": \"192.0.2.28\""
2618         "                        }"
2619         "                    ]"
2620         "                }"
2621         "            ]"
2622         "        }"
2623         "    ]"
2624         "}";
2625 
2626     testPrecedence(config, "192.0.2.5");
2627 }
2628 
2629 // Verify option processing precedence
2630 // Order is global < class < shared-network < subnet < pool < host reservation
TEST_F(Dhcpv4SharedNetworkTest,precedenceReservation)2631 TEST_F(Dhcpv4SharedNetworkTest, precedenceReservation) {
2632     const std::string config =
2633         "{"
2634         "    \"interfaces-config\": {"
2635         "        \"interfaces\": [ \"*\" ]"
2636         "    },"
2637         "    \"valid-lifetime\": 600,"
2638         "    \"option-data\": ["
2639         "        {"
2640         "           \"name\": \"domain-name-servers\","
2641         "           \"data\": \"192.0.2.1\""
2642         "        }"
2643         "    ],"
2644         "    \"client-classes\": ["
2645         "        {"
2646         "            \"name\": \"alpha\","
2647         "            \"test\": \"'' == ''\","
2648         "            \"option-data\": ["
2649         "                {"
2650         "                   \"name\": \"domain-name-servers\","
2651         "                   \"data\": \"192.0.2.2\""
2652         "                }"
2653         "            ]"
2654         "        }"
2655         "    ],"
2656         "    \"shared-networks\": ["
2657         "        {"
2658         "            \"name\": \"frog\","
2659         "            \"interface\": \"eth1\","
2660         "            \"option-data\": ["
2661         "                {"
2662         "                   \"name\": \"domain-name-servers\","
2663         "                   \"data\": \"192.0.2.3\""
2664         "                }"
2665         "            ],"
2666         "            \"subnet4\": ["
2667         "                {"
2668         "                    \"subnet\": \"192.0.2.0/26\","
2669         "                    \"id\": 10,"
2670         "                    \"option-data\": ["
2671         "                        {"
2672         "                           \"name\": \"domain-name-servers\","
2673         "                           \"data\": \"192.0.2.4\""
2674         "                        }"
2675         "                    ],"
2676         "                    \"pools\": ["
2677         "                        {"
2678         "                            \"pool\": \"192.0.2.1 - 192.0.2.63\","
2679         "                            \"option-data\": ["
2680         "                                {"
2681         "                                   \"name\": \"domain-name-servers\","
2682         "                                   \"data\": \"192.0.2.5\""
2683         "                                }"
2684         "                            ]"
2685         "                        }"
2686         "                    ],"
2687         "                    \"reservations\": ["
2688         "                        {"
2689         "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
2690         "                            \"ip-address\": \"192.0.2.28\","
2691         "                            \"option-data\": ["
2692         "                                {"
2693         "                                   \"name\": \"domain-name-servers\","
2694         "                                   \"data\": \"192.0.2.6\""
2695         "                                }"
2696         "                            ]"
2697         "                        }"
2698         "                    ]"
2699         "                }"
2700         "            ]"
2701         "        }"
2702         "    ]"
2703         "}";
2704 
2705     testPrecedence(config, "192.0.2.6");
2706 }
2707 
2708 // Verify authoritative sanitization.
2709 // This test generates many similar configs. They all have similar
2710 // structure:
2711 // - 1 shared-network (with possibly authoritative flag in it)
2712 //   2 subnets (with each possibly having its own authoritative flag)
2713 //
2714 // If the flag is specified on subnet-level, the value must match those
2715 // specified on subnet level.
TEST_F(Dhcpv4SharedNetworkTest,authoritative)2716 TEST_F(Dhcpv4SharedNetworkTest, authoritative) {
2717 
2718     // Each scenario will be defined using those parameters.
2719     typedef struct scenario {
2720         bool exp_success;
2721         AuthoritativeFlag global;
2722         AuthoritativeFlag subnet1;
2723         AuthoritativeFlag subnet2;
2724     } scenario;
2725 
2726     // We have the following scenarios. The default is no.
2727     // The only allowed combinations are those that end up with
2728     // having all three values (global, subnet1, subnet2) agree.
2729     scenario scenarios[] = {
2730         //result, global,      subnet1,      subnet2
2731         { true, AUTH_DEFAULT,  AUTH_DEFAULT, AUTH_DEFAULT },
2732         { true, AUTH_YES,      AUTH_DEFAULT, AUTH_DEFAULT },
2733         { true, AUTH_YES,      AUTH_YES,     AUTH_YES },
2734         { true, AUTH_NO,       AUTH_DEFAULT, AUTH_DEFAULT },
2735         { true, AUTH_NO,       AUTH_NO,      AUTH_NO },
2736         { false, AUTH_DEFAULT, AUTH_YES,     AUTH_NO },
2737         { false, AUTH_DEFAULT, AUTH_NO,      AUTH_YES },
2738         { false, AUTH_DEFAULT, AUTH_YES,     AUTH_YES },
2739         { false, AUTH_YES,     AUTH_NO,      AUTH_NO },
2740         { false, AUTH_YES,     AUTH_DEFAULT, AUTH_NO },
2741         { false, AUTH_YES,     AUTH_NO,      AUTH_DEFAULT },
2742         { false, AUTH_YES,     AUTH_NO,      AUTH_NO },
2743         { false, AUTH_YES,     AUTH_NO,      AUTH_YES },
2744         { false, AUTH_YES,     AUTH_YES,     AUTH_NO }
2745     };
2746 
2747     // Let's test them one by one
2748     int cnt = 0;
2749     for ( auto s : scenarios) {
2750         cnt++;
2751 
2752         string cfg = generateAuthConfig(s.global, s.subnet1, s.subnet2);
2753 
2754         // Create client and set MAC address to the one that has a reservation.
2755         Dhcp4Client client(Dhcp4Client::SELECTING);
2756 
2757         stringstream tmp;
2758         tmp << "Testing scenario " << cnt;
2759         SCOPED_TRACE(tmp.str());
2760         // Create server configuration.
2761         auto result = configureWithStatus(cfg, *client.getServer(), true, s.exp_success? 0 : 1);
2762         if (s.exp_success) {
2763             EXPECT_EQ(result.first, 0) << result.second;
2764         } else {
2765             EXPECT_EQ(result.first, 1) << "Configuration expected to fail, but succeeded";
2766         }
2767     }
2768 }
2769 
2770 } // end of anonymous namespace
2771