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 <exceptions/exceptions.h>
9 #include <dhcp/option_string.h>
10 #include <dhcpsrv/cfg_shared_networks.h>
11 #include <testutils/test_to_element.h>
12 #include <asiolink/io_address.h>
13 #include <gtest/gtest.h>
14 
15 using namespace isc;
16 using namespace isc::dhcp;
17 using namespace asiolink;
18 
19 namespace {
20 
21 /// @brief Attempts to verify an expected network within a collection
22 /// of networks
23 ///
24 /// @param networks set of networks in which to look
25 /// @param name name of the expected network
26 /// @param exp_valid expected valid lifetime of the network
27 /// @param exp_subnets list of subnet IDs the network is expected to own
checkMergedNetwork(const CfgSharedNetworks4 & networks,const std::string & name,const Triplet<uint32_t> & exp_valid,const std::vector<SubnetID> & exp_subnets)28 void checkMergedNetwork(const CfgSharedNetworks4& networks, const std::string& name,
29                        const Triplet<uint32_t>& exp_valid,
30                        const std::vector<SubnetID>& exp_subnets) {
31     auto network = networks.getByName(name);
32     ASSERT_TRUE(network) << "expected network: " << name << " not found";
33     ASSERT_EQ(exp_valid, network->getValid()) << " network valid lifetime wrong";
34     const Subnet4Collection* subnets = network->getAllSubnets();
35     ASSERT_EQ(exp_subnets.size(), subnets->size()) << " wrong number of subnets";
36     for (auto exp_id : exp_subnets) {
37         ASSERT_TRUE(network->getSubnet(exp_id))
38                     << " did not find expected subnet: " << exp_id;
39     }
40 }
41 
42 // This test verifies that shared networks can be added to the configruation
43 // and retrieved by name.
TEST(CfgSharedNetworks4Test,getByName)44 TEST(CfgSharedNetworks4Test, getByName) {
45     SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
46     SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
47 
48     CfgSharedNetworks4 cfg;
49     ASSERT_NO_THROW(cfg.add(network1));
50     ASSERT_NO_THROW(cfg.add(network2));
51 
52     SharedNetwork4Ptr returned_network1 = cfg.getByName("frog");
53     ASSERT_TRUE(returned_network1);
54     SharedNetwork4Ptr returned_network2 = cfg.getByName("dog");
55     ASSERT_TRUE(returned_network2);
56 
57     // Check that non-existent name does not return bogus data.
58     EXPECT_FALSE(cfg.getByName("ant"));
59 }
60 
61 // This test verifies that it is possible to delete a network.
TEST(CfgSharedNetworks4Test,deleteByName)62 TEST(CfgSharedNetworks4Test, deleteByName) {
63     SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
64     SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
65 
66     // Add two networks to the configuration.
67     CfgSharedNetworks4 cfg;
68     ASSERT_NO_THROW(cfg.add(network1));
69     ASSERT_NO_THROW(cfg.add(network2));
70 
71     // Try to delete non-existing network. This should throw.
72     ASSERT_THROW(cfg.del("lion"), BadValue);
73 
74     // Delete network #1.
75     ASSERT_NO_THROW(cfg.del(network1->getName()));
76     ASSERT_FALSE(cfg.getByName(network1->getName()));
77     ASSERT_TRUE(cfg.getByName(network2->getName()));
78 
79     // Delete network #2.
80     ASSERT_NO_THROW(cfg.del(network2->getName()));
81     ASSERT_FALSE(cfg.getByName(network1->getName()));
82     ASSERT_FALSE(cfg.getByName(network2->getName()));
83 
84     // Check that attempting to delete the same subnet twice will fail.
85     ASSERT_THROW(cfg.del(network1->getName()), BadValue);
86     ASSERT_THROW(cfg.del(network2->getName()), BadValue);
87 }
88 
89 // Checks that subnets have their shared network pointers updated when
90 // the network is deleted. This is used when the shared network is deleted
91 // by admin commands.
TEST(CfgSharedNetworks4Test,deleteNetworkWithSubnets)92 TEST(CfgSharedNetworks4Test, deleteNetworkWithSubnets) {
93     CfgSharedNetworks4 cfg;
94     SharedNetwork4Ptr network(new SharedNetwork4("frog"));
95     SubnetID id1(100);
96     SubnetID id2(101);
97     Subnet4Ptr sub1(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, id1));
98     Subnet4Ptr sub2(new Subnet4(IOAddress("192.0.3.0"), 24, 1, 2, 3, id2));
99     network->add(sub1);
100     network->add(sub2);
101     cfg.add(network);
102 
103     // Make sure the subnets are part of the network.
104     SharedNetwork4Ptr test;
105     sub1->getSharedNetwork(test);
106     EXPECT_TRUE(test);
107     EXPECT_EQ(network->toElement()->str(), test->toElement()->str());
108     sub2->getSharedNetwork(test);
109     EXPECT_TRUE(test);
110     EXPECT_EQ(network->toElement()->str(), test->toElement()->str());
111 
112     // Now remove the network. Subnets should be disassociated with the network.
113     cfg.del("frog");
114     sub1->getSharedNetwork(test);
115     EXPECT_FALSE(test);
116     sub2->getSharedNetwork(test);
117     EXPECT_FALSE(test);
118 }
119 
120 // This test verifies that it is possible to delete a shared network by
121 // its database identifier.
TEST(CfgSharedNetworks4Test,deleteNetworksById)122 TEST(CfgSharedNetworks4Test, deleteNetworksById) {
123     // Create three shared networks.
124     CfgSharedNetworks4 cfg;
125     SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
126     SharedNetwork4Ptr network2(new SharedNetwork4("whale"));
127     SharedNetwork4Ptr network3(new SharedNetwork4("fly"));
128 
129     // Add one subnet to each shared network.
130     Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 1));
131     Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.3.0"), 24, 1, 2, 3, 2));
132     Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.4.0"), 24, 1, 2, 3, 3));
133 
134     network1->add(subnet1);
135     network2->add(subnet2);
136     network3->add(subnet3);
137 
138     // Set unique identifier for the second shared network.
139     network2->setId(123);
140 
141     // Verify that we have two networks with a default identifier and one
142     // with a unique identifier.
143     EXPECT_EQ(0, network1->getId());
144     EXPECT_EQ(123, network2->getId());
145     EXPECT_EQ(0, network3->getId());
146 
147     // Add our networks to the configuration.
148     cfg.add(network1);
149     cfg.add(network2);
150     cfg.add(network3);
151 
152     // Delete second network by id.
153     uint64_t deleted_num = 0;
154     ASSERT_NO_THROW(deleted_num = cfg.del(network2->getId()));
155     EXPECT_EQ(1, deleted_num);
156 
157     // Make sure that the subnet no longer points to the deleted network.
158     SharedNetwork4Ptr returned_network;
159     subnet2->getSharedNetwork(returned_network);
160     EXPECT_FALSE(returned_network);
161     EXPECT_FALSE(cfg.getByName("whale"));
162 
163     // Delete the remaining two shared network using id of 0.
164     ASSERT_NO_THROW(deleted_num = cfg.del(network1->getId()));
165     EXPECT_EQ(2, deleted_num);
166 
167     // The subnets should no longer point to the deleted networks and
168     // the shared networks should no longer exist in the configuration.
169     subnet1->getSharedNetwork(returned_network);
170     EXPECT_FALSE(returned_network);
171     EXPECT_FALSE(cfg.getByName("frog"));
172 
173     subnet3->getSharedNetwork(returned_network);
174     EXPECT_FALSE(returned_network);
175     EXPECT_FALSE(cfg.getByName("fly"));
176 
177     EXPECT_EQ(0, cfg.del(network1->getId()));
178 }
179 
180 // This test verifies that shared networks must have unique names.
TEST(CfgSharedNetworks4Test,duplicateName)181 TEST(CfgSharedNetworks4Test, duplicateName) {
182     SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
183     SharedNetwork4Ptr network2(new SharedNetwork4("frog"));
184 
185     CfgSharedNetworks4 cfg;
186     ASSERT_NO_THROW(cfg.add(network1));
187     ASSERT_THROW(cfg.add(network2), BadValue);
188 }
189 
190 // This test verifies that unparsing shared networks returns valid structure.
TEST(CfgSharedNetworks4Test,unparse)191 TEST(CfgSharedNetworks4Test, unparse) {
192     SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
193     SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
194     SharedNetwork4Ptr network3(new SharedNetwork4("cat"));
195 
196     network1->setIface("eth0");
197     network1->addRelayAddress(IOAddress("198.16.1.1"));
198     network1->addRelayAddress(IOAddress("198.16.1.2"));
199     network1->setCalculateTeeTimes(true);
200     network1->setT1Percent(.35);
201     network1->setT2Percent(.655);
202     network1->setDdnsSendUpdates(true);
203     network1->setDdnsOverrideNoUpdate(true);
204     network1->setDdnsOverrideClientUpdate(true);
205     network1->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
206     network1->setDdnsGeneratedPrefix("prefix");
207     network1->setDdnsQualifyingSuffix("example.com.");
208     network1->setHostnameCharSet("[^A-Z]");
209     network1->setHostnameCharReplacement("x");
210     network1->setCacheThreshold(.20);
211 
212     network2->setIface("eth1");
213     network2->setT1(Triplet<uint32_t>(100));
214     network2->setT2(Triplet<uint32_t>(200));
215     network2->setValid(Triplet<uint32_t>(200, 300, 400));
216     network2->setDdnsSendUpdates(false);
217     network2->setStoreExtendedInfo(true);
218     network2->setCacheMaxAge(50);
219 
220     network3->setIface("eth2");
221     network3->setValid(Triplet<uint32_t>(100));
222 
223     CfgSharedNetworks4 cfg;
224     ASSERT_NO_THROW(cfg.add(network1));
225     ASSERT_NO_THROW(cfg.add(network2));
226     ASSERT_NO_THROW(cfg.add(network3));
227 
228     std::string expected =
229         "[\n"
230         "  {\n"
231         "    \"interface\": \"eth2\",\n"
232         "    \"name\": \"cat\",\n"
233         "    \"option-data\": [ ],\n"
234         "    \"relay\": { \"ip-addresses\": [ ] },\n"
235         "    \"subnet4\": [ ],\n"
236         "    \"valid-lifetime\": 100\n"
237         "  },\n"
238         "  {\n"
239         "    \"ddns-send-updates\": false,\n"
240         "    \"interface\": \"eth1\",\n"
241         "    \"name\": \"dog\",\n"
242         "    \"rebind-timer\": 200,\n"
243         "    \"option-data\": [ ],\n"
244         "    \"renew-timer\": 100,\n"
245         "    \"relay\": { \"ip-addresses\": [ ] },\n"
246         "    \"subnet4\": [ ],\n"
247         "    \"valid-lifetime\": 300,\n"
248         "    \"min-valid-lifetime\": 200,\n"
249         "    \"max-valid-lifetime\": 400,\n"
250         "    \"store-extended-info\": true,\n"
251         "    \"cache-max-age\": 50\n"
252         "  },\n"
253         "  {\n"
254         "    \"calculate-tee-times\": true,\n"
255         "    \"ddns-generated-prefix\": \"prefix\",\n"
256         "    \"ddns-override-no-update\": true,\n"
257         "    \"ddns-override-client-update\": true,\n"
258         "    \"ddns-qualifying-suffix\": \"example.com.\",\n"
259         "    \"ddns-replace-client-name\": \"always\",\n"
260         "    \"ddns-send-updates\": true,\n"
261         "    \"interface\": \"eth0\",\n"
262         "    \"name\": \"frog\",\n"
263         "    \"option-data\": [ ],\n"
264         "    \"relay\": { \"ip-addresses\": [ \"198.16.1.1\", \"198.16.1.2\" ] },\n"
265         "    \"subnet4\": [ ],\n"
266         "    \"t1-percent\": .35,\n"
267         "    \"t2-percent\": .655,\n"
268         "    \"hostname-char-replacement\": \"x\",\n"
269         "    \"hostname-char-set\": \"[^A-Z]\",\n"
270         "    \"cache-threshold\": .20\n"
271         "  }\n"
272         "]\n";
273 
274     test::runToElementTest<CfgSharedNetworks4>(expected, cfg);
275 }
276 
277 // This test verifies that shared-network configurations are properly merged.
TEST(CfgSharedNetworks4Test,mergeNetworks)278 TEST(CfgSharedNetworks4Test, mergeNetworks) {
279     // Create custom options dictionary for testing merge. We're keeping it
280     // simple because they are more rigorous tests elsewhere.
281     CfgOptionDefPtr cfg_def(new CfgOptionDef());
282     cfg_def->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "string"))));
283 
284     Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"),
285                                    26, 1, 2, 100, SubnetID(1)));
286     Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"),
287                                    26, 1, 2, 100, SubnetID(2)));
288     Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"),
289                                    26, 1, 2, 100, SubnetID(3)));
290     Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.4.0"),
291                                    26, 1, 2, 100, SubnetID(4)));
292 
293     // Create network1 and add two subnets to it
294     SharedNetwork4Ptr network1(new SharedNetwork4("network1"));
295     network1->setValid(Triplet<uint32_t>(100));
296     ASSERT_NO_THROW(network1->add(subnet1));
297     ASSERT_NO_THROW(network1->add(subnet2));
298 
299     // Create network2 with no subnets.
300     SharedNetwork4Ptr network2(new SharedNetwork4("network2"));
301     network2->setValid(Triplet<uint32_t>(200));
302 
303     // Create network3 with one subnet.
304     SharedNetwork4Ptr network3(new SharedNetwork4("network3"));
305     network3->setValid(Triplet<uint32_t>(300));
306     ASSERT_NO_THROW(network3->add(subnet3));
307 
308     // Create our "existing" configured networks.
309     // Add all three networks to the existing config.
310     CfgSharedNetworks4 cfg_to;
311     ASSERT_NO_THROW(cfg_to.add(network1));
312     ASSERT_NO_THROW(cfg_to.add(network2));
313     ASSERT_NO_THROW(cfg_to.add(network3));
314 
315     // Merge in an "empty" config. Should have the original config, still intact.
316     CfgSharedNetworks4 cfg_from;
317     ASSERT_NO_THROW(cfg_to.merge(cfg_def, cfg_from));
318 
319     ASSERT_EQ(3, cfg_to.getAll()->size());
320     ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network1", Triplet<uint32_t>(100),
321                                                std::vector<SubnetID>{SubnetID(1), SubnetID(2)}));
322     ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network2", Triplet<uint32_t>(200),
323                                                std::vector<SubnetID>()));
324 
325     ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network3", Triplet<uint32_t>(300),
326                                                std::vector<SubnetID>{SubnetID(3)}));
327 
328     // Create network1b, this is an "update" of network1
329     // We'll double the valid time and add subnet4 to it
330     SharedNetwork4Ptr network1b(new SharedNetwork4("network1"));
331     network1b->setValid(Triplet<uint32_t>(200));
332 
333     // Now let's a add generic option 1 to network1b.
334     std::string value("Yay!");
335     OptionPtr option(new Option(Option::V4, 1));
336     option->setData(value.begin(), value.end());
337     ASSERT_NO_THROW(network1b->getCfgOption()->add(option, false, "isc"));
338     ASSERT_NO_THROW(network1b->add(subnet4));
339 
340     // Network2 we will not touch.
341 
342     // Create network3b, this is an "update" of network3.
343     // We'll double it's valid time, but leave off the subnet.
344     SharedNetwork4Ptr network3b(new SharedNetwork4("network3"));
345     network3b->setValid(Triplet<uint32_t>(600));
346 
347     // Create our "existing" configured networks.
348     ASSERT_NO_THROW(cfg_from.add(network1b));
349     ASSERT_NO_THROW(cfg_from.add(network3b));
350 
351     ASSERT_NO_THROW(cfg_to.merge(cfg_def, cfg_from));
352 
353     // Should still have 3 networks.
354 
355     // Network1 should have doubled its valid lifetime but still only have
356     // the orignal two subnets.  Merge should discard assocations on CB
357     // subnets and preserve the associations from existing config.
358     ASSERT_EQ(3, cfg_to.getAll()->size());
359     ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network1", Triplet<uint32_t>(200),
360                                                std::vector<SubnetID>{SubnetID(1), SubnetID(2)}));
361 
362     // Make sure we have option 1 and that it has been replaced with a string option.
363     auto network = cfg_to.getByName("network1");
364     auto desc = network->getCfgOption()->get("isc", 1);
365     OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
366     ASSERT_TRUE(opstr);
367     EXPECT_EQ("Yay!", opstr->getValue());
368 
369     // No changes to network2.
370     ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network2", Triplet<uint32_t>(200),
371                                                std::vector<SubnetID>()));
372 
373     // Network1 should have doubled its valid lifetime and still subnet3.
374     ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network3", Triplet<uint32_t>(600),
375                                                std::vector<SubnetID>{SubnetID(3)}));
376 }
377 
378 } // end of anonymous namespace
379