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