config_parser_unittest.cc 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. // Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <config/ccsession.h>
  16. #include <dhcp/libdhcp++.h>
  17. #include <dhcp/option6_ia.h>
  18. #include <dhcp6/config_parser.h>
  19. #include <dhcp6/dhcp6_srv.h>
  20. #include <dhcpsrv/cfgmgr.h>
  21. #include <dhcpsrv/subnet.h>
  22. #include <boost/foreach.hpp>
  23. #include <gtest/gtest.h>
  24. #include <fstream>
  25. #include <iostream>
  26. #include <sstream>
  27. #include <arpa/inet.h>
  28. using namespace std;
  29. using namespace isc;
  30. using namespace isc::dhcp;
  31. using namespace isc::asiolink;
  32. using namespace isc::data;
  33. using namespace isc::config;
  34. namespace {
  35. class Dhcp6ParserTest : public ::testing::Test {
  36. public:
  37. Dhcp6ParserTest() :rcode_(-1), srv_(0) {
  38. // srv_(0) means to not open any sockets. We don't want to
  39. // deal with sockets here, just check if configuration handling
  40. // is sane.
  41. }
  42. ~Dhcp6ParserTest() {
  43. // Reset configuration database after each test.
  44. resetConfiguration();
  45. };
  46. /// @brief Create the simple configuration with single option.
  47. ///
  48. /// This function allows to set one of the parameters that configure
  49. /// option value. These parameters are: "name", "code" and "data".
  50. ///
  51. /// @param param_value string holiding option parameter value to be
  52. /// injected into the configuration string.
  53. /// @param parameter name of the parameter to be configured with
  54. /// param value.
  55. std::string createConfigWithOption(const std::string& param_value,
  56. const std::string& parameter) {
  57. std::map<std::string, std::string> params;
  58. if (parameter == "name") {
  59. params["name"] = param_value;
  60. params["code"] = "80";
  61. params["data"] = "AB CDEF0105";
  62. params["csv-format"] = "False";
  63. } else if (parameter == "code") {
  64. params["name"] = "option_foo";
  65. params["code"] = param_value;
  66. params["data"] = "AB CDEF0105";
  67. params["csv-format"] = "False";
  68. } else if (parameter == "data") {
  69. params["name"] = "option_foo";
  70. params["code"] = "80";
  71. params["data"] = param_value;
  72. params["csv-format"] = "False";
  73. } else if (parameter == "csv-format") {
  74. params["name"] = "option_foo";
  75. params["code"] = "80";
  76. params["data"] = "AB CDEF0105";
  77. params["csv-format"] = param_value;
  78. }
  79. return (createConfigWithOption(params));
  80. }
  81. std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
  82. std::ostringstream stream;
  83. stream << "{ \"interface\": [ \"all\" ],"
  84. "\"preferred-lifetime\": 3000,"
  85. "\"rebind-timer\": 2000, "
  86. "\"renew-timer\": 1000, "
  87. "\"subnet6\": [ { "
  88. " \"pool\": [ \"2001:db8:1::/80\" ],"
  89. " \"subnet\": \"2001:db8:1::/64\", "
  90. " \"option-data\": [ {";
  91. bool first = true;
  92. typedef std::pair<std::string, std::string> ParamPair;
  93. BOOST_FOREACH(ParamPair param, params) {
  94. if (!first) {
  95. stream << ", ";
  96. } else {
  97. // cppcheck-suppress unreadVariable
  98. first = false;
  99. }
  100. if (param.first == "name") {
  101. stream << "\"name\": \"" << param.second << "\"";
  102. } else if (param.first == "code") {
  103. stream << "\"code\": " << param.second;;
  104. } else if (param.first == "data") {
  105. stream << "\"data\": \"" << param.second << "\"";
  106. } else if (param.first == "csv-format") {
  107. stream << "\"csv-format\": " << param.second;
  108. }
  109. }
  110. stream <<
  111. " } ]"
  112. " } ],"
  113. "\"valid-lifetime\": 4000 }";
  114. return (stream.str());
  115. }
  116. /// @brief Reset configuration database.
  117. ///
  118. /// This function resets configuration data base by
  119. /// removing all subnets and option-data. Reset must
  120. /// be performed after each test to make sure that
  121. /// contents of the database do not affect result of
  122. /// subsequent tests.
  123. void resetConfiguration() {
  124. ConstElementPtr status;
  125. string config = "{ \"interface\": [ \"all\" ],"
  126. "\"preferred-lifetime\": 3000,"
  127. "\"rebind-timer\": 2000, "
  128. "\"renew-timer\": 1000, "
  129. "\"valid-lifetime\": 4000, "
  130. "\"subnet6\": [ ], "
  131. "\"option-data\": [ ] }";
  132. try {
  133. ElementPtr json = Element::fromJSON(config);
  134. status = configureDhcp6Server(srv_, json);
  135. } catch (const std::exception& ex) {
  136. FAIL() << "Fatal error: unable to reset configuration database"
  137. << " after the test. The following configuration was used"
  138. << " to reset database: " << std::endl
  139. << config << std::endl
  140. << " and the following error message was returned:"
  141. << ex.what() << std::endl;
  142. }
  143. // status object must not be NULL
  144. if (!status) {
  145. FAIL() << "Fatal error: unable to reset configuration database"
  146. << " after the test. Configuration function returned"
  147. << " NULL pointer" << std::endl;
  148. }
  149. comment_ = parseAnswer(rcode_, status);
  150. // returned value should be 0 (configuration success)
  151. if (rcode_ != 0) {
  152. FAIL() << "Fatal error: unable to reset configuration database"
  153. << " after the test. Configuration function returned"
  154. << " error code " << rcode_ << std::endl;
  155. }
  156. }
  157. /// @brief Test invalid option parameter value.
  158. ///
  159. /// This test function constructs the simple configuration
  160. /// string and injects invalid option configuration into it.
  161. /// It expects that parser will fail with provided option code.
  162. ///
  163. /// @param param_value string holding invalid option parameter value
  164. /// to be injected into configuration string.
  165. /// @param parameter name of the parameter to be configured with
  166. /// param_value (can be any of "name", "code", "data")
  167. void testInvalidOptionParam(const std::string& param_value,
  168. const std::string& parameter) {
  169. ConstElementPtr x;
  170. std::string config = createConfigWithOption(param_value, parameter);
  171. ElementPtr json = Element::fromJSON(config);
  172. EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
  173. ASSERT_TRUE(x);
  174. comment_ = parseAnswer(rcode_, x);
  175. ASSERT_EQ(1, rcode_);
  176. }
  177. /// @brief Test option against given code and data.
  178. ///
  179. /// @param option_desc option descriptor that carries the option to
  180. /// be tested.
  181. /// @param expected_code expected code of the option.
  182. /// @param expected_data expected data in the option.
  183. /// @param expected_data_len length of the reference data.
  184. /// @param extra_data if true extra data is allowed in an option
  185. /// after tested data.
  186. void testOption(const Subnet::OptionDescriptor& option_desc,
  187. uint16_t expected_code, const uint8_t* expected_data,
  188. size_t expected_data_len,
  189. bool extra_data = false) {
  190. // Check if option descriptor contains valid option pointer.
  191. ASSERT_TRUE(option_desc.option);
  192. // Verify option type.
  193. EXPECT_EQ(expected_code, option_desc.option->getType());
  194. // We may have many different option types being created. Some of them
  195. // have dedicated classes derived from Option class. In such case if
  196. // we want to verify the option contents against expected_data we have
  197. // to prepare raw buffer with the contents of the option. The easiest
  198. // way is to call pack() which will prepare on-wire data.
  199. util::OutputBuffer buf(option_desc.option->getData().size());
  200. option_desc.option->pack(buf);
  201. if (extra_data) {
  202. // The length of the buffer must be at least equal to size of the
  203. // reference data but it can sometimes be greater than that. This is
  204. // because some options carry suboptions that increase the overall
  205. // length.
  206. ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
  207. expected_data_len);
  208. } else {
  209. ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
  210. expected_data_len);
  211. }
  212. // Verify that the data is correct. Do not verify suboptions and a header.
  213. const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
  214. EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
  215. expected_data_len));
  216. }
  217. Dhcpv6Srv srv_;
  218. int rcode_;
  219. ConstElementPtr comment_;
  220. };
  221. // Goal of this test is a verification if a very simple config update
  222. // with just a bumped version number. That's the simplest possible
  223. // config update.
  224. TEST_F(Dhcp6ParserTest, version) {
  225. ConstElementPtr x;
  226. EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
  227. Element::fromJSON("{\"version\": 0}")));
  228. // returned value must be 0 (configuration accepted)
  229. ASSERT_TRUE(x);
  230. comment_ = parseAnswer(rcode_, x);
  231. EXPECT_EQ(0, rcode_);
  232. }
  233. /// The goal of this test is to verify that the code accepts only
  234. /// valid commands and malformed or unsupported parameters are rejected.
  235. TEST_F(Dhcp6ParserTest, bogusCommand) {
  236. ConstElementPtr x;
  237. EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
  238. Element::fromJSON("{\"bogus\": 5}")));
  239. // returned value must be 1 (configuration parse error)
  240. ASSERT_TRUE(x);
  241. comment_ = parseAnswer(rcode_, x);
  242. EXPECT_EQ(1, rcode_);
  243. }
  244. /// The goal of this test is to verify if wrongly defined subnet will
  245. /// be rejected. Properly defined subnet must include at least one
  246. /// pool definition.
  247. TEST_F(Dhcp6ParserTest, emptySubnet) {
  248. ConstElementPtr status;
  249. EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
  250. Element::fromJSON("{ \"interface\": [ \"all\" ],"
  251. "\"preferred-lifetime\": 3000,"
  252. "\"rebind-timer\": 2000, "
  253. "\"renew-timer\": 1000, "
  254. "\"subnet6\": [ ], "
  255. "\"valid-lifetime\": 4000 }")));
  256. // returned value should be 0 (success)
  257. ASSERT_TRUE(status);
  258. comment_ = parseAnswer(rcode_, status);
  259. EXPECT_EQ(0, rcode_);
  260. }
  261. /// The goal of this test is to verify if defined subnet uses global
  262. /// parameter timer definitions.
  263. TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
  264. ConstElementPtr status;
  265. string config = "{ \"interface\": [ \"all\" ],"
  266. "\"preferred-lifetime\": 3000,"
  267. "\"rebind-timer\": 2000, "
  268. "\"renew-timer\": 1000, "
  269. "\"subnet6\": [ { "
  270. " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
  271. " \"subnet\": \"2001:db8:1::/64\" } ],"
  272. "\"valid-lifetime\": 4000 }";
  273. cout << config << endl;
  274. ElementPtr json = Element::fromJSON(config);
  275. EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
  276. // check if returned status is OK
  277. ASSERT_TRUE(status);
  278. comment_ = parseAnswer(rcode_, status);
  279. EXPECT_EQ(0, rcode_);
  280. // Now check if the configuration was indeed handled and we have
  281. // expected pool configured.
  282. Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
  283. ASSERT_TRUE(subnet);
  284. EXPECT_EQ(1000, subnet->getT1());
  285. EXPECT_EQ(2000, subnet->getT2());
  286. EXPECT_EQ(3000, subnet->getPreferred());
  287. EXPECT_EQ(4000, subnet->getValid());
  288. }
  289. // This test checks if it is possible to override global values
  290. // on a per subnet basis.
  291. TEST_F(Dhcp6ParserTest, subnetLocal) {
  292. ConstElementPtr status;
  293. string config = "{ \"interface\": [ \"all\" ],"
  294. "\"preferred-lifetime\": 3000,"
  295. "\"rebind-timer\": 2000, "
  296. "\"renew-timer\": 1000, "
  297. "\"subnet6\": [ { "
  298. " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
  299. " \"renew-timer\": 1, "
  300. " \"rebind-timer\": 2, "
  301. " \"preferred-lifetime\": 3,"
  302. " \"valid-lifetime\": 4,"
  303. " \"subnet\": \"2001:db8:1::/64\" } ],"
  304. "\"valid-lifetime\": 4000 }";
  305. cout << config << endl;
  306. ElementPtr json = Element::fromJSON(config);
  307. EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
  308. // returned value should be 0 (configuration success)
  309. ASSERT_TRUE(status);
  310. comment_ = parseAnswer(rcode_, status);
  311. EXPECT_EQ(0, rcode_);
  312. Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
  313. ASSERT_TRUE(subnet);
  314. EXPECT_EQ(1, subnet->getT1());
  315. EXPECT_EQ(2, subnet->getT2());
  316. EXPECT_EQ(3, subnet->getPreferred());
  317. EXPECT_EQ(4, subnet->getValid());
  318. }
  319. // Test verifies that a subnet with pool values that do not belong to that
  320. // pool are rejected.
  321. TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
  322. ConstElementPtr status;
  323. string config = "{ \"interface\": [ \"all\" ],"
  324. "\"preferred-lifetime\": 3000,"
  325. "\"rebind-timer\": 2000, "
  326. "\"renew-timer\": 1000, "
  327. "\"subnet6\": [ { "
  328. " \"pool\": [ \"4001:db8:1::/80\" ],"
  329. " \"subnet\": \"2001:db8:1::/64\" } ],"
  330. "\"valid-lifetime\": 4000 }";
  331. cout << config << endl;
  332. ElementPtr json = Element::fromJSON(config);
  333. EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
  334. // returned value must be 2 (values error)
  335. // as the pool does not belong to that subnet
  336. ASSERT_TRUE(status);
  337. comment_ = parseAnswer(rcode_, status);
  338. EXPECT_EQ(2, rcode_);
  339. }
  340. // Goal of this test is to verify if pools can be defined
  341. // using prefix/length notation. There is no separate test for min-max
  342. // notation as it was tested in several previous tests.
  343. TEST_F(Dhcp6ParserTest, poolPrefixLen) {
  344. ConstElementPtr x;
  345. string config = "{ \"interface\": [ \"all\" ],"
  346. "\"preferred-lifetime\": 3000,"
  347. "\"rebind-timer\": 2000, "
  348. "\"renew-timer\": 1000, "
  349. "\"subnet6\": [ { "
  350. " \"pool\": [ \"2001:db8:1::/80\" ],"
  351. " \"subnet\": \"2001:db8:1::/64\" } ],"
  352. "\"valid-lifetime\": 4000 }";
  353. cout << config << endl;
  354. ElementPtr json = Element::fromJSON(config);
  355. EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
  356. // returned value must be 1 (configuration parse error)
  357. ASSERT_TRUE(x);
  358. comment_ = parseAnswer(rcode_, x);
  359. EXPECT_EQ(0, rcode_);
  360. Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
  361. ASSERT_TRUE(subnet);
  362. EXPECT_EQ(1000, subnet->getT1());
  363. EXPECT_EQ(2000, subnet->getT2());
  364. EXPECT_EQ(3000, subnet->getPreferred());
  365. EXPECT_EQ(4000, subnet->getValid());
  366. }
  367. // Goal of this test is to verify that global option
  368. // data is configured for the subnet if the subnet
  369. // configuration does not include options configuration.
  370. TEST_F(Dhcp6ParserTest, optionDataDefaults) {
  371. ConstElementPtr x;
  372. string config = "{ \"interface\": [ \"all\" ],"
  373. "\"preferred-lifetime\": 3000,"
  374. "\"rebind-timer\": 2000,"
  375. "\"renew-timer\": 1000,"
  376. "\"option-data\": [ {"
  377. " \"name\": \"option_foo\","
  378. " \"code\": 100,"
  379. " \"data\": \"AB CDEF0105\","
  380. " \"csv-format\": False"
  381. " },"
  382. " {"
  383. " \"name\": \"option_foo2\","
  384. " \"code\": 101,"
  385. " \"data\": \"01\","
  386. " \"csv-format\": False"
  387. " } ],"
  388. "\"subnet6\": [ { "
  389. " \"pool\": [ \"2001:db8:1::/80\" ],"
  390. " \"subnet\": \"2001:db8:1::/64\""
  391. " } ],"
  392. "\"valid-lifetime\": 4000 }";
  393. ElementPtr json = Element::fromJSON(config);
  394. EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
  395. ASSERT_TRUE(x);
  396. comment_ = parseAnswer(rcode_, x);
  397. ASSERT_EQ(0, rcode_);
  398. Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
  399. ASSERT_TRUE(subnet);
  400. const Subnet::OptionContainer& options = subnet->getOptions();
  401. ASSERT_EQ(2, options.size());
  402. // Get the search index. Index #1 is to search using option code.
  403. const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
  404. // Get the options for specified index. Expecting one option to be
  405. // returned but in theory we may have multiple options with the same
  406. // code so we get the range.
  407. std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
  408. Subnet::OptionContainerTypeIndex::const_iterator> range =
  409. idx.equal_range(100);
  410. // Expect single option with the code equal to 100.
  411. ASSERT_EQ(1, std::distance(range.first, range.second));
  412. const uint8_t foo_expected[] = {
  413. 0xAB, 0xCD, 0xEF, 0x01, 0x05
  414. };
  415. // Check if option is valid in terms of code and carried data.
  416. testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
  417. range = idx.equal_range(101);
  418. ASSERT_EQ(1, std::distance(range.first, range.second));
  419. // Do another round of testing with second option.
  420. const uint8_t foo2_expected[] = {
  421. 0x01
  422. };
  423. testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
  424. // Check that options with other option codes are not returned.
  425. for (uint16_t code = 102; code < 110; ++code) {
  426. range = idx.equal_range(code);
  427. EXPECT_EQ(0, std::distance(range.first, range.second));
  428. }
  429. }
  430. // Goal of this test is to verify options configuration
  431. // for a single subnet. In particular this test checks
  432. // that local options configuration overrides global
  433. // option setting.
  434. TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
  435. ConstElementPtr x;
  436. string config = "{ \"interface\": [ \"all\" ],"
  437. "\"preferred-lifetime\": 3000,"
  438. "\"rebind-timer\": 2000, "
  439. "\"renew-timer\": 1000, "
  440. "\"option-data\": [ {"
  441. " \"name\": \"option_foo\","
  442. " \"code\": 100,"
  443. " \"data\": \"AB\","
  444. " \"csv-format\": False"
  445. " } ],"
  446. "\"subnet6\": [ { "
  447. " \"pool\": [ \"2001:db8:1::/80\" ],"
  448. " \"subnet\": \"2001:db8:1::/64\", "
  449. " \"option-data\": [ {"
  450. " \"name\": \"option_foo\","
  451. " \"code\": 100,"
  452. " \"data\": \"AB CDEF0105\","
  453. " \"csv-format\": False"
  454. " },"
  455. " {"
  456. " \"name\": \"option_foo2\","
  457. " \"code\": 101,"
  458. " \"data\": \"01\","
  459. " \"csv-format\": False"
  460. " } ]"
  461. " } ],"
  462. "\"valid-lifetime\": 4000 }";
  463. ElementPtr json = Element::fromJSON(config);
  464. EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
  465. ASSERT_TRUE(x);
  466. comment_ = parseAnswer(rcode_, x);
  467. ASSERT_EQ(0, rcode_);
  468. Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
  469. ASSERT_TRUE(subnet);
  470. const Subnet::OptionContainer& options = subnet->getOptions();
  471. ASSERT_EQ(2, options.size());
  472. // Get the search index. Index #1 is to search using option code.
  473. const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
  474. // Get the options for specified index. Expecting one option to be
  475. // returned but in theory we may have multiple options with the same
  476. // code so we get the range.
  477. std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
  478. Subnet::OptionContainerTypeIndex::const_iterator> range =
  479. idx.equal_range(100);
  480. // Expect single option with the code equal to 100.
  481. ASSERT_EQ(1, std::distance(range.first, range.second));
  482. const uint8_t foo_expected[] = {
  483. 0xAB, 0xCD, 0xEF, 0x01, 0x05
  484. };
  485. // Check if option is valid in terms of code and carried data.
  486. testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
  487. range = idx.equal_range(101);
  488. ASSERT_EQ(1, std::distance(range.first, range.second));
  489. // Do another round of testing with second option.
  490. const uint8_t foo2_expected[] = {
  491. 0x01
  492. };
  493. testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
  494. }
  495. // Goal of this test is to verify options configuration
  496. // for multiple subnets.
  497. TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
  498. ConstElementPtr x;
  499. string config = "{ \"interface\": [ \"all\" ],"
  500. "\"preferred-lifetime\": 3000,"
  501. "\"rebind-timer\": 2000, "
  502. "\"renew-timer\": 1000, "
  503. "\"subnet6\": [ { "
  504. " \"pool\": [ \"2001:db8:1::/80\" ],"
  505. " \"subnet\": \"2001:db8:1::/64\", "
  506. " \"option-data\": [ {"
  507. " \"name\": \"option_foo\","
  508. " \"code\": 100,"
  509. " \"data\": \"0102030405060708090A\","
  510. " \"csv-format\": False"
  511. " } ]"
  512. " },"
  513. " {"
  514. " \"pool\": [ \"2001:db8:2::/80\" ],"
  515. " \"subnet\": \"2001:db8:2::/64\", "
  516. " \"option-data\": [ {"
  517. " \"name\": \"option_foo2\","
  518. " \"code\": 101,"
  519. " \"data\": \"FFFEFDFCFB\","
  520. " \"csv-format\": False"
  521. " } ]"
  522. " } ],"
  523. "\"valid-lifetime\": 4000 }";
  524. ElementPtr json = Element::fromJSON(config);
  525. EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
  526. ASSERT_TRUE(x);
  527. comment_ = parseAnswer(rcode_, x);
  528. ASSERT_EQ(0, rcode_);
  529. Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
  530. ASSERT_TRUE(subnet1);
  531. const Subnet::OptionContainer& options1 = subnet1->getOptions();
  532. ASSERT_EQ(1, options1.size());
  533. // Get the search index. Index #1 is to search using option code.
  534. const Subnet::OptionContainerTypeIndex& idx1 = options1.get<1>();
  535. // Get the options for specified index. Expecting one option to be
  536. // returned but in theory we may have multiple options with the same
  537. // code so we get the range.
  538. std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
  539. Subnet::OptionContainerTypeIndex::const_iterator> range1 =
  540. idx1.equal_range(100);
  541. // Expect single option with the code equal to 100.
  542. ASSERT_EQ(1, std::distance(range1.first, range1.second));
  543. const uint8_t foo_expected[] = {
  544. 0x01, 0x02, 0x03, 0x04, 0x05,
  545. 0x06, 0x07, 0x08, 0x09, 0x0A
  546. };
  547. // Check if option is valid in terms of code and carried data.
  548. testOption(*range1.first, 100, foo_expected, sizeof(foo_expected));
  549. // Test another subnet in the same way.
  550. Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
  551. ASSERT_TRUE(subnet2);
  552. const Subnet::OptionContainer& options2 = subnet2->getOptions();
  553. ASSERT_EQ(1, options2.size());
  554. const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
  555. std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
  556. Subnet::OptionContainerTypeIndex::const_iterator> range2 =
  557. idx2.equal_range(101);
  558. ASSERT_EQ(1, std::distance(range2.first, range2.second));
  559. const uint8_t foo2_expected[] = {
  560. 0xFF, 0xFE, 0xFD, 0xFC, 0xFB
  561. };
  562. testOption(*range2.first, 101, foo2_expected, sizeof(foo2_expected));
  563. }
  564. // Verify that empty option name is rejected in the configuration.
  565. TEST_F(Dhcp6ParserTest, optionNameEmpty) {
  566. // Empty option names not allowed.
  567. testInvalidOptionParam("", "name");
  568. }
  569. // Verify that empty option name with spaces is rejected
  570. // in the configuration.
  571. TEST_F(Dhcp6ParserTest, optionNameSpaces) {
  572. // Spaces in option names not allowed.
  573. testInvalidOptionParam("option foo", "name");
  574. }
  575. // Verify that negative option code is rejected in the configuration.
  576. TEST_F(Dhcp6ParserTest, optionCodeNegative) {
  577. // Check negative option code -4. This should fail too.
  578. testInvalidOptionParam("-4", "code");
  579. }
  580. // Verify that out of bounds option code is rejected in the configuration.
  581. TEST_F(Dhcp6ParserTest, optionCodeNonUint16) {
  582. // The valid option codes are uint16_t values so passing
  583. // uint16_t maximum value incremented by 1 should result
  584. // in failure.
  585. testInvalidOptionParam("65536", "code");
  586. }
  587. // Verify that out of bounds option code is rejected in the configuration.
  588. TEST_F(Dhcp6ParserTest, optionCodeHighNonUint16) {
  589. // Another check for uint16_t overflow but this time
  590. // let's pass even greater option code value.
  591. testInvalidOptionParam("70000", "code");
  592. }
  593. // Verify that zero option code is rejected in the configuration.
  594. TEST_F(Dhcp6ParserTest, optionCodeZero) {
  595. // Option code 0 is reserved and should not be accepted
  596. // by configuration parser.
  597. testInvalidOptionParam("0", "code");
  598. }
  599. // Verify that option data which contains non hexadecimal characters
  600. // is rejected by the configuration.
  601. TEST_F(Dhcp6ParserTest, optionDataInvalidChar) {
  602. // Option code 0 is reserved and should not be accepted
  603. // by configuration parser.
  604. testInvalidOptionParam("01020R", "data");
  605. }
  606. // Verify that option data containins '0x' prefix is rejected
  607. // by the configuration.
  608. TEST_F(Dhcp6ParserTest, optionDataUnexpectedPrefix) {
  609. // Option code 0 is reserved and should not be accepted
  610. // by configuration parser.
  611. testInvalidOptionParam("0x0102", "data");
  612. }
  613. // Verify that option data consisting od an odd number of
  614. // hexadecimal digits is rejected in the configuration.
  615. TEST_F(Dhcp6ParserTest, optionDataOddLength) {
  616. // Option code 0 is reserved and should not be accepted
  617. // by configuration parser.
  618. testInvalidOptionParam("123", "data");
  619. }
  620. // Verify that either lower or upper case characters are allowed
  621. // to specify the option data.
  622. TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
  623. ConstElementPtr x;
  624. std::string config = createConfigWithOption("0a0b0C0D", "data");
  625. ElementPtr json = Element::fromJSON(config);
  626. EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
  627. ASSERT_TRUE(x);
  628. comment_ = parseAnswer(rcode_, x);
  629. std::cout << comment_->str() << std::endl;
  630. ASSERT_EQ(0, rcode_);
  631. Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
  632. ASSERT_TRUE(subnet);
  633. const Subnet::OptionContainer& options = subnet->getOptions();
  634. ASSERT_EQ(1, options.size());
  635. // Get the search index. Index #1 is to search using option code.
  636. const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
  637. // Get the options for specified index. Expecting one option to be
  638. // returned but in theory we may have multiple options with the same
  639. // code so we get the range.
  640. std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
  641. Subnet::OptionContainerTypeIndex::const_iterator> range =
  642. idx.equal_range(80);
  643. // Expect single option with the code equal to 100.
  644. ASSERT_EQ(1, std::distance(range.first, range.second));
  645. const uint8_t foo_expected[] = {
  646. 0x0A, 0x0B, 0x0C, 0x0D
  647. };
  648. // Check if option is valid in terms of code and carried data.
  649. testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
  650. }
  651. // Verify that specific option object is returned for standard
  652. // option which has dedicated option class derived from Option.
  653. TEST_F(Dhcp6ParserTest, stdOptionData) {
  654. ConstElementPtr x;
  655. std::map<std::string, std::string> params;
  656. params["name"] = "OPTION_IA_NA";
  657. // Option code 3 means OPTION_IA_NA.
  658. params["code"] = "3";
  659. params["data"] = "12345, 6789, 1516";
  660. params["csv-format"] = "True";
  661. std::string config = createConfigWithOption(params);
  662. ElementPtr json = Element::fromJSON(config);
  663. EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
  664. ASSERT_TRUE(x);
  665. comment_ = parseAnswer(rcode_, x);
  666. std::cout << comment_->str() << std::endl;
  667. ASSERT_EQ(0, rcode_);
  668. Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
  669. ASSERT_TRUE(subnet);
  670. const Subnet::OptionContainer& options = subnet->getOptions();
  671. ASSERT_EQ(1, options.size());
  672. // Get the search index. Index #1 is to search using option code.
  673. const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
  674. // Get the options for specified index. Expecting one option to be
  675. // returned but in theory we may have multiple options with the same
  676. // code so we get the range.
  677. std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
  678. Subnet::OptionContainerTypeIndex::const_iterator> range =
  679. idx.equal_range(D6O_IA_NA);
  680. // Expect single option with the code equal to IA_NA option code.
  681. ASSERT_EQ(1, std::distance(range.first, range.second));
  682. // The actual pointer to the option is held in the option field
  683. // in the structure returned.
  684. OptionPtr option = range.first->option;
  685. ASSERT_TRUE(option);
  686. // Option object returned for here is expected to be Option6IA
  687. // which is derived from Option. This class is dedicated to
  688. // represent standard option IA_NA.
  689. boost::shared_ptr<Option6IA> optionIA =
  690. boost::dynamic_pointer_cast<Option6IA>(option);
  691. // If cast is unsuccessful than option returned was of a
  692. // differnt type than Option6IA. This is wrong.
  693. ASSERT_TRUE(optionIA);
  694. // If cast was successful we may use accessors exposed by
  695. // Option6IA to validate that the content of this option
  696. // has been set correctly.
  697. EXPECT_EQ(12345, optionIA->getIAID());
  698. EXPECT_EQ(6789, optionIA->getT1());
  699. EXPECT_EQ(1516, optionIA->getT2());
  700. }
  701. };